Go测试由go test和testing包原生支持,需严格遵循_test.go命名、同包存放、TestXxx函数定义等规则;慎用t.Parallel()避免竞态;推荐testify提升可读性但需注意assert与require语义差异。

Go 的测试不是靠第三方库驱动的,go test 命令和 testing 包原生支持,写法简单但容易忽略边界和组织逻辑。
测试文件命名和位置必须严格匹配
Go 要求测试文件名以 _test.go 结尾,且与被测代码放在同一包(即同一目录)下。例如,utils.go 的测试必须写在 utils_test.go 中;如果误写成 test_utils.go 或放在其他目录,go test 会直接跳过。
- 测试文件中可定义
TestXxx函数(首字母大写,Xxx 非小写),否则不被识别为测试用例 - 若想测试内部未导出函数(如
parseInput),测试文件必须和源文件同包(不能新建utils_test包) - 跨包测试只能调用已导出(首字母大写)的符号,无法直接测私有逻辑
使用 t.Run 组织子测试,避免变量捕获陷阱
循环中直接用 for _, tc := range cases 调 t.Run 时,闭包常捕获循环变量地址,导致所有子测试读到同一个 tc 值。这是新手高频翻车点。
// ❌ 错误写法:所有子测试共享 tc 的最终值
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := SomeFunc(tc.input); got != tc.want {
t.Errorf("got %v, want %v", got, tc.want)
}
})
}
// ✅ 正确写法:显式复制变量或用索引
for _, tc := range tests {
tc := tc // 创建新变量绑定
t.Run(tc.name, func(t *testing.T) {
if got := SomeFunc(tc.input); got != tc.want {
t.Errorf("got %v, want %v", got, tc.want)
}
})
}
慎用 t.Parallel(),注意资源竞争和初始化顺序
加 t.Parallel() 可加速测试执行,但它只保证子测试间并行,不保证与其他测试(包括非并行测试)隔离。若多个测试共用全局状态(如修改包级变量、临时文件、数据库连接池),极易出现随机失败。
立即学习“go语言免费学习笔记(深入)”;
- 仅对完全无状态、不依赖外部资源的纯函数测试启用
t.Parallel() - 涉及文件操作、环境变量修改、HTTP server 启动等,必须串行或显式加锁/重置
- 不要在
init()或包级变量初始化里做副作用操作,否则t.Parallel()下行为不可控
用 testify/assert 或 require 提升可读性,但别替代 t.Error 语义
原生 t.Errorf 输出信息简陋,testify/assert 能自动打印期望/实际值差异,但要注意:assert.Equal 失败后继续执行,require.Equal 则直接 return。后者适合前置校验(如检查 error 是否为 nil),前者适合多断言场景。
- 避免在循环内大量用
require.Xxx—— 一次失败就中断,看不到后续 case 结果 - CI 环境中建议优先用原生
t.Error或t.Fatal,减少对第三方库的依赖和版本漂移风险 - 若用
testify,确保go.mod中固定版本,避免因 minor 更新导致断言行为变化(如 v1.8.x 对浮点比较的容忍策略调整)
Go 测试真正的难点不在语法,而在如何让测试既快又稳——它要求你提前想清楚哪些状态是共享的、哪些 IO 是可 mock 的、哪些 panic 是该捕获的。写完一个 TestXxx 后,删掉一行代码再跑一遍,看失败信息是否清晰指向问题根源,这才是检验测试质量最朴素的标准。










