Go原生fuzzing要求严格:函数名必须以Fuzz开头且首字母大写,参数唯一且为*testing.F;f.Fuzz()回调内仅支持基础可序列化类型,断言和panic须在回调中,种子需用f.Add()显式注入。

Go 原生 fuzzing 不是“多写几个随机字符串”就能跑起来的测试,它对函数签名、参数类型、校验位置有硬性约束;写错就静默跳过,连报错都没有——这是新手踩坑最频繁的地方。
func FuzzXxx(f *testing.F) 签名必须严格匹配
Go 的 go test -fuzz=. 只识别以 Fuzz 开头(首字母大写)、且唯一参数为 *testing.F 的函数。写成 func fuzzXxx(f *testing.F)(小写 f)或 func TestFuzz(t *testing.T) 都会被完全忽略,命令行不报错、不提示、不执行。
-
Fuzz必须大写开头,例如FuzzProcessPrompt,不是fuzzProcessPrompt - 参数只能是
*testing.F,不能是testing.F(少指针)、*testing.T(类型错)或任何其他类型 - 函数体内不能调用
t.Error、t.Log等*testing.T方法——*testing.F没有这些方法
f.Fuzz() 回调里只能传基础可序列化类型
f.Fuzz() 的回调函数参数类型决定了 fuzz engine 能生成什么输入。Go 不支持在回调里传 struct{ io.Reader }、map[string]int、含指针或函数字段的 struct,编译会直接失败,错误信息是 cannot fuzz type xxx。
- 安全类型:
string、[]byte、int/int64、float64、bool - 复合类型仅限 flat struct:所有字段必须是上述安全类型,且不能含 slice of struct、interface{}、channel、func —— 例如
type args struct{ A int; B string }可以,但type bad struct{ Data *[]byte }不行 - 文本类逻辑(如 JSON 解析、Prompt 处理)优先用
string或[]byte,覆盖 UTF-8 边界、空字节、超长重复字符更直接
所有断言和 panic 必须放在 f.Fuzz() 回调内部
模糊测试不是先跑一遍再检查结果。fuzz engine 每次调用 f.Fuzz() 回调时,都是一次独立执行,上下文不保留。把 if result != expected 写在回调外,等于没校验;在回调里调用 panic 或触发未捕获错误,才会被识别为失败用例。
立即学习“go语言免费学习笔记(深入)”;
- 正确姿势:
f.Fuzz(func(t *testing.T, input string) { result, err := ProcessPrompt(input); if err != nil { t.Fatal(err) } }) - 错误姿势:在
f.Fuzz()外写if len(result) == 0 { t.Error(...) }—— 这段代码根本不会被执行 - 回调内避免耗时操作:HTTP 请求、文件读写、sleep 等,fuzz 可能执行数万次,超时会导致整个测试中断
seed corpus 必须用 f.Add() 显式注入,不能靠全局变量或文件
Go fuzz 不读 testdata/ 目录,也不执行包级 init()。所有初始种子(比如易触发 panic 的 "\u0000"、"{\x00"、超长字符串)必须在 FuzzXxx 函数开头用 f.Add() 注册,否则引擎从纯随机起点开始,可能几小时都碰不到关键边界。
-
f.Add("{}", "{\"a\":1}", "\xc0\x80", strings.Repeat("a", 100000))—— UTF-8 非法序列、JSON 边界、OOM 触发点都要手动加 -
f.Add()可多次调用,每次传入一组与f.Fuzz()参数类型一致的值(如回调是func(t *testing.T, a string, b int),则f.Add("x", 42)) - 不要依赖环境变量或配置文件预设状态:每次 fuzz 运行都是干净的 goroutine,无共享状态
真正容易被忽略的,是 fuzz 引擎对「可序列化」的隐式要求——它不是在运行你的函数,而是在序列化/反序列化输入数据并做位级变异。一旦类型不满足这个前提,连第一轮变异都启动不了,但你只会看到 “no fuzz tests found” 这种误导性输出,而不是具体哪一行错了。


















