深夜整个人项目,泛型函数单元测试写到吐血了,发帖来吐槽下。单元测试我们知道,一般写法是像下面这样用表驱动测试来写(用到了匿名 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
文件
嗯,上面就是我的深夜吐槽。不知道今后有没有什么好的工具能结束这种痛苦的写法
1
visitant 2022-03-22 03:00:25 +08:00
runTestCases 函数里的循环执行不能被写在 TestAdd 里?还没用过泛型,如果这样写是哪里语法不对么?
|
2
yzbythesea 2022-03-22 03:08:57 +08:00 1
以后用多了肯定有库来简化,就像 java 的 mockito 这种。但是 golang 真的没必要用泛型。
|
3
cmdOptionKana 2022-03-22 08:24:57 +08:00 1
不是,在需要泛型的场景,你不用泛型也得想办法覆盖多种类型情况,复杂度是一样的。在不需要泛型的场景就不要强行用泛型。
|
4
bthulu 2022-03-22 08:26:23 +08:00
golang 用什么泛型啊, 开发组都说不要泛型, 是你们非逼着上的泛型
|
5
SorcererXW 2022-03-22 08:43:01 +08:00
我理解的泛型的意义在于提高代码复用率,相比反射性能更好。这两点在单元测试里面似乎没有那么重要,单测里面可能直接用 interface 就好了
type testCase[T any] struct { name string a any b any want any } 然后在调用 Add 之前 switch tt.a.(type) { case string |
6
SorcererXW 2022-03-22 08:44:56 +08:00
我理解的泛型的意义在于提高代码复用率,相比反射性能更好。这两点在单元测试里面似乎没有那么重要,单测里面可能直接用 interface+反射 就好了
type testCase[T any] struct { name string a any b any want any } 然后在调用 Add 之前做强转就好了 switch tt.a.(type) { case string: Add(reflect.ValueOf(tt.a).String(), reflect.ValueOf(tt.b).String()) } |
7
BeautifulSoap OP @visitant 你明显都没看懂我想说什么,建议重新看一下我的帖子
|
8
BeautifulSoap OP @visitant intTestCases 和 strTestCases 是基于同一个泛型类型实例化出的两个不同的类型的变量,所以如果想在 TestAdd 里跑循环的话,就得分别写两个 for 循环来执行。如果想测的类型多了(float32,float64,int8...),就要写相对应数量的 for 循环。最终肯定是要抽象出一个函数的,但又不能在函数里定义非匿名函数。最终结果就变成了我帖子里这个样子,想更简化的话,得像 ls 说的那样用接口
|
9
BeautifulSoap OP @SorcererXW 按照老哥的写法改写了下(实际上其实也用不到反射)的确用不着在测试函数外定义了,但是问题在于每个 case 里都需要重复一遍 t.Run( Add(...)) 的代码,需要测试类型一多就成了这样的画风:
https://gist.github.com/WonderfulSoap/a65747d4296af7ca09e6703ff6e9afbb 如果不介意 case 这一坨的话的确是个不错的解决办法 |
10
BeautifulSoap OP @bthulu
@visitant @yzbythesea 虽然但是。。。。我这是在讨论泛型函数怎么写单元测试,你们说别用泛型。。。这话题根本对不上啊。 一些工具函数还有数据结构很适合用泛型来写(Add()这个例子很简单所以拿来举例),既然写了函数那肯定要写单元测试的,到头来我帖子里这个问题是躲不开的。 |
11
tairan2006 2022-03-22 10:08:39 +08:00
go generate 走起
|
12
lysS 2022-03-22 10:53:23 +08:00
坚决不用泛型,除非需要用 tmp 生成代码差不多的情况才用泛型
|
13
yl20181003 2022-03-22 11:02:30 +08:00
go 的泛型感觉很别扭,很怪,不过也算能解决些问题
|
14
Sunshineplan 2022-03-22 11:52:13 +08:00
```go
func runTestCases[T constraints.Ordered](t *testing.T, name string, a, b, want T) { t.Run(name, func(t *testing.T) { if got := Add(a, b); !reflect.DeepEqual(got, want) { t.Errorf("xxxxxxxxxxxxxx") } }) } func TestAdd(t *testing.T) { runTestCases(t, "ok", 1, 1, 2) runTestCases(t, "ok2", 10, 10, 20) runTestCases(t, "ok", "A", "B", "AB") runTestCases(t, "ok2", "Hello", "World", "HelloWorld") } ``` 这样行么? |
15
zzzkkk 2022-03-22 13:32:03 +08:00
写个毛单元测试
现在美国公司就我一个码农 我现在的政策就是反着来 代码尽量冗余 不然改了这个 影响了那个 得不偿失 哈哈哈哈哈 |
16
anonydmer 2022-03-22 13:36:46 +08:00
看起来是很麻烦,范型我还没怎么用,但是已经觉得 go 的单元测试写起来很麻烦了
|
17
wwaayyaa 2022-03-22 15:06:38 +08:00
感觉还好,我最近也在尝试写写泛型的链式调用的工具包,只不过 1.18 部分功能没办法实现。
|