前言
在Go 1.18开始提供了Fuzzing能力的支持,testing包在我们常见的T B类型之外新增了F的类型,用于支持模糊测试。
Fuzzing Test
日常测试代码的时候我们经常使用table driven test的方式来构造一组输入和预期的结果,之后调用我们的待测函数,检查结果是否和我们的预期匹配,也就是我们平常说的Mock数据
这就引出了一个问题,这个table要多大呢?
通常大家都只是写几个【正常】的case 几个【异常】的case
但是这些样例其实是不够的,比如一些异常值、corner case,可能无法处理,或者可能有程序挂掉、安全问题等等
而fuzzing test的作用就是帮我们自动生成输入数据,以下是维基百科对于fuzzing test的定义
Fuzzing is a technique where you automagically generate input values for your functions to find bugs
模糊测试能够【持续】、【自动】地生成一系列【半随机】的数据作为待测函数的输入,来找到程序里隐藏的bug,对于边界case能够很好的验证。模糊测试中的输入不是由人工指定的,而是自动生成的随机数据,所以可以规避掉人工主观判断造出来的数据。
模糊测试通常可以不依赖于开发测试人员定义好的数据集,取而代之的则是一组通过数据构造引擎自行构造的一系列随机数据。 模糊测试会讲这些数据作为输入提供给待测程序,并且监测程序是否出现panic、断言失败、无限循环,或者其他的异常情况
这些通过数据构造引擎生成的数据被称为语料(corpus) ,另外模糊测试其实也是一种持续测试的手段,因为如果不限制执行的次数或者执行的最大时间,它就会一直不停的执行下去
Go模糊测试
让我们来看看一个Golang实现的模糊测试长什么样
签名部分:从常见的func TestXxx(t *testing.T) 变成了func FuzzXxx(f *testing.F)
seed corpus:一组用户提供的语料,fuzzing引擎会使用这个语料来生成随机数据。其实就是一个样本,之后引擎就知道要生成什么类型的随机数据了
Fuzzing arguments: 接受*testing.t和想要随机生成的数据类型
模糊测试的要求
- 模糊测试必须是一个名称类似于FuzzXxx的函数,仅接受一个*testing.F参数,无返回值
- 模糊测试必须在*_test.go中运行
- Fuzz target(模糊目标)必须是对(testing.F).Fuzz的方法调用,参数是一个函数,并且这个函数的第一个参数是tesing.T,然后是模糊参数(fuzzing argument),没有返回值
- 一个模糊测试中必须只有一个模糊目标
- 所有种子语料库(seed corpus)必须具有与模糊参数相同的类型,顺序相同
- 模糊参数只能是以下的类型
go
string, []byte
int, int8, int16, int32/rune, int64
uint, uint8/byte, uint16, uint32, uint64
float32, float64
bool
需要注意的一点是,在Go执行的过程中,多个fuzzing target是并行来处理的,底层会有多个worker,调度的顺序也不一定,所以不能做持久化,也不能依赖一些全局状态,不要尝试改变入参
运行模糊测试
我们依然可以使用go test命令来跑模糊测试,只是需要加上一个-fuzz=FuzzTestName的选项。同时这个包下所有其他类型的test都会优先于模糊测试执行,毕竟比较耗费资源,随机数据生成是有样本的。
执行结果如下
yaml
~ go test -fuzz FuzzFoo
fuzz: elapsed: 0s, gathering baseline coverage: 0/192 completed
fuzz: elapsed: 0s, gathering baseline coverage: 192/192 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 325017 (108336/sec), new interesting: 11 (total: 202)
fuzz: elapsed: 6s, execs: 680218 (118402/sec), new interesting: 12 (total: 203)
fuzz: elapsed: 9s, execs: 1039901 (119895/sec), new interesting: 19 (total: 210)
fuzz: elapsed: 12s, execs: 1386684 (115594/sec), new interesting: 21 (total: 212)
PASS
ok foo 12.692s
Fuzzing test的局限性,在单元测试中因为测试输入是固定的,所以可以和把得到的结果和预期结果进行比较来判断执行结果是否与预期相符合。
但是在使用fuzzing的时候,我们无法预测输出结果是什么,因为测试的输入除了我们代码只能给指定的用例之外,还有fuzzing随机生成的输入,所以我们无法提前知道预期结果是什么。