用 go test 测并发需手动启 goroutine 并用 sync.WaitGroup 等待完成,避免 time.Sleep;t.Parallel() 仅让不同 TestXxx 函数并行,不控制函数内并发;正确做法是构造可验证副作用、显式同步、隔离共享状态,并配合 -race 检测竞态。

怎么用 go test 跑并发场景?
直接用 go test 本身不支持“启动 N 个 goroutine 并发执行测试逻辑”,它默认是串行跑每个 TestXxx 函数。真要测并发行为,得自己在测试函数里手动启 goroutine,并配合适当的同步机制。
常见错误是写完 go f() 就结束测试函数,结果主 goroutine 退出、子 goroutine 被强制终止,测试看似“通过”实则没跑完。
- 必须用
sync.WaitGroup或chan等方式等所有并发任务完成 - 避免在测试中用
time.Sleep等待——它不可靠,CI 环境负载高时容易误判 - 如果测试涉及共享状态(如全局 map、计数器),记得加
sync.Mutex或改用sync/atomic
testing.T.Parallel() 是干啥的?能测并发逻辑吗?
不能。testing.T.Parallel() 的作用只是让多个 TestXxx 函数**彼此并行运行**(比如 TestLogin 和 TestLogout 同时跑),和你代码里是否用了 goroutine 完全无关。它不提供任何并发控制能力,也不影响单个测试函数内部的行为。
典型误用:在 TestConcurrentUpdate 里调了 t.Parallel(),然后以为这样就能“测出竞态”——其实只是让这个测试和其他测试并行跑,自己内部还是串行执行。
立即学习“go语言免费学习笔记(深入)”;
- 它只对顶层测试函数生效,对子 goroutine 无感知
- 开启后,该测试不能调用
t.Parallel()的兄弟测试会跳过(因为它们不是同一层级) - 真正想暴露竞态,得开
go test -race,而不是依赖t.Parallel()
怎么写一个靠谱的并发正确性测试?
核心是:构造可验证的并发副作用 + 显式等待 + 隔离干扰。比如测试一个并发安全的计数器:
func TestCounter_ConcurrentInc(t *testing.T) {
var c Counter
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for j := 0; j < 100; j++ {
c.Inc()
}
}()
}
wg.Wait()
if got := c.Load(); got != 10000 {
t.Errorf("expected 10000, got %d", got)
}
}
注意几个关键点:
- 不要在 goroutine 闭包里直接用循环变量
i(上面示例没犯这个错,但新手常犯) - 被测对象(如
c)要是测试函数内定义的,避免和其他测试共享状态 - 用
go test -race运行,才能捕获实际的读写冲突;光靠断言值对不对,可能掩盖竞态 - 如果被测函数有 I/O(如 HTTP 请求、文件读写),建议用
httptest.Server或临时目录隔离,否则并发下容易端口冲突或文件覆盖
为什么本地跑通了,CI 上却偶尔失败?
本质是并发测试对环境更敏感。最常见原因有三个:
- 资源竞争:比如多个测试共用同一个端口、临时文件名、数据库表名,CI 上测试并行度更高,冲突概率上升
- 时间假设失效:比如测试里写了 “等 50ms 后检查结果”,本地快就过了,CI 上 GC 暂停或调度延迟导致超时
- 未清理状态:前一个测试写入了全局变量或缓存,下一个并发测试读到了脏数据(尤其用
t.Parallel()时更容易暴露)
解决方向很明确:所有测试必须自治。端口用 0 让系统分配,文件用 os.MkdirTemp,数据库用内存实例或每次清库。别省这几行代码,不然花半天 debug CI 失败不值得。










