go子测试必须用t.run()包裹,否则panic;禁止在子测试中调用log.fatal等非测试方法;t.parallel()后不可再调t.log/t.error;子基准测试不支持独立计时,需手动重置;子测试名禁用/等特殊字符;并发子测试需避免共享可变状态。

Go 子测试怎么写才不会 panic
子测试没用 t.Run() 包裹,或者在子测试里调用了 t.Fatal() 以外的顶层测试方法(比如直接 log.Fatal()),就会 panic 并中断整个测试流程。根本原因是子测试必须通过 t.Run() 注册进父测试上下文,否则 Go 测试框架不识别其生命周期。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 所有子测试逻辑必须放在
t.Run()的回调函数里,不能“裸写” - 子测试中禁止使用
t.Parallel()后再调用t.Log()或t.Error()—— 这会导致竞态(Go 1.21+ 会报testing: t.Log from non-test goroutine) - 如果需要共享 setup/teardown,用闭包捕获变量,别依赖外部作用域的可变状态
- 示例正确写法:
func TestAPI(t *testing.T) { t.Run("status_200", func(t *testing.T) { t.Parallel() resp := callEndpoint("/health") if resp.StatusCode != 200 { t.Errorf("expected 200, got %d", resp.StatusCode) } }) }
子基准测试(sub-benchmarks)为什么跑不起来
Go 的 testing.B 不支持 b.Run() 返回新的 *testing.B,所以你不能像子测试那样嵌套调用 b.Run() 来启动子基准——它只接受 func(*testing.B),且内部不支持并发或重置计时器。最常见错误是误以为子基准能独立计时,结果所有子项共用同一轮 b.N,数据完全失真。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 子基准测试只是逻辑分组,不是独立 benchmark;
b.Run()内部仍共享父b.N,不能单独控制迭代次数 - 想对比不同实现,必须拆成顶层
BenchmarkXxx函数,否则无法被go test -bench=.单独识别和排序 - 若仅用于组织代码,可用
b.Run(),但需手动重置计时:在子项开头调用b.ResetTimer(),结尾调用b.StopTimer()(注意顺序) - 错误示例:
b.Run("json", func(b *testing.B) { b.ReportAllocs() })——ReportAllocs()必须在顶层Benchmark函数里调用才生效
子测试名含斜杠导致 go test -run 失效
子测试名里用了 /(比如 t.Run("user/create", ...)),会导致 go test -run "user" 匹配失败——Go 的 -run 参数按字面路径匹配,/ 被当作层级分隔符,而非普通字符。实际运行时,该子测试会被跳过,且无任何提示。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 子测试名避免使用
/、.、[、]等特殊字符;推荐用下划线_或连字符- - 如果必须模拟层级感,用命名约定即可,例如
"user_create_success",而不是"user/create" - 调试时用
go test -v -run="^TestName$" 2>&1 | grep "=== RUN"查看实际注册的测试名,确认是否被截断或转义 - CI 中若用正则匹配子测试名,注意 Go 测试输出中的名字是经过转义的(空格变下划线,斜杠保留但不参与通配)
子测试并发(t.Parallel)踩内存竞争的坑
多个子测试启用 t.Parallel() 后共享父测试的局部变量(比如一个全局 map 或 struct 字段),很容易触发 data race —— 因为它们实际运行在不同 goroutine,而 Go 测试框架不会自动加锁或隔离作用域。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 每个
t.Run()回调函数内,用:=声明全新变量,别复用父测试里的可变对象 - 如果必须共享资源(如 mock DB),用
sync.Mutex或sync.Once控制初始化,读写都加锁 - 开启 race 检测:
go test -race,尤其在 CI 阶段强制启用;本地开发时也建议常开 - 注意:子测试并发不等于并行;默认 GOMAXPROCS=1 时,
t.Parallel()只是让调度器有机会切换,未必提速
go test -v -run="TestName/subname" 单独复现,比猜快得多。










