深夜整个人项目,泛型函数单元测试写到吐血了,发帖来吐槽下。单元测试我们知道,一般写法是像下面这样用表驱动测试来写(用到了匿名 struct ):
func Add(a, b int) int { return a + b } // ========== 单元测试分界线 ============
func TestAdd(t *testing.T) { // 这里定义了一个匿名的 struct ,让代码更简洁容易维护 tests := []struct { name string a int b int want int }{ { name: "ok", a: 1, b: 1, want: 2, }, { name: "ok2", a: 10, b: 10, want: 20, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Add(tt.a, tt.b); got != tt.want { t.Errorf("xxxxxxxxxxxxxxxx") } }) } } 但如果是泛型函数的话,因为目前存在几个问题:
- 匿名函数无法使用泛型
- 匿名结构体无法使用泛型
- 无法在函数里定义非匿名函数
所以泛型函数的单元测试代码就变成了下面这样的写法:
func Add[T constraints.Ordered](a, b T) T { return a + b } // ======== 单元测试分界线 ===========
// 必须在测试函数外单独定义测试用例的结构体 type testCase[T constraints.Ordered] struct { name string a T b T want T } // 同时还必须在测试函数外定义一个执行泛型用例的泛型函数 func runTestCases[T constraints.Ordered](t *testing.T, cases []testCase[T]) { for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { if got := Add(tt.a, tt.b); !reflect.DeepEqual(got, tt.want) { t.Errorf("xxxxxxxxxxxxxx") } }) } } // 单元测试函数 func TestAdd(t *testing.T) { intTestCases := []testCase[int]{ { name: "ok", a: 1, b: 1, want: 2, }, { name: "ok2", a: 10, b: 10, want: 20, }, } strCases := []testCase[string]{ { name: "ok", a: "A", b: "B", want: "AB", }, { name: "ok2", a: "Hello", b: "World", want: "HelloWorld", }, } runTestCases(t, intTestCases) runTestCases(t, strCases) } 也许你会说不就是多定义了个函数还有结构体类型吗,但是我想说的是就是因为这个问题,导致这样子的单元测试代码写起来真的太折磨人了,非常烦人。这段时间我写泛型函数的单元测试都要写吐血了
最重要的是,如果我们在一个文件里定义了多个函数,那么也往往会把他们的单元测试给统一写到同一个 _test.go 文件里。 这种写法导致的结果就是点开一个单元测试代码,里面满眼都是定义在单元测试函数之外的 type xxxTestCase Struct{} 结构体还有 runXXXTestCases[T xxx]() 的泛型函数。可读性和维护起来非常难受。为了可读性解决办法只有一个:给每个泛型函数单独整个 _test.go 文件
嗯,上面就是我的深夜吐槽。不知道今后有没有什么好的工具能结束这种痛苦的写法
