Idiomatic Go Testing: Table Driven Test

Go 测试的地道写法:Table Driven Test

前言

最近在写 Go 测试时发现官方与社区中有一种大力推崇的惯例:TableDrivenTest🔗,我一直以为测试应该保持简单愚蠢,但这种做法让我想到程序员的三大美德之一:「懒惰」。

什麼是 Table Driven Testing

基本就是把测试会用到的状态存储到 Slice 当中,并用循环统一执行测试:

func Add(a, b int) int {
return a + b
}
func Test_Add(t *testing.T) {
tests := []struct {
name string
a, b int
expected int
}{
{"正数相加", 1, 1, 2},
{"加零", 1, 0, 1},
{"负数相加", -1, -1, -2},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := Add(tt.a, tt.b)
if got != tt.expected {
t.Errorf("Add(%d, %d) = %d,预期 %d", tt.a, tt.b, got, tt.expected)
}
})
}
}

與使用之前比對

普通测试写法每个案例都要复制相同的测试模板:

func Test_Add(t *testing.T) {
t.Run("正数相加", func(t *testing.T) {
got := Add(1, 1)
if got != 2 {
t.Errorf("Add(1, 1) = %d,预期 2", got)
}
})
t.Run("加零", func(t *testing.T) {
got := Add(1, 0)
if got != 1 {
t.Errorf("Add(1, 0) = %d,预期 1", got)
}
})
t.Run("负数相加", func(t *testing.T) {
got := Add(-1, -1)
if got != -2 {
t.Errorf("Add(-1, -1) = %d,预期 -2", got)
}
})
}

每新增一个测试案例就得把测试逻辑再抄一次,而 Table Driven Test 把「测试逻辑」与「测试数据」分离,编辑案例只需要在 slice 里多加一行 struct,测试逻辑本身完全不用动。

总结

我好奇为什么其他语言不太出现这种测试模式,可能是因为这种模式通常只是测试框架包装下的方便手段,而 Go 社区文化下人人对于追求简单程序有一种莫名的执着。

test.each([
["正数相加", 1, 1, 2],
["加零", 1, 0, 1],
["负数相加", -1, -1, -2],
])("%s", (name, a, b, expected) => {
expect(add(a, b)).toBe(expected);
});

延伸閱讀