什么是模糊测试
在 Go 1.18 版本中,Go
引入了一个新的测试工具:模糊测试(Fuzzing
)。模糊测试是一种自动化测试技术,它通过随机输入来发现程序中的错误。
模糊测试的原理很简单:随机生成输入,然后运行程序,检查程序的输出是否符合预期。如果程序的输出不符合预期,那么就说明程序中存在错误。
模糊测试的优点是可以发现一些边缘情况下的错误或者可能导致程序崩溃的输入,这些错误很难通过手工测试来发现。因此,模糊测试是一种非常有效的测试方法。
如何进行模糊测试
在这里引入一下官方博客的图:
下面是详细说明:
- 首先,我们需要创建一个模糊测试函数,函数名以
Fuzz
开头,后面跟着要测试的函数名,函数名第一个字母大写,接收一个*testing.F
参数。 - 模糊测试需要写在
_test.go
文件中。 - 一个模糊测试函数里面,必须包含一个模糊目标,也就是需要调用
(*testing.F).Fuzz
方法,这个方法的第一个参数是*testing.T
,后续是模糊测试自动生成的输入(我们需要使用这些随机的输入去调用我们的函数)。这个模糊目标没有返回值。 - 一个模糊测试只能有一个模糊目标(也就是上图的
Fuzz target
只能有一个)。 - 所有的种子语料库的类型必须和
Fuzz
函数的输入参数(上图的Seed corpus addition
跟Fuzzing arguments
)类型一致,因为模糊测试是根据Seed corpus
的类型生成的随机参数来传递给Fuzzing arguments
的。 - 模糊测试的参数只能是以下的类型:
string
,[]byte
int
,int8
,int16
,int32
,int64
uint
,uint8/byte
,uint16
,uint32
,uint64
float32
,float64
bool
模糊测试示例
假设我们有以下这个反转字符串的函数:
1 | func Reverse(s string) string { |
rune
在 Go 里面是int32
的别名,用来表示 Unicode 字符(因为 Unicode 字符最多只有 4 字节,所以rune
足够存储一个 Unicode 字符)。
接着,为这个 Reverse
函数写一个模糊测试函数:
1 | func FuzzReverse(f *testing.F) { |
最后,通过下面的命令来执行一下模糊测试:
1 | go test -fuzz=FuzzReverse -fuzztime=3s . |
输出如下:
1 | fuzz: elapsed: 0s, gathering baseline coverage: 0/4 completed |
从上述输出可以看到,我们有一个模糊测试的用例失败了,然后 Go
帮我们把错误的测试用例写入到了
testdata/fuzz/FuzzReverse/98fce631eb9c5dd5
文件中:
1 | go test fuzz v1 |
修正这个问题
我们从 98fce631eb9c5dd5
这个文件可以看出,模糊测试给我们生成的字符串并不是一个有效的 UTF-8
字符串,所以我们需要在 FuzzReverse
函数中加入一些判断:
1 | func Reverse(s string) (string, error) { |
在判断到传入的字符串不是有效的 UTF-8
字符串的时候,我们返回一个错误。然后在 FuzzReverse
函数中加入对错误的判断:
1 | func FuzzReverse(f *testing.F) { |
跳过不是 UTF-8 字符串的测试用例,然后再次执行模糊测试:
1 | go test -fuzz=FuzzReverse -fuzztime=3s . |
输出如下:
1 | fuzz: elapsed: 0s, gathering baseline coverage: 0/6 completed |
这次模糊测试通过了,没有发现问题。
运行模糊测试一些建议
模糊测试的时间
需要注意的是,在进行模糊测试的时候,我们可能需要指定一个合适的时间,否则模糊测试可能会一直运行下去。可以通过
-fuzztime
参数来指定模糊测试的时间:
1 | go test -fuzz=FuzzReverse -fuzztime=3s . |
如果我们需要在 CI 中集成,这个可能是必须的。否则,CI 会一直运行模糊测试。
调整种子语料库
检查提供给模糊器的种子语料库,确保其多样性足以探索各种代码路径,但又不会过于宽泛,导致模糊器陷入过多路径。有时,过于通用的种子会导致模糊器在无益路径上花费过多时间。
并行模糊测试
如果我们需要控制模糊测试的并行度,可以通过 -parallel
参数来指定模糊测试的并行度:
默认情况下,Go 会使用所有的 CPU 核心来运行模糊测试。
1 | go test -fuzz=FuzzReverse -fuzztime=3s -parallel=10 . |
输出:
1 | fuzz: elapsed: 0s, gathering baseline coverage: 0/52 completed |
我们从第二行输出可以看到 10 workers
,因为我们通过了
-parallel
参数指定了只使用 10 个 CPU
核心来运行模糊测试。
总结
模糊测试是一种自动化测试技术,它通过随机输入来发现程序中的错误。Go
1.18 版本引入了模糊测试,我们可以通过 (*testing.F).Fuzz
方法来进行模糊测试。模糊测试是一种非常有效的测试方法,可以发现一些边缘情况下的错误或者可能导致程序崩溃的输入。
单元测试可以帮助我们发现一些常规路径上的错误,而模糊测试可以帮助我们发现一些边缘情况下的错误。因此,单元测试和模糊测试是互补的,我们可以同时使用这两种测试方法来提高代码的质量。