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

Go testing.T.TempDir 失败时怎样保留现场

来源:17golang原创

时间:2026-10-31 22:07:29 209浏览 收藏

Go testing.T.TempDir 失败时怎样保留现场

testing.T.TempDir 适合放测试过程中的临时输入和中间文件,但它不是失败现场的长期存储:测试及其所有子测试结束后,目录会自动删除。要保留现场,推荐把需要诊断的文件复制到 t.ArtifactDir(),并用 go test -artifacts -outputdir=... 保存测试产物;如果使用的 Go 版本还没有 ArtifactDir,就在失败前复制到 CI 已知的输出目录。

官方地址:https://pkg.go.dev/testing#T.TempDir

核心边界只有一句话:TempDir 负责隔离和自动清理,ArtifactDir 负责接收诊断产物;失败时必须在测试结束前完成复制,不能等目录被清理后再找。

先看清 TempDir 的生命周期

官方文档说明,TempDir 每次调用都会返回唯一目录;如果创建失败会终止测试;目录会在测试和全部子测试完成后自动删除。这个生命周期很适合避免测试污染,但也意味着在 t.Errorf、t.Fatal 之后,仍要确认负责复制现场的代码已经运行。

Go testing.T.TempDir、子测试结束、自动清理与 artifact 目录之间的静态关系图
图1:生命周期结构说明图,区分 TempDir 的临时边界与 artifact 目录的持久化出口。
func TestParseFixture(t *testing.T) {
	// TempDir 只承载本次测试的输入和中间文件,测试结束后由 testing 清理。
	tmp := t.TempDir()
	input := filepath.Join(tmp, "fixture.json")
	if err := os.WriteFile(input, []byte(`{"name":"demo"}`), 0o600); err != nil {
		// 创建临时输入失败时直接结束,避免继续读取不存在的文件。
		t.Fatal(err)
	}
	// 这里的断言失败不会把 tmp 变成长期目录,保留工作交给后续复制逻辑。
	if got := parse(input); got == nil {
		t.Errorf("parse(%q) 返回 nil", input)
	}
}

示例中的 parse 只代表被测函数,代码没有把运行输出描述成实测结果。实际排查时,最重要的是不要把临时目录路径直接写进跨任务的缓存或报告里,因为测试结束后该路径可能已经不存在。

优先把诊断文件写入 ArtifactDir

较新的 Go 版本提供了 T.ArtifactDir()。它为每个测试或子测试提供唯一的产物目录;未开启产物保存时,它仍可能是测试结束后被清理的临时目录;开启 -artifacts 后,目录会位于 -outputdir 指定的位置。这样,测试代码不必猜 CI 的工作目录,只需把日志、输入快照和中间结果写到这个目录。

func writeFailureSnapshot(t *testing.T, name string, data []byte) {
	// ArtifactDir 是诊断产物的统一出口,文件名只使用受控名称。
	dir := t.ArtifactDir()
	path := filepath.Join(dir, name)
	if err := os.WriteFile(path, data, 0o600); err != nil {
		// 现场写不出去时记录失败原因,不能静默丢掉诊断信息。
		t.Errorf("写入测试产物 %q 失败: %v", path, err)
		return
	}
	// 只记录位置,不把敏感输入正文写到测试日志。
	t.Logf("测试产物已写入: %s", path)
}

命令行可以把产物集中到一个可收集的目录:

# -artifacts 让 testing.T.ArtifactDir 指向 outputdir 下的产物目录。
go test -run '^TestParseFixture$' -artifacts -outputdir ./test-artifacts ./...

-artifacts 由当前 go test 支持时使用;如果项目必须兼容更旧的 Go 版本,应把版本差异写进 CI 配置,并采用下一节的显式复制方案。

失败前复制 TempDir 中真正有价值的文件

很多测试已经由第三方库写入了 TempDir,这时不需要改变被测逻辑,只需在断言失败前把目录内容复制到 artifact 目录。复制不是“保留整个临时目录”的同义词:应该选择输入快照、关键中间文件和结构化诊断,避免把缓存、随机数据或超大文件全部带走。

TempDir 文件、失败判断、ArtifactDir 快照和测试日志之间的静态关系图
图2:失败现场快照说明图,展示 TempDir 中受控文件经过复制后进入可收集的 artifact 目录。
func copyFile(src, dst string) error {
	// 只复制普通文件,避免把符号链接或目录误当成诊断文件。
	info, err := os.Stat(src)
	if err != nil {
		return err
	}
	if !info.Mode().IsRegular() {
		return fmt.Errorf("不是普通文件: %s", src)
	}

	in, err := os.Open(src)
	if err != nil {
		return err
	}
	defer in.Close() // 复制完成后立即关闭输入文件。
	out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o600)
	if err != nil {
		return err
	}
	defer out.Close() // 关闭输出文件,确保写入句柄得到释放。
	_, err = io.Copy(out, in)
	return err
}

func preserveOnFailure(t *testing.T, tmp, name string) {
	// 先确认目标目录,再复制失败分析真正需要的文件。
	dst := filepath.Join(t.ArtifactDir(), name)
	if err := copyFile(filepath.Join(tmp, name), dst); err != nil {
		// 复制失败本身也是现场的一部分,保留错误但不覆盖原始断言。
		t.Errorf("保留失败现场 %q: %v", name, err)
		return
	}
	t.Logf("已保留失败现场: %s", dst)
}

如果失败路径调用了 t.Fatal,复制逻辑必须放在它之前,或者改用先记录复制错误、最后统一 t.Fatalf 的写法。不要在另一个 goroutine 里依赖 t.Fatal 触发收尾;测试控制方法应由运行测试函数的 goroutine 调用。

让 CI 现场可复盘,而不是只留下一个路径

一个可复盘的失败现场至少包含:测试名称、输入摘要、关键中间文件和复制错误。建议在 artifact 目录中使用固定的相对文件名,并在失败日志中记录测试名和文件名;不要记录令牌、个人路径或完整业务数据。对于大文件,可先写大小、摘要和截断片段,避免 CI 产物无限增长。

需求推荐位置收尾边界
临时输入、缓存和中间文件t.TempDir()测试结束自动清理
失败诊断与结构化快照t.ArtifactDir()配合 -artifacts 收集
旧 Go 版本的持久化目录CI 预先约定的 outputdir失败前显式复制并检查错误
  • 先写快照,再调用 Fatalf;否则后续复制不会执行。
  • 用受控文件名拼接目标路径,不把外部输入直接当成路径。
  • 复制后仍然关闭文件,并把复制错误记录为测试失败信息。
  • 确认 CI 收集的是 -outputdir 下的产物,而不是测试结束即删除的 TempDir。

常见问题

TempDir 失败后还能手动进去查看吗?

只能在测试仍未结束时查看;测试和所有子测试结束后,目录会被自动删除。需要跨任务保留时,应提前复制到 artifact 目录。

只调用 ArtifactDir 就一定能保留文件吗?

不一定。按官方语义,未提供 -artifacts 时它也可能是测试结束后被清理的临时目录;CI 需要明确开启产物保存并收集对应的 outputdir。

小结:把 TempDir 当作短生命周期工作区,把 ArtifactDir 当作诊断出口;失败现场的关键动作是“在测试结束前复制,并让 CI 收集正确的输出目录”。

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