go单元测试必须严格遵循约定:文件名以_test.go结尾、函数名以test开头且首字母大写、与被测代码同包;必须用t.run隔离子测试并避免变量捕获;推荐表驱动测试,精准校验error和复杂返回值;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 工具忽略
t.Run 为什么不是“锦上添花”,而是必须用
不用 t.Run,多个测试 case 共享变量、defer、panic 会互相污染,失败时根本看不出是哪个输入出了问题。
- 循环中必须写
tt := tt,否则所有子测试看到的都是最后一次迭代的值——这是 Go 测试最常踩的坑 - 子测试名别用空格或斜杠,比如
"case/1",否则go test -run TestParseConfig/case/1会匹配失败(shell 层面就解析错了) -
t.Parallel()只能放在t.Run内部,且子测试之间不能共享状态;外部再调一次t.Parallel()会 panic
表驱动测试(table-driven)怎么组织才不翻车
把输入、期望输出、错误预期打包进结构体切片,不是为了“看起来高级”,而是为了快速加 case、一眼看出覆盖缺口、避免 if 嵌套爆炸。
- 每个 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 或 gomonkey 这类库 patch 函数,它们不兼容 go tip,且让测试变成“改了源码才过”,不是真验证逻辑
- 时间相关逻辑必须注入可控的
clock或time.Now替代函数,而不是靠time.Sleep等真实时间——这会让测试慢、不稳定、无法覆盖边界
最难的不是写测试,是让测试真正反映代码行为:mock 是否隔离了副作用、time 是否可控、error 路径是否全覆盖、并行子测试有没有隐式共享状态——这些点稍一松懈,测试就变成“绿灯幻觉”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











