介绍

在 Go 1.18 中,Go 语言新增模糊测试。

Fuzzing,又叫fuzz testing,中文叫做模糊测试或随机测试。其本质上是一种自动化测试技术,更具体一点,它是一种基于随机输入的自动化测试技术,常被用于发现处理用户输入的代码中存在的bug和问题。

在具体实现上,Fuzzing不需要像单元测试那样使用预先定义好的数据集作为程序输入,而是会通过数据构造引擎自行构造或基于开发人员提供的初始数据构造一些随机数据,并作为输入提供给我们的程序,然后监测程序是否出现panic、断言失败、无限循环等。这些构造出来的随机数据被称为语料(corpus)。另外Fuzz testing不是一次性执行的测试,如果不限制执行次数和执行时间,Fuzz testing会一直执行下去,因此它也是一种持续测试的技术。

当模糊测试时候,因为输入不可控,故不能精准预测输出。这也是模糊测试和常规的功能测试主要区别。

通过大量的变异数据输入,来验证函数功能是否完整,这就是模糊测试。

模糊测试的函数名以Fuzz开头,函数签名是f testing.F

支持的数据类型

1
string, []byte, int, int8, int16, int32/rune, int64 uint, uint8/byte, uint16, uint32, uint64 float32, float64 bool

应用

一个test文件中只能有一个Fuzz函数。

Assert.Equal函数也是第三方包testify中的函数,此处用的是convey包中的So断言函数。

f.Fuzz函数中func参数中从第二个参数开始可以自定义传参类型,其个数和参数类型必须和f.Add函数的参数个数和类型一致。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import (
"os"
"testing"

"github.com/smartystreets/goconvey/convey"
)

// FuzzEncodeJSON fuzzing test for `EncodeJSON`, case 1: one kind of input
func FuzzEncodeJSON(f *testing.F) {
testCases := []string {"abc", "", "\\U000f59fcş", " "}
for _, tc := range testCases {
f.Add(tc)
}
f.Fuzz(func(t *testing.T, input string) {
convey.Convey("fuzz string", t, func() {
_, err := EncodeJSON(input)
convey.So(err, convey.ShouldBeNil)
})
})
}

// FuzzEncodeJSON2 fuzzing test for `EncodeJSON`, case 2: multiple kind of input
func FuzzEncodeJSON2(f *testing.F) {
testCases := []struct {
stringData string
intData int
}{
{stringData: "\U000f59fcş", intData: 10},
{stringData: "", intData: 1},
{stringData: " ", intData: 100},
}
for _, tc := range testCases {
f.Add(tc.stringData, tc.intData)
}
f.Fuzz(func(t *testing.T, stringData string, intData int) {
convey.Convey("fuzz string", t, func() {
_, err := EncodeJSON(stringData)
convey.So(err, convey.ShouldBeNil)
})
convey.Convey("fuzz int", t, func() {
_, err := EncodeJSON(intData)
convey.So(err, convey.ShouldBeNil)
})
})
}

运行模糊测试

1
go test -v -fuzz=. -fuzztime=10s

输出

1
2
3
4
5
6
7
8
9
=== FUZZ  FuzzEncodeJSON
fuzz: elapsed: 0s, gathering baseline coverage: 0/65 completed
fuzz: elapsed: 0s, gathering baseline coverage: 65/65 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 16065 (5348/sec), new interesting: 4 (total: 69)
fuzz: elapsed: 6s, execs: 36665 (6867/sec), new interesting: 6 (total: 71)
fuzz: elapsed: 9s, execs: 70925 (11434/sec), new interesting: 9 (total: 74)
fuzz: elapsed: 11s, execs: 70925 (0/sec), new interesting: 9 (total: 74)
--- PASS: FuzzEncodeJSON (11.03s)
PASS

出现错误时会在本地生成testdata目录,每个用例错误会生成一个文件,文件内容如下,下次运行的时候会读取该缓存目录下的用例。

1
2
3
4
cat testdata/fuzz/FuzzEncodeJSON/0768acd7f9a71cd2033c4c3d60978477f9fce228bf76e5b33710c71eeaa432ab 
go test fuzz v1
string("\xf3\xa7\xbc\xf3\xb5\xa70")
int(35)