请考虑以下使用 testing.F
的测试:
func FuzzAtoi(f *testing.F) {
for _, tc := range []struct {
s string
expected int
}{
{s: "1", expected: 1},
{s: "0", expected: 1},
} {
actual, err := strconv.Atoi(tc.s)
if err != nil {
f.Fatal(err)
}
if tc.expected != actual {
f.Error(actual)
}
}
}
它会因为一条不友好的错误信息而失败:
=== RUN FuzzAtoi
atoi_test.go:77: 0
--- FAIL: FuzzAtoi (0.00s)
FAIL
通过在 f.Error
行中添加额外的上下文,可能会使其变得更好。但是对于 testing.T
来说,还有另一种方法可用,但 testing.F
不行 - 子测试:
func TestAtoi(t *testing.T) {
for _, tc := range []struct {
s string
expected int
}{
{s: "1", expected: 1},
{s: "0", expected: 1},
} {
tc := tc
t.Run(tc.s, func(t *testing.T) {
actual, err := strconv.Atoi(tc.s)
if err != nil {
t.Fatal(err)
}
if tc.expected != actual {
t.Error(actual)
}
})
}
}
这会自动在错误信息中包含子测试名称,非常有帮助:
=== RUN TestAtoi
=== RUN TestAtoi/1
=== RUN TestAtoi/0
atoi_test.go:77: 0
--- FAIL: TestAtoi (0.00s)
--- PASS: TestAtoi/1 (0.00s)
--- FAIL: TestAtoi/0 (0.00s)
FAIL
最后,我希望能够这样做:
func FuzzAtoi(f *testing.F) {
for _, tc := range []struct {
s string
expected int
}{
{s: "1", expected: 1},
{s: "0", expected: 1},
} {
tc := tc
f.Run(tc.s, func(t *testing.T) { // note 1
t.Parallel()
actual, err := strconv.Atoi(tc.s)
if err != nil {
f.Fatal(err)
}
if tc.expected != actual {
f.Error(actual)
}
f.Add(tc.s) // note 2
})
f.Add(tc.s) // note 3
}
f.Fuzz(func(t *testing.T, s string) {
t.Parallel()
// ...
})
}
请注意,f.Run
接受一个接受 testing.T
而不是 testing.F
的函数。这允许并行运行子测试,并消除了关于模糊测试语料库的歧义 - 这样一来,整个 FuzzAtoi
函数只有一个。f.Add
可以在两个地方被调用 - 在子测试内部和外部。因此,它应该具有线程安全性。
这个问题与 #46780 不同,因为它要求为 testing.F
支持子测试,而不是更复杂的子模糊目标。
3条答案
按热度按时间8tntrjer1#
对不起,我仍然不确定我理解这个。
F.Add
。为什么?如果给F.Run
额外的名字在输出中是有用的,那么为什么不在两个地方都有用呢?F.Add
之前测试种子输入值?F.Fuzz
可以检查它们吗?我想你只会对种子输入值有预期值,但为什么不只是有一个单独的单元测试呢?F.Add
,然后是F.Fuzz
。它可能不应该做太多计算,除了那个。传递给F.Fuzz
的模糊函数可能会调用T.Parallel
,这将并行运行种子输入。但是在模糊时没有效果;多个输入将在不同的进程中并行运行,无论是否调用了T.Parallel
。我希望这是这样写的:
lg40wkob2#
在你的最后一个例子中,你对每个输入都调用了两次F.Add。为什么?
我在文本中尝试解释过这个问题:“
f.Add
可能在子测试的内部和外部都被调用。因此,它应该是线程安全的。”当然,它不应该这样,但我的想法是展示用户可以在任何地方选择它,而且它们都不会出错。为什么要在调用F.Add之前测试种子输入值?F.Fuzz可以检查它们吗?我想你只会为种子输入值有预期值,但为什么不单独编写一个单元测试呢?
我想这就是问题的核心——我不想单独编写一个单元测试。我正在处理的实际包是一个数据编组器/解组器。正确数据的单元测试几乎与模糊测试(将字节解组到对象,重新编组该对象,比较字节)相同,所以我将它们合并在一起,就像这个例子试图展示的那样。模糊测试提案草案提到了这一点:“从长远来看,这种设计可能会开始取代现有的表测试,无缝地融入到现有的Go测试生态系统中。”也许我只是活在未来。
我不确定在这里看到并行性优势。
如果你把那部分当作一个单元测试,那么并行性优势就很明显了。
我希望这会被写成这样:
请注意,你的示例:
TestAtoi
和FuzzAtoi
中包含类似的代码(实际情况下共享代码更多);我的建议允许编写一个单一的测试函数,既测试正常情况,又将它们提供给模糊测试器。然后唯一应该单独编写的测试是返回错误值的测试。
zc0qhyus3#
感谢您的解释,我认为我更好地理解了您的意思。
这将是一个相当大的设计变化,所以我想确保@katiehockman和@rolandshoemaker也对此有发言权。Katie休假几周,所以我们不能立即做出决定。
我的API设计直觉是,保持
testing.F
更简单、专注于模糊测试会更好,即使它可以适应做testing.T
在非模糊测试中能做的大部分事情。当然,这里还是有一些重叠的。