中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

PouchContainer 集成測試覆蓋率統計

發布時間:2020-08-11 15:59:30 來源:ITPUB博客 閱讀:192 作者:阿里系統軟件技術 欄目:服務器

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 工具生成的,具體實現細節可以參見https://github.com/golang/go/blob/3f150934e274f9ce167e1ed565fb3e60b8ea8223/src/cmd/go/internal/test/test.go#L1887),生成 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{
        340x9001a// [0]
        12120x130002// [1]
        560x14000d// [2]
        780x10000e// [3]
        9100x11000e// [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

文件的第一行標識覆蓋率統計模式為 setgo 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 參見https://github.com/alibaba/pouch/pull/1338),這個 PR(由于代碼的不斷迭代,最新的代碼位置已改變,請讀者參照本文所對應的 commit 代碼)中,我們做了如下工作:

  1. 根目錄下新增 main_test.go 測試文件

  2. hack/build 腳本中,新增 testserver 函數用于編譯 main package,生成可執行測試文件

  3. hack/make.sh 腳本中,后臺啟動步驟 2 生成的測試文件,并運行 API 和命令行測試

  4. 測試結束后,給測試進程發送信號,并收集測試覆蓋率

接下來將詳細講述實現細節,首先,新增 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 int1)

    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%,這將更準確展示測試現狀。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

宜丰县| 漳浦县| 育儿| 姚安县| 旺苍县| 南开区| 大城县| 鲁山县| 定州市| 晋江市| 汉中市| 肥西县| 三亚市| 分宜县| 丘北县| 湟源县| 宁化县| 文水县| 长汀县| 南通市| 宣威市| 汨罗市| 微博| 宿松县| 德庆县| 荆州市| 荣昌县| 晋城| 资溪县| 宜宾县| 文昌市| 蒲城县| 开化县| 长宁区| 佛坪县| 大同县| 大城县| 永丰县| 甘谷县| 华阴市| 喜德县|