优先用 t.Logf,它支持格式化且行为类似 fmt.Printf;t.Log 仅输出纯字符串并自动换行;两者均只在 go test -v 下执行输出。

测试中用 t.Log 还是 t.Logf?
优先用 t.Logf,它支持格式化,和 fmt.Printf 行为一致,写调试信息更自然。直接用 t.Log 容易漏掉换行或类型转换问题——比如传入一个 struct,t.Log 会输出带字段名的完整表示,而 t.Logf("%v", s) 更可控。
注意:这两者只在 go test -v 模式下才输出;不加 -v 时日志完全静默,不是被过滤,是根本不会执行打印逻辑。
-
t.Log("start")→ 输出纯字符串,自动加换行 -
t.Logf("got %d items, err: %v", len(items), err)→ 推荐,可读性强 - 不要在
if err != nil分支里只写t.Log(err),容易忽略错误上下文,至少写成t.Logf("failed to load config: %v", err)
go test -v 不输出日志?检查是否误用了 log.Print*
Go 标准库的 log.Printf、log.Println 在测试中仍会输出,但它们走的是全局 logger,默认输出到 os.Stderr,和 t.Log 的测试上下文无关——这意味着:
- 这些日志无法被
go test -v的层级缩进识别,也不随测试失败/跳过状态变化 - 在 CI 环境中可能被截断或混入其他日志流,难以定位归属测试函数
- 若测试并发运行(如
t.Parallel()),log.Printf输出会乱序,而t.Logf是线程安全且绑定到当前测试实例的
简单判断:如果日志没出现在 === RUN TestXXX 下方缩进块里,大概率用了 log 包而非 t.Log。
想看更详细的执行路径?用 t.Helper() 配合 t.Logf
当把日志逻辑抽到辅助函数里(比如 mustParse(t, jsonStr)),默认 t.Logf 的文件/行号会指向辅助函数内部,而不是调用它的测试函数。加 t.Helper() 可修正这个行为:
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
func mustParse(t *testing.T, s string) map[string]interface{} {
t.Helper() // ← 关键:让 t.Logf 的位置回溯到调用处
var v map[string]interface{}
if err := json.Unmarshal([]byte(s), &v); err != nil {
t.Fatalf("invalid JSON %q: %v", s, err)
}
t.Logf("parsed %q → %+v", s, v)
return v
}
不加 t.Helper(),日志显示的行号是 mustParse 函数里的 t.Logf 行;加了之后,显示的是测试函数里调用 mustParse 的那行——这对排查真实出问题的位置很重要。
性能敏感场景下,避免在循环里频繁调用 t.Logf
t.Logf 不是零开销操作:它要捕获调用栈、格式化字符串、写入内部 buffer。在每轮迭代都打日志的 Benchmark 或大数据量测试中,可能拖慢执行甚至掩盖真实性能瓶颈。
- 先用
if testing.Verbose()做开关:只在-v模式下构建并输出 - 对高频路径,改用字符串拼接 + 单次
t.Logf,而不是每次迭代都调用 - 切忌在
for i := range bigSlice里写t.Logf("item[%d]: %v", i, bigSlice[i])—— 日志体积爆炸且无实际调试价值
真正需要逐项检查时,宁可用 fmt.Printf + 注释说明“仅调试”,测试提交前删掉,也别留着 t.Logf 循环。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










