Blog Go

Độ phủ mã cho các bài kiểm tra tích hợp Go

Than McIntosh
Ngày 8 tháng 3 năm 2023

Các công cụ đo độ phủ mã giúp các nhà phát triển xác định phần nào của mã nguồn cơ sở được thực thi (được bảo vệ) khi thực hiện một bộ kiểm tra nhất định.

Trong một thời gian, Go đã hỗ trợ (giới thiệu tại bản phát hành Go 1.2) để đo độ phủ mã ở mức gói, sử dụng cờ "-cover" của lệnh "go test".

Phần công cụ này hoạt động tốt trong hầu hết các trường hợp, nhưng có một số điểm yếu đối với các ứng dụng Go lớn hơn. Đối với các ứng dụng như vậy, các nhà phát triển thường viết các bài kiểm tra "tích hợp" để xác minh hành vi của toàn bộ chương trình (ngoài các bài kiểm tra đơn vị ở cấp gói).

Loại kiểm tra này thường liên quan đến việc xây dựng một nhị phân ứng dụng hoàn chỉnh, sau đó chạy nhị phân trên một tập hợp các đầu vào đại diện (hoặc tải sản xuất, nếu đó là máy chủ) để đảm bảo rằng tất cả các gói thành phần hoạt động chính xác cùng nhau, trái ngược với việc kiểm tra từng gói riêng lẻ.

Do nhị phân bài kiểm tra tích hợp được xây dựng bằng lệnh "go build" chứ không phải "go test" nên cho đến hiện tại, phần công cụ của Go vẫn chưa cung cấp bất kỳ cách đơn giản nào để thu thập hồ sơ độ phủ cho các bài kiểm tra này.

Với Go 1.20, bạn có thể xây dựng các chương trình được trang bị độ phủ bằng lệnh "go build -cover", sau đó đưa các nhị phân được trang bị này vào bài kiểm tra tích hợp để mở rộng phạm vi của bài kiểm tra độ phủ.

在這個部落格文章中,我們將提供一個範例說明這些新功能如何運作,並概述從整合測試收集涵蓋率剖析檔的一些使用案例和工作流程。

範例

我們來示範一個非常小的範例程式,為它撰寫一個簡單的整合測試,然後從整合測試中收集涵蓋率剖析檔。

對於這個練習,我們將使用來自 gitlab.com/golang-commonmark/mdtool 的「mdtool」標記處理工具。這是為展示客戶如何使用 gitlab.com/golang-commonmark/markdown 這個標記轉換 HTML 函式庫而設計的示範程式。

設定 mdtool

首先,讓我們下載「mdtool」本身的副本(我們挑選一個特定版本,只是為了讓這些步驟可重製)

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

一個簡單的整合測試

現在我們將為「mdtool」撰寫一個簡單的整合測試;我們的測試將建立「mdtool」二進位檔,然後在一個輸入標記檔案集合上執行它。這個非常簡單的指令碼在試驗資料目錄中對每個檔案執行「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 Developer Survey 2023 Q1 結果
上一篇:所有可比較的類型
部落格索引