go test -p N 控制测试进程并行数,N 为最大并发进程数(需大于0且不超CPU核心数),每个进程内测试函数仍串行执行;t.Parallel() 则在测试函数级启用goroutine并发,需首行调用且独立于 -p 参数。
Go 的 go test 默认串行执行所有测试函数。想让多个 TestXxx 函数同时跑,得用 -p 参数指定最大并行进程数,比如 go test -p 4 表示最多启动 4 个 go test 进程,每个进程仍按顺序跑自己负责的测试函数。这不是「单个测试里开 goroutine」,而是「多个测试函数跨进程并行」。
注意:-p 值不能超过 CPU 核心数(runtime.NumCPU()),超出部分会被截断;它影响的是测试包的构建和执行调度粒度,不是测试逻辑内部行为。
-p 1:强制串行,适合调试或有全局状态冲突的测试-p 0:非法,会报错 invalid value "0" for flag -p: must be greater than 0
GOMAXPROCS 和系统资源限制,不一定等于你指定的值让单个测试函数(如 TestLogin)在运行时与其他测试函数并发执行,必须在函数开头显式调用 t.Parallel()。它不会自动开启,也不依赖 -p —— 即使 -p 1,只要多个测试都调用了 t.Parallel(),它们仍可能被调度到同一进程的不同 goroutine 中并发执行(前提是测试框架认为安全)。
关键规则:
t.Log、t.Error 等任何使用 *testing.T 方法之前调用 t.Parallel()
t,不能假设执行顺序或共享未加锁的变量t.Run)中也可以调用 t.Parallel(),但父测试未调用时,子测试的 Parallel 无效func TestFetchUser(t *testing.T) {
t.Parallel() // 必
须放第一行
user, err := FetchUser(123)
if err != nil {
t.Fatal(err)
}
if user.Name == "" {
t.Fail()
}
}
当多个 t.Parallel() 测试同时写日志或失败时,t.Log 输出可能交错,t.Error 的定位信息也可能指向错误的测试上下文。这不是 bug,是并发执行的自然结果。
缓解方式:
t.TempDir() 替代硬编码路径,确保每个测试有独立目录t.Parallel(),或用 go test -v -run=^TestName$ 单独跑一个用 t.Run 拆分子测试再配合 t.Parallel(),是 Go 中组织数据驱动测试的标准做法。但要注意:只有顶层测试或子测试显式调用 Parallel() 才真正并发;父测试不并发,其所有子测试即使调了 Parallel() 也不会跨父测试并发。
例如:
func TestMathOps(t *testing.T) {
tests := []struct{
a, b, want int
}{{1,2,3}, {2,3,5}}
for _, tt := range tests {
tt := tt // 必须重声明,否则闭包捕获循环变量
t.Run(fmt.Sprintf("Add(%d,%d)", tt.a, tt.b), func(t *testing.T) {
t.Parallel()
if got := tt.a + tt.b; got != tt.want {
t.Errorf("got %d, want %d", got, tt.want)
}
})
}
}
这个例子中,两个子测试会并发执行;但如果把 t.Parallel() 放到外层 TestMathOps 里,效果一样 —— 因为子测试默认继承父测试的并发策略。不过更推荐在子测试里调用,语义更清晰。
别嵌太深:三层以上 t.Run 套用 + Parallel() 容易导致失败定位困难,也增加调度开销。