Go 博客

Go 集成测试的代码覆盖率

Than McIntosh
2023 年 3 月 8 日

代码覆盖率工具帮助开发人员确定在执行给定测试套件时,源代码库的哪一部分被执行(覆盖)。

Go 一直以来都提供支持(在 Go 1.2 版本中引入)来衡量包级别的代码覆盖率,使用“go test”命令的“-cover”标志。

这种工具在大多数情况下都能正常工作,但对于较大的 Go 应用程序来说有一些缺点。对于此类应用程序,开发人员通常会编写“集成”测试,以验证整个程序的行为(除了包级别的单元测试之外)。

这种类型的测试通常涉及构建一个完整的应用程序二进制文件,然后在代表性的一组输入上运行该二进制文件(或者在生产负载下运行,如果它是一个服务器),以确保所有组件包都能正确地协同工作,而不是单独测试各个包。

由于集成测试二进制文件是使用“go build”而不是“go test”构建的,因此 Go 的工具一直没有提供任何简单的方法来收集这些测试的覆盖率配置文件,直到现在。

使用 Go 1.20,您现在可以使用“go build -cover”构建已覆盖率检测的程序,然后将这些已检测的二进制文件提供给集成测试,以扩展覆盖率测试的范围。

在本博文中,我们将举例说明这些新功能的工作原理,并概述一些从集成测试收集覆盖率配置文件的用例和工作流程。

示例

让我们举一个非常小的示例程序,为它编写一个简单的集成测试,然后从集成测试中收集一个覆盖率配置文件。

在本练习中,我们将使用来自gitlab.com/golang-commonmark/mdtool的“mdtool” Markdown 处理工具。这是一个演示程序,旨在展示客户端如何使用包gitlab.com/golang-commonmark/markdown,一个 Markdown 到 HTML 转换库。

为 mdtool 设置

首先,让我们下载“mdtool”本身的副本(我们选择一个特定的版本只是为了使这些步骤可重现)

$ git clone https://gitlab.com/golang-commonmark/mdtool.git
...
$ cd mdtool
$ git tag example e210a4502a825ef7205691395804eefce536a02f
$ git checkout example
...
$

一个简单的集成测试

现在,我们将为“mdtool”编写一个简单的集成测试;我们的测试将构建“mdtool”二进制文件,然后在一些输入 Markdown 文件上运行它。这个非常简单的脚本在测试数据目录中的每个文件上运行“mdtool”二进制文件,检查它是否产生了输出并且没有崩溃。

$ cat integration_test.sh
#!/bin/sh
BUILDARGS="$*"
#
# Terminate the test if any command below does not complete successfully.
#
set -e
#
# Download some test inputs (the 'website' repo contains various *.md files).
#
if [ ! -d testdata ]; then
  git clone https://go.googlesource.com/website testdata
  git -C testdata tag example 8bb4a56901ae3b427039d490207a99b48245de2c
  git -C testdata checkout example
fi
#
# Build mdtool binary for testing purposes.
#
rm -f mdtool.exe
go build $BUILDARGS -o mdtool.exe .
#
# Run the tool on a set of input files from 'testdata'.
#
FILES=$(find testdata -name "*.md" -print)
N=$(echo $FILES | wc -w)
for F in $FILES
do
  ./mdtool.exe +x +a $F > /dev/null
done
echo "finished processing $N files, no crashes"
$

以下是我们测试的一个示例运行结果

$ /bin/sh integration_test.sh
...
finished processing 380 files, no crashes
$

成功:我们已经验证了“mdtool”二进制文件成功地消化了一组输入文件……但我们实际上运行了多少工具的源代码?在下一节中,我们将收集一个覆盖率配置文件来找出答案。

使用集成测试来收集覆盖率数据

让我们编写另一个包装脚本,它调用之前的脚本,但构建了用于覆盖率的工具,然后对生成的配置文件进行后处理

$ cat wrap_test_for_coverage.sh
#!/bin/sh
set -e
PKGARGS="$*"
#
# Setup
#
rm -rf covdatafiles
mkdir covdatafiles
#
# Pass in "-cover" to the script to build for coverage, then
# run with GOCOVERDIR set.
#
GOCOVERDIR=covdatafiles \
  /bin/sh integration_test.sh -cover $PKGARGS
#
# Post-process the resulting profiles.
#
go tool covdata percent -i=covdatafiles
$

关于上面的包装器,需要重点注意以下几点

  • 它在运行integration_test.sh时传递了“-cover”标志,这使我们得到一个已覆盖率检测的“mdtool.exe”二进制文件
  • 它设置了 GOCOVERDIR 环境变量,覆盖率数据文件将写入该目录
  • 测试完成后,它运行“go tool covdata percent”来生成关于覆盖语句百分比的报告

以下是我们运行此新包装脚本时的输出结果

$ /bin/sh wrap_test_for_coverage.sh
...
    gitlab.com/golang-commonmark/mdtool coverage: 48.1% of statements
$
# Note: covdatafiles now contains 381 files.

瞧!我们现在对集成测试在运行“mdtool”应用程序的源代码方面的工作情况有所了解。

如果我们更改测试套件以增强它,然后进行第二次覆盖率收集运行,我们将看到这些更改反映在覆盖率报告中。例如,假设我们通过向integration_test.sh添加这两行来改进测试

./mdtool.exe +ty testdata/README.md  > /dev/null
./mdtool.exe +ta < testdata/README.md  > /dev/null

再次运行覆盖率测试包装器

$ /bin/sh wrap_test_for_coverage.sh
finished processing 380 files, no crashes
    gitlab.com/golang-commonmark/mdtool coverage: 54.6% of statements
$

我们可以看到我们更改的影响:语句覆盖率从 48% 提高到 54%。

选择要覆盖的包

默认情况下,“go build -cover”只检测作为正在构建的 Go 模块一部分的包,在本例中是gitlab.com/golang-commonmark/mdtool包。但是,在某些情况下,将覆盖率检测扩展到其他包很有用;这可以通过将“-coverpkg”传递给“go build -cover”来实现。

对于我们的示例程序,“mdtool”实际上只是一个围绕包gitlab.com/golang-commonmark/markdown的包装器,因此,将markdown包含在被检测的包集中很有意思。

以下是“mdtool”的go.mod文件

$ head go.mod
module gitlab.com/golang-commonmark/mdtool

go 1.17

require (
    github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8
    gitlab.com/golang-commonmark/markdown v0.0.0-20211110145824-bf3e522c626a
)

我们可以使用“-coverpkg”标志来控制选择哪些包包含在覆盖率分析中,以包含上面的依赖项之一。以下是一个示例

$ /bin/sh wrap_test_for_coverage.sh -coverpkg=gitlab.com/golang-commonmark/markdown,gitlab.com/golang-commonmark/mdtool
...
    gitlab.com/golang-commonmark/markdown   coverage: 70.6% of statements
    gitlab.com/golang-commonmark/mdtool coverage: 54.6% of statements
$

处理覆盖率数据文件

当一个覆盖率集成测试完成并写出了一组原始数据文件(在本例中是covdatafiles目录的内容)后,我们可以通过各种方式对这些文件进行后处理。

将配置文件转换为“-coverprofile”文本格式

在处理单元测试时,您可以运行go test -coverprofile=abc.txt来为给定的覆盖率测试运行写入一个文本格式的覆盖率配置文件。

使用go build -cover构建的二进制文件,您可以在事后通过对写入 GOCOVERDIR 目录的文件运行go tool covdata textfmt来生成文本格式的配置文件。

完成此步骤后,您可以使用go tool cover -func=<file>go tool cover -html=<file>来解释/可视化数据,就像您使用go test -coverprofile一样。

示例

$ /bin/sh wrap_test_for_coverage.sh
...
$ go tool covdata textfmt -i=covdatafiles -o=cov.txt
$ go tool cover -func=cov.txt
gitlab.com/golang-commonmark/mdtool/main.go:40:     readFromStdin   100.0%
gitlab.com/golang-commonmark/mdtool/main.go:44:     readFromFile    80.0%
gitlab.com/golang-commonmark/mdtool/main.go:54:     readFromWeb 0.0%
gitlab.com/golang-commonmark/mdtool/main.go:64:     readInput   80.0%
gitlab.com/golang-commonmark/mdtool/main.go:74:     extractText 100.0%
gitlab.com/golang-commonmark/mdtool/main.go:88:     writePreamble   100.0%
gitlab.com/golang-commonmark/mdtool/main.go:111:    writePostamble  100.0%
gitlab.com/golang-commonmark/mdtool/main.go:118:    handler     0.0%
gitlab.com/golang-commonmark/mdtool/main.go:139:    main        51.6%
total:                          (statements)    54.6%
$

使用‘go tool covdata merge’合并原始配置文件

每次执行“-cover”构建的应用程序都会将一个或多个数据文件写入 GOCOVERDIR 环境变量中指定的目录。如果一个集成测试执行 N 次程序执行,您将在输出目录中得到 O(N) 个文件。数据文件中通常有许多重复的内容,因此,为了压缩数据和/或合并来自不同集成测试运行的数据集,您可以使用go tool covdata merge命令来合并配置文件。例如

$ /bin/sh wrap_test_for_coverage.sh
finished processing 380 files, no crashes
    gitlab.com/golang-commonmark/mdtool coverage: 54.6% of statements
$ ls covdatafiles
covcounters.13326b42c2a107249da22f6e0d35b638.772307.1677775306041466651
covcounters.13326b42c2a107249da22f6e0d35b638.772314.1677775306053066987
...
covcounters.13326b42c2a107249da22f6e0d35b638.774973.1677775310032569308
covmeta.13326b42c2a107249da22f6e0d35b638
$ ls covdatafiles | wc
    381     381   27401
$ rm -rf merged ; mkdir merged ; go tool covdata merge -i=covdatafiles -o=merged
$ ls merged
covcounters.13326b42c2a107249da22f6e0d35b638.0.1677775331350024014
covmeta.13326b42c2a107249da22f6e0d35b638
$

go tool covdata merge操作还接受一个-pkg标志,可以用来选择特定的包或一组包,如果需要的话。

此合并功能还有助于合并来自不同类型的测试运行的结果,包括由其他测试套件生成的运行结果。

总结

这就是全部内容:随着 1.20 版本的发布,Go 的覆盖率工具不再局限于包测试,而是支持从较大的集成测试中收集配置文件。我们希望您能充分利用这些新功能,帮助您了解较大和更复杂的测试的工作情况,以及它们运行了源代码的哪些部分。

请尝试这些新功能,并且像往常一样,如果您遇到问题,请在我们的GitHub 问题跟踪器上提交问题。谢谢。

下一篇文章:Go 开发者调查 2023 年第一季度结果
上一篇文章:所有可比较的类型
博客索引