我有一个像这样的表驱动测试用例:
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.DeepEqual
的 Map
案例的 source code,您会看到它首先检查两个映射是否为 nil,然后检查它们是否具有相同的长度,最后检查它们是否具有同一组(键,值)对。
因为 reflect.DeepEqual
采用接口类型,所以它适用于任何有效的地图(map[string]bool, map[struct{}]interface{}
等)。请注意,它也适用于非映射值,因此请注意您传递给它的实际上是两个映射。如果你给它传递两个整数,它会很高兴地告诉你它们是否相等。
在表驱动测试中比较两个地图的惯用方法是什么?
您需要项目 go-test/deep
提供帮助。
但是:原生Go 1.12(2019 年 2 月)应该会更容易:请参阅release notes。
fmt.Sprint(map1) == fmt.Sprint(map2)
fmt
地图现在以按键排序的顺序打印以简化测试。排序规则是: 适用时,nil 比较低整数、浮点数和字符串排序 < NaN 比较小于非 NaN 浮点数 bool 在 true 之前比较 false 复杂比较实数,然后是虚数 指针按机器地址比较 通道值按机器地址比较结构依次比较每个字段 数组依次比较每个元素 接口值首先按描述具体类型的反射类型进行比较,然后按前面规则中描述的具体值进行比较。打印地图时,像 NaN 这样的非自反键值以前显示为
资料来源:
golang/go 问题 21095,
推文(该补丁的原始想法:ᴊᴀᴍᴇꜱ ᴊᴜꜱᴛ ᴊᴀᴍᴇꜱ(purpleidea)
CL 142737:
CL 添加:(CL stands for "Change List")
为此,我们在根目录下添加了一个包 internal/fmtsort,它实现了对映射键进行排序的通用机制,而不管它们的类型如何。这有点混乱,可能很慢,但地图的格式化打印从来没有这么快,而且一直是反射驱动的。新包是内部的,因为我们真的不希望每个人都使用它来排序。它很慢,不通用,并且只适用于可以是映射键的类型子集。
还可以使用 text/template
中的包,它已经具有此机制的较弱版本。
您可以看到在 src/fmt/print.go#printValue(): case reflect.Map:
中使用的
fmt
行为究竟如何帮助测试地图的等效性?您是否建议比较字符串表示而不是使用 DeepEqual
?
DeepEqual
还是不错的。 (或 rather cmp.Equal
)用例在 twitter.com/mikesample/status/1084223662167711744 中有更多说明,例如区分 logs,如原始问题中所述:github.com/golang/go/issues/21095。含义:根据测试的性质,可靠的差异可以提供帮助。
fmt.Sprint(map1) == fmt.Sprint(map2)
用于 tl;dr
这就是我要做的(未经测试的代码):
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
的实例。 eq
仅适用于 map[string]int
地图。每次我想比较一种新型地图的实例时,是否应该实现一个版本的 eq
函数?
a
。
请改用 cmp (https://github.com/google/go-cmp):
if !cmp.Equal(src, expectedSearchSource) {
t.Errorf("Wrong object received, got=%s", cmp.Diff(expectedSearchSource, src))
}
https://i.stack.imgur.com/MPJg9.png
当您预期输出中的地图“顺序”不是您的函数返回的内容时,它仍然会失败。但是,cmp
仍然能够指出不一致的地方。
作为参考,我发现了这条推文:
https://twitter.com/francesc/status/885630175668346880?lang=en
“在测试中使用 reflect.DeepEqual 通常是个坏主意,这就是我们开源 http://github.com/google/go-cmp 的原因” - Joe Tsai
免责声明:与 map[string]int
无关,但与在 Go 中测试地图的等价性有关,这是问题的标题
如果您有一个指针类型的映射(如 map[*string]int
),那么您 do not want to use reflect.DeepEqual 因为它将返回 false。
最后,如果键是包含未导出指针的类型,例如 time.Time,则在这样的映射 can also return false 上反射.DeepEqual。
使用 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",
+ },
},
}
最简单的方法:
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)
}
c.Assert(m1, DeepEquals, m2)
一样简单。这样做的好处是它会中止测试并告诉你你得到了什么以及你对输出的期望。