单元测试里,一句断言失败往往已经够用;但到了接口测试、截图比对、模糊测试这类场景,开发者通常还需要看到原始响应、生成结果或诊断文件,才能真正定位问题。Go 1.26 新增的 ArtifactDir,正是用来解决“测试产物该放哪、什么时候会被保留、在 CI 里怎么拿到”这些实际问题,下面按使用场景把规则和边界一次讲清。
ArtifactDir 到底解决了什么
Go 1.26 为三类测试入口分别提供了 T.ArtifactDir、B.ArtifactDir 和 F.ArtifactDir。它们都会返回一个由测试框架管理、当前测试可写的目录,适合保存截图、日志、响应数据、分析报告等测试产物。

这套机制解决的核心问题,是把测试产生的辅助文件从“随手找个目录写进去”,变成“由测试框架统一分配和管理”。过去常见的两种做法都有明显问题:一种是直接写到项目目录里,时间一长就会污染仓库;另一种是写入临时目录,但测试结束后文件位置难找,甚至自动消失。
使用 ArtifactDir 后,目录分配也有明确规则:
- 同一个测试里多次调用
ArtifactDir,拿到的是同一个目录。 - 不同测试、不同子测试,各自拥有独立目录。
- 子测试目录不一定位于父测试目录之下,因此不要依赖父子路径关系去拼接或查找文件。
模糊测试还有一个使用边界需要单独记住:F.ArtifactDir 应该在调用 F.Fuzz 之前使用。进入模糊测试目标函数后,*testing.F 只允许调用 Failed 和 Name;这时如果需要写测试产物,应改用回调参数 *testing.T 的 t.ArtifactDir()。
接口测试里怎么保存原始响应
在接口测试中,一个很典型的需求是把服务端返回的原始 JSON 留下来,方便失败时回看。做法并不复杂,直接把响应写到 t.ArtifactDir() 返回的目录即可:
func TestUserAPI(t *testing.T) {
body := requestUser(t)
path := filepath.Join(t.ArtifactDir(), "response.json")
if err := os.WriteFile(path, body, 0o644); err != nil {
t.Fatal(err)
}
assertUser(t, body)
}
这里有两个细节值得保留。第一,路径应通过 filepath.Join 生成,这样可以正确处理不同系统上的路径分隔符。第二,写文件失败不能静默跳过,应该像示例里这样直接让测试失败,否则留下来的“证据链”本身就不可靠。
上面的写法会在每次测试执行时都保存响应。如果产物体积较大,或者你只关心失败现场,更实用的方式是先做比较,再决定是否落盘。这样可以减少磁盘占用,也能避免本地和 CI 中堆积大量无用文件。
不过,这种写法有个前提:断言逻辑不能一上来就调用 t.Fatal。如果想做到“仅在失败时保存”,断言函数应先返回比较结果,再由外层测试根据结果决定是否写入 ArtifactDir 中。
默认模式会放哪,怎样保留下来
直接运行 go test ./... 时,不需要额外参数,ArtifactDir 也可以正常工作。只是这时它返回的是临时目录:测试执行期间可以读写,测试结束后目录会自动删除。

这种默认模式适合两类场景:一类是测试运行过程中临时生成的中间文件;另一类是只需要在测试内部消费、不需要事后人工查看的产物。它的优点是不用手动清理,也不会把临时文件残留到工作目录里。
但如果你希望在测试结束后继续查看截图、JSON 响应或诊断报告,就要显式启用保留模式。Go 1.26 为 go test 增加了 -artifacts 参数:
go test -v -artifacts ./...
启用后,测试框架会保留这些产物。首次调用 ArtifactDir 时,测试日志里还会记录实际目录,例如 === ARTIFACTS TestUserAPI /path/to/artifact/dir。这里建议同时加上 -v,因为只有在详细输出下,测试通过时你也能看到这条日志。
还有一个容易踩坑的点:官方只保证产物位于输出目录之下,并不承诺内部子目录的命名规则。也就是说,脚本不要自己猜目录结构,更不要手工推导测试名对应的路径;真正可靠的方式,是读取日志里 === ARTIFACTS 后面给出的实际路径。
如果你还想控制保留位置,可以配合 -outputdir 使用:
mkdir -p test-results
go test -artifacts -outputdir ./test-results ./...
-artifacts 决定的是“是否保留”,-outputdir 决定的是“保留到哪里”。另外,-outputdir 也会影响性能分析文件的位置,不只是测试产物目录本身。
按 Go 1.26.x 当前工具链的行为,-outputdir 指定的目录需要事先存在,因此命令前先执行一次 mkdir -p test-results 会更稳妥。
子测试、并行测试有哪些边界
在表驱动测试里,ArtifactDir 对子测试尤其有用,因为它会自动为每个子测试分配独立目录:
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
path := filepath.Join(t.ArtifactDir(), "result.json")
saveResult(t, path, tc.input)
})
}
这意味着,即便多个子测试都把文件命名为 result.json,最终落到磁盘上的实际路径也不会冲突。对于并发表驱动测试来说,这一点能明显减少人为拼接目录和文件名前缀的负担。
不过要注意,目录隔离只发生在“测试之间”。如果是同一个测试函数内部启动了多个 Goroutine,同时向同一个目录写文件,ArtifactDir 并不会替你解决并发写入问题。这里仍然需要自己保证文件名不同,或者通过锁、队列等方式完成同步。
因此可以把它理解为:ArtifactDir 负责测试级别的目录隔离,但目录内部的并发安全,仍然是测试代码自己的职责。
在 CI 里怎么留下失败现场
一旦本地跑通,下一步通常就是把测试产物接入 CI。Go 1.26 的这套机制,和常见 CI 平台的归档能力天然适配,例如下面这段 GitHub Actions 配置:

- run: mkdir -p test-results
- run: go test -artifacts -outputdir ./test-results ./...
- uses: actions/upload-artifact@v4
if: always()
with:
path: test-results
if: always() 的作用很关键,它可以保证测试失败后,归档步骤仍然会执行。这样排查问题时,开发者拿到的不只是“某个断言失败了”,还有当次运行留下的响应数据、截图或诊断报告。
当然,测试产物始终只是断言的补充,而不是替代品。没有明确断言的测试,就算保留了再多现场,也很难形成稳定、可自动判断的测试结果。
把它接入 CI 时,还应额外注意几件事:
- 控制大文件体积,以及 CI 平台的产物保留时长。
- 对令牌、Cookie、用户信息等敏感内容做脱敏处理。
- 不要在业务代码中依赖测试专用的
ArtifactDir。 - 项目工具链需要使用 Go 1.26 或更高版本。
什么时候值得用 ArtifactDir
ArtifactDir 解决的并不只是“测试里怎么写文件”,而是把测试产物的保存位置、生命周期和保留方式统一了起来。默认模式下,文件只在测试期间存在,适合临时中间结果;启用 -artifacts 后,则可以把失败现场稳定地留下来。
如果你的测试已经不再局限于简单断言,而是需要保存响应、截图、分析报告或模糊测试输出,那么 Go 1.26 提供的这套标准接口,基本就是当前最清晰也最省心的做法。再配合 -outputdir 和 CI 归档,测试产物终于有了一个不靠约定俗成、而是由工具链正式支持的去处。









