提高 Go 程序健壮性,Fuzzing 要来了!
大家好,我是煎鱼。
就在前几天,Go1.17 beta1 正式发布:
兴冲冲本想着看一下当初在 Go1.17 的计划中,预计会支持的新特性:模糊测试(Fuzzing)。不过没想到…计划赶不上变化,官方正式宣告 Fuzzing 不会出现在 Go1.17 的新功能中。
煎鱼在悲伤之际,发现 Go 在 dev.fuzz 分支上提供了该功能的 Beta 测试,因此今天带大家一起来深入该特性。
什么是 Fuzzing
Fuzzing 是一种自动测试技术,包括向计算机程序提供随机数据作为输入。然后监测程序是否出现恐慌、断言失败、无限循环等。
Fuzzing 不是使用一个小的、预先定义好的手动创建的输入集(如单元测试),而是用新的案例不断地测试代码,以努力 ”锻炼“ 有关软件的所有方面。
这听起来很 ”难“。但在过去的几年里,Fuzzing 的技术水平有了很大的提高。Fuzzing 不再是需要专业知识才能成功使用的东西,现代模糊测试策略能更快、更有效地找到有用的输入。
在应用程序中,就是你只要引入一个 package,对着 API 一顿用就可以了。
为什么要做 Fuzzing
可能会有小伙伴说,测试?直接人工测试,再把测试数据准备一下,配套 YAPI 等接口管理平台,把自动化接口测试一弄就好了。还需要 Fuzzing 吗?
其实 Fuzzing 是对其他形式的测试、代码审查和静态分析的补充,它通过生成一个随机测试用例去覆盖人为测不到的各种复杂场景。而这些输入几乎不可能人为去构造,总会被传统测试所遗漏。
发生在身边的 Fuzzing
实际上 Go-fuzz 对 Go 标准库进行过测试,依然这这之中发现了 200 多个 bug:
这还是建立在标准库已经比较成熟,且由非常有经验的开发者编写,在生产中使用多年的情况下,依然有如此多的问题。
快速上手
我们需要在本地执行如下命令,需开启 GO111MODULE 和天梯:
$ go get golang.org/dl/gotip
$ gotip download dev.fuzz
执行完毕后会从 dev.fuzz 分支构建 Go 工具链,同时 gotip 可以作为 go 命令的替代者命令,也就是可以运行 Fuzzing 的相关代码了。
// +build gofuzzbeta
package tests
import (
"net/url"
"reflect"
"testing"
)
func FuzzParseQuery(f *testing.F) {
f.Add("x=1&y=2")
f.Fuzz(func(t *testing.T, queryStr string) {
query, err := url.ParseQuery(queryStr)
if err != nil {
t.Skip()
}
queryStr2 := query.Encode()
query2, err := url.ParseQuery(queryStr2)
if err != nil {
t.Fatalf("ParseQuery failed to decode a valid encoded query %s: %v", queryStr2, err)
}
if !reflect.DeepEqual(query, query2) {
t.Errorf("ParseQuery gave different query after being encoded\nbefore: %v\nafter: %v", query, query2)
}
})
}
在相应的目录下执行 gotip test -fuzz=FuzzParseQuery
命令,输出结果:
fuzzing, elapsed: 3.0s, execs: 319 (106/sec), workers: 4, interesting: 15
fuzzing, elapsed: 6.0s, execs: 665 (111/sec), workers: 4, interesting: 15
fuzzing, elapsed: 9.0s, execs: 1019 (113/sec), workers: 4, interesting: 15
fuzzing, elapsed: 12.0s, execs: 1400 (117/sec), workers: 4, interesting: 15
...
需要注意的是:
- Fuzzing 会消耗大量的内存,在运行时会影响到机器的性能(一运行,小风扇就转了起来)。
- Fuzzing 会默认使用
GOMAXPROCS
相同的核数,可以通过执行-parallel
标识来控制数量。 - Fuzzing 会默认在运行时,将扩大测试范围的数值写入
$GOCACHE/fuzz
内的模糊缓存目录,目前是没有限制的,可以通过运行gotip clean -fuzzcache
来清除。
总结
在今天这篇文章中,我们介绍了 Fuzzing 是什么。 简单而言,模糊测试(Fuzzing)在真实环境已经被验证了其有效性,其可以随机生成测试用例去覆盖人为测不到的各种复杂场景,带来很大的收益。
在接下来中,除了依赖开源的 go-fuzz 库外,Go 语言也正式的在支持 Fuzzing,虽然他放了 Go1.17 的鸽子…
这会对构建 Go 程序健壮性的又一强心剂!