Go 模糊测试
Go 1.18 内置的模糊测试框架,通过随机输入自动发现代码中的边界情况和潜在崩溃
#type / concept
#status / growing
#tech / dev
#resource / go
[!info] related notes
- 所属 MOC: Go 测试与质量
- 前置概念: [[go-testing-basics|Go 测试基础]]
- 并列概念: [[go-benchmark-testing|Go 基准测试]]
Go 模糊测试
一句话定义
Go 1.18 内置的模糊测试(fuzzing)允许你定义输入种子,由测试引擎自动生成大量随机输入,持续寻找代码中的崩溃、死锁和逻辑错误。
核心机制 / 工作原理
模糊测试函数以 Fuzz 开头,接收 *testing.F 参数:
func FuzzReverse(f *testing.F) {
// 种子语料:提供有意义的初始输入
f.Add("hello")
f.Add("世界")
f.Add("")
// 模糊目标:每个输入都会调用此函数
f.Fuzz(func(t *testing.T, input string) {
rev := Reverse(input)
doubleRev := Reverse(rev)
if input != doubleRev {
t.Errorf("Reverse(Reverse(%q)) != %q", input, input)
}
})
}
执行流程:
go test -fuzz=FuzzReverse启动模糊测试- 引擎先运行种子语料(seed corpus)作为热身
- 引擎对种子进行变异(mutation):随机修改字节、翻转位、插入/删除片段
- 每次变异后的输入传入 fuzz target 函数
- 如果触发了 panic 或
t.Error/t.Fatal,引擎保存该输入到语料库 - 保存的失败用例会在后续
go test中作为回归测试运行
支持的变异类型:
f.Add 接受的类型决定了变异策略。支持的类型包括:string、[]byte、int、int8/int16/int32/int64、uint 系列、float32/float64、bool、rune。多参数的 fuzz target 可以组合多种类型。
语料库管理:
# 运行模糊测试(持续运行直到发现失败或 Ctrl+C)
go test -fuzz=FuzzReverse -fuzztime=30s
# 仅运行语料库中的回归测试
go test -run=FuzzReverse
# 语料库存放在 testdata/fuzz/FuzzReverse/ 目录下
# 每个文件是一个失败用例,格式为 go-fuzz 的二进制格式
最小例子 / 最小场景
测试一个 URL 解析函数的健壮性:
func FuzzParseURL(f *testing.F) {
f.Add("https://example.com/path?q=1")
f.Add("http://localhost:8080")
f.Add("ftp://files.example.com/pub")
f.Add("")
f.Fuzz(func(t *testing.T, rawURL string) {
u, err := url.Parse(rawURL)
if err != nil {
return // 解析失败是可接受的
}
// 不变量:解析后重新格式化不应 panic
_ = u.String()
// 不变量:scheme 不应包含空格
if strings.Contains(u.Scheme, " ") {
t.Errorf("scheme contains space: %q", u.Scheme)
}
})
}
为什么重要
- 自动化边界发现:人类编写的测试倾向于覆盖”正常路径”,模糊测试擅长找到那些你没想到的边界输入——超长字符串、Unicode 特殊字符、畸形编码等。
- 零额外依赖:Go 1.18+ 开箱即用,无需安装 go-fuzz 等第三方工具,降低了使用门槛。
- 持续回归:发现的失败用例自动保存为回归测试,防止问题复现。
- 安全审计利器:对解析器、编解码器、协议实现等安全敏感代码特别有效,历史上大量 CVE 由模糊测试发现。
边界与易混淆点
- 模糊测试 vs 基准测试:模糊测试寻找 bug,基准测试衡量性能。两者目标完全不同,不要混淆
Fuzz*和Benchmark*函数。 - 模糊测试 vs 属性测试:属性测试(如
gopter、rapid)是声明式地定义不变量,模糊测试是引擎驱动的变异探索。Go 内置 fuzzing 更接近后者,但可以通过在 fuzz target 中编写断言来模拟属性测试。 - CI 集成策略:在 CI 中运行完整 fuzzing 不现实(可能需要数小时)。推荐做法:CI 中只运行
go test(回归语料库),定期(如每夜构建)运行go test -fuzz发现新用例。 - 确定性与并行:模糊测试默认使用所有 CPU 核心并行运行,因此错误的复现需要使用相同的随机种子(
-fuzzcachedir)。 f.Add的参数数量:fuzz target 的参数数量必须与f.Add调用的参数数量一致,否则编译报错。- 不支持的类型:自定义结构体不能直接作为 fuzz 参数,需要拆解为基本类型或使用
[]byte后手动反序列化。