登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go 测试 Cleanup 怎样让子测试各自释放资源

来源:17golang原创

时间:2026-10-22 22:37:19 317浏览 收藏

在表格驱动测试里,最稳妥的做法是让每个 t.Run 自己创建资源,并在创建成功后立刻调用 t.Cleanup 注册释放函数。这样子测试即使失败、提前退出或被精确单独运行,资源仍由自己的测试作用域负责回收,而不是依赖父测试最后一次性清理。

结论:t.Cleanup 会在当前测试及其所有子测试完成后执行,多个清理函数按后注册先执行。子测试资源应在子测试内部创建和注册;父级只负责真正由整个测试组共享的资源。
  • 资源归属:每次 t.Run 创建自己的文件、服务器、连接或临时状态。
  • 注册时机:创建成功后立即注册,避免中途失败留下半初始化资源。
  • 并行边界:t.Parallel 不能让子测试共享会互相修改的资源。

先把资源所有权放进子测试

子测试的关键不是“最后调用一次 Close”,而是让创建点和所有权在同一个闭包中。下面的示例用一个可关闭的测试资源代表文件、连接或测试服务器;真实项目中可以把 Close 换成连接池归还、删除临时目录之外的自定义清理动作。

func TestRepository(t *testing.T) {
	cases := []struct {
		name string
		bad  bool
	}{
		{name: "empty-key"},
		{name: "duplicate-key", bad: true},
	}

	for _, tc := range cases {
		tc := tc // 中文注释:固定本轮循环值,避免闭包读取后续迭代。
		t.Run(tc.name, func(t *testing.T) {
			res, err := openTestResource(tc.name)
			if err != nil {
				t.Fatal(err) // 中文注释:创建失败时没有资源可注册,直接终止本子测试。
			}
			t.Cleanup(func() {
				res.Close() // 中文注释:释放权属于当前子测试,不依赖父测试的收尾顺序。
			})

			if tc.bad {
				t.Errorf("故意保留一个失败分支") // 中文注释:即使失败,Cleanup 仍会运行。
			}
		})
	}
}
Go t.Run 子测试各自创建资源并由 t.Cleanup 绑定释放的作用域说明图
图1:说明图展示每个子测试独立持有资源,创建成功后立即绑定自己的 Cleanup。

Cleanup 的执行边界和顺序

官方 testing 文档把 Cleanup 定义为:当前测试(或子测试)及其所有子测试完成后调用,且清理函数按后注册先执行。因此它适合表达资源的生命周期,而不是只在测试成功时执行的“收尾代码”。例如一个子测试先注册连接关闭,再注册临时状态恢复,那么恢复动作会先发生;如果两者有依赖关系,应反过来注册。

父子关系也很重要。子测试的 Cleanup 归子测试;父测试的 Cleanup 要等整个父测试和所有子测试完成。不要在父级先关闭一个仍被并行子测试使用的连接,也不要把每行用例都写入同一个可变客户端后再假定它们能互不影响。httptest.NewTestServer(t, handler) 这类测试辅助函数本身也采用同样的生命周期思路:创建服务器时把关闭动作注册到传入的测试对象。

并行子测试怎样避免互相清理

调用 t.Parallel() 后,子测试会与同组的其他并行子测试运行;父级 Run 只有在这些子测试完成后才返回。实际设计可以按“子测试独占、父测试共享”分层:每个子测试创建自己的临时目录或内存资源,父级只创建只读配置或不可变 fixture。

func TestParallelCases(t *testing.T) {
	for _, name := range []string{"alpha", "beta"} {
		name := name // 中文注释:让并行闭包绑定当前用例名称。
		t.Run(name, func(t *testing.T) {
			t.Parallel()
			dir := t.TempDir() // 中文注释:临时目录由测试框架在测试组结束时处理。
			_ = dir
			t.Cleanup(func() {
				// 中文注释:这里只清理本子测试额外创建的状态,不能动其他用例的目录。
				resetCaseState(name)
			})
		})
	}
}
Go 测试父子作用域与并行子测试完成后 Cleanup 执行顺序说明图
图2:结构说明图展示并行子测试先各自完成,父级作用域再进入自己的清理阶段。

用最小范围检查资源是否成对

排查泄漏时,先把测试缩小到一个用例:go test -run='^TestRepository$/duplicate-key$' -count=1。再按“创建一次、注册一次、释放一次”核对代码。若资源需要按层级清理,就在注册处写出顺序;若资源会跨并行用例共享,则改成只读数据,或把创建与释放提升到父测试并加锁。不要因为 Cleanup 能兜底,就在后台 goroutine 里继续使用已结束测试的对象;goroutine 的停止和同步仍由测试代码负责。

Go 测试 Cleanup 常见问题

Cleanup 会在测试失败后执行吗?

会。它绑定的是测试生命周期,不是成功分支;失败、Fatal 或跳过导致测试结束时,已注册的清理函数仍会按规则执行。

父测试和子测试都注册 Cleanup 时谁先执行?

先结束的子测试先运行自己的清理函数,父测试要等所有子测试完成后才进入父级收尾;同一作用域内则是后注册先执行。

可以把所有资源都放在父测试里吗?

只有真正由整个测试组共享且线程安全的资源才适合这样做。逐用例变化的资源放在子测试内,才能缩小污染范围并支持单独运行。

把 t.Cleanup 当作所有权声明:谁创建资源,谁注册释放;谁共享资源,谁负责整个作用域的生命周期。这个边界比单纯把 Close 写在函数末尾更适合表格驱动和并行测试。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>