106

我有一个像这样的表驱动测试用例:

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

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

4

7 回答 7

211

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

如果您查看 's 案例的源代码您会发现它首先检查两个映射是否为 nil,然后检查它们是否具有相同的长度,最后检查它们是否具有相同的 (key,值)对。reflect.DeepEqualMap

因为reflect.DeepEqual采用接口类型,所以它将适用于任何有效的地图(map[string]bool, map[struct{}]interface{}等)。请注意,它也适用于非映射值,因此请注意您传递给它的实际上是两个映射。如果你给它传递两个整数,它会很高兴地告诉你它们是否相等。

于 2013-08-13T14:21:19.203 回答
19

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

你有项目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:

于 2019-01-13T21:18:11.797 回答
13

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

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
}
于 2013-08-13T11:59:18.793 回答
5

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

如果您有一个指针类型的映射(如map[*string]int),那么您不想使用reflect.DeepEqual,因为它会返回 false。

最后,如果键是包含未导出指针的类型,例如 time.Time,那么在这样的映射上 reflect.DeepEqual也可以返回 false

于 2017-03-28T21:34:53.560 回答
5

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

于 2020-02-24T12:47:32.147 回答
3

使用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",
+       },
    },
  }
于 2019-04-23T09:30:39.273 回答
2

最简单的方法:

    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)
}
于 2019-09-11T11:17:27.500 回答