如何用好 Go 的測試黑科技

a_wei發表於2020-01-19

測試是每一個開發人員都需要掌握的技能,儘管你不需要像測試人員那麼專業,但你也應該儘可能的做到那麼專業,據我瞭解到我身邊的一些Go開發人員,他們對Go的測試僅僅侷限於寫一個_test.go 測試檔案,對執行方法進行測試,然後在goland的Ide中右鍵run方法執行,觀測結果是否為綠色,僅此而已,我想說的是這只是一些皮毛,所以今天分享一些Go的測試技能,希望大家有收穫。

Go測試用例

Go的測試檔案命名規則為xxx_test.go,其中xxx是需要測試的原始碼檔案的名稱。在test檔案中,可以編寫測試函式,Go的測試函式整理分為4種,如下,其中XXX是需要測試的方法名稱

  1. 單元測試:TestXXX(t *testing.T)
  2. 基準測試:BenchmarkXXX(b *testing.B)
  3. Main函式測試:TestMain(m *testing.M)
  4. 控制檯測試 ExampleXXX()

假設我們有一個array_utils.go的原始碼檔案,包名為array_utils,我們在該包建立一個測試檔案,名稱為:array_utils_test.go檔案,原始碼檔案中有一個求最大子序列和的方法,我們針對該方法測試,如下程式碼,_test.go檔案中可以有任意多個測試方法,這些測試方法的合集被稱作測試套件。

我們的原始碼檔案如下:
 package array_utils
 //求最大子序列和 (就是說子序列加起來和最大)
 func FindMaxSeqSum(array []int) int {
   SeqSum := make([]int, 0) // 儲存子序列和
   // 初始子序列和為 陣列下標為0的值
   SeqSum = append(SeqSum, array[0])
   for i := 1; i < len(array); i++ {
      if array[i] > SeqSum[i-1]+array[i] {
         SeqSum = append(SeqSum, array[i])
      } else {
         SeqSum = append(SeqSum, SeqSum[i-1]+array[i])
      }
   }
   max := SeqSum[0]
   for j := 1; j < len(SeqSum); j++ {
      if SeqSum[j] > SeqSum[j-1] {
         max = SeqSum[j]
      }
   }
   fmt.Println(max) //列印結果
   return max
}
我們的測試檔案如下
package array_utils

import (
   "fmt"
   "os"
   "testing"
)
//TestMain會在下面所有測試方法執行開始前先執行,一般用於初始化資源和執行完後釋放資源
func TestMain(m *testing.M) {
   fmt.Println("初始化資源")
   result := m.Run() //執行go的測試,相當於呼叫main方法
   fmt.Println("釋放資源")
   os.Exit(result) //退出程式
}
//單元測試
func TestFindMaxSeqSum(t *testing.T) {
   sum := FindMaxSeqSum([]int{1, 3, -9, 6, 8, -19})
   if sum == 14 {
      t.Log("successful")
   } else {
      t.Error("failed")
   }
}
//基準測試
func BenchmarkFindMaxSeqSum(b *testing.B) {
   for i := 0; i < b.N; i++ {
      FindMaxSeqSum([]int{1, 3, -9, 6, 8, -19})
   }
}
//這個驗證的是FindMaxSeqSum方法控制檯輸出的max和OutPut後面的14是否一致,如果相同,則表示驗證通過,否則測試用例失敗
func ExampleFindMaxSeqSum() {
   FindMaxSeqSum([]int{1, 3, -9, 6, 8, -19})
   // OutPut: 14
}

我們通過命令go test來執行這段測試程式碼,進入到array_utils包下面,go test會遍歷當前包下所有的xxx_test.go中符合上述命名規則的函式,然後生成一個臨時的main包用於呼叫相應的測試函式,然後構建並執行、報告測試結果,最後清理測試中生成的臨時檔案,結果如下:

初始化資源
=== RUN   TestFindMaxSeqSum
14
--- PASS: TestFindMaxSeqSum (0.00s)
    array_utils_test.go:16: successful
PASS
釋放資源

Go test 有兩種執行模式

本地目錄模式,在沒有包引數(例如 go test 或 go test -v )呼叫時發生。在此模式下, go test 編譯當前目錄中找到的包和測試,然後執行測試二進位制檔案。在這種模式下,caching 是禁用的。在包測試完成後,go test 列印一個概要行,顯示測試狀態、包名和執行時間

  1. go test 當軟體包屬於$GOPATH時,不需要從 Go Module 內部執行此命令,就進行測試

包列表模式,在使用顯示包引數呼叫 go test 時發生(例如 go test math , go test ./... 甚至是 go test .)。在此模式下,Go測試編譯並測試在命令上列出的每個包。如果一個包測試通過, go test 只列印最終的 ok 總結行。如果一個包測試失敗, go test 將輸出完整的測試輸出。如果使用 -bench 或 -v 標誌,則 go test 會輸出完整的輸出,甚至是通過包測試,以顯示所請求的基準測試結果或詳細日誌記錄

  1. go test math ,指的是測試Go的SDK的math包中的test檔案
  2. go test ./... , 指的是測試遞迴測試當前目錄下的所有test檔案,因為當前目錄下還有還有子資料夾,子資料夾下面還有子資料夾。
  3. go test . 測試當前目錄中的軟體包
  4. go test ./tranform 來測試 ./tranform 目錄中的包

在包列表模式下,Go快取成功的測試結果,以避免重複執行相同的測試。每當 GO 在包上執行測試時,Go都會建立一個測試二進位制檔案並執行它,如果要全域性禁用快取,可以將GOCACHE環境變數設定為off,

set GOCACHE=off  //關閉,go1.12版本後必須開啟,否則編譯器報錯
set GOCACHE=on  //開啟
go clean -testcache  //手動清除快取

Go的test常用引數實踐

上面我們只是執行了go test的命令,關於go test可能的flag還有很多,不同的flag其對應的功能不同,接下來我們來實踐一下。

基礎功能引數

go test -c 生成用於執行測試的可執行檔案,但不執行,在window平臺下生成的是.exe檔案,截圖如下

如何用好Go的測試黑科技

go test -i 安裝/重新安裝執行測試所需的依賴包,但不編譯和執行測試程式碼。

go test -o array_utils.test.exe 執行指定的的可執行的測試檔案

go test -v 輸出列印有關測試函式的其它資訊

go test -v array_utils_test.go array_utils.go 測試指定的的檔案

go test -v array_utils_test.go array_utils.go -test.run TestFindMaxSeqSum 測試指定檔案的指定方法

go test -v -run=TestFindMaxSeqSum 測試指定檔案的指定方法,-run後面可以匹配正規表示式,這個指的是測試名字等於TestFindMaxSeqSum的方法,如果是多個方法的話,可以使用|來隔開方法名。

程式碼覆蓋率

由單元測試的程式碼,觸發執行到的被測試程式碼的程式碼行數佔所有程式碼行數的比例,被稱為測試覆蓋率,程式碼覆蓋率不一定完全精準,但是可以作為參考,可以幫我們測量和我們預計的覆蓋率之間的差距

go test -cover 生成程式碼測試覆蓋率 ,coverage: 8.1% of statements

go test -v -coverprofile=c.out 將生成的程式碼測試覆蓋率放入一個檔案,然後執行下面的命令可以將c.out檔案轉換成一個html檔案用瀏覽器閱讀,截圖如下,no coverage 代表沒有覆蓋的程式碼,high coverage代表高覆蓋率的代表,一個紅色,一個綠色,這裡紅色的截圖上沒體現出來,大家可本地試驗一下。

go tool cover -html=c.out -o=tag.html

如何用好Go的測試黑科技

go test -covermode=set 覆蓋測試模式,有三種值set,count,atomic,其中set代表的是這個語句執行嗎?count代表的是這個語句執行多少次,atomic代表的是多執行緒正確使用的,耗資源的。

基準測試

go test預設情況下只會執行單元測試,那麼基準測試如何執行呢?接下來一起看看。

go test -bench=. 執行當前測試包下的基準測試方法,在執行過程中會根據實際case的執行時間是否穩定會增加b.N的次數,要注意如果是要測試一個非穩態的函式,那麼它可能永遠也執行不完,記住-bench後面跟的是正規表示式

這是執行結果,-8指的是執行時對應的 GOMAXPROCS 的值,5000000指的是for迴圈的次數,249 ns/op 指的是每一次迴圈耗時239納秒
BenchmarkFindMaxSeqSum-8         5000000               249 ns/op

go test -run=none -bench=. 通過指定方法名稱為none來過濾掉單元測試,只執行基準測試的方法,當然也可以根據-bench後面的正規表示式來匹配。

go test -benchtime=3s -bench=. 在持續時間3s內執行每個基準測試

go test -benchmem -bench=. 列印基準測試時的記憶體分配

120 B/op代表每次操作消耗120B記憶體(1kb=1024b),  4 allocs/op 代表每次操作分配記憶體的次數
BenchmarkFindMaxSeqSum-8   5000000   300 ns/op    120 B/op    4 allocs/op

go test -count=2 -bench=. 執行指定次數的基準測試,在-count=1時相當於禁用快取

go test -cpu=1 -bench=. 設定指定的cpu數量來進行基準測試,可以指定多個不同的cpu個數列別,比如:-cpu=1,2,4

其它一些引數控制

go test -timeout=3s預設情況下,測試執行超過10分鐘就會超時而退出,我們可以通過這個時間指定超時時間

go test -parallel=2 當測試使用t.Parallel()方法將測試轉為併發時,將受到最大併發數的限制,預設情況下最多有GOMAXPROCS個測試併發,其他的測試只能阻塞等待,這個可以用來併發安全的測試。

go test -short 縮短長時間執行的測試的測試時間。預設關閉

go test -v -cpuprofile=cpuprof.out 生成cpuprof的檔案,通過執行下面的命令可以檢視cpuprof的檔案,預設是在控制檯檢視,當然也可以web介面檢視,這不是本篇文章的重點,後面會單說。

go tool pprof prof.out

go test -trace trace.out 在退出之前,將執行跟蹤寫入指定檔案。

go test -race 檢測併發情況下資料競爭的問題,這個的使用比較複雜,後面也會單寫文章來介紹。

testing 的變數

test.short : 一個快速測試的標記,在測試用例中可以使用 testing.Short() 來繞開一些測試
test.outputdir : 輸出目錄
test.coverprofile : 測試覆蓋率引數,指定輸出檔案
test.run : 指定正則來執行某個 / 某些測試用例
test.memprofile : 記憶體分析引數,指定輸出檔案
test.memprofilerate : 記憶體分析引數,記憶體分析的抽樣率
test.cpuprofile : cpu 分析輸出引數,為空則不做 cpu 分析
test.blockprofile : 阻塞事件的分析引數,指定輸出檔案
test.blockprofilerate : 阻塞事件的分析引數,指定抽樣頻率
test.timeout : 超時時間
test.cpu : 指定 cpu 數量
test.parallel : 指定執行測試用例的並行數

還有很多,需要讀者們自行研究,總結,分享,可以留言區討論

testing.T 和 testing.B

testing 包內的結構

B : 壓力測試
BenchmarkResult : 壓力測試結果
Cover : 程式碼覆蓋率相關結構體
CoverBlock : 程式碼覆蓋率相關結構體
InternalBenchmark : 內部使用的結構
InternalExample : 內部使用的結構
InternalTest : 內部使用的結構
M : main 測試使用的結構
PB : Parallel benchmarks 並行測試使用結果
T : 普通測試用例
TB : 測試用例的介面

testing包的方法

如何用好Go的測試黑科技

如何用好Go的測試黑科技

本作品採用《CC 協議》,轉載必須註明作者和本文連結

那小子阿偉

相關文章