这是一个典型的测试隔离问题,通常与全局状态或共享资源有关。crypto/rand 本身是线程安全的,但你的方法 X 或测试代码可能存在状态残留。
问题分析
当测试单独运行时,每个测试都从干净状态开始。但使用 go test ./... 时:
- 测试可能并行执行(默认
-p 值为 GOMAXPROCS)
- 包级别的初始化只执行一次
- 全局变量或
init() 函数中的状态会被所有测试共享
示例场景
假设你的代码类似这样:
// 可能的问题:全局状态
var globalBuffer []byte
func X() error {
if globalBuffer == nil {
globalBuffer = make([]byte, 16)
}
_, err := rand.Read(globalBuffer)
// ... 使用 buffer
return err
}
测试隔离解决方案
1. 使用测试重置函数
func TestX_First(t *testing.T) {
// 保存并重置状态
oldBuffer := globalBuffer
defer func() { globalBuffer = oldBuffer }()
globalBuffer = nil
// 执行测试
}
func TestX_Second(t *testing.T) {
oldBuffer := globalBuffer
defer func() { globalBuffer = oldBuffer }()
globalBuffer = make([]byte, 32)
// 执行测试
}
2. 避免全局状态(推荐)
func X(buffer []byte) error {
_, err := rand.Read(buffer)
return err
}
// 测试代码
func TestX(t *testing.T) {
buffer := make([]byte, 16)
err := X(buffer)
// 断言
}
3. 使用 t.Parallel() 和独立资源
func TestX_Isolated(t *testing.T) {
t.Parallel()
// 每个并行测试使用独立资源
localBuffer := make([]byte, 16)
// 测试逻辑
}
诊断步骤
添加调试代码定位问题:
func TestX_Problematic(t *testing.T) {
t.Logf("Test start - global state: %v", globalState)
defer t.Logf("Test end - global state: %v", globalState)
// 测试逻辑
}
运行测试观察状态变化:
go test -v -p 1 ./... 2>&1 | grep -A2 -B2 "global state"
go test ./... 的行为
- 资源管理:每个包测试独立编译执行,但包内的测试共享进程
- 优化:Go 1.10+ 默认缓存测试结果,可能跳过某些执行
- 内联:编译器优化可能影响测试间的状态
- 执行顺序:默认按字母顺序,但
-shuffle 可以改变
强制隔离执行
如果问题持续,可以强制完全隔离:
# 每个测试在独立进程运行
go test -v -count=1 -p=1 ./...
或者使用 go test -exec:
go test -v -exec "sh -c" ./...
关键是要确保方法 X 和测试代码没有隐式的状态依赖。检查是否有:
- 包级别变量
init() 函数中的初始化
- 单例模式实现
- 缓存或池的复用
通过添加适当的清理代码或重构避免共享状态,可以解决这类测试间相互影响的问题。