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