go模糊测试要求函数名以fuzz开头且大驼峰、参数为*testing.f、文件名含_test.go后缀;必须调用f.add()提供与fuzz函数参数类型一致的种子,至少覆盖合法值、非法格式和边界值。

函数名和文件名必须严格匹配规则
Go 的模糊测试不是随便写个函数就能跑起来的,Fuzz开头、大驼峰、*testing.F参数——三者缺一不可,否则 go test -fuzz=... 直接静默跳过。更隐蔽的问题是:文件名没带 _test.go 后缀,或者函数定义在非测试文件里,命令会提示 no fuzz tests to run,但不报错,容易误以为环境没配好。
-
FuzzParseUser✅ 正确;fuzzParseUser❌ 不识别;Fuzzparseuser❌ 首字母后必须大写 - 必须放在
xxx_test.go文件中,哪怕只是空的package main测试文件也不行 - 一个文件里可以有多个
FuzzXxx函数,但每个函数只能调用一次f.Fuzz(),多调会 panic
f.Add() 不是可选的,而是收敛速度的关键
很多人跳过 f.Add(),指望 fuzzer 靠纯随机变异撞上边界条件——这在结构化输入(比如 JSON、URL、协议帧)里基本无效。没有种子,它可能花 10 分钟还在试 "a"、"aa"、"aaa",根本碰不到 {"arr":[{},{},...]} 这种嵌套结构。
- 种子类型必须和
f.Fuzz()闭包参数完全一致:如果闭包是func(t *testing.T, data []byte),f.Add()就得传[]byte,不能传string - 推荐至少覆盖三类种子:合法典型值(
{"name":"a"})、非法格式({"name":缺右括号)、边界值(strings.Repeat("x", 1) - 含 Unicode 控制字符或
\x00的种子特别有用,比如f.Add([]byte(`{"name":"\u0000evil"}`)),能快速暴露 UTF-8 校验缺失问题
panic 是默认捕获目标,但 recover 可能失效
Go 原生 fuzz 最擅长抓 panic,比如 index out of range、nil pointer dereference、invalid memory address。但它不会帮你发现“recover 了但状态已损坏”这类问题——比如解析器 recover 之后继续返回部分解析结果,下游逻辑却基于错误假设运行。
- 如果被测函数内部启了 goroutine 并 panic,主 goroutine 不会中断,fuzzer 也看不到,必须确保所有逻辑在当前 goroutine 执行
- 避免在
f.Fuzz()闭包里做耗时操作(如全量解码再校验),否则迭代太慢;可先快速检查长度、括号平衡、基础结构,再进主逻辑 - 对数值运算,别依赖“加完不 panic 就安全”——
int64(math.MaxInt64) + 1会静默回绕,需用math.AddOverflow主动检测
运行命令和崩溃复现要注意路径与缓存
go test -fuzz=FuzzParseUser -fuzztime=30s 看似简单,但实际常卡在路径或缓存上。默认会在当前包下生成 fuzz/corpus/ 和 fuzz/crashers/,如果权限不足或磁盘满,会静默失败;更麻烦的是,不同 Go 版本或 GOPATH 设置可能导致语料无法复用。
- 建议显式指定缓存目录:
go test -fuzz=FuzzParseUser -fuzzcachedir=./fuzzcache -fuzztime=1m - 崩溃用例保存为二进制文件(如
00a7e1),直接cat fuzz/crashers/00a7e1可看到原始输入,但注意它是 raw bytes,可能含不可见控制字符 - 复现 crash 时,不要手动改输入再跑单元测试——要确保用完全相同的 fuzz driver 和 seed,否则可能因副作用(如全局变量、时间戳)导致无法稳定触发
最常被忽略的一点:fuzz target 必须是纯函数。任何对外部状态的读写(文件、网络、环境变量、time.Now())都会让输入-输出关系不稳定,fuzzer 无法最小化 crash 输入,也就失去了自动定位根因的能力。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











