Go测试规范核心是确保go test稳定可维护地验证行为,需严格命名(_test.go、TestXxx)、禁用log.Fatal/os.Exit、用t.Fatal报错、表驱动控制粒度、合理隔离依赖。

Go 测试代码规范的核心不是“写得像样”,而是让 go test 能稳定、可读、可维护地验证行为——尤其要避免测试本身成为 bug 温床。
测试文件名和函数签名必须严格匹配
Go 要求测试文件以 _test.go 结尾,且测试函数必须是 func TestXxx(t *testing.T) 形式(Xxx 首字母大写)。不满足则 go test 直接忽略。
- 错误写法:
func testAdd(t *testing.T)(首字母小写)、func TestAdd(t *testing.T)放在util.go里(没加_test.go后缀) - 正确路径示例:
calculator.go对应calculator_test.go - 子测试也需遵守命名:用
t.Run("case_name", func(t *testing.T) { ... }),名称字符串不能含空格或斜杠,否则go test -run匹配失败
不要在测试里调用 log.Fatal 或 os.Exit
这类调用会终止整个测试进程,导致后续测试不执行、覆盖率统计中断、CI 流水线误判为“部分通过”。测试出错唯一合法方式是调用 t.Fatal、t.Error 或 t.Fatalf。
- 常见陷阱:被测函数内部用了
log.Fatal,测试时 panic 后无法捕获 —— 应重构该函数,把错误返回出来,或用testify/mock替换日志输出目标 - 若必须测 panic 场景,用
func() { ... }()包裹并配合recover(),但更推荐用assert.Panics(需引入testify/assert) -
t.Log和t.Logf仅在-v模式下输出,不影响测试结果;而fmt.Println会混入标准输出,干扰 CI 日志解析
表驱动测试要控制粒度,避免“一个 case 跑 100 行”
表驱动(table-driven)是 Go 测试主流风格,但滥用会导致调试困难、失败定位模糊、setup/teardown 逻辑耦合过重。
- 每个
testCase结构体字段应聚焦输入/输出/期望,不塞业务逻辑:比如不要放setupFunc字段,而应在循环外统一初始化依赖 - 避免在
for _, tc := range cases内部做耗时操作(如启动 HTTP server、读大文件)——应提取到TestMain或init()中 - 当某个 case 失败时,
t.Errorf("case %q: expected %v, got %v", tc.name, tc.want, got)必须带tc.name,否则你只能靠行号猜是哪个 case 崩了
Mock 和 interface 设计要服务于测试,而非炫技
Go 不强制依赖注入,但测试需要可控的边界。关键不是“用不用 mock”,而是“能否把外部依赖(DB、HTTP、time.Now)隔离掉”。
- 优先用函数变量替代全局状态:比如把
time.Now替换为可设的var nowFunc = time.Now,测试时赋值nowFunc = func() time.Time { return fixedTime } - 对 HTTP 客户端等,直接传入
*http.Client参数比 mock interface 更轻量;只有当接口方法多、行为复杂时才定义 interface + mock 实现 - 慎用
monkey等运行时打桩工具:它破坏类型安全、难以追踪、与 go mod vendor 冲突,只在极少数 legacy 代码中兜底使用
最难的不是写通测试,而是当被测逻辑变更时,测试是否能立刻告诉你“哪里坏了、为什么坏、改哪能修好”——这取决于你有没有把 setup 逻辑收口、error message 写清楚、case 边界划明白。










