大家好,我是 阿星,今天给大家分享一篇 go 1.18新特性的另外一个特性,『模糊测试』。这个概念,我也是第一次听说,因而激起了我的一些兴趣,翻阅了一些资料,结合 go 1.18 的语法,我们一起来看看这个新知识吧。
序0:模糊测试是什么
Fuzz Testing或Fuzzing是一种软件测试技术,将称为FUZZ的无效或随机数据放入软件系统中,以发现编码错误和安全漏洞。模糊测试的目的是使用自动化或半自动化技术插入数据,并对系统进行各种异常测试,例如系统崩溃或内置代码故障等。
简单来说就是提供一些无效数据和预料之外的数据来对我们的编码进行测试,属于安全测试范围内的一种测试手段,能够帮助我们测试出编码中更多的问题。
作为一个开发人员,基本都知道单元测试是保证我们程序稳定运行的一个保障,因而很多公司的代码提交平台也需要单元测试的代码覆盖率达到多少以上才允许合并,但单元测试能过,也不能代表我们程序不会出现bug。因为按照我们的编码思路来写自己的单元测试,经常会囿于自己的编程思维之中,都很容易漏掉一些正向思维难以步骤到的异常场景。
「不识庐山真面目,只缘身在此山中」,因而,我们需要「模糊测试」来对我们的程序提供一个更可靠的保障。
序1:模糊测试的工作原理
模糊测试的工作原理:
-
输入模糊测试的语料库并生成模糊数据
-
执行系统单元测试并监控系统行为
-
记录bug
工作原理很简单,也就是一些简单的官方解释,没什么需要着重介绍的,下面直接来看看 go 语言原生支持的模糊测试具体应该怎么使用。
序2:在 go 语言中如何使用模糊测试
go 的模糊测试提供了两个方法:
-
t.Add:用于开发者输入模糊测试的种子数据,fuzzing 根据这些种子数据,自动随机生成更多测试数据
-
t.Fuzz:开始运行模糊测试,t.Fuzz 的入参是一个 Fuzz Target 函数(官方这么叫的),这个 Fuzz Target 函数的编写逻辑跟单元测试就一样了
下面来看个示例,我们有这样一个反转字符串的例子:
func Reverse(s string) string {
b := []byte(s)
for i, j := 0, len(b)-1; i < len(b)/2; i, j = i+1, j-1 {
b[i], b[j] = b[j], b[i]
}
return string(b)
}
对其编写单元测试:
func TestReverse(t *testing.T) {
testcases := []struct {
in, want string
}{
{"Hello, world", "dlrow ,olleH"},
{" ", " "},
{"!12345", "54321!"},
}
for _, tc := range testcases {
rev := Reverse(tc.in)
if rev != tc.want {
t.Errorf("Reverse: %q, want %q", rev, tc.want)
}
}
}
=== RUN TestReverse
--- PASS: TestReverse (0.00s)
PASS
func FuzzReverse(f *testing.F) {
testcases := []string{"Hello, world", " ", "!12345"}
for _, tc := range testcases {
f.Add(tc) // 1. 用前面的测试case作为输入预料库
}
//2. f.Fuzz() 就会随机生成一些模糊测试数据并执行单元测试
f.Fuzz(func(t *testing.T, orig string) {
rev := Reverse(orig)
doubleRev := Reverse(rev)
//3. 处理结果
if orig != doubleRev {
t.Errorf("Before: %q, after: %q", orig, doubleRev)
}
if utf8.ValidString(orig) && !utf8.ValidString(rev) {
t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
}
})
}
fuzz: elapsed: 0s, gathering baseline coverage: 0/8 completed
failure while testing seed corpus entry: FuzzReverse/c05903cd28106c02aebdaccb2f39e8f55f62c75cbac4d0bd82f1ae0fe19d1425
fuzz: elapsed: 0s, gathering baseline coverage: 3/8 completed
--- FAIL: FuzzReverse (0.02s)
--- FAIL: FuzzReverse (0.00s)
main_test.go:25: Reverse produced invalid UTF-8 string "\xb4\xce000000"
FAIL
exit status 1
FAIL utils/retry 0.026s
func Reverse(s string) (string, error) {
if !utf8.ValidString(s) {
return s, errors.New("input is not valid UTF-8")
}
r := []rune(s)
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return string(r), nil
}
func FuzzReverse(f *testing.F) {
testcases := []string{"Hello, world", " ", "!12345"}
for _, tc := range testcases {
f.Add(tc) // Use f.Add to provide a seed corpus
}
f.Fuzz(func(t *testing.T, orig string) {
rev, err1 := Reverse(orig)
if err1 != nil {
return
}
doubleRev, err2 := Reverse(rev)
if err2 != nil {
return
}
if orig != doubleRev {
t.Errorf("Before: %q, after: %q", orig, doubleRev)
}
if utf8.ValidString(orig) && !utf8.ValidString(rev) {
t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
}
})
}
fuzz: elapsed: 0s, gathering baseline coverage: 0/8 completed
fuzz: elapsed: 0s, gathering baseline coverage: 8/8 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 290059 (96583/sec), new interesting: 32 (total: 40)
fuzz: elapsed: 6s, execs: 843477 (184663/sec), new interesting: 35 (total: 43)
fuzz: elapsed: 9s, execs: 1255007 (137157/sec), new interesting: 37 (total: 45)
fuzz: elapsed: 12s, execs: 1676071 (140285/sec), new interesting: 37 (total: 45)
fuzz: elapsed: 15s, execs: 2091844 (138668/sec), new interesting: 37 (total: 45)
fuzz: elapsed: 18s, execs: 2513711 (140623/sec), new interesting: 37 (total: 45)
fuzz: elapsed: 21s, execs: 2919301 (135200/sec), new interesting: 37 (total: 45)
fuzz: elapsed: 24s, execs: 3318661 (133099/sec), new interesting: 37 (total: 45)
fuzz: elapsed: 27s, execs: 3732331 (137872/sec), new interesting: 37 (total: 45)
fuzz: elapsed: 30s, execs: 4150770 (139518/sec), new interesting: 37 (total: 45)
^Cfuzz: elapsed: 30s, execs: 4177381 (102922/sec), new interesting: 37 (total: 45)
PASS
ok utils/retry 30.277s
序4:总结
模糊测试的存在,并不是为了替代原单元测试,而是为单元测试提供更好的保障,是一个补充方案,而非替代方案。
单元测试的局限性在于,你只能用预期的输入进行测试;模糊测试在发现暴露出奇怪行为的意外输入方面非常出色。一个好的模糊测试系统也会对被测试的代码进行分析,因此它可以有效地产生输入,从而扩大代码覆盖面。
参考资料:
https://go.dev/doc/tutorial/fuzz
今天的分享就到这里了,如果本文对您有帮助,还是希望多多点赞,转发,分享,谢谢您的支持~
更多内容,可点击下方关注公众号~
文章评论