bigywater.tech
Go · 108

go test 缓存:什么情况下结果会被复用

go test 的缓存让重复运行几乎免费,但“(cached)”也会让人踩坑:改了外部文件的测试没有重跑,依赖时间的测试结果被冻住,甚至根本没触发缓存的命令也有。这篇逐条实测哪些情况会命中、哪些会失效,并对照 go help test 的规则。

0.10 秒
命中缓存时 go test 的整体耗时(不命中时 TestSlow 要 1.79 秒)
模块外
读模块外的文件,改了内容缓存仍命中,输出还是旧的 "o1"
无包参数
没有包参数的 go test 根本不缓存

先看有没有缓存

实测 · 本机执行输出
  go test -run TestSlow -v(无包参数)第 1 次          → 1.801s     (go test 整体耗时 2.46 s)
  同上,第 2 次                                     → 1.616s     (go test 整体耗时 1.79 s)

不带包参数的 go test(本地目录模式)根本不缓存;带上 . 或 ./...(包列表模式)才有缓存。我一开始就是用的无包参数的写法,怎么跑都没有 (cached),查了 go help test 才明白。

命中与强制重跑

实测 · 本机执行输出
  第 1 次 go test -run TestSlow -v .             → 1.608s     (go test 整体耗时 1.79 s)
  第 2 次,什么都没改                                  → (cached)   (go test 整体耗时 0.10 s)
  第 3 次,加 -count=1                             → 1.612s     (go test 整体耗时 1.77 s)

什么都不改:(cached),整体只要 0.10 秒(不命中时这个 1.5 秒的测试要 1.79 秒);-count=1 强制重跑。

什么会让它失效

实测 · 本机执行输出
  基线:TestAdd                                   → 0.095s     (go test 整体耗时 0.27 s)
  再跑一次                                         → (cached)   (go test 整体耗时 0.09 s)
  只改了注释之后                                      → 0.298s     (go test 整体耗时 0.49 s)
  把 a + b 改写成 b + a 之后(语义相同)                   → 0.327s     (go test 整体耗时 0.52 s)
  加了一个 if a == 0 分支(真的改了代码)                    → 1.104s     (go test 整体耗时 1.30 s)
  再跑一次                                         → (cached)   (go test 整体耗时 0.09 s)
  加 -race(不同的测试二进制)                            → 1.329s     (go test 整体耗时 1.80 s)
  再跑一次 -race                                   → (cached)   (go test 整体耗时 0.11 s)
  加 -short:第 1 次                               → 0.094s     (go test 整体耗时 0.27 s)
  加 -short:第 2 次                               → (cached)   (go test 整体耗时 0.09 s)
  加 -v:第 1 次                                   → 0.100s     (go test 整体耗时 0.27 s)
  加 -v:第 2 次                                   → (cached)   (go test 整体耗时 0.10 s)
  加 -timeout=5m:第 1 次                          → 0.100s     (go test 整体耗时 0.26 s)
  加 -timeout=5m:第 2 次                          → (cached)   (go test 整体耗时 0.09 s)
  加 -count=2:第 1 次                             → 0.098s     (go test 整体耗时 0.26 s)
  加 -count=2:第 2 次                             → 0.128s     (go test 整体耗时 0.37 s)

测试二进制变了就失效——连只在函数前加一行注释也会(行号信息变了);-race 是另一个二进制,有自己的缓存;-short、-v、-timeout 各有各的缓存条目,第一次运行、第二次命中;-count=2 不允许缓存,两次都重跑。

环境变量

实测 · 本机执行输出
  FOO=a                                        → 0.120s     (go test 整体耗时 0.31 s)  FOO="a"
  FOO=a 再跑                                     → (cached)   (go test 整体耗时 0.11 s)  FOO="a"
  FOO=b                                        → 0.100s     (go test 整体耗时 0.34 s)  FOO="b"
  FOO=a,另外加一个测试没读的 OTHER=x                     → (cached)   (go test 整体耗时 0.11 s)  FOO="a"

只有测试真的读过的环境变量变了才失效:FOO 从 a 变 b 就重跑,多设一个没被读的 OTHER 仍然命中。

文件

实测 · 本机执行输出
  读模块内的 data.txt(内容 v1)                        → 0.105s     (go test 整体耗时 0.31 s)  data.txt="v1"
  再跑一次                                         → (cached)   (go test 整体耗时 0.10 s)  data.txt="v1"
  把 data.txt 改成 v2                             → 0.095s     (go test 整体耗时 0.27 s)  data.txt="v2"
  读模块外的文件(内容 o1)                               → 0.111s     (go test 整体耗时 0.28 s)  outside="o1"
  再跑一次                                         → (cached)   (go test 整体耗时 0.09 s)  outside="o1"
  把模块外的文件改成 o2                                 → (cached)   (go test 整体耗时 0.09 s)  outside="o1"

读模块内的 data.txt,改了内容就重跑;读模块外的文件,内容从 o1 改成 o2,缓存仍然命中,打印的还是 outside="o1"——过期的结果。

时间

实测 · 本机执行输出
  第 1 次                                        → 0.103s     (go test 整体耗时 0.26 s)  秒数的个位=8
  1.2 秒之后再跑一次                                  → (cached)   (go test 整体耗时 0.11 s)  秒数的个位=8

依赖当前时间的测试,1.2 秒后再跑,命中缓存,打印的秒数还是旧的:缓存的是上一次的输出。

清缓存

实测 · 本机执行输出
  go clean -testcache 之后:第 1 次                 → 0.098s     (go test 整体耗时 0.25 s)  秒数的个位=9
  第 2 次                                        → (cached)   (go test 整体耗时 0.09 s)  秒数的个位=9

go clean -testcache 清空所有测试缓存,之后第一次重跑,第二次又命中。

规则原文

实测 · 本机执行输出
  The rule for a match in the cache is that the run involves the same test binary and the flags on the command line come entirely from a restricted set of 'cacheable' test flags, defined as -benchtime, -coverprofile, -cpu, -failfast, -fullpath, -list, -outputdir, -parallel, -run, -short, -skip, -timeout and -v. If a run of go test has any test or non-test flags outside this set, the result is not cached. To disable test caching, use any test flag or argument other than the cacheable flags. The idiomatic way to disable test caching explicitly is to use -count=1. Tests that open files within the package's module or that consult environment variables only match future runs in which the files and environment variables are unchanged. A cached test result is treated as executing in no time at all, so a successful package test result will be cached and reused regardless of -timeout setting.

go help test 列出了 13 个可缓存标志,说明缓存的结果视为耗时为零,所以成功的结果会无视 -timeout 被复用;显式禁用缓存的惯用写法是 -count=1。

分步演示

go test 缓存
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1(brew);用 go run / go build / go test 直接编译运行,需要第三方库时经代理从 proxy.golang.org 下载;计时用 time.Now / testing.B,数字每次运行会有小幅抖动。实验里的测试包是自己写的(Add 标了 noinline,用 len(os.Args) 当输入避免被折叠);每次实验开始前先 go clean -testcache;所有命令在模块里用 go test -run … .(包列表模式)运行;读过的文档:go help test
    • 没有验证:-coverprofile 与缓存的关系、-bench 的缓存、go vet 一起运行时的缓存、GOFLAGS 里设置 -count 的影响、远程缓存(GOCACHEPROG)、Linux 上的差异
    京ICP备2026067502号-1京公网安备11010502063487号