Golang에서지도의 동등성을 테스트하는 방법은 무엇입니까?


92

다음과 같은 테이블 기반 테스트 케이스가 있습니다.

func CountWords(s string) map[string]int

func TestCountWords(t *testing.T) {
  var tests = []struct {
    input string
    want map[string]int
  }{
    {"foo", map[string]int{"foo":1}},
    {"foo bar foo", map[string]int{"foo":2,"bar":1}},
  }
  for i, c := range tests {
    got := CountWords(c.input)
    // TODO test whether c.want == got
  }
}

길이가 동일한 지 확인하고 모든 키-값 쌍이 동일한 지 확인하는 루프를 작성할 수 있습니다. 그러나 다른 유형의지도에 사용하려면이 수표를 다시 작성해야합니다 (예 :) map[string]string.

결국 내가 한 것은 맵을 문자열로 변환하고 문자열을 비교하는 것입니다.

func checkAsStrings(a,b interface{}) bool {
  return fmt.Sprintf("%v", a) != fmt.Sprintf("%v", b) 
}

//...
if checkAsStrings(got, c.want) {
  t.Errorf("Case #%v: Wanted: %v, got: %v", i, c.want, got)
}

이것은 동등한 맵의 문자열 표현이 동일하다고 가정합니다.이 경우에는 사실 인 것처럼 보입니다 (키가 동일하면 동일한 값으로 해시되므로 순서가 동일합니다). 이 작업을 수행하는 더 좋은 방법이 있습니까? 테이블 기반 테스트에서 두 맵을 비교하는 관용적 방법은 무엇입니까?


4
Err, no : 맵을 반복하는 순서가 예측 가능 하다고 보장 할 수 없습니다 . "맵에 대한 반복 순서가 지정되지 않았으며 한 반복에서 다음 반복까지 동일하다는 보장이 없습니다. ..." .
zzzz

2
또한 특정 크기의 맵의 경우 Go는 의도적으로 순서를 무작위로 지정합니다. 그 순서에 의존하지 않는 것이 좋습니다.
Jeremy Wall

지도를 비교하는 것은 프로그램의 디자인 결함입니다.
Inanc Gumus 2019

4
go 1.12 (2019 년 2 월)에서는 테스트를 쉽게하기 위해지도가 키 정렬 순서로 인쇄됩니다 . 아래 내 대답을
VonC

답변:


174

Go 라이브러리는 이미 당신을 덮었습니다. 이 작업을 수행:

import "reflect"
// m1 and m2 are the maps we want to compare
eq := reflect.DeepEqual(m1, m2)
if eq {
    fmt.Println("They're equal.")
} else {
    fmt.Println("They're unequal.")
}

당신이 보면 소스 코드 에 대한 reflect.DeepEqualMap경우 그들이 전에 마지막으로 그들이 (키의 동일한 세트가 있는지 확인 같은 길이가 있다면, 당신은 다음 수표, 모두 매핑하고있는 경우에 최초로 확인이 전무 것을 볼 수 있습니다 값) 쌍.

reflect.DeepEqual인터페이스 유형을 취하기 때문에 유효한 모든지도 ( map[string]bool, map[struct{}]interface{}등)에서 작동합니다. 비맵 값에서도 작동하므로 전달하는 것이 실제로 두 개의 맵이라는 점에 유의하십시오. 두 개의 정수를 전달하면 같은지 여부를 기꺼이 알려줍니다.


굉장합니다. 바로 제가 찾던 것입니다. 나는 jnml이 성능이 좋지 않다고 말했지만 테스트 케이스에서 누가 신경을 쓰는지 생각합니다.
andras

예, 프로덕션 응용 프로그램에 이것을 원하면 가능하면 사용자 정의 함수를 사용하고 싶지만 성능이 문제가되지 않으면 확실히 트릭입니다.
joshlf

1
@andras 또한 gocheck를 확인해야합니다 . 단순하게 c.Assert(m1, DeepEquals, m2). 이것에 대해 좋은 점은 테스트를 중단하고 출력에서 ​​얻은 것과 예상 한 것을 알려준다는 것입니다.
Luke

8
DeepEqual 은 슬라이스의 ORDER 도 동일 해야 한다는 점은 주목할 가치가 있습니다 .
Xeoncross 2017


13

테이블 기반 테스트에서 두 맵을 비교하는 관용적 방법은 무엇입니까?

당신은 go-test/deep도울 프로젝트 가 있습니다.

하지만 기본적으로 Go 1.12 (2019 년 2 월)를 사용하면이 작업이 더 쉬워 질 것입니다 . 출시 노트를 참조하세요 .

fmt.Sprint(map1) == fmt.Sprint(map2)

fmt

이제 맵은 테스트를 쉽게하기 위해 키 정렬 순서로 인쇄됩니다 .

주문 규칙은 다음과 같습니다.

  • 적용 가능한 경우 nil은 낮음을 비교합니다.
  • 정수, 부동 소수점 및 문자열 순서 <
  • NaN은 비 NaN 수레보다 적은 수를 비교합니다.
  • boolfalse전에 비교true
  • 복잡함은 실제와 가상의 비교
  • 컴퓨터 주소로 포인터 비교
  • 컴퓨터 주소로 채널 값 비교
  • 구조체는 각 필드를 차례로 비교합니다.
  • 배열은 각 요소를 차례로 비교합니다.
  • 인터페이스 값은 먼저 reflect.Type구체적인 유형 을 설명하여 비교 한 다음 이전 규칙에서 설명한대로 구체적인 값으로 비교 합니다.

지도를 인쇄 할 때 NaN과 같은 비 반사 키 값은 이전에로 표시되었습니다 <nil>. 이 릴리스부터 올바른 값이 인쇄됩니다.

출처 :

CL은 다음을 추가합니다. ( CL은 "변경 목록"을 나타냄)

이를 위해 루트에 패키지를internal/fmtsort 추가 합니다.이 패키지는 유형에 관계없이 맵 키를 정렬하는 일반적인 메커니즘을 구현합니다.

이것은 약간 지저분하고 느릴 수 있지만 맵의 형식화 된 인쇄는 결코 빠르지 않았으며 이미 항상 반사 중심입니다.

새 패키지는 내부에 있습니다. 모든 사람이 이것을 사용하여 물건을 분류하는 것을 원하지 않기 때문입니다. 일반적이지 않고 느리고 맵 키가 될 수있는 유형의 하위 집합에만 적합합니다.

또한 text/template이미이 메커니즘의 약한 버전이있는 의 패키지를 사용하십시오 .

당신은에서 사용 된 것을 볼 수 있습니다 src/fmt/print.go#printValue(): case reflect.Map:


무지해서 죄송합니다. 저는 Go를 처음 사용하지만이 새로운 fmt동작 이 맵의 동등성을 테스트하는 데 정확히 어떻게 도움 이 됩니까? 사용하는 대신 문자열 표현을 비교할 것을 제안하고 DeepEqual있습니까?
sschuberth

@sschuberth DeepEqual는 여전히 좋습니다. (또는 오히려cmp.Equal )를 사용하는 경우 더에 예시되어있다 twitter.com/mikesample/status/1084223662167711744 diffing의 같은 로그 : 원래 문제에 명시된대로 github.com/golang/go/issues/21095을 . 의미 : 테스트의 특성에 따라 신뢰할 수있는 diff가 도움이 될 수 있습니다.
VonC

fmt.Sprint(map1) == fmt.Sprint(map2)for the tl; dr
425nesp

@ 425nesp 감사합니다. 나는 그에 따라 대답을 편집했습니다.
VonC

11

이것은 내가 할 일입니다 (테스트되지 않은 코드).

func eq(a, b map[string]int) bool {
        if len(a) != len(b) {
                return false
        }

        for k, v := range a {
                if w, ok := b[k]; !ok || v != w {
                        return false
                }
        }

        return true
}

map[string]float64좋습니다. 하지만 .NET의 인스턴스를 비교하려는 다른 테스트 케이스가 있습니다. 지도 eq에서만 작동 map[string]int합니다. eq새로운 유형의지도 인스턴스를 비교하고 싶을 때마다 함수 버전을 구현해야합니까 ?
andras 2013-08-13

@andras : 11 개의 SLOC. 나는 이것에 대해 물어 보는 것보다 더 짧은 시간에 그것을 "복사 붙여 넣기"전문화 할 것이다. 하지만 다른 많은 사람들이 "반사"를 사용하여 동일한 작업을 수행하지만 성능이 훨씬 더 떨어집니다.
zzzz

1
지도의 순서가 같을 것으로 기대하지 않습니까? blog.golang.org/go-maps-in-action
nathj07

3
@ nathj07 아니요, a.
Torsten Bronger 2016

5

면책 조항 : map[string]int질문의 제목 인 Go에서지도의 동등성 테스트와 관련이 없지만 관련이 있음

당신이 포인터 타입의지도가있는 경우 (같은 map[*string]int), 당신은 수 없습니다 reflect.DeepEqual를 사용하려면 false를 반환 때문입니다.

마지막으로, 키가 time.Time과 같이 내 보내지 않은 포인터를 포함하는 유형 인 경우 이러한지도에서 reflect.DeepEqual 도 false를 반환 할 수 있습니다 .


3

github.com/google/go-cmp/cmp 의 "Diff"방법을 사용합니다 .

암호:

// Let got be the hypothetical value obtained from some logic under test
// and want be the expected golden data.
got, want := MakeGatewayInfo()

if diff := cmp.Diff(want, got); diff != "" {
    t.Errorf("MakeGatewayInfo() mismatch (-want +got):\n%s", diff)
}

산출:

MakeGatewayInfo() mismatch (-want +got):
  cmp_test.Gateway{
    SSID:      "CoffeeShopWiFi",
-   IPAddress: s"192.168.0.2",
+   IPAddress: s"192.168.0.1",
    NetMask:   net.IPMask{0xff, 0xff, 0x00, 0x00},
    Clients: []cmp_test.Client{
        ... // 2 identical elements
        {Hostname: "macchiato", IPAddress: s"192.168.0.153", LastSeen: s"2009-11-10 23:39:43 +0000 UTC"},
        {Hostname: "espresso", IPAddress: s"192.168.0.121"},
        {
            Hostname:  "latte",
-           IPAddress: s"192.168.0.221",
+           IPAddress: s"192.168.0.219",
            LastSeen:  s"2009-11-10 23:00:23 +0000 UTC",
        },
+       {
+           Hostname:  "americano",
+           IPAddress: s"192.168.0.188",
+           LastSeen:  s"2009-11-10 23:03:05 +0000 UTC",
+       },
    },
  }

2

대신 cmp ( https://github.com/google/go-cmp )를 사용 하세요 .

if !cmp.Equal(src, expectedSearchSource) {
    t.Errorf("Wrong object received, got=%s", cmp.Diff(expectedSearchSource, src))
}

실패한 테스트

예상 출력의 맵 "순서"가 함수가 반환하는 것과 다를 때 여전히 실패합니다. 그러나 cmp여전히 불일치가 어디에 있는지 지적 할 수 있습니다.

참고로이 트윗을 찾았습니다.

https://twitter.com/francesc/status/885630175668346880?lang=en

"테스트에서 reflect.DeepEqual을 사용하는 것은 종종 나쁜 생각입니다. 그래서 우리는 http://github.com/google/go-cmp를 오픈 소스로 제공합니다. "-Joe Tsai


1

가장 간단한 방법 :

    assert.InDeltaMapValues(t, got, want, 0.0, "Word count wrong. Got %v, want %v", got, want)

예:

import (
    "github.com/stretchr/testify/assert"
    "testing"
)

func TestCountWords(t *testing.T) {
    got := CountWords("hola hola que tal")

    want := map[string]int{
        "hola": 2,
        "que": 1,
        "tal": 1,
    }

    assert.InDeltaMapValues(t, got, want, 0.0, "Word count wrong. Got %v, want %v", got, want)
}

-5

옵션 중 하나는 rng를 수정하는 것입니다.

rand.Reader = mathRand.New(mathRand.NewSource(0xDEADBEEF))

실례 합니다만이 질문과 귀하의 답변은 어떤 관련이 있습니까?
Dima Kozhevin 2018

@DimaKozhevin golang은 내부적으로 rng를 사용하여 맵의 항목 순서를 혼합합니다. rng를 수정하면 테스트 목적으로 예측 가능한 순서를 얻을 수 있습니다.
Grozz

@Grozz 그렇습니까? 왜!? 나는 그것이 그럴지도 모른다고 반드시 이의를 제기하지는 않습니다.
msanford

나는 Golang에서 일하지 않기 때문에 그들의 추론을 설명 할 수는 없지만 적어도 v1.9에서 확인 된 행동입니다. 그러나 나는 "우리는 당신이지도에서 주문에 의존해서는 안되는 것을 강요하고 싶다"는 줄에 따라 몇 가지 설명을 보았다.
Grozz
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.