Individual sub test cases can be executed directly
由于 sub tests 具有名称,因此您可以使用 go test -run
flag 按名称运行一系列 sub tests。
% **go test -run=.*/trailing -v** === RUN TestSplit === RUN TestSplit/trailing_sep --- FAIL: TestSplit (0.00s) --- FAIL: TestSplit/trailing_sep (0.00s) split_test.go:25: expected: [a b c], got: [a b c ]
Comparing what we got with what we wanted
现在我们已准备好修复测试用例。 我们来看看错误。
--- FAIL: TestSplit (0.00s) --- FAIL: TestSplit/trailing_sep (0.00s) split_test.go:25: expected: [a b c], got: [a b c ]
你能发现问题吗? 很明显,切片是不同的,这就是 reflect.DeepEqual
令人烦恼的原因。 但是要找到实际的差异并不容易,你必须发现在 c
之后额外的空格。 在这个简单的例子中,这可能看起来很简单,但是当你比较两个复杂的深层嵌套的gRPC结构时,它是任何东西(不一定是空格)。
如果我们切换到 %#v
语法以将值视为Go(ish)声明,则可以改进输出:
got := Split(tc.input, tc.sep) if !reflect.DeepEqual(tc.want, got) { t.Fatalf("**expected: %#v, got: %#v**", tc.want, got) }
现在,当我们运行测试时,很明显问题在于 slice 中有一个额外的空白元素。
% go test --- FAIL: TestSplit (0.00s) --- FAIL: TestSplit/trailing_sep (0.00s) split_test.go:25: **expected: []string{"a", "b", "c"}, got: []string{"a", "b", "c", ""}**
但是在我们开始修复测试失败之前,我想多谈一点关于选择正确的方法来呈现测试失败的问题。我们的 Split
函数很简单,它接受一个原始的字符串并返回一段字符串,但是如果它处理结构,或者更糟的,指向结构的指针呢?
下面是一个例子,其中 %v
不起作用:
func main() { type T struct { I int } x := []*T{ {1}, {2}, {3} } y := []*T{ {1}, {2}, {4} } fmt.Printf("%v %v\n", x, y) fmt.Printf("%#v %#v\n", x, y) }
第一个 fmt.Printf
打印毫无帮助但符合预期的 addresses slice;[0xc000096000 0xc000096008 0xc000096010] [0xc000096018 0xc000096020 0xc000096028]
。 但是,我们的 %#v
并没有做任何改进。打印一个 addresses slice 强制转换为 *main.T
;[]*main.T{(*main.T)(0xc000096000), (*main.T)(0xc000096008), (*main.T)(0xc000096010)} []*main.T{(*main.T)(0xc000096018), (*main.T)(0xc000096020), (*main.T)(0xc000096028)}
由于使用任何 fmt.Printf
verb 的局限性,我想从Google引入 go-cmp 库。
cmp 库的目标是专门比较两个值。这类似于 reflect.DeepEqual
,但它有更多的功能。所以,使用cmp pacakge,你可以编写如下:
func main() { type T struct { I int } x := []*T{ {1}, {2}, {3} } y := []*T{ {1}, {2}, {4} } fmt.Println(cmp.Equal(x, y)) **// false** }
但是对于我们的测试函数来说,更有用的是 cmp.Diff
函数,它将产生一个文本描述,递归地描述两个值之间的区别。
func main() { type T struct { I int } x := []*T{ {1}, {2}, {3} } y := []*T{ {1}, {2}, {4} } diff := cmp.Diff(x, y) fmt.Printf(diff) }
取而代之输出:
% go run {[]*main.T}[2].I: -: 3 +: 4
以上表明,在类型 T
的 slice 第二个元素,I
字段应该是3,但实际上是4。
综上所述,我们进行了 table driven go-cmp test
func TestSplit(t *testing.T) { tests := map[string]struct { input string sep string want []string }{ "simple": {input: "a/b/c", sep: "/", want: []string{"a", "b", "c"}}, "wrong sep": {input: "a/b/c", sep: ",", want: []string{"a/b/c"}}, "no sep": {input: "abc", sep: "/", want: []string{"abc"}}, "trailing sep": {input: "a/b/c/", sep: "/", want: []string{"a", "b", "c"}}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { got := Split(tc.input, tc.sep) **diff := cmp.Diff(tc.want, got) if diff != "" { t.Fatalf(diff) }** }) } }
运行,我们得到
% go test --- FAIL: TestSplit (0.00s) --- FAIL: TestSplit/trailing_sep (0.00s) split_test.go:27: {[]string}[?->3]: -: <non-existent> +: "" FAIL exit status 1 FAIL split 0.006s
使用 cmp.Diff
我们的测试工具不仅仅是告诉我们,我们得到的和想要的是不同的。我们的测试告诉我们,字符串的长度是不同的,结构中的第三个索引不应该存在,但是实际输出得到一个空字符串,“”。从此开始,修复测试失败是直接了当的。
原文:https://dave.cheney.net/2019/05/07/prefer-table-driven-tests
本文作者 : cyningsun
本文地址 : https://www.cyningsun.com/07-24-2019/prefer-table-driven-tests-cn.html
版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!