一、測試基礎(chǔ)概述
Go語言內(nèi)建一套基于表格驅(qū)動測試的機(jī)制,只需編寫少量代碼,就可對特定功能進(jìn)行單元測試和基準(zhǔn)測試(性能測試或壓力測試)
二、單元測試
所謂單元測試,是程序中對最小功能單元進(jìn)行檢查和驗(yàn)證。
Go進(jìn)行單元測試非常簡單:
創(chuàng)建一個go文件,在命名文件時以_test.go結(jié)尾,例如: unit_test.go
每個測試文件可支持多個測試用例的編寫,每個測試用例函數(shù)需要以Test為前綴,且其只有一個*testing.T指針類型的入?yún)?。例如?/p>
func TestAXXX( t *testing.T ){}
func TestBXXX( t *testing.T ){}
- 運(yùn)行g(shù)o test -v {testing_filename.go} 執(zhí)行測試并打印測試詳細(xì)數(shù)據(jù)
go test 指定文件時默認(rèn)執(zhí)行文件內(nèi)的所有測試用例??梢允褂?run參數(shù)選擇需要的測試用例單獨(dú)執(zhí)行
以下對一個斐波那契數(shù)列計算函數(shù)的測試示例
測試目標(biāo)功能函數(shù):
//遞歸實(shí)現(xiàn)的斐波那契數(shù)列
func Fibonacci(n int) int {
rs := 0
//歸
if n == 1 || n == 2 {
return 1
}
//遞
rs = Fibonacci(n-1) + Fibonacci(n-2)
return rs
}
單元測試用例:
import "testing"
func TestFibonacci(t *testing.T) {
//先編一個預(yù)定義的參數(shù)和結(jié)果集
caseMap := make(map[int]int)
caseMap[1] = 1
caseMap[3] = 2
caseMap[5] = 5
caseMap[10] = 55
//測試運(yùn)行一個函數(shù),得到結(jié)果
for i, v := range caseMap {
rs := Fibonacci(i) //目標(biāo)測試函數(shù)
if rs != v {
t.Fatalf("測試用例發(fā)現(xiàn)錯誤:參數(shù)%d,預(yù)計得到結(jié)果%d,實(shí)際得到結(jié)果%d", i, v, rs)
}
}
t.Log("測試結(jié)束,沒有發(fā)現(xiàn)問題。")
}
//OUTPUT SUCCESSFUL:
=== RUN TestFibonacci
--- PASS: TestFibonacci (0.00s)
TestUnit_test.go:48: 測試結(jié)束,沒有發(fā)現(xiàn)問題。
PASS
ok command-line-arguments 0.005s
//OUTPUT FAIL:
//為模擬測試失敗,更改一個預(yù)定義結(jié)果集讓其不通過
=== RUN TestFibonacci
--- FAIL: TestFibonacci (0.00s)
TestUnit_test.go:44: 測試用例發(fā)現(xiàn)錯誤:參數(shù)10,預(yù)計得到結(jié)果50,實(shí)際得到結(jié)果55
FAIL
FAIL command-line-arguments 0.005s
//注意:在使用go test 測試單個文件的時候,本人運(yùn)行出現(xiàn)報錯:
# command-line-arguments [command-line-arguments.test]
Code/go/src/mydemo/base/TestUnit_test.go:42:9: undefined: Fibonacci
FAIL command-line-arguments [build failed]
//可看到對測試目標(biāo)運(yùn)行測試時沒有發(fā)現(xiàn)目標(biāo)函數(shù),看到后面[build failed]失敗了,這里需要把測試目標(biāo)的文件也添加到命令:
go test -v {testing_filename.go} {target_filename.go}
以上為最簡單的功能測試實(shí)現(xiàn),可以發(fā)現(xiàn)主要為testing包的支持,那么testing.T這個類型可以提供哪些測試功能呢?下面解析一下:
//標(biāo)記失敗但繼續(xù)運(yùn)行該測試
t.Fail()
//標(biāo)記失敗并立刻終止該測試
t.FailNow()
//打印測試日志
t.Log("日志記錄...")
t.Logf("[Error]:%s/n","錯誤原因")
//t.Log() + t.Fail()
t.Error("勞資是日志")
//t.Logf() + t.Fail()
t.Errorf("[Error]:%s/n","錯誤原因")
//t.Log() + t.FailNow()
t.Fatal("日志記錄...")
//t.Logf() + t.FailNow()
t.Fatalf("[Error]:%s/n","錯誤原因")
以上可看到主要是測試運(yùn)行時的日志記錄和錯誤處理功能
三、基準(zhǔn)測試
基準(zhǔn)測試可以測試一段程序的運(yùn)行性能及耗費(fèi) CPU 的程度。Go 語言中提供了基準(zhǔn)測試框架,使用方法類似于單元測試,使用者無須準(zhǔn)備高精度的計時器和各種分析工具,基準(zhǔn)測試本身即可以打印出非常標(biāo)準(zhǔn)的測試報告。
Go進(jìn)行基準(zhǔn)測試和單元測試一樣簡單:
創(chuàng)建一個go文件,在命名文件時以_test.go結(jié)尾,例如: benchmark_test.go
每個測試文件可支持多個測試用例的編寫,每個測試用例函數(shù)需要以Benchmark為前綴,且其只有一個*testing.B指針類型的入?yún)?。例如?/p>
func Benchmark_AXXX( t *testing.B ){}
func Benchmark_BXXX( t *testing.B ){}
- 運(yùn)行g(shù)o test -v -bench=. benchmark_test.go 執(zhí)行基準(zhǔn)測試
-bench=.表示運(yùn)行 benchmark_test.go 文件里的所有基準(zhǔn)測試,和單元測試中的-run類似
基準(zhǔn)測試用例:
func BenchmarkFibonacci(b *testing.B) {
b.ReportAllocs() //內(nèi)存開銷
//b.N為常規(guī)寫法
for i := 0; i < b.N; i++ {
Fibonacci(10)
}
}
//OUTPUT:
goos: darwin
goarch: amd64
BenchmarkFibonacci-4 10000000 204 ns/op 0 B/op 0 allocs/op
--- BENCH: BenchmarkFibonacci-4
PASS
ok command-line-arguments 2.257s
//10000000 表示測試的次數(shù),也就是 testing.B 結(jié)構(gòu)中提供給程序使用的 N?!?04 ns/op”表示每一個操作耗費(fèi)多少時間(納秒)。
基準(zhǔn)測試原理:
基準(zhǔn)測試框架對一個測試用例的默認(rèn)測試時間是 1 秒。開始測試時,當(dāng)以 Benchmark 開頭的基準(zhǔn)測試用例函數(shù)返回時還不到 1 秒,那么 testing.B 中的 N 值將按 1、2、5、10、20、50……遞增,同時以遞增后的值重新調(diào)用基準(zhǔn)測試用例函數(shù)。
通過-benchtime參數(shù)可以自定義測試時間:
go test -v -bench=. -benchtime=5s benchmark_test.go
//OUTPUT:
goos: darwin
goarch: amd64
BenchmarkFibonacci-4 30000000 204 ns/op 0 B/op 0 allocs/op
--- BENCH: BenchmarkFibonacci-4
PASS
ok command-line-arguments 6.337s
通過-benchmem參數(shù)以顯示內(nèi)存分配情況
go test -v -bench=BenchmarkFibonacci -benchmem benchmark_test.go
//OUTPUT:
goos: darwin
goarch: amd64
BenchmarkFibonacci-4 10000000 203 ns/op 0 B/op 0 allocs/op
--- BENCH: BenchmarkFibonacci-4
PASS
ok command-line-arguments 2.251s
//輸出差不多,這個斐波那契例子內(nèi)存分配幾乎忽略不計
控制計時器
有些測試需要一定的啟動和初始化時間,如果從 Benchmark() 函數(shù)開始計時會很大程度上影響測試結(jié)果的精準(zhǔn)性。testing.B 提供了一系列的方法可以方便地控制計時器,從而讓計時器只在需要的區(qū)間進(jìn)行測試。
示例:
func Benchmark_Add_TimerControl(b *testing.B) {
// 重置計時器
b.ResetTimer()
// 停止計時器
b.StopTimer()
// 開始計時器
b.StartTimer()
var n int
for i := 0; i < b.N; i++ {
n++
}
}
四、性能分析
go提供兩種pprof包來做代碼的性能分析
- runtime/pprof : 基本性能分析包
- net/http/pprof : 基于runtime/pprof封裝,并在http端口暴露
1. pprof是什么?
pprof是Go提供的可視化性能分析工具,在性能測試中讀取分析樣本的集合,并生成報告以可視化并幫助分析數(shù)據(jù)。pprof既能生成報告文件,也可以借助graphviz生成web界面。
你能看到什么?
- CPU Profiling:CPU 分析,按照一定的頻率采集所監(jiān)聽的應(yīng)用程序 CPU(含寄存器)的使用情況,可確定應(yīng)用程序在主動消耗 CPU 周期時花費(fèi)時間的位置
- Memory Profiling:內(nèi)存分析,在應(yīng)用程序進(jìn)行堆分配時記錄堆棧跟蹤,用于監(jiān)視當(dāng)前和歷史內(nèi)存使用情況,以及檢查內(nèi)存泄漏
- Block Profiling:阻塞分析,記錄 goroutine 阻塞等待同步(包括定時器通道)的位置
- Mutex Profiling:互斥鎖分析,報告互斥鎖的競爭情況
2.pprof使用方式
2.1 基于基準(zhǔn)測試生成性能分析文件:
go test -bench=. -benchtime="3s" -cpuprofile=profile_cpu.out
運(yùn)行完成后,會發(fā)現(xiàn)在當(dāng)前目錄生成兩個文件
profile_cpu.out —— 分析報告文件
base.test —— 可執(zhí)行程序
2.2 查看性能分析報告
- 終端查看
//使用go提供的工具在終端查看
go tool pprof base.test profile_cpu.out
//進(jìn)入終端pprof查看模式
File: base.test
Type: cpu
Time: Jul 4, 2019 at 11:21am (CST)
Duration: 6.55s, Total samples = 6.02s (91.95%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof)
//輸入help查看命令集合,可見還是功能還是很強(qiáng)大的。
(pprof) help
Commands:
callgrind Outputs a graph in callgrind format
comments Output all profile comments
disasm Output assembly listings annotated with samples
dot Outputs a graph in DOT format
eog Visualize graph through eog
evince Visualize graph through evince
gif Outputs a graph image in GIF format
gv Visualize graph through gv
kcachegrind Visualize report in KCachegrind
list Output annotated source for functions matching regexp
pdf Outputs a graph in PDF format
peek Output callers/callees of functions matching regexp
png Outputs a graph image in PNG format
proto Outputs the profile in compressed protobuf format
ps Outputs a graph in PS format
raw Outputs a text representation of the raw profile
svg Outputs a graph in SVG format
tags Outputs all tags in the profile
text Outputs top entries in text form
top Outputs top entries in text form
topproto Outputs top entries in compressed protobuf format
traces Outputs all profile samples in text form
tree Outputs a text rendering of call graph
web Visualize graph through web browser
weblist Display annotated source in a web browser
o/options List options and their current values
quit/exit/^D Exit pprof
...
//使用top 5查看前5個耗cpu的調(diào)用
(pprof) top 5
Showing nodes accounting for 5.99s, 99.50% of 6.02s total
Dropped 5 nodes (cum <= 0.03s)
flat flat% sum% cum cum%
5.94s 98.67% 98.67% 5.99s 99.50% command-line-arguments.Fibonacci
0.05s 0.83% 99.50% 0.05s 0.83% runtime.newstack
0 0% 99.50% 5.99s 99.50% command-line-arguments.BenchmarkFibonacci
0 0% 99.50% 5.99s 99.50% testing.(*B).launch
0 0% 99.50% 5.99s 99.50% testing.(*B).runN
...
- web查看
//先安裝[graphviz](http://www.graphviz.org/download/)
//加上--web
go tool pprof --web base.test profile_cpu.out
//此方式會生成一個.svg格式的文件,可以用任何支持.svg的軟件打開
- pdf輸出
//加上-pdf選項,并把數(shù)據(jù)重定向到新的pdf格式文件即可
go tool pprof base.test -pdf profile_cpu.out > profile_cpu.pdf
3. 服務(wù)版的pprof性能分析
要使用服務(wù)版的pprof,只需在啟動服務(wù)的main中引入相關(guān)包,啟動服務(wù)即可。
"net/http"
_ "net/http/pprof"
運(yùn)行服務(wù)時,你的 HTTP 服務(wù)會多出 /debug/pprof 這個訪問路徑,用于查看服務(wù)器版的性能分析報告,例如:訪問http://{hostname}:{port}/debug/pprof/
可以通過訪問各自類型的性能分析頁面了解服務(wù)的總體情況:
- cpu: http://{hostname}:{port}/debug/pprof/profile,默認(rèn)進(jìn)行 30s 的 CPU Profiling,得到一個分析用的 profile 文件
- block:http://{hostname}:{port}/debug/pprof/block,查看導(dǎo)致阻塞同步的堆棧跟蹤
- goroutine:http://{hostname}:{port}/debug/pprof/goroutine,查看當(dāng)前所有運(yùn)行的 goroutines 堆棧跟蹤
- heap: http://{hostname}:{port}/debug/pprof/heap,查看活動對象的內(nèi)存分配情況
- mutex:http://{hostname}:{port}/debug/pprof/mutex,查看導(dǎo)致互斥鎖的競爭持有者的堆棧跟蹤
- threadcreate:http://{hostname}:{port}/debug/pprof/threadcreate,查看創(chuàng)建新OS線程的堆棧跟蹤