PouchContainer 整合測試覆蓋率統計
作者| 阿里雲智慧事業群高階測試開發工程師 劉璐
PouchContainer 是阿里巴巴開源的富容器技術,已於 2018 年 9 月正式釋出 GA 版本,已經完全達到生產級別。PouchContainer 一直非常重視專案質量,專案的開發者需在提交 PR 時提供與之對應的單測與整合測試程式碼。這種要求,一方面保證迴歸質量,同時也減少程式碼 review 成本,提高合作效率。(更多參考:PouchContainer 開源版本及內部版本一致性實踐)
最初,PouchContainer 結合 TravisCI 與 Codecov 工具,為每次 PR 提交執行測試並展示單元測試覆蓋率。對於一些新增整合測試的 PR,整合測試的增減所帶來的測試覆蓋率變化並沒有納入到測試覆蓋率的統計中。
整合測試覆蓋率的缺失,使得開發者缺少對專案測試覆蓋率的更完整認知。為了更全面的展示 PouchContainer 的測試覆蓋率,現在 PouchContainer 已經加入了整合測試覆蓋率的統計功能。本文主要介紹整合測試覆蓋率統計在 PouchContainer 中的實現。
Go 測試覆蓋率
在介紹整合測試覆蓋率統計實現之前,我們需要了解 Golang 的覆蓋率統計的原理。Golang 的覆蓋率統計,是透過在編譯之前重寫包的原始碼,加入統計資訊,然後編譯、執行、收集測試覆蓋率。有關 Go 測試覆蓋率的原理可參考 The cover story (https://blog.golang.org/cover),接下來的內容,主要參考上述文章,並具體列出執行過程。
首先,給出一個待測 Size() 函式,它有多個 switch 分支,程式碼如下:
package size
func Size(a int) string {
switch {
case a < 0:
return "negative"
case a == 0:
return "zero"
case a < 10:
return "small"
}
return "enormous"
}
對應的測試程式碼如下:
$ cat size_test.go
package size
import (
"testing"
"fmt"
)
type Test struct {
in int
out string
}
var tests = []Test{
{-1, "negative"},
{5, "small"},
}
func TestSize(t *testing.T) {
fmt.Println("a")
for i, test := range tests {
size := Size(test.in)
if size != test.out {
t.Errorf("#%d: Size(%d)=%s; want %s", i, test.in, size, test.out)
}
}
}
執行 go test -x -cover -coverprofile=./size.out
命令,執行測試並統計測試覆蓋率。其中,-x
引數列印上述命令的執行過程(需注意:列印的執行步驟資訊不完整,如果手動執行輸出的步驟,則會執行失敗,這是因為 go test 的一些執行步驟並沒有列印資訊),-cover
引數開啟測試覆蓋率統計功能,-coverprofile
引數指定儲存測試覆蓋率檔案,執行結果如下:
$ go test -x -cover -coverprofile=./size.out
WORK=/var/folders/d2/0gxc6wf16hb6t8ng0w00czpm0000gn/T/go-build982568783
mkdir -p $WORK/test/_test/
mkdir -p $WORK/test/_test/_obj_test/
cd $WORK/test/_test/_obj_test/
/usr/local/go/pkg/tool/darwin_amd64/cover -mode set -var GoCover_0 -o .size.go /Users/letty/work/code/go/src/test/size.go
cd /Users/letty/work/code/go/src/test
/usr/local/go/pkg/tool/darwin_amd64/compile -o $WORK/test/_test/test.a -trimpath $WORK -p test -complete -buildid 6033df309978241f19d83a0e6bad252ee3ba376e -D _/Users/letty/work/code/go/src/test -I $WORK -pack $WORK/test/_test/_obj_test/size.go ./size_test.go
cd $WORK/test/_test
/usr/local/go/pkg/tool/darwin_amd64/compile -o ./main.a -trimpath $WORK -p main -complete -D "" -I . -I $WORK -pack ./_testmain.go
cd .
/usr/local/go/pkg/tool/darwin_amd64/link -o $WORK/test/_test/test.test -L $WORK/test/_test -L $WORK -w -extld=clang -buildmode=exe $WORK/test/_test/main.a
$WORK/test/_test/test.test -test.coverprofile=./size.out -test.outputdir /Users/letty/work/code/go/src/test
a
PASS
coverage: 60.0% of statements
ok test 0.006s
從上述輸出的倒數第二行可知,測試覆蓋率為 60%。分析 go test
的執行步驟,第五行呼叫 /usr/local/go/pkg/tool/darwin_amd64/cover
工具,這個工具重寫待測原始碼,在程式碼中加入計數點,用以統計測試覆蓋率。第 8-13 行編譯待測檔案和 _testmain.go
檔案(這個檔案是 go test
工具生成的,具體實現細節可以參見),生成 test.test
測試執行檔案。第 13 行,執行 test.test
測試檔案,傳入測試相關引數,即可執行測試。
檢視 cover
命令的幫助資訊,再次執行 cover
命令,可以檢視被重寫後的測試程式碼:
$ cat .size.go
package size
func Size(a int) string {
GoCover_0.Count[0] = 1
switch {
case a < 0:
GoCover_0.Count[2] = 1
return "negative"
case a == 0:
GoCover_0.Count[3] = 1
return "zero"
case a < 10:
GoCover_0.Count[4] = 1
return "small"
}
GoCover_0.Count[1] = 1
return "enormous"
}
var GoCover_0 = struct {
Count [5]uint32
Pos [3 * 5]uint32
NumStmt [5]uint16
} {
Pos: [3 * 5]uint32{
3, 4, 0x9001a, // [0]
12, 12, 0x130002, // [1]
5, 6, 0x14000d, // [2]
7, 8, 0x10000e, // [3]
9, 10, 0x11000e, // [4]
},
NumStmt: [5]uint16{
1, // 0
1, // 1
1, // 2
1, // 3
1, // 4
},
}
檢視 go test
執行測試後的覆蓋率統計檔案,資訊如下:
$ cat size.out
mode: set
test/size.go:3.26,4.9 1 1
test/size.go:12.2,12.19 1 0
test/size.go:5.13,6.20 1 1
test/size.go:7.14,8.16 1 0
test/size.go:9.14,10.17 1 1
檔案的第一行標識覆蓋率統計模式為 set
,go test
提供 set、count、atomic 三種模式:
set
模式僅統計語句是否執行;count
模式統計語句執行的次數;atomic
模式與count
類似,統計語句執行次數,適用於多執行緒測試。
第二行開始的格式為:name.go:line.column,line.column numberOfStatements count
,即檔名、程式碼的起始位置、語句的行數以及被執行的次數。本次示例程式碼中,待統計的語句共 5 行,統計模式為 set
,共有 3 個 count 被置為 1(讀者可以將 covermode 設定為 count,觀察 count 輸出有何變化),所以最終的測試覆蓋率結果為 60%。
PouchContainer 測試覆蓋率
PouchContainer 整合 CodeCov 工具,每次執行 TravisCI 會將測試覆蓋率檔案上傳至 CodeCov 網站,完成覆蓋率的視覺化展示與持續追蹤。
TravisCI 與 CodeCov 可以很容易的整合,只需在測試路徑下生成一個 coverage.txt 名字的覆蓋率統計檔案,並在 .tarvis.yml
檔案中呼叫 CodeCov 的指令碼,即可上傳覆蓋率統計檔案,具體命令可以參考 Makefile 中 TEST_FLAGS= make build-integration-test 裡面的實現,感興趣的同學也可以直接檢視 CodeCov 指令碼,瞭解其實現細節。
接下來,我們從單測和整合測試覆蓋率統計兩方面展開,詳細闡述 PouchContainer 的實現細節。
單測覆蓋率統計
PouchContianer 收集單測覆蓋率相對簡單,只需要執行 make unit-test
命令,即可實現覆蓋率統計收集。單測覆蓋率統計的實現可以可以參考 Makefile。需要注意的是,覆蓋率統計時需要排除一些無關 package,例如 vendor 目錄、types 目錄等,否則會影響測試覆蓋率的準確性。
整合測試覆蓋率統計
PouchContainer 整合測試,是透過啟動 pouch daemon,然後執行 pouch 命令列或者直接傳送 API 請求,實現對 daemon API 和命令列的測試。正常情況下,待測試 pouch daemon 是透過 go build
編譯,原始碼中沒有插入計數器,無法統計測試覆蓋率。
實現統計 pouch daemon 的測試覆蓋率的 PR 參見),這個 PR(由於程式碼的不斷迭代,最新的程式碼位置已改變,請讀者參照本文所對應的 commit 程式碼)中,我們做了如下工作:
根目錄下新增 main_test.go 測試檔案
hack/build 指令碼中,新增 testserver 函式用於編譯 main package,生成可執行測試檔案
hack/make.sh 指令碼中,後臺啟動步驟 2 生成的測試檔案,並執行 API 和命令列測試
測試結束後,給測試程式傳送訊號,並收集測試覆蓋率
接下來將詳細講述實現細節,首先,新增 main_test.go 測試檔案,並在檔案中定義一個測試函式 TestMain
,程式碼如下:
package main
import (
"os"
"os/signal"
"strings"
"syscall"
"testing"
)
func TestMain(t *testing.T) {
var (
args []string
)
for _, arg := range os.Args {
switch {
case strings.HasPrefix(arg, "DEVEL"):
case strings.HasPrefix(arg, "-test"):
default:
args = append(args, arg)
}
}
waitCh := make(chan int, 1)
os.Args = args
go func() {
main()
close(waitCh)
}()
signalCh := make(chan os.Signal, 1)
signal.Notify(signalCh, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGHUP)
select {
case <-signalCh:
return
case <-waitCh:
return
}
}
透過新增 main_test.go
檔案,可以使我們使用現有的 go test
工具編譯 pouch daemon
,當執行如下命令時,go test
將編譯當前路徑下以 _test
結尾的檔案所屬的 package,即我們需要的 main
package,然後連結到 go test
提供的測試主程式中(即前面提到的 _testmain.go
檔案),生成測試可執行檔案:
# go test -c -race -cover -covermode=atomic -o pouchd-test -coverpkg $pkgs
其中 \$pkg 指定需要統計測試覆蓋率的包名,go test
呼叫 cover
工具對指定的 package 原始碼重寫,加入測試覆蓋率計數器;-o
引數指示僅編譯不執行,且指定測試二進位制名為 pouchd-test
。執行上述命令後,即可得到一個呼叫 main()
函式的測試二進位制檔案。
第三步,啟動 pouch-test
執行測試程式碼,由於測試程式碼中呼叫 pouch daemon
的入口 main()
函式,即可達到啟動 pouch daemon
並提供服務的目的。具體命令如下:
# pouchd-test -test.coverprofile=$DIR/integrationcover.out DEVEL --debug
其中,-test
字首的引數由 go test
處理,DEVEL
之後的引數,則會傳遞給 main()
函式。此時,正常執行測試用例,測試結束後殺掉 pouchd-test
程式,go test
工具會列印出測試覆蓋率,並生成覆蓋率檔案,完成整合測試覆蓋率的統計。
從上述步驟可以看到,統計整合測試覆蓋率的主要工作在於提供一個 main_test.go
檔案,接下來我們分析一下這個檔案做了哪些工作。
首先,檔案中定義了一個測試函式 TestMain()
,這是入口函式,執行測試可執行檔案時,會呼叫這個函式。
函式中 16-27 行進行了引數處理,過濾 -test
開頭以及 DEVEL
引數,並將餘下引數全部賦值給 os.Args
。這是因為 go test
預設將第一個非破折號 -
開頭的引數,交由測試函式處理,main_test.go
程式碼中,過濾引數並重新賦值 os.Args
,將引數傳給 main()
函式,使得我們可以如常使用 daemon 引數。
第 28-31 行呼叫 main 函式,啟動 daemon 服務。第 33-40 行,接收指定訊號並直接退出。注意,我們還定義了一個 waitCh channel
,用於 main
函式退出時,通知測試函式退出,以防止出現 main
函式呼叫自身而其引起的程式永不退出問題。
有關整合測試覆蓋率統計的實現方法,還可以參考這篇文章 《Generating Coverage Profiles for Golang Integration Tests》(https://www.cyphar.com/blog/post/20170412-golang-integration-coverage)。
結語
整合測試覆蓋率的統計,需要靈活運用 Golang 提供的工具,並根據自身專案程式碼特點適配測試檔案。加入整合測試覆蓋率統計後,PouchContainer 的覆蓋率從僅統計單測時的 18% 提升至 60%,這將更準確展示測試現狀。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31555606/viewspace-2638998/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 程式碼覆蓋率與測試覆蓋率比較
- 單元測試的覆蓋率計算
- go 程式碼覆蓋率測試Go
- 程式碼測試覆蓋率分析
- 前端精準測試探索:覆蓋率實時統計工具前端
- 基於JaCoCo的Android測試覆蓋率統計(二)Android
- 簡記前後端如何實現統計測試覆蓋率後端
- Jacoco--測試覆蓋率工具
- 多程式下的測試覆蓋率
- Mockito提升單元測試覆蓋率Mockito
- iOS 覆蓋率檢測原理與增量程式碼測試覆蓋率工具實現iOS
- 基於Jacoco的單元測試程式碼覆蓋率統計
- 使用Rational PureCoverage測試程式碼覆蓋率
- JavaScript覆蓋率統計實現JavaScript
- Linux下lcov單元測試覆蓋率Linux
- 使用EMMA獲取Android測試覆蓋率Android
- 如何使用 jacoco 統計多個 docker 容器服務的測試覆蓋率Docker
- 如何制定介面自動化測試的覆蓋率?
- 測試開發之單元測試-實現Git增量程式碼的Jacoco覆蓋率統計Git
- vivo 基於 JaCoCo 的測試覆蓋率設計與實踐
- 從零入門專案整合Karate和Jacoco,配置測試程式碼覆蓋率
- java覆蓋率檢測-jacocoJava
- 生成Github JS 倉庫的測試覆蓋率徽標GithubJS
- 程式碼覆蓋率測試:從誤傳到現實
- 安卓app功能或自動化測試覆蓋率統計(不用instrumentation啟動app)安卓APP
- 使用 coverlet 檢視.NET Core應用的測試覆蓋率
- 我們是如何做 go 語言系統測試覆蓋率收集的?Go
- idea2022.1 檢視單測覆蓋率展示分支覆蓋率Idea
- EMMA 覆蓋率工具
- James Shore:不要使用單元測試的程式碼覆蓋率
- C++語言的單元測試與程式碼覆蓋率C++
- ant中使用cobertura分析測試用例的程式碼覆蓋率
- 你真正需要的程式碼測試覆蓋率是多少?
- 關於super-jacoco測試覆蓋率具體實現
- python自動統計zabbix系統監控覆蓋率Python
- 【Lua】實現程式碼執行覆蓋率統計工具
- maven 多模組專案的測試覆蓋率分析 - jacoco 聚合分析Maven
- 軟體測試培訓之:白盒測試的語句覆蓋法和判定覆蓋法