Mam przypadek testowy oparty na tabeli, taki jak ten:
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
}
}
Mógłbym sprawdzić, czy długości są takie same i napisać pętlę, która sprawdza, czy każda para klucz-wartość jest taka sama. Ale potem muszę napisać ten czek ponownie, gdy chcę go użyć do innego typu mapy (powiedzmy map[string]string
).
Skończyło się na tym, że przekonwertowałem mapy na ciągi i porównałem ciągi:
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)
}
Zakłada się, że reprezentacje łańcuchowe równoważnych map są takie same, co wydaje się być prawdą w tym przypadku (jeśli klucze są takie same, to mają tę samą wartość, więc ich kolejność będzie taka sama). Czy jest lepszy sposób, aby to zrobić? Jaki jest idiomatyczny sposób porównywania dwóch map w testach tabelarycznych?
testing
maps
go
equivalence
table-driven
andras
źródło
źródło
Odpowiedzi:
Biblioteka Go już Cię obejmuje. Zrób to:
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.") }
Jeśli spojrzeć na kod źródłowy dla
reflect.DeepEqual
„sMap
przypadku, zobaczysz, że najpierw sprawdza, czy obu mapach są zerowe, to sprawdza, czy mają taką samą długość, zanim w końcu sprawdzić, czy mają ten sam zestaw (klucz, wartość) pary.Ponieważ
reflect.DeepEqual
przyjmuje typ interfejsu, będzie działać na każdej prawidłowej mapie (map[string]bool, map[struct{}]interface{}
itp.). Zauważ, że będzie działać również na wartościach innych niż mapy, więc uważaj, aby to, co do niego przekazujesz, to tak naprawdę dwie mapy. Jeśli przekażesz mu dwie liczby całkowite, z radością powie ci, czy są równe.źródło
c.Assert(m1, DeepEquals, m2)
. Fajne w tym jest to, że przerywa test i informuje, co otrzymałeś i czego się spodziewałeś na wyjściu.Masz projekt
go-test/deep
do pomocy.Ale: powinno to być łatwiejsze w wersji natywnej Go 1.12 (luty 2019) : zobacz informacje o wydaniu .
Źródła:
golang/go
wydanie 21095 ,purpleidea
)CL dodaje: ( CL oznacza „Lista zmian” )
Skorzystaj również z pakietu w
text/template
, który miał już słabszą wersję tego mechanizmu.Możesz zobaczyć, że jest używany w
src/fmt/print.go#printValue(): case reflect.Map:
źródło
fmt
zachowanie pomaga w testowaniu równoważności map? Czy sugerujesz porównanie reprezentacji ciągów zamiast używaniaDeepEqual
?DeepEqual
jest nadal dobry. (a raczejcmp.Equal
) Przypadek użycia jest lepiej zilustrowany na twitter.com/mikesample/status/1084223662167711744 , podobnie jak różne dzienniki, jak podano w oryginalnym wydaniu: github.com/golang/go/issues/21095 . Znaczenie: w zależności od charakteru testu, pomocna może być wiarygodna różnica.fmt.Sprint(map1) == fmt.Sprint(map2)
dla tl; drOto co bym zrobił (nieprzetestowany kod):
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 }
źródło
map[string]float64
.eq
działa tylko w przypadkumap[string]int
map. Czy powinienem zaimplementować wersjęeq
funkcji za każdym razem, gdy chcę porównać wystąpienia mapy nowego typu?a
.Zastrzeżenie : niezwiązane z
map[string]int
testowaniem równoważności map w Go, ale związane z tym, co jest tytułem pytaniaJeśli masz mapę typu wskaźnika (jak
map[*string]int
), to czy nie chcą korzystać reflect.DeepEqual ponieważ będzie return false.Wreszcie, jeśli klucz jest typem zawierającym niewyeksportowany wskaźnik, na przykład time.Time, to reflekt.DeepEqual na takiej mapie może również zwrócić false .
źródło
Użyj metody „Diff” na github.com/google/go-cmp/cmp :
Kod:
// 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) }
Wynik:
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", + }, }, }
źródło
Najprostszy sposób:
assert.InDeltaMapValues(t, got, want, 0.0, "Word count wrong. Got %v, want %v", got, want)
Przykład:
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) }
źródło
Zamiast tego użyj cmp ( https://github.com/google/go-cmp ):
if !cmp.Equal(src, expectedSearchSource) { t.Errorf("Wrong object received, got=%s", cmp.Diff(expectedSearchSource, src)) }
Nadal kończy się niepowodzeniem, gdy „kolejność” mapy w oczekiwanym wyniku nie jest tym, co zwraca funkcja. Jednak
cmp
nadal jest w stanie wskazać, gdzie jest niespójność.Dla porównania znalazłem ten tweet:
https://twitter.com/francesc/status/885630175668346880?lang=en
źródło
Jedną z opcji jest naprawienie rng:
rand.Reader = mathRand.New(mathRand.NewSource(0xDEADBEEF))
źródło