集成测试的代码覆盖率分析支持
目录
概览
为代码覆盖率分析构建二进制文件
运行经过代码覆盖率插桩的二进制文件
处理代码覆盖率数据文件
常见问题
资源
词汇表
从 Go 1.20 开始,Go 支持从应用程序和集成测试中收集代码覆盖率配置文件,集成测试是 Go 程序更大、更复杂的测试。
概览
Go 通过“go test -coverprofile=... <pkg_target>
”命令提供易于使用的支持,用于在包单元测试级别收集代码覆盖率配置文件。从 Go 1.20 开始,用户现在可以为更大的集成测试收集代码覆盖率配置文件:更重量级、更复杂的测试,它们对给定的应用程序二进制文件执行多次运行。
对于单元测试,收集代码覆盖率配置文件并生成报告需要两个步骤:运行 go test -coverprofile=...
,然后调用 go tool cover {-func,-html}
来生成报告。
对于集成测试,需要三个步骤:一个构建步骤,一个运行步骤(可能涉及多次调用构建步骤中的二进制文件),最后是一个报告步骤,如下所述。
为代码覆盖率分析构建二进制文件
要构建用于收集代码覆盖率配置文件的应用程序,在对应用程序二进制文件目标调用 go build
时传递 -cover
标志。有关 go build -cover
调用的示例,请参阅下面的章节。然后可以使用环境变量设置来运行生成的二进制文件以捕获代码覆盖率配置文件(请参阅下一节关于运行)。
如何选择要进行插桩的包
在给定的“go build -cover
”调用期间,Go 命令将选择主模块中的包进行代码覆盖率分析;默认情况下,不会包含其他用于构建的包(go.mod 中列出的依赖项,或 Go 标准库的一部分的包)。
例如,这是一个包含 main 包、本地主模块包 greetings
以及从模块外部导入的一组包(其中包括 rsc.io/quote
和 fmt
)的玩具程序(完整程序链接)。
$ cat go.mod
module mydomain.com
go 1.20
require rsc.io/quote v1.5.2
require (
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c // indirect
rsc.io/sampler v1.3.0 // indirect
)
$ cat myprogram.go
package main
import (
"fmt"
"mydomain.com/greetings"
"rsc.io/quote"
)
func main() {
fmt.Printf("I say %q and %q\n", quote.Hello(), greetings.Goodbye())
}
$ cat greetings/greetings.go
package greetings
func Goodbye() string {
return "see ya"
}
$ go build -cover -o myprogram.exe .
$
如果您使用“-cover
”命令行标志构建并运行此程序,则配置文件中将只包含两个包:main
和 mydomain.com/greetings
;其他依赖包将被排除。
希望对要包含在代码覆盖率中的包进行更多控制的用户可以使用“-coverpkg
”标志进行构建。示例
$ go build -cover -o myprogramMorePkgs.exe -coverpkg=io,mydomain.com,rsc.io/quote .
$
在上面的构建中,mydomain.com
的 main 包以及 rsc.io/quote
和 io
包被选中进行分析;由于 mydomain.com/greetings
没有明确列出,它将从配置文件中排除,即使它位于主模块中。
运行经过代码覆盖率插桩的二进制文件
使用“-cover
”构建的二进制文件在执行结束时将配置文件数据写入通过环境变量 GOCOVERDIR
指定的目录。示例
$ go build -cover -o myprogram.exe myprogram.go
$ mkdir somedata
$ GOCOVERDIR=somedata ./myprogram.exe
I say "Hello, world." and "see ya"
$ ls somedata
covcounters.c6de772f99010ef5925877a7b05db4cc.2424989.1670252383678349347
covmeta.c6de772f99010ef5925877a7b05db4cc
$
请注意写入目录 somedata
的两个文件:这些(二进制)文件包含代码覆盖率结果。有关如何从这些数据文件生成人类可读的结果,请参阅下一节关于报告。
如果未设置 GOCOVERDIR
环境变量,则经过代码覆盖率插桩的二进制文件仍将正确执行,但会发出警告。示例
$ ./myprogram.exe
warning: GOCOVERDIR not set, no coverage data emitted
I say "Hello, world." and "see ya"
$
涉及多次运行的测试
集成测试在许多情况下可能涉及多次程序运行;当程序使用“-cover
”构建时,每次运行都会生成一个新的数据文件。示例
$ mkdir somedata2
$ GOCOVERDIR=somedata2 ./myprogram.exe // first run
I say "Hello, world." and "see ya"
$ GOCOVERDIR=somedata2 ./myprogram.exe -flag // second run
I say "Hello, world." and "see ya"
$ ls somedata2
covcounters.890814fca98ac3a4d41b9bd2a7ec9f7f.2456041.1670259309405583534
covcounters.890814fca98ac3a4d41b9bd2a7ec9f7f.2456047.1670259309410891043
covmeta.890814fca98ac3a4d41b9bd2a7ec9f7f
$
代码覆盖率数据输出文件有两种类型:元数据文件(包含每次运行不变的项目,例如源文件名和函数名)和计数器数据文件(记录程序执行的部分)。
在上面的示例中,第一次运行生成了两个文件(计数器和元数据),而第二次运行只生成了一个计数器数据文件:由于元数据在每次运行中不会改变,因此只需写入一次。
处理代码覆盖率数据文件
Go 1.20 引入了一个新工具“covdata
”,可用于从 GOCOVERDIR
目录读取和操作代码覆盖率数据文件。
Go 的 covdata
工具以多种模式运行。covdata
工具调用的通用形式为
$ go tool covdata <mode> -i=<dir1,dir2,...> ...flags...
其中“-i
”标志提供要读取的目录列表,每个目录都派生自代码覆盖率插桩二进制文件(通过 GOCOVERDIR
)的执行。
创建代码覆盖率配置文件报告
本节讨论如何使用“go tool covdata
”从代码覆盖率数据文件生成人类可读的报告。
报告语句覆盖率百分比
要报告每个经过插桩的包的“语句覆盖率百分比”指标,请使用命令“go tool covdata percent -i=<directory>
”。使用上面运行部分中的示例
$ ls somedata
covcounters.c6de772f99010ef5925877a7b05db4cc.2424989.1670252383678349347
covmeta.c6de772f99010ef5925877a7b05db4cc
$ go tool covdata percent -i=somedata
main coverage: 100.0% of statements
mydomain.com/greetings coverage: 100.0% of statements
$
这里的“语句覆盖率”百分比直接对应于 go test -cover
报告的百分比。
转换为旧版文本格式
您可以使用 covdata textfmt
选择器将二进制代码覆盖率数据文件转换为由“go test -coverprofile=<outfile>
”生成的旧版文本格式。然后,生成的文本文件可以与“go tool cover -func
”或“go tool cover -html
”一起使用,以创建其他报告。示例
$ ls somedata
covcounters.c6de772f99010ef5925877a7b05db4cc.2424989.1670252383678349347
covmeta.c6de772f99010ef5925877a7b05db4cc
$ go tool covdata textfmt -i=somedata -o profile.txt
$ cat profile.txt
mode: set
mydomain.com/myprogram.go:10.13,12.2 1 1
mydomain.com/greetings/greetings.go:3.23,5.2 1 1
$ go tool cover -func=profile.txt
mydomain.com/greetings/greetings.go:3: Goodbye 100.0%
mydomain.com/myprogram.go:10: main 100.0%
total: (statements) 100.0%
$
合并
“go tool covdata
”的 merge
子命令可用于合并来自多个数据目录的配置文件。
例如,考虑一个在 macOS 和 Windows 上运行的程序。该程序的作者可能希望将来自每个操作系统上的单独运行的代码覆盖率配置文件合并到一个统一的配置文件语料库中,以便生成一个跨平台的代码覆盖率摘要。例如
$ ls windows_datadir
covcounters.f3833f80c91d8229544b25a855285890.1025623.1667481441036838252
covcounters.f3833f80c91d8229544b25a855285890.1025628.1667481441042785007
covmeta.f3833f80c91d8229544b25a855285890
$ ls macos_datadir
covcounters.b245ad845b5068d116a4e25033b429fb.1025358.1667481440551734165
covcounters.b245ad845b5068d116a4e25033b429fb.1025364.1667481440557770197
covmeta.b245ad845b5068d116a4e25033b429fb
$ ls macos_datadir
$ mkdir merged
$ go tool covdata merge -i=windows_datadir,macos_datadir -o merged
$
上面的合并操作将合并指定输入目录中的数据,并将一组新的合并数据文件写入“merged”目录。
包选择
大多数“go tool covdata
”命令都支持“-pkg
”标志,以在操作中执行包选择;“-pkg
”的参数形式与 Go 命令的“-coverpkg
”标志使用的形式相同。示例
$ ls somedata
covcounters.c6de772f99010ef5925877a7b05db4cc.2424989.1670252383678349347
covmeta.c6de772f99010ef5925877a7b05db4cc
$ go tool covdata percent -i=somedata -pkg=mydomain.com/greetings
mydomain.com/greetings coverage: 100.0% of statements
$ go tool covdata percent -i=somedata -pkg=nonexistentpackage
$
“-pkg
”标志可用于选择给定报告感兴趣的特定包子集。
常见问题
- 如何为
go.mod
文件中提到的所有导入包请求代码覆盖率插桩 - 我可以在 GOPATH/GO111MODULE=off 模式下使用
go build -cover
吗? - 如果我的程序出现 panic,会写入代码覆盖率数据吗?
-coverpkg=main
会选择我的 main 包进行分析吗?
如何为 go.mod
文件中提到的所有导入包请求代码覆盖率插桩
默认情况下,go build -cover
将插桩所有主模块包以进行代码覆盖率,但不会插桩主模块之外的导入(例如标准库包或 go.mod
中列出的导入)。请求对所有非标准库依赖项进行插桩的一种方法是将 go list
的输出馈送到 -coverpkg
中。这是一个示例,再次使用上面引用的示例程序
$ go list -f '{{if not .Standard}}{{.ImportPath}}{{end}}' -deps . | paste -sd "," > pkgs.txt
$ go build -o myprogram.exe -coverpkg=`cat pkgs.txt` .
$ mkdir somedata
$ GOCOVERDIR=somedata ./myprogram.exe
$ go tool covdata percent -i=somedata
golang.org/x/text/internal/tag coverage: 78.4% of statements
golang.org/x/text/language coverage: 35.5% of statements
mydomain.com coverage: 100.0% of statements
mydomain.com/greetings coverage: 100.0% of statements
rsc.io/quote coverage: 25.0% of statements
rsc.io/sampler coverage: 86.7% of statements
$
我可以在 GO111MODULE=off 模式下使用 go build -cover
吗?
是的,go build -cover
确实适用于 GO111MODULE=off
。当在 GO111MODULE=off 模式下构建程序时,只有命令行上明确指定为目标的包才会被插桩进行分析。使用 -coverpkg
标志将其他包包含在配置文件中。
如果我的程序出现 panic,会写入代码覆盖率数据吗?
使用 go build -cover
构建的程序只有在程序调用 os.Exit()
或从 main.main
正常返回时,才会在执行结束时写入完整的配置文件数据。如果程序因未恢复的 panic 终止,或者程序遇到致命异常(例如分段错误、除以零等),则运行期间执行的语句的配置文件数据将丢失。
-coverpkg=main
会选择我的 main 包进行分析吗?
-coverpkg
标志接受导入路径列表,而不是包名称列表。如果您想选择您的 main
包进行代码覆盖率插桩,请通过导入路径而不是名称来标识它。示例(使用此示例程序)
$ go list -m
mydomain.com
$ go build -coverpkg=main -o oops.exe .
warning: no packages being built depend on matches for pattern main
$ go build -coverpkg=mydomain.com -o myprogram.exe .
$ mkdir somedata
$ GOCOVERDIR=somedata ./myprogram.exe
I say "Hello, world." and "see ya"
$ go tool covdata percent -i=somedata
mydomain.com coverage: 100.0% of statements
$
资源
- Go 1.2 中引入单元测试覆盖率的博客文章:
- 单元测试的代码覆盖率分析作为 Go 1.2 版本的一部分引入;有关详细信息,请参阅此博客文章。
- 文档:
cmd/go
包文档描述了与代码覆盖率相关的构建和测试标志。
- 技术细节:
词汇表
单元测试: 与特定 Go 包关联的 *_test.go
文件中的测试,利用 Go 的 testing
包。
集成测试: 对给定应用程序或二进制文件的更全面、更重量级的测试。集成测试通常涉及构建一个程序或一组程序,然后在一系列输入和场景下执行多次程序运行,由一个测试工具控制,该工具可能基于也可能不基于 Go 的 testing
包。