必须用 t.Run 而不是多个 TestXxx 函数,因其共享 setup/teardown、子测试失败不中断其他、错误路径带层级名、支持精准运行、天然适配表格驱动;需注意命名规范、循环中显式拷贝变量、t.Parallel() 和 t.Helper() 位置正确。

为什么必须用 t.Run 而不是写多个 TestXxx 函数
因为共享 setup/teardown 逻辑——比如打开一次数据库连接、创建一个临时目录、设置环境变量,这些操作在父测试函数里做一次就够了;如果拆成多个独立 TestXxx,每个都会重复执行,既慢又容易污染状态。
- 子测试失败不会中断其他子测试,而
t.Fatal在顶层测试里会直接终止整个函数 - 错误路径带层级名(如
TestParseJSON/empty_string),日志和 IDE 测试视图里一眼定位到具体用例 -
go test -run="TestAdd/positive"可精准运行某条,调试时省掉注释/删代码的麻烦 - 天然适配表格驱动测试,增删用例只需改数据结构,不碰主干逻辑
t.Run 的命名和层级陷阱
名字不是随便起的字符串,它直接影响 -run 过滤行为、IDE 解析和日志分组。Go 把 / 当作层级分隔符,所以传入 "user/login" 会被解析为嵌套结构,但你通常并不需要真嵌套。
- ❌ 避免:用
/拼接名字(如fmt.Sprintf("%s/%s", group, tc.name)),会导致意外缩进和过滤异常 - ✅ 推荐:用下划线或连字符,如
"user_login"、"parse_json_empty" - 名字要描述输入特征,别叫
"case1"或"test_a"——调试时你得再翻代码猜含义 - 重复名字会 panic,尤其在循环中没处理好变量捕获时(见下一条)
循环中用 t.Run 必须显式拷贝变量
这是最常踩的坑:在 for _, tc := range cases 中直接引用 tc,所有子测试闭包实际捕获的是同一个内存地址,最终全看到最后一个用例的值。
家电公司网站源码是一个以米拓为核心进行开发的家电商城网站模板,程序采用metinfo5.3.9 UTF8进行编码,软件包含完整栏目与数据。安装方法:解压上传到空间,访问域名进行安装,安装好后,到后台-安全与效率-数据备份还原,恢复好数据后到设置-基本信息和外观-电脑把网站名称什么的改为自己的即可。默认后台账号:admin 密码:132456注意:如本地测试中127.0.0.1无法正常使用,请换成l
func TestAdd(t *testing.T) {
cases := []struct{ a, b, want int }{{1,2,3}, {0,0,0}}
for _, tc := range cases {
tc := tc // ⚠️ 必须加这一行!否则全部子测试都用最后一个 tc
t.Run(fmt.Sprintf("Add(%d,%d)", tc.a, tc.b), func(t *testing.T) {
if got := Add(tc.a, tc.b); got != tc.want {
t.Errorf("got %d, want %d", got, tc.want)
}
})
}
}
- 不加
tc := tc,运行结果可能是两个子测试都报Add(0,0)的错 - 同理,如果用
range索引,也别直接传i,要i := i - 这个规则和
go启动 goroutine 时一模一样,本质是 Go 闭包绑定变量地址而非值
并行、辅助函数和生命周期隔离
t.Parallel() 和 t.Helper() 都有严格位置要求:前者必须放在子测试函数第一行,后者必须在每个封装的断言函数开头调用,否则失效或误导堆栈。
-
t.Parallel()放在第二行就无效,且只对当前子测试生效,不影响其他子测试是否并行 - 自定义断言函数(如
assertEqual(t, got, want))不加t.Helper(),失败时堆栈指向调用点而非断言内部,排查成本翻倍 - 每个子测试有独立
*testing.T实例,defer清理只作用于本子测试,适合按需建 mock server、临时文件等 - 共用资源(如单例 DB 连接)要自己加锁或确保线程安全,Go 不帮你管
t.Helper() 位置这三点,几乎每个团队初期都会反复踩坑。写完记得跑一遍 go test -run="xxx/yyy" 和 go test -v 看输出是否符合预期。









