📋 如何保持 Go 单元测试日志整洁:仅记录失败的输入 🔗

在 Go 中编写单元测试(尤其是表格驱动测试)时,向标准全局 log 包写入日志的函数很快就会让你的终端变得杂乱不堪。

如果你有十几个测试用例,而只有一个失败了,你仍然不得不翻阅所有成功用例的日志输出才能找到错误。这种噪音会拖慢调试速度,并使测试输出难以阅读。

让我们来看一个常见场景,以及如何通过捕获日志并仅在测试失败时打印它们来解决这个问题。

📑 目录 🔗


🔊 问题:嘈杂的测试输出 🔗

假设我们有一个简单的函数,它会记录其输入和最终结果:

package add

import (
	"log"
)

func add(x, y int) int {
	log.Printf("add %d %d", x, y)
	res := x + y
	log.Printf("add %d %d = %d", x, y, res)
	return res
}

现在,我们编写一个包含三个用例的标准表格驱动单元测试(其中最后一个用例是故意设计为失败的):

package add

import (
	"testing"
)

func TestAdd(t *testing.T) {
	inputs := []struct {
		x    int
		y    int
		want int
	}{
		{x: 1, y: 2, want: 3},
		{x: 3, y: 2, want: 5},
		{x: 4, y: 5, want: 0}, // 故意失败的用例
	}

	for _, in := range inputs {
		got := add(in.x, in.y)
		if in.want != got {
			t.Errorf("add %d %d got %d want %d", in.x, in.y, got, in.want)
		}
	}
}

当我们运行 go test 时,输出会先包含所有通过的输入的日志,最后才向我们展示失败信息:

$ go test
2020/09/05 09:01:25 add 1 2
2020/09/05 09:01:25 add 1 2 = 3
2020/09/05 09:01:25 add 3 2
2020/09/05 09:01:25 add 3 2 = 5
2020/09/05 09:01:25 add 4 5
2020/09/05 09:01:25 add 4 5 = 9
--- FAIL: TestAdd (0.00s)
    add_test.go:28: add 4 5 got 9 want 0
FAIL
exit status 1

🛠️ 解决方案:依赖注入与缓冲区 🔗

如果只看到失败输入所生成的日志,效果会好得多。我们可以通过向函数传入一个日志记录器(依赖注入),并在测试期间将日志写入临时缓冲区来实现这一点。

首先,让我们更新函数以接受一个 *log.Logger:

package add

import "log"

func add(logger *log.Logger, x, y int) int {
	logger.Printf("add %d %d", x, y)
	res := x + y
	logger.Printf("add %d %d = %d", x, y, res)
	return res
}

接下来,我们更新测试。对于每次测试用例迭代,我们都会创建一个新的 bytes.Buffer 并为其绑定一个新的日志记录器。如果测试通过,缓冲区就会被直接丢弃。如果测试失败,我们会将缓冲区的内容与错误消息一起打印出来!

(注:我们还将测试升级为使用 t.Run,这是在 Go 中运行表格驱动子测试的地道方式)。

package add

import (
	"bytes"
	"log"
	"testing"
)

func TestAdd(t *testing.T) {
	inputs := []struct {
		name string
		x    int
		y    int
		want int
	}{
		{name: "1+2", x: 1, y: 2, want: 3},
		{name: "3+2", x: 3, y: 2, want: 5},
		{name: "4+5", x: 4, y: 5, want: 0}, // 故意失败的用例
	}

	for _, in := range inputs {
		t.Run(in.name, func(t *testing.T) {
			// 1. 创建一个缓冲区来捕获日志
			var buf bytes.Buffer
			
			// 2. 创建一个写入该缓冲区的新日志记录器
			logger := log.New(&buf, "", log.LstdFlags)
			
			// 3. 将自定义日志记录器传入我们的函数
			got := add(logger, in.x, in.y)
			
			if in.want != got {
				// 4. 仅在测试失败时打印缓冲区内容
				t.Errorf("got %d want %d\n\nExecution Logs:\n%s", got, in.want, buf.String())
			}
		})
	}
}

✨ 整洁的输出 🔗

现在,当我们运行测试时,输出非常干净且极具可操作性。我们只会看到与失败相关的日志:

$ go test
--- FAIL: TestAdd (0.00s)
    --- FAIL: TestAdd/4+5 (0.00s)
        add_test.go:35: got 9 want 0
            
            Execution Logs:
            2020/09/05 09:22:48 add 4 5
            2020/09/05 09:22:48 add 4 5 = 9
            
FAIL
exit status 1

通过将函数与全局日志记录器解耦,我们让代码更加灵活、测试套件更加安静,并显著加快了调试过程。


📚 免费下载我的电子书 🔗

获取《卡乌埃的生活方式》精选离线版——一份全面优化 🍏 健康、💰 财富、❤️ 爱 与 🧘 意义 的实践指南。

卡乌埃的生活方式

卡乌埃的生活方式

📬 订阅邮件通讯 🔗

当我发布关于美好生活的新随笔、读书笔记和实践指南时接收通知。绝无垃圾邮件——可随时退订。


💬 评论与讨论 🔗

有任何问题、见解或建议吗?欢迎在下方分享您的想法。评论功能由 GitHub Discussions 驱动(无广告、无垃圾信息)——点击下方的使用 GitHub 登录即可发表评论或互动(注册免费 GitHub 账号不到一分钟即可完成)。



最后更新于 2026年10月11日.

免责声明:本站所有内容仅供信息参考与教育用途,不构成任何医疗、心理、财务、投资、税务或法律建议。此处呈现的观点、整理与见解仅代表我个人,不代表我雇主的立场。书籍、演讲及文章摘要均为独立的教育性评论,其知识产权归原作者所有。本站可能使用 AI 工具辅助编辑、排版与归纳;尽管我们竭力确保准确无误,但仍可能存在疏漏。隐私政策:本站使用第三方服务提供可选的邮件通讯订阅(follow.it)、评论(giscus / GitHub Discussions)以及嵌入媒体(YouTube),这些服务可能会依据各自的隐私政策处理访客的 IP 地址和 Cookie。