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 包。但在某些情況下,將覆蓋率檢測擴充套件到其他包是有用的;這可以透過向“go build -cover”傳遞“-coverpkg”來實現。

對於我們的示例程式,“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 構建的二進位制檔案,您可以在事後執行 go tool covdata textfmt 處理寫入 GOCOVERDIR 目錄的檔案來生成文字格式的配置檔案。

完成此步驟後,您可以使用 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 年第一季度結果
上一篇文章:所有可比較的型別
部落格索引