
文章讨论了如何使用 Go 语言进行单元测试和增量覆盖率分析,并介绍了如何提高代码覆盖率的效率。
前言
团队中要求提交代码的增量覆盖率要达某阈值后才能合入代码,但每次使用CI检测很麻烦。由此,我在本地搭建出了一套测试环境,来帮助我确认是否达到阈值,以及查看单侧覆盖了哪些代码。
Go test工具
在Go语言中,单元测试文件必须以 *_test.go 形式存在,而Go提供了单元测试工具go test,执行以下命令可以运行项目目录中的所有单元测试。
go test ./...
查看覆盖率
go test 工具同时也提供了分析单测覆盖率的功能,当我们使用如下命令进行时。
go test -cover ./...
控制台将输出每一个package的单侧覆盖率。当然,如果我们想将覆盖率以文件的形式输出,我们可以执行以下命令。
go test -coverprofile=c.out ./...
文件将被命名为c.out,保存至运行命令的目录中。
生成覆盖率报告
我们可以使用go提供的工具go tool来生成覆盖率报告,需要用到上述c.out文件。
go tool cover -html=c.out -o coverage.html
该命令将生成一个名为coverage.html的HTML文件,我们可以在浏览器中打开该文件来查看代码覆盖率报告。
但到此为止,我们仍然无法获知增量覆盖率是多少。即使覆盖率已经达到了90%,也不能保证新增的代码被覆盖其中。
工具准备
diff_cover
diffcover是用python开发的工具,通过 git diff 来对比当前分支和需要比对的分支,主要针对新增代码做覆盖率分析。支持读取 Cobertura 格式的 XML 文件作为输入。
graph LR
A[代码测试] --> B[覆盖率数据]
B --> C[Cobertura XML]
C --> D[diff-cover-report]
E[Git Diff] --> D
pip install diff_cover
gocov
gocov是一个Go语言的代码覆盖率分析工具。它的主要功能包括:
- 收集Go代码的测试覆盖率数据
- 将覆盖率数据转换成多种格式用于展示和分析
- 生成覆盖率报告
与go原生工具相比,gocov提供更丰富的输出格式和更详细的报告。
go install github.com/axw/gocov/gocov@latest
gocov-xml
gocov-xml是一个简单的辅助工具,用于从gocov输出中生成Cobertura格式的XML文件。
go install github.com/AlekSi/gocov-xml@latest
工作步骤
为了生成单元测试的增量覆盖率,我们需要先使用go test 命令生成覆盖率报告,再使用gocov工具将覆盖率报告转换为json形式,再使用gocov-xml工具将json形式的报告转换为xml,最后使用diff_cover分析xml文件,得出增量覆盖率,如下图所示。
暂时无法在飞书文档外展示此内容
具体步骤如下:
- 执行
go test -coverprofile=out/c.out ./...,将覆盖率报告c.out输出至./out文件夹中 - 执行
gocov convert out/c.out | gocov-xml > out/c.xml,将c.out转换为c.xml - 执行
diff-cover out/c.xml --compare-branch=master --html-report=out/c.html --fail-under=60,将当前分支与mater分支对比,要求增量覆盖率达到60%,并输出报告的html文件。
举例
假设代码正处于git master分支上,我们在animal包下面实现了如下函数:
package animal
func IsAnimal(name string) bool {
switch name {
case "dog":
return true
case "cat":
return true
default:
return false
}
}
并为此编写了单元测试。
func TestIsAnimal(t *testing.T) {
type args struct {
name string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "case 1",
args: args{
name: "dog",
},
want: true,
},
{
name: "case 2",
args: args{
name: "cat",
},
want: true,
},
{
name: "case 3",
args: args{
name: "none",
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := IsAnimal(tt.args.name); got != tt.want {
t.Errorf("IsAnimal() = %v, want %v", got, tt.want)
}
})
}
}
很明显,该单元测试能够100%覆盖master的代码,为了确定能否100%覆盖,我们执行go test -v -cover ./...命令,结果如下:
coverage: 100.0% of statements
ok cov/animal 0.002s coverage: 100.0% of statements
现在,我们新增一行代码,但并不扩充单元测试。
package animal
func IsAnimal(name string) bool {
switch name {
case "dog":
return true
case "cat":
return true
case "rabbit":
return true
default:
return false
}
}
再次执行go test -v -cover ./...,此时单侧覆盖率为80%。
ok cov/animal (cached) coverage: 80.0% of statements
根据流程执行命令,我们得到增量覆盖率为0%:
-------------
animal/check.go (0.0%): Missing lines 10
-------------
Total: 1 line
Missing: 1 line
Coverage: 0%
-------------
提高效率
工作中,我们可以使用make工具帮助我们提高效率。
首先我们先检查环境中是否已经安装了以上工具。
check_tools:
@command -v gocov > /dev/null 2>&1 || (echo 'goconv is required';exit 1)
@command -v gocov-xml >/dev/null 2>&1 || (echo 'gocov-xml is required'; exit 1)
@command -v diff-cover >/dev/null 2>&1 || (echl 'diff-cover is required'; exit 1)
然后运行单元测试并输出增量覆盖率。
# 在外部定义变量
compare_branch := master #对比分支
fail_under := 60 #最低覆盖率要求
out_file := out/coverprofile.out #原覆盖率报告
out_xml := out/coverage.xml #覆盖率xml报告
out_report := out/report.html #增量覆盖率html报告
cover: check_tools
@go test -coverprofile=$(out_file) ./...
@gocov convert $(out_file) | gocov-xml > $(out_xml)
@diff-cover $(out_xml) --compare-branch=$(compare_branch) --html-report=$(out_report) --fail-under=$(fail_under)
那么,当我们每次需要进行测试时,只需要输入以下命令即可。
make cover
仍然使用上述例子,执行之后运行结果如下:
root@LAPTOP-QRJSTFAO:~/cov# make cover
cov coverage: 0.0% of statements
ok cov/animal 0.002s coverage: 80.0% of statements
gocov convert out/coverprofile.out | gocov-xml > out/coverage.xml
diff-cover out/coverage.xml --compare-branch=master --html-report=out/report.html --fail-under=60
Failure. Coverage is below 60%.
-------------
Diff Coverage
Diff: master...HEAD, staged and unstaged changes
-------------
animal/check.go (0.0%): Missing lines 10
-------------
Total: 1 line
Missing: 1 line
Coverage: 0%
-------------