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

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

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)
}

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



답변

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{}등)에서 작동합니다. 비맵 값에서도 작동하므로 전달하는 것이 실제로 두 개의 맵이라는 점에 유의하십시오. 두 개의 정수를 전달하면 같은지 여부를 기꺼이 알려줍니다.


답변

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

당신은 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:


답변

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

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]int질문의 제목 인 Go에서지도의 동등성 테스트와 관련이 없지만 관련이 있음

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

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


답변

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",
+       },
    },
  }


답변

대신 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


답변

가장 간단한 방법 :

    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)
}