该用 testing.t.log 而不是 fmt.println 是因为 t.log 绑定测试用例、仅失败或加 -v 时输出,避免 ci 日志污染;t.fatal 立即终止当前测试函数,因内部调用 t.failnow() 触发 panic 并被框架 recover。

什么时候该用 testing.T.Log 而不是 fmt.Println
testing.T.Log 会把输出和当前测试用例绑定,只在测试失败或加 -v 时才显示;而 fmt.Println 无条件刷屏,CI 环境里会污染日志、干扰失败定位。
- 测试中调试中间值(比如
t.Log("got", result))必须用t.Log,否则跑go test默认不显示 - 不要在循环里高频调用
t.Log,尤其大数据量时——它带锁且格式化开销不小,可能拖慢测试执行 - 如果想让某些日志“始终可见”,用
t.Logf("DEBUG: %v", x)并配合go test -v,别硬编码os.Stdout
为什么 testing.T.Fatal 会立即终止当前测试函数
t.Fatal 内部调用 t.FailNow(),它会直接 panic 并被测试框架 recover,跳过后续语句。这不是普通错误返回,没法用 defer 拦截或靠 return 绕过。
- 常见误用:在
defer里调用t.Fatal—— defer 不会执行,因为t.Fatal已经让当前 goroutine 退出了 - 需要清理资源?先显式调用清理逻辑,再
t.Fatal,例如:close(ch); t.Fatal("channel closed unexpectedly") - 想同时打印错误并终止?用
t.Fatalf("expected %v, got %v", want, got),比t.Log + t.Fatal更简洁安全
t.Fatal 和 t.Error 的行为差异直接影响测试结果
t.Error 只标记失败但继续执行,t.Fatal 标记失败且立刻停止。选错会导致断言漏检或掩盖真正问题。
- 检查前置条件(如文件是否存在、端口是否空闲)用
t.Fatal:后续逻辑依赖它成立,不成立就没必要跑下去 - 验证多个独立字段(如结构体的 name/email/age)用
t.Error:一次看到所有问题,而不是修一个再跑一次 - 注意子测试(
t.Run)里t.Fatal只终止当前子测试,不影响父测试或其他子测试
容易被忽略的并发场景下 t.Log 和 t.Fatal 的陷阱
在 goroutine 里直接调用 t.Log 或 t.Fatal 是未定义行为:*testing.T 不是线程安全的,可能 panic 或丢失输出。
- 绝对不要在 go func() { ... t.Log(...) }() 里用
t.Log—— 改用 channel 收集日志,主 goroutine 统一t.Log - 并发测试中出错要终止?别在 goroutine 里调
t.Fatal,改用t.Errorf+sync.Once控制只报一次,或让主 goroutine 检查 done channel - 如果必须同步失败,用
t.Helper()配合封装函数,但核心仍需确保调用发生在主 goroutine
t.Log 不只是“打印”,它绑定测试生命周期;t.Fatal 不只是“报错”,它控制执行流。漏掉这个认知,很容易写出看似通过、实则跳过关键校验的测试。











