Go测试中t.Log/t.Logf默认不显示,需加-v标志;失败时自动显示,支持子测试绑定和格式化输出,优于fmt.Println。

Go测试中用t.Log和t.Logf输出调试信息
默认情况下,Go测试(go test)不会显示t.Log或t.Logf的输出,除非显式启用日志打印。这是最容易踩的坑:写了日志却看不到,误以为没执行或被跳过。
必须加-v标志才能看到测试函数内的日志:
go test -v
t.Log适合输出简单值,t.Logf支持格式化(类似fmt.Printf):
func TestSomething(t *testing.T) {
x := 42
t.Log("x =", x) // 输出: x = 42
t.Logf("x is %d", x) // 输出: x is 42
}
- 日志只在测试通过时默认隐藏;失败时会自动显示(含
t.Log内容) - 若想强制显示所有日志(包括成功测试),必须加
-v -
t.Log输出带时间戳和测试名前缀,例如:=== RUN TestSomething\n example_test.go:12: x is 42
为什么fmt.Println在测试里不推荐
直接用fmt.Println也能打印,但它绕过了测试框架的生命周期管理,带来几个实际问题:
- 输出无上下文:不带测试名、文件行号、时间戳,难以定位来源
- 并发测试下输出可能交错(
go test -race或-parallel时更明显) - 无法被
go test -json捕获,不利于CI集成或结构化日志分析 - 在子测试(
t.Run)中,fmt输出无法绑定到具体子测试实例
对比示例:
func TestOuter(t *testing.T) {
t.Run("inner1", func(t *testing.T) {
fmt.Println("bad: no context") // ❌ 没有inner1标识
t.Log("good: bound to inner1") // ✅ 自动带上子测试名
})
}
子测试中调试日志的正确写法
使用t.Run组织测试时,每个子测试都有独立的*testing.T,应始终对当前t调用Log,而非外层t。
- 错误写法:在外层
t上调用Log,日志归属不清 - 正确写法:在每个子测试闭包内,用参数
t(即子测试自身的t)输出 - 子测试失败时,只有它自己的
t.Log会被展示,不会混入其他子测试日志
func TestHTTPHandlers(t *testing.T) {
tests := []struct{
name string
path string
}{
{"root", "/"},
{"api", "/api/v1"},
}
for _, tt := range tests {
tt := tt // 避免循环变量捕获问题
t.Run(tt.name, func(t *testing.T) {
resp := httpGet(tt.path)
t.Logf("GET %s → status=%d", tt.path, resp.StatusCode)
if resp.StatusCode != 200 {
t.Errorf("expected 200, got %d", resp.StatusCode)
}
})
}
}
生产级调试:结合-run和-v精准定位
大型测试套件中,全量go test -v输出太多,真正需要的是「只跑一个测试 + 显示它的日志」:
- 用
-run匹配测试名(支持正则)缩小范围 - 必须同时加
-v,否则日志仍不显示 - 可叠加
-count=1避免重复运行(尤其当测试含状态变更时)
go test -v -run=^TestLogin$ -count=1 go test -v -run=TestAPI.*Create -count=1
注意:-run匹配的是测试函数名(func TestXXX中的XXX),不是t.Run的字符串参数。
调试时最容易忽略的是:日志是否真的属于你正在看的那个测试层级——特别是嵌套t.Run和表格驱动测试混用时,t.Log调用位置稍偏,就可能打到父测试或别的分支里。建议在关键路径开头加一句t.Log("entering...")快速验证执行流。











