Go测试中t.Log/t.Logf默认不显示,需加-v标志;失败时自动显示,支持子测试绑定和格式化输出,优于fmt.Println。
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...")快速验证执行流。