t.Run不是语法糖,而是测试结构分水岭;必须显式拷贝循环变量、避免斜杠命名、正确调用t.Parallel()、精准匹配完整路径,并依共享资源决定拆合逻辑。

t.Run 不是语法糖,它是测试结构的分水岭;不用它,就等于放弃精准定位、并发控制和可维护性。
循环中调用 t.Run 必须显式拷贝变量
这是最常静默失败的坑:for 循环遍历测试用例,直接在闭包里用 tt,所有子测试实际跑的都是最后一组数据。
原因在于 Go 闭包捕获的是变量地址,不是值。哪怕 tt 是 struct,字段全是值类型,也逃不过这个陷阱。
- 必须在循环体第一行写
tt := tt—— 这行不能省,IDE 或staticcheck会报SA9003,别忽略 - 如果用
rangemap,同样要拷贝 key 和 value,k, v := k, v - 不拷贝的后果不是 panic,而是测试“看似通过”,实则漏测,CI 里更难发现
t.Run 名字里为什么不能用斜杠 /
/ 是 go test 解析嵌套路径的分隔符,但 t.Run 本身不支持物理嵌套。你写 t.Run("JSON/valid", ...),框架只是把名字当字符串注册,日志里显示成树状,但匹配逻辑很脆弱。
立即学习“go语言免费学习笔记(深入)”;
-
go test -run TestParse/valid会同时匹配TestParse/valid和TestParse/valid_json,因为正则默认前缀匹配 - 名字以
/开头、结尾,或含//,go test直接报错:test name "xxx" contains illegal characters - 推荐用下划线:
"json_valid"、"empty_input";如需分组语义,靠父子关系自然形成路径,比如父测试叫TestParse,子测试叫"json_valid",完整路径就是TestParse/json_valid,过滤稳定
子测试并发必须显式调用 t.Parallel()
t.Parallel() 不是声明,是运行时指令,且只在 setup 完成后、断言前调用才有效。放错位置等于没写。
- 必须放在子测试函数体第一行(或至少在任何状态读写之前),否则竞态检测可能失效
- 父测试里调
t.Parallel()没用,它只对当前子测试生效 - 并发子测试不能共享非线程安全资源(如全局 map、未加锁的文件句柄),否则
go test -race会报 data race - 如果子测试之间依赖同一份初始化结果(比如共用一个 mock server),就别并发 —— 并发不是目标,并发+正确才是
go test -run 怎么精准命中单个子测试
-run 匹配的是完整测试路径,格式为 ParentName/ChildName,不是子测试名本身。
- 运行单个:
go test -run "TestParse/json_valid"—— 引号防 shell 解析空格,双引号或单引号都行 - 匹配一组:
go test -run "TestParse/json_.*",注意正则语法,.要转义成\.才匹配字面点 - 大小写不敏感:
go test -run "(?i)testparse/json_valid" - 切忌只写
-run "json_valid",它可能意外触发TestJSON/json_valid、TestConfig/json_valid等多个测试
真正难的不是写对 t.Run,而是判断哪些逻辑该拆、哪些该合——比如子测试间若要复用临时目录或 DB 连接,又要求顺序执行,那大概率说明它们本就不该是独立子测试,而该合并或改用 TestMain 统一管理生命周期。


















