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 TestSomethingn 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=1go test -v-run=TestAPI.*Create -count=1
注意:-run匹配的是测试函数名(func TestXXX中的XXX),不是t.Run的字符串参数。
调试时最容易忽略的是:日志是否真的属于你正在看的那个测试层级——特别是嵌套t.Run和表格驱动测试混用时,t.Log调用位置稍偏,就可能打到父测试或别的分支里。建议在关键路径开头加一句t.Log("entering...")快速验证执行流。