bigywater.tech
Go · 109

覆盖率:go test -cover 是怎么插桩的

go test -cover 只给你一个百分比,背后是编译器在每个代码块里插入计数器。这篇看覆盖率文件里到底写了什么,三种模式有什么区别,插桩对速度的影响有多大,以及怎样给不是测试的程序收集覆盖率。

47.1%
17 个语句里 8 个被测试覆盖到
2.33 / 6.34 / 26.41 倍
紧循环里 set / count / atomic 模式相对不插桩的耗时倍数
GOCOVERDIR
go build -cover 编译的程序,运行时靠它指定覆盖率数据的输出目录

百分比

实测 · 本机执行输出
  ok  	lab	0.112s	coverage: 47.1% of statements

包里共 17 个语句(按覆盖率文件里各块的语句数相加:Classify 8 个、Unused 3 个、Hot 6 个),测试只调了 Classify,覆盖 8 个,47.1%。

覆盖率文件

实测 · 本机执行输出
  -covermode=set:mode: set(每行:文件:起始行.列,结束行.列 语句数 计数)
    f.go:4.2,4.11 1 1
    f.go:5.3,6.1 1 1
    f.go:7.2,7.12 1 1
    f.go:8.3,9.1 1 1
    f.go:10.2,10.28 1 1
    f.go:11.3,11.15 1 1
    f.go:12.4,13.1 1 1
    f.go:15.2,15.22 1 1
    f.go:19.2,22.1 3 0
    f.go:25.2,26.25 2 0
    f.go:27.3,27.15 1 0
    f.go:28.4,29.1 1 0
    f.go:30.4,31.1 1 0
    f.go:33.2,33.10 1 0
  -covermode=count:mode: count(每行:文件:起始行.列,结束行.列 语句数 计数)
    f.go:4.2,4.11 1 6
    f.go:5.3,6.1 1 1
    f.go:7.2,7.12 1 5
    f.go:8.3,9.1 1 1
    f.go:10.2,10.28 1 4
    f.go:11.3,11.15 1 5
    f.go:12.4,13.1 1 1
    f.go:15.2,15.22 1 3
    f.go:19.2,22.1 3 0
    f.go:25.2,26.25 2 0
    f.go:27.3,27.15 1 0
    f.go:28.4,29.1 1 0
    f.go:30.4,31.1 1 0
    f.go:33.2,33.10 1 0
  -covermode=atomic:mode: atomic;各行的计数与 count 模式完全相同(单线程测试里没有区别)

每一行是一个“块”:f.go:4.2,4.11 1 6 表示第 4 行第 2 列到第 4 行第 11 列的块,含 1 个语句,执行了 6 次。set 模式只记 0/1,count 记次数,atomic 的数值相同但多线程下正确。

按函数看

实测 · 本机执行输出
  f.go:3: Classify 100.0%
  f.go:18: Unused 0.0%
  f.go:24: Hot 0.0%
  total: (statements) 47.1%

go tool cover -func 把块汇总成每个函数的百分比,方便快速找到完全没被测到的函数(Unused、Hot 都是 0.0%)。

插桩的代价

实测 · 本机执行输出
  不插桩 304 ns/op;-covermode=set 710 ns/op(2.33 倍);count 1930 ns/op(6.34 倍);atomic 8038 ns/op(26.41 倍)

对一个只有几条语句的紧循环,set 模式让耗时变成 2.33 倍,count 6.34 倍,atomic 26.41 倍。真实函数里每条语句的工作量大得多,比例会小很多;但说明了为什么 -race 默认配 atomic、测基准时别带 -cover。

普通程序的覆盖率

实测 · 本机执行输出
  程序输出:质数或 1 负数;GOCOVERDIR 里生成了 2 个文件
  go tool covdata percent:lab		coverage: 35.3% of statements
	lab/cmd		coverage: 100.0% of statements

go build -cover 编译出来的程序,运行时把数据写到 GOCOVERDIR 指定的目录;用 go tool covdata percent 汇总,实验里 lab 包 35.3%、lab/cmd 100.0%。可以用来收集集成测试、手工运行的覆盖率。

文档

实测 · 本机执行输出
  -covermode set,count,atomic Set the mode for coverage analysis for the package[s] being tested. The default is "set" unless -race is enabled, in which case it is "atomic". The values: set: bool: does this statement run? count: int: how many times does this statement run? atomic: int: count, but correct in multithreaded tests; significantly more expensive.

go help testflag 对三种模式的说明与实验一致:set 是“执行过吗”,count 是“几次”,atomic 是多线程正确的 count。

分步演示

覆盖率
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1(brew);用 go run / go build / go test 直接编译运行,需要第三方库时经代理从 proxy.golang.org 下载;计时用 time.Now / testing.B,数字每次运行会有小幅抖动。被测包是自己写的三个函数;覆盖率文件、-func 汇总来自 go test -coverprofile 与 go tool cover;开销用 go test -bench(-benchtime=300ms),每种取 3 次最小;读过的文档:go help testflag 中 -covermode
    • 没有验证:覆盖率插桩的实现方式(编译器还是源码改写,没有读源码)、-coverpkg 跨包覆盖、多线程测试里 count 与 atomic 的计数差别、html 报告(go tool cover -html)、语句覆盖率与分支覆盖率的区别
    京ICP备2026067502号-1京公网安备11010502063487号