Go test 基礎用法大全
【導讀】go 語言的 test 命令有很多參數,怎麼利用 test 命令和它提供的參數,又能做到什麼?本文做了詳細解讀。
當直接使用
IDE
進行單元測試時,有沒有好奇它時如何實現的?比如GoLand
寫的測試用例。
所有的代碼都需要寫測試用例。這不僅僅是對自己的代碼負責,也是對別人的負責。
最近工作中使用glog
這個庫,因爲它對外提供的方法都很簡單,想封裝處理一下。但卻遇到了點麻煩:這個包需要在命令行傳遞log_dir
參數,來指定日誌文件的路徑。
所以,正常運行的話,首先需要編譯可執行文件,然後命令行指定參數執行。如下示例:
go build main.go
./main -log_dir="/data" //當前目錄作爲日誌輸出目錄
但在go test
的時候,如何指定這個參數了?
Test
調查發現,發現go test
也可以生成可執行文件。需要使用-c
來指定。示例如下:
go test -c param_test_dir //最後一個參數是待測試的目錄
執行後就會發現:這樣的做法,會運行所有的Test
用例。如何僅僅執行某一個測試用例了(編譯器到底是如何做到的?)。
這裏有另一個屬性-run
,用來指定執行的測試用例的匹配模式。舉個例子:
func TestGetRootLogger(t *testing.T) {
writeLog("測試")
}
func TestGetRootLogger2(t *testing.T) {
writeLog("測試 2")
}
當我在命令行明確匹配執行Logger2
,運行的時候確實僅僅執行該測試用例
go test -v -run Logger2 ./util/ //-v 表示 verbose,輸出相信信息
但是,我發現,在指定了c
參數之後,run
參數無法生效!這樣的話,還真是沒有好的辦法來處理這種情況。
option
-timeout
默認go test
運行超過10m
會發生panic
。如果需要運行更長的時間,需要明確指定。將timeout
指定爲 0,用於忽略時間限制。
nohup go test -v -timeout 0 -run TestGetRange . > log.txt
使用map
的測試
可以結合使用閉包,設置期望值,來寫測試用例。Run
函數內部是阻塞的,所以TestSum
方法依次執行測試。
同時testSumFunc
返回了test
方法使用了閉包的特性,對返回函數內部的值是無法確定的。
func TestSum(t *testing.T) {
t.Run("A", testSumFunc([]int{1, 2, 3}, 7))
t.Run("B", testSumFunc([]int{2, 3, 4}, 8))
}
func Sum(numbers []int) int {
total := 0
for _, v := range numbers {
total += v
}
return total
}
func testSumFunc(numbers []int, expected int) func(t *testing.T) {
return func(t *testing.T) {
actual := Sum(numbers)
if actual != expected {
t.Error(fmt.Sprintf("Expected the sum of %v to be %d but instead got %d!", numbers, expected, actual))
}
}
}
Main
非常簡單,看如下示例。這樣在執行任何test case
時都首先執行準備,在測試用例執行完畢後,會運行清理工作。需要特別說明的是:flag.Parse()
以及os.Exit(m.Run())
是不可缺少的兩步。
func TestMain(m *testing.M) {
//準備工作
fmt.Println("start prepare")
flag.Parse()
exitCode := m.Run()
//清理工作
fmt.Println("prepare to clean")
os.Exit(exitCode)
}
性能測試pprof
定位服務是否存在資源泄漏或者濫用API
的行爲,光靠review
代碼是不行的,最好能借助工具。
Profile
引用 godoc for pprof
描述:
A Profile is a collection of stack traces showing the call sequences that led to instances of a particular event, such as allocation. Packages can create and maintain their own profiles; the most common use is for tracking resources that must be explicitly closed, such as files or network connections.
性能測試涉及如下方面:
-
CPU Profiling
:CPU
分析,按照一定的頻率採集所監聽的應用程序CPU
(含寄存器)的使用情況,可確定應用程序在主動消耗CPU
週期時花費時間的位置 -
Memory Profiling
:內存分析,在應用程序進行堆分配時記錄堆棧跟蹤,用於監視當前和歷史內存使用情況,以及檢查內存泄漏 -
Block Profiling
:阻塞分析,記錄goroutine
阻塞等待同步(包括定時器通道)的位置 -
Mutex Profiling
:互斥鎖分析,報告互斥鎖的競爭情況
在程序中引入如下包,便可以通過 web 方式查看性能情況,訪問的路徑爲:/debug/pprof/
,該路徑下會顯示多個查看項。該路徑下還有其他子頁面。
_ "net/http/pprof"
關於/debug/pprof/
下的子頁面:
-
$HOST/debug/pprof/profile
-
$HOST/debug/pprof/block
-
$HOST/debug/pprof/goroutine
-
$HOST/debug/pprof/heap
-
$HOST/debug/pprof/mutex
-
$HOST/debug/pprof/threadcreate
在終端查看性能
只要服務器在啓動時,引入pprof
包,便可在終端獲取profile
文件。如下所示:
pprof -seconds=10 http://192.168.77.77:3900/debug/pprof/profile
如果獲取到cpu.prof
文件,可以通過如下命令可視化查看運行結果,這是另一種格式的火焰圖,也是挺帥的:
## 通過在瀏覽器中 localhost:1313 可以在 web 端查看
##
pprof -http=:1313 cpu.prof
## 或直接在終端查看
go tool pprof cpu.prof
$ web | top
Benchmark
測試
基本用法:
func BenchmarkBadgeRelationMapper_GetCountByUid(b *testing.B) {
count := 0
for i := 0; i < b.N; i++ {
count++
}
fmt.Println("total:", count)
}
bench
測試輸出結果,函數體被重複執行了 6 次,並對b.N
的值做了調整:
total: 1
total: 100
total: 10000
total: 1000000
total: 100000000
total: 1000000000
1000000000 0.598 ns/op
併發用法:
func BenchmarkBadgeRelationMapper_GetCountByUid(b *testing.B) {
count := 0
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
count++
}
})
fmt.Println("total:", count)
}
輸出的結果:
total: 1
total: 100
total: 6336
total: 306207
total: 34221963
total: 129821900
378966799 2.94 ns/op
在並行用法中,b.N
被RunParallel
接管。
簡單分析一下源碼
核心思路在於Next
方法,通過atomic.AddUint64
併發安全的操作pb.globalN
,pb.cache
用來存儲該goroutine
執行的次數。當某個goroutine
計算到pb.bN<=n<=pb.bN+pb.grain
時,雖然程序迭代次數已經完全超過b.N
,但還是會讓它繼續執行。
// Next reports whether there are more iterations to execute.
func (pb *PB) Next() bool {
if pb.cache == 0 {
n := atomic.AddUint64(pb.globalN, pb.grain)
if n <= pb.bN {
pb.cache = pb.grain
} else if n < pb.bN+pb.grain {
pb.cache = pb.bN + pb.grain - n
} else {
return false
}
}
pb.cache--
return true
}
regular expression
先列舉參考的example
。在我們要運行特定case
時,可以通過指定正則表達式來實現:
// -bench takes a regular expression that matches the names of the benchmarks you want to run
go test -bench=. ./examples/fib/
// -run flag with a regex that matches nothing
go test -run=^$
關於如何運行Benchmark
測試,默認執行go test
並不會執行Benchmark
,需要在命令行明確加上-bench=標記
,它接受一個表達式作爲參數,匹配基準測試的函數,. 表示運行所有基準測試。
go test -bench=.
// 明確指定要運行哪個測試,傳遞一個正則表達式給 run 屬性,XXX=BenchmarkReceiveGift_GetGiftReceiveList
go test -run=XXX -bench=.
默認情況下,benchmark
最小運行時長爲1s
。如果benchmark
函數執行返回,但1s
的時間還沒有結束,b.N
會根據某種機制依次遞增。可以通過參數-benchtime=20s
來改變這種行爲。
還有一個參數:benchmem
。可以提供每次操作分配內存的次數,以及每次操作分配的字節數。
go test -bench=Fib40 -benchtime=20s
Run Example
獲取線上的pprof
數據到本地,這裏是另一個工具:
go-torch -u http://192.168.77.77:3900/debug/pprof/profile -t 10
在Go 代碼調優利器-火焰圖
這篇文章中,對例子介紹的挺精彩的。
## 對函數 GetGiftReceiveList 進行 Benchmark 測試 因爲只想壓測 GetGiftReceiveList 這個函數
## 所以指定了 run 參數
go test -bench . -run=GetGiftReceiveList -benchmem -cpuprofile prof.cpu
## 其中 present.test 是壓測的二進制文件,prof.cpu 也是生產的文件
## (pprof) top10
## (pprof) list Marshal 單獨查看這個函數的耗時,這裏應該是正則匹配的
go tool pprof present.test prof.cpu
## 查看內存使用情況
go test -bench . -benchmem -memprofile prof.mem
go tool pprof --alloc_objects present.test prof.mem
覆蓋率
跟執行go test
不同的是,需要多加一個參數-coverprofile
, 所以完整的命令:
go test -v -coverprofile=c.out
生成報告有 go 爲我們提供的工具,使用
go tool cover -html=c.out -o=tag.html
即可生成一個名字爲 tag.html 的 HTML 格式的測試覆蓋率報告,這裏有詳細的信息告訴我們哪一行代碼測試到了,哪一行代碼沒有測試到。
火焰圖
學習瞭解火焰圖,分析函數調用棧的信息。主要是相關的工具:
## tool1
git clone https://github.com/brendangregg/FlameGraph.git
cp flamegraph.pl /usr/local/bin
flamegraph.pl -h
go get -v github.com/uber/go-torch
go-torch -h
轉自:
neojos.com/blog/2018/05-02-go-test%E5%9F%BA%E7%A1%80%E7%94%A8%E6%B3%95/
Go 開發大全
參與維護一個非常全面的 Go 開源技術資源庫。日常分享 Go, 雲原生、k8s、Docker 和微服務方面的技術文章和行業動態。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/-aFPd3EoVVF6-MWZOfKlXA