Go 模糊测试实战:用 testing.F 与 fuzz target 找出隐藏的边界崩溃

test12026-10-210 次阅读

什么是 fuzz 测试

单元测试靠你手写的样例,覆盖的是你“想到”的输入。模糊测试让 Go 在种子输入的基础上,自动变异出海量随机输入,持续轰击你的函数,专门抓那些没想到的边界:空串、超长串、特殊字符、负数。Go 1.18 起标准库自带 testing.F,无需第三方框架。

写一个 fuzz target

函数名以 Fuzz 开头,参数是 *testing.F。先 f.Add 放几个种子,再用 f.Fuzz 定义对每一个输入都成立的不变式。

package parse

import (
	"strings"
	"testing"
)

func Normalize(input string) string {
	return strings.TrimSpace(strings.ToLower(input))
}

func FuzzNormalize(f *testing.F) {
	f.Add("  Hello  ")
	f.Add("WORLD")
	f.Fuzz(func(t *testing.T, input string) {
		out := Normalize(input)
		if out != strings.TrimSpace(strings.ToLower(input)) {
			t.Fatalf("unexpected: %q", out)
		}
	})
}

真实案例:解析一行 CSV

解析函数很容易在空字段、前后空格、只有单个逗号这些输入上翻车。下面给一个宽松版本加不变式,至少保证“一定返回一个字段”。

func ParseCSVLine(line string) []string {
	fields := strings.Split(line, ",")
	for i := range fields {
		fields[i] = strings.Trim(fields[i], " ")
	}
	return fields
}

func FuzzParseCSVLine(f *testing.F) {
	f.Add("a,b,c")
	f.Add("")
	f.Fuzz(func(t *testing.T, line string) {
		fields := ParseCSVLine(line)
		if len(fields) == 0 {
			t.Fatal("expected at least one field")
		}
	})
}

运行与语料库

执行 go test -fuzz=FuzzParseCSVLine -fuzztime 30s。一旦崩溃,Go 会把触发崩溃的最小输入写进 testdata/fuzz,并自动生成一条普通单元测试,下次直接回归。之后可以把那条失败输入手动复查、修代码、再跑,形成闭环。

  • 模糊测试跑得久才有价值,建议放进 CI 给足时间预算。
  • 不变式要写得稳:宁可少断言,也别写出本身不成立的断言导致误报。
  • 崩溃输入会被固化成用例,相当于让 fuzz 帮你写了回归测试。
T

test1

文章作者

什么是 fuzz 测试 单元测试靠你手写的样例,覆盖的是你“想到”的输入。模糊测试让 Go 在种子输入的基础上,自动变异...

分类
技术
发布时间
2026-10-21
字数
约 1280 字
阅读
0 次