t.parallel() 仅声明子测试可并行执行,不提供任何并发安全机制;禁止在其中读写共享变量,必须用 sync.mutex 或 atomic 保护状态,并启用 -race 检测竞态。

t.Parallel() 不提供并发安全,别把它当 sync.Mutex 用
加了 t.Parallel() 就以为测试函数能共享变量?结果是随机失败、竞态报错、本地绿 CI 红。它只告诉测试运行器“这个子测试可以和其他并行跑”,不加锁、不隔离、不同步。
- 禁止在并行测试中读写包级变量、全局
map、未加锁的切片、文件句柄或数据库连接池 - 需要验证并发行为(比如库存扣减、计数器累加),单独写
TestConcurrentDeduct,内部用sync.WaitGroup控制 goroutine 生命周期,用atomic或sync.Mutex保护共享状态 - 必须检测数据竞争:CI 中固定加
go test -race,本地调试时也别跳过——-race会显著拖慢执行,但它捕获的是真实运行时问题,不是模拟
并发测试里怎么写断言才不 panic
assert.NoError(t, err) 在 defer 或 if 里调用会直接触发 t.Fatal(),导致 defer 清理逻辑跳过、后续代码中断——这不是你想要的“断言失败”,而是测试流程被意外截断。
- 别写
defer assert.NoError(t, db.Close())——t.Fatal()会让 defer 失效 - 别写
if assert.NoError(t, err) { ... }—— 断言失败时已终止,花括号内永不执行 - 更稳妥:先显式检查错误
if err != nil { t.Fatal(err) };或换require.NoError(t, err),它不返回bool,语义上就是“失败即终止”,无歧义
表驱动并发测试的子名称必须唯一
用 t.Run("valid", fn) 写多个 case,名字重复,go test -run=TestParse/valid 只匹配第一个,其余静默跳过。Go 运行时虽会自动重命名为 valid_1、valid_2,但 -run 参数不识别这种命名规则。
- 子测试名需唯一且可区分:推荐用
fmt.Sprintf("concurrent_%d_routines", n)或哈希摘要,避免含/、空格、特殊字符 - 调试时精确命中单个 case:
go test -v -run=TestConcurrentDeduct/concurrent_100_routines - 别依赖自动编号——它不可控,也不可测
HTTP 并发测试要隔离网络、控制 client、设超时
别在测试里 go run main.go 启服务再用 http.Get 打,端口冲突、TLS 错误、清理遗漏全都会让测试不稳定。真实微服务测试中最常被忽略的是「测试进程与服务进程的网络隔离」。
- 优先用
httptest.NewRecorder():绕过网络层,直接传*http.Request给 handler,快且确定 - 真要测 HTTP 客户端行为(如重试、重定向、Cookie)才用
httptest.NewServer(),记得用defer srv.Close() - 每个 goroutine 必须用独立
*http.Client或至少设置Timeout(建议10 * time.Second),禁用http.DefaultClient - 用
sync.WaitGroup等待所有请求完成,defer wg.Done()必须放在 goroutine 内部
并发测试最难的不是写 goroutine,而是确认你测的到底是逻辑 bug、竞态条件,还是资源泄漏——runtime.NumGoroutine() 持续上涨、pprof 显示大量阻塞在 net/http 或 database/sql,大概率不是并发没写对,而是清理没做全。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











