如何在 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)
}

这假设等价映射的字符串表示相同,在这种情况下似乎是正确的(如果键是相同的,那么它们散列为相同的值,因此它们的顺序是相同的)。还有更好的办法吗?在表驱动测试中比较两个映射的惯用方法是什么?

99046 次浏览

这就是我要做的(未测试的代码) :

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
}

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{}等)上工作。注意,它也会处理非 map 值,所以要小心传递给它的实际上是两个 map。如果你传递给它两个整数,它会很高兴地告诉你它们是否相等。

免责声明 : 与 map[string]int无关,但与在 Go 中测试地图的等价性有关,这是问题的标题

如果您有一个指针类型的映射(如 map[*string]int) ,那么您就是 要用反映,因为它将返回 false。

最后,如果键是包含未导出指针的类型,如 time。时间,然后反思。在这样的地图上深平等。

在表驱动测试中比较两个映射的惯用方法是什么?

You have the project go-test/deep to help.

但是: 这应该更容易与 1分12秒(2019年2月) 天生的: 见 释放通知书

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

fmt

地图现在以键排序的顺序打印,以简化测试

订购规则如下:

  • 如果适用,零比较低
  • 整数、浮点数和字符串按 <排序
  • NaN 比非 NaN 浮子少
  • bool比较 falsetrue
  • 复杂比较真实,然后是想象
  • 按机器地址比较指针
  • 通过机器地址比较通道值
  • Structs compare each field in turn
  • 数组依次比较每个元素
  • 接口值首先通过描述具体类型的 reflect.Type进行比较,然后通过前面规则中描述的具体值进行比较。

在打印地图时,像 NaN 这样的非自反键值以前显示为 <nil>。在此版本中,将打印正确的值。

资料来源:

劳工处补充: (CL stands for "Change List")

To do this, we add 根目录下的一个包 internal/fmtsort, that implements a general mechanism for sorting map keys regardless of their type.

This is a little messy and probably slow, but formatted printing of maps has never been fast and is already always reflection-driven.

The new package is internal because we really do not want everyone using this to sort things. It is slow, not general, and only suitable for the subset of types that can be map keys.

还可以使用 text/template中的包,它已经有了这种机制的较弱版本。

你可以在 src/fmt/print.go#printValue(): case reflect.Map:中看到它的使用

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

改为使用 cmp (https://github.com/google/go-cmp) :

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

Failed test

当预期输出中的 map“ order”不是函数返回的结果时,它仍然会失败。但是,cmp仍然能够指出不一致之处。

作为参考,我发现了这条推特:

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

“使用反射,在测试中使用深度平等通常是个坏主意,这就是为什么我们开源 http://github.com/google/go-cmp” 蔡乔