Go单元测试必须满足硬性条件才能执行:文件名以_test.go结尾、函数名以Test开头且驼峰命名、与被测代码同包;需用t.Run隔离子测试并避免变量捕获;推荐表驱动测试和接口抽象mock,禁用运行时patch。

Go 单元测试不是“写了就能跑”,而是必须严格满足几个硬性条件,否则 go test 会静默跳过——比如函数名写成 testAdd 或 Test_add,参数漏掉 *testing.T,或者测试文件没放在同包下,全都不执行。
TestXxx 函数和 *_test.go 文件怎么才被识别
Go 测试框架只认两件事:文件名以 _test.go 结尾,函数名是 Test 开头 + 首字母大写的驼峰命名。其他任何变体都无效。
-
TestCalculateTotal✅ 合规;testCalculateTotal❌ 不执行(小写开头);Test_calculate_total❌ 不执行(下划线后接小写) - 测试文件必须和被测代码在同一个包里,不能丢进
internal/testutil这类独立包——否则访问不到未导出字段或函数 - 文件名建议用
calculate_test.go,而不是test_calculate.go;后者虽能运行,但部分 CI 工具或 IDE 插件可能忽略
t.Run 为什么必须用,而不是可选
不用 t.Run,多个测试 case 共享变量、defer、panic 会互相污染,失败时根本看不出是哪个输入出了问题。
- 循环中必须写
tt := tt,否则所有子测试看到的都是最后一次迭代的值——这是 Go 测试最常踩的坑 - 子测试名别用空格或斜杠,比如
"case/1",否则go test -run TestParseConfig/case/1在 shell 层面就解析失败 -
t.Parallel()只能放在t.Run内部,且子测试之间不能共享状态;外部再调一次会 panic
表驱动测试(table-driven)怎么组织不翻车
把输入、期望输出、错误预期打包进结构体切片,不是为了“看起来高级”,而是为了快速加 case、一眼看出覆盖缺口、避免 if 嵌套爆炸。
立即学习“go语言免费学习笔记(深入)”;
- 每个 case 字段命名要直白:
name、input、want、errIsNil比tc或data强得多 - 对
error的检查别只写if err != nil,要明确期望它非空还是为空——比如除零操作必须err != nil,而正常路径必须err == nil - 复杂返回值别用
==比较,改用reflect.DeepEqual(got, tt.want),否则 struct 字段顺序或指针差异会导致误报
mock 外部依赖时最容易栽在哪
Go 没有运行时 patch 能力,硬改全局变量(比如直接赋值 time.Now = func() time.Time {})在并发测试里会崩,而且无法隔离。
- 真正安全的做法是:把依赖抽象成接口,构造时传入——比如把
http.Client换成HTTPDoer接口,测试时传个 fake 实现 - 别用
monkey这类运行时 patch 库(已被证实不适用于并发测试场景) - 时间依赖必须可控注入,例如把
time.Now作为函数参数或结构体字段传入,而非直接调用
最难的不是写测试,而是让测试真正隔离、可重复、不依赖环境——尤其是时间、网络、数据库这些外部边界。一旦 mock 方式错了,测试就从保障变成幻觉。


















