Eu tenho um caso de teste baseado em tabela como este:
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
}
}
Eu poderia verificar se os comprimentos são iguais e escrever um loop que verifica se todos os pares de valores-chave são iguais. Mas então eu tenho que escrever essa verificação novamente quando quiser usá-la para outro tipo de mapa (digamos map[string]string
).
O que acabei fazendo é converter os mapas em strings e comparar as strings:
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)
}
Isso pressupõe que as representações de string de mapas equivalentes são as mesmas, o que parece ser verdade neste caso (se as chaves forem as mesmas, então eles hash para o mesmo valor, portanto, suas ordens serão as mesmas). Existe uma maneira melhor de fazer isso? Qual é a maneira idiomática de comparar dois mapas em testes baseados em tabelas?
testing
maps
go
equivalence
table-driven
andras
fonte
fonte
Respostas:
A biblioteca Go já está ajudando você. Faça isso:
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.") }
Se você olhar o código-fonte para
reflect.DeepEqual
oMap
caso de, verá que ele primeiro verifica se ambos os mapas são nulos, depois verifica se eles têm o mesmo comprimento antes de finalmente verificar se eles têm o mesmo conjunto de (chave, valor) pares.Por ter
reflect.DeepEqual
um tipo de interface, funcionará em qualquer mapa válido (map[string]bool, map[struct{}]interface{}
, etc). Observe que ele também funcionará em valores que não sejam do mapa, portanto, tome cuidado para que o que você está passando sejam realmente dois mapas. Se você passar dois inteiros, ficará feliz em saber se eles são iguais.fonte
c.Assert(m1, DeepEquals, m2)
. O que é bom nisso é que ele aborta o teste e informa o que você obteve e o que esperava na saída.Você tem o projeto
go-test/deep
para ajudar.Mas: isso deve ser mais fácil com Go 1.12 (fevereiro de 2019) nativamente : consulte as notas de lançamento .
Fontes:
golang/go
edição 21095 ,purpleidea
)O CL adiciona: ( CL significa "Lista de Mudanças" )
Use também o pacote em
text/template
, que já tinha uma versão mais fraca desse mecanismo.Você pode ver que usado em
src/fmt/print.go#printValue(): case reflect.Map:
fonte
fmt
comportamento ajuda a testar a equivalência de mapas? Você está sugerindo comparar as representações de string em vez de usarDeepEqual
?DeepEqual
ainda é bom. (ou melhorcmp.Equal
) O caso de uso é mais ilustrado em twitter.com/mikesample/status/1084223662167711744 , como a comparação de logs conforme declarado no problema original: github.com/golang/go/issues/21095 . Significado: dependendo da natureza do seu teste, um diff confiável pode ajudar.fmt.Sprint(map1) == fmt.Sprint(map2)
para o tl; drIsso é o que eu faria (código não testado):
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 }
fonte
map[string]float64
.eq
só funciona paramap[string]int
mapas. Devo implementar uma versão daeq
função sempre que quiser comparar instâncias de um novo tipo de mapa?a
.Isenção de responsabilidade : não
map[string]int
relacionado, mas relacionado ao teste de equivalência de mapas em Go, que é o título da perguntaSe você tem um mapa de um tipo de ponteiro (como
map[*string]int
), então você não não quer usar reflect.DeepEqual porque ele vai retornar falso.Finalmente, se a chave for um tipo que contém um ponteiro não exportado, como time.Time, então, reflect.DeepEqual em tal mapa também pode retornar false .
fonte
Use o método "Diff" de github.com/google/go-cmp/cmp :
Código:
// 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) }
Resultado:
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", + }, }, }
fonte
Maneira mais simples:
assert.InDeltaMapValues(t, got, want, 0.0, "Word count wrong. Got %v, want %v", got, want)
Exemplo:
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) }
fonte
Em vez disso, use cmp ( https://github.com/google/go-cmp ):
if !cmp.Equal(src, expectedSearchSource) { t.Errorf("Wrong object received, got=%s", cmp.Diff(expectedSearchSource, src)) }
Ele ainda falha quando a "ordem" do mapa em sua saída esperada não é o que sua função retorna. Porém,
cmp
ainda é capaz de apontar onde está a inconsistência.Para referência, encontrei este tweet:
https://twitter.com/francesc/status/885630175668346880?lang=en
fonte
Uma das opções é corrigir o rng:
rand.Reader = mathRand.New(mathRand.NewSource(0xDEADBEEF))
fonte