Go test 默认不递归子目录,需用 ./...;t.Parallel() 不提供并发安全,禁用共享状态;assert 忽略返回值易致 panic,应改用 require;表驱动测试子名称须唯一,避免匹配冲突。

Go test 命令默认不运行子目录,容易漏测
很多团队发现 go test 看似跑完了,但实际只测了当前包顶层文件,internal/ 或 cmd/ 下的子包完全没被覆盖。这是因为 go test 默认只扫描当前目录,不递归——除非显式加 -recursive 或用 ./...。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 统一用
go test ./...启动测试,确保所有子包参与执行 - CI 中禁用
go test(无参数),避免因路径切换导致行为不一致 - 若需排除某些目录(如
examples/),用go test $(go list ./... | grep -v examples),比-exclude更可靠(Go 1.21+ 才支持-exclude)
testing.T 的 Parallel 方法不是并发安全的共享状态
看到 t.Parallel() 就开多个 goroutine 共享变量?这是最常见误用。它只控制测试函数本身的调度顺序,不提供任何同步机制——var count int 在并行测试里会竞态。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 并行测试中禁止读写全局变量、包级变量或外部文件
- 需要模拟并发场景时,用
sync.WaitGroup+ 显式 goroutine 控制,而不是依赖t.Parallel() - 真正想测并发逻辑?写一个独立函数(如
func TestConcurrentUpdate(t *testing.T)),内部启动多 goroutine 并用t.Log记录中间状态,最后断言终态
使用 testify/assert 时忽略错误返回值导致 panic
assert.Equal(t, expected, actual) 返回 bool,但很多人直接调用不接返回值。一旦断言失败,它默认调用 t.Fatal() —— 这没问题;但如果在 defer 里用、或嵌套在 if 条件中,就可能触发未预期的提前退出。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 避免在
defer中调用assert.XXX:defer 是函数退出时执行,而t.Fatal()会终止当前测试函数,导致 defer 不再运行 - 不要用
if assert.NoError(t, err) { ... }:这逻辑上矛盾——assert.NoError失败时已t.Fatal,后续代码根本不会执行 - 更稳妥的做法是先检查错误:
if err != nil { t.Fatal(err) },再用require包(如require.NoError(t, err))替代assert,避免分支干扰
表驱动测试中 t.Name() 与子测试命名冲突
用 t.Run(name, fn) 写表驱动测试时,如果 name 包含斜杠 / 或空格,Go 会自动转义成 _,但更隐蔽的问题是:多个子测试共用同一 name 字符串(比如都叫 "valid"),会导致 go test -run=TestParse/valid 只匹配第一个,其余被跳过。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 子测试名必须唯一且可区分:用
tc.name+tc.input的哈希片段,例如fmt.Sprintf("parse_%s_%x", tc.name, md5.Sum([]byte(tc.input))) - 调试时快速定位:在
t.Run内第一行加t.Logf("input: %q, expect: %v", tc.input, tc.want) - 避免用
t.Name()做业务逻辑判断——它只是测试标识符,不是运行时上下文
测试不是越快越好,而是越能暴露边界条件越好。别省略 error 检查,也别迷信 t.Parallel() 能代替并发逻辑验证——Go 的测试框架轻量,但对“怎么写”很敏感。

















