Go語言的測試技術是相對低級的。它依賴一個 go test 測試命令和一組按照約定方式編寫的 測試函數,測試命令能夠運行這些測試函數。編寫相對輕量級的純測試代碼是有效的,並且它很容易延伸到基準測試和示例文檔。html
編寫測試代碼和編寫普通的Go代碼過程是相似的,並不須要學習新的語法、規則或工具。
在包目錄內,全部以_test.go爲後綴名的源代碼文件都是go test測試的一部分,不會被go build編譯到最終的可執行文件中。正則表達式
類型 | 格式 | 做用 |
測試函數 | 函數名前綴爲Test | 測試程序的一些邏輯行爲是否正確 |
基準函數 | 函數名前綴爲Benchmark | 測試函數的性能 |
示例函數 | 函數名前綴爲Example | 爲文檔提供示例文檔 |
go test命令會遍歷全部的*_test.go文件中符合上述命名規則的函數,而後生成一個臨時的main包用於調用相應的測試函數,而後構建並運行、報告測試結果,最後清理測試中生成的臨時文件。瀏覽器
格式:測試函數的名字必須以Test開頭,可選的後綴名必須以大寫字母開頭,參數t用於報告測試失敗和附加的日誌信息。 併發
func TestName(t *testing.T){ // ... }
testing.T的擁有的方法:app
func (c *T) Error(args ...interface{}) func (c *T) Errorf(format string, args ...interface{}) func (c *T) Fail() func (c *T) FailNow() func (c *T) Failed() bool func (c *T) Fatal(args ...interface{}) func (c *T) Fatalf(format string, args ...interface{}) func (c *T) Log(args ...interface{}) func (c *T) Logf(format string, args ...interface{}) func (c *T) Name() string func (t *T) Parallel() func (t *T) Run(name string, f func(t *T)) bool func (c *T) Skip(args ...interface{}) func (c *T) SkipNow() func (c *T) Skipf(format string, args ...interface{}) func (c *T) Skipped() bool
示例:自定義一個split函數,實現split功能:函數
func Split(s,sep string) (result []string) { i := strings.Index(s,sep) for i > -1{ result = append(result,s[:i]) s = s[i+1:] i = strings.Index(s,sep) } result =append(result,s) return }
在當前目錄下,建立一個split_test.go的測試文件,並定義一個測試函數以下:工具
func TsetSplit(t *testing.T) { got := Split("a:b:c",":") want := []string{"a","b","c"} if !reflect.DeepEqual(want,got){ t.Errorf("期待:%v,獲得:%v",want,got) } }
在split包路徑下,執行go test命令,能夠看到輸出結果以下:性能
testing: warning: no tests to run PASS
出現pass意味測試經過!
若是有多個測試函數,其中一個測試不過,會出現如下提示:學習
--- FAIL: TestMoreSplit (0.00s) split_test.go:40: excepted:[a d], got:[a cd] FAIL exit status 1
若是要查看測試函數名稱和運行時間,可使用go test -v測試
=== RUN TestMoreSplit --- FAIL: TestMoreSplit (0.00s) split_test.go:40: excepted:[a d], got:[a cd] FAIL
還能夠在go test命令後添加-run參數,它對應一個正則表達式,只有函數名匹配上的測試函數纔會被go test命令執行。
若是有多個測試,沒必要每一個測試都寫一個函數,可使用測試組來完成。
//測試組 func TestSplit(t *testing.T) { type test []struct{ input string sep string want []string } tests := test{ {input:"a:b:c",sep:":",want:[]string{"a","b","c"}}, {input:"a:b:c",sep:",",want:[]string{"a","b","c"}}, {input:"abcdbce",sep:"bc",want:[]string{"a","d","e"}}, {input:"上海自來水來自海上",sep:"海",want:[]string{"上","自來水來自","上"}}, } for _,tc := range tests{ got := Split(tc.input,tc.sep) if !reflect.DeepEqual(got,tc.want){ t.Errorf("期待:%v,得到:%v",tc.want,got) } } }
執行go test -v
,得到
=== RUN TestSplit --- FAIL: TestSplit (0.00s) split_test.go:59: 期待:[a b c],得到:[a:b:c] split_test.go:59: 期待:[a d e],得到:[a cd ce] split_test.go:59: 期待:[上 自來水來自 上],得到:[上 ��自來水來自 ��上] FAIL exit status 1
中文有亂碼,這種狀況下可使用%#v的格式化方式查看亂碼是什麼:
t.Errorf("期待:%#v,得到:%#v",tc.want,got)
獲得:
split_test.go:59: 期待:[]string{"上", "自來水來自", "上"},得到:[]string{"上", "\xb5\xb7自來水來自", "\xb5\xb7上"}
當測試組中測試比較多的時候,能夠加個name來查看是哪一個測試用例出了問題:
//測試組 func TestSplit(t *testing.T) { type test struct{ // 定義test結構體 input string sep string want []string } tests := map[string]test{ "simple": {input:"a:b:c",sep:":",want:[]string{"a","b","c"}}, "simple1": {input:"a:b:c",sep:",",want:[]string{"a","b","c"}}, "simple2": {input:"abcdbce",sep:"bc",want:[]string{"a","d","e"}}, "simple3": {input:"上海自來水來自海上",sep:"海",want:[]string{"上","自來水來自","上"}}, } for name,tc := range tests{ got := Split(tc.input,tc.sep) if !reflect.DeepEqual(got,tc.want){ t.Errorf("測試名:%#v,期待:%#v,得到:%#v",name,tc.want,got) } } }
輸出的時候就會把測試用例的名字帶上:
=== RUN TestSplit --- FAIL: TestSplit (0.00s) split_test.go:66: 測試名:"simple1",期待:[]string{"a", "b", "c"},得到:[]string{"a:b:c"} split_test.go:66: 測試名:"simple2",期待:[]string{"a", "d", "e"},得到:[]string{"a", "cd", "ce"} split_test.go:66: 測試名:"simple3",期待:[]string{"上", "自來水來自", "上"},得到:[]string{"上", "\xb5\xb7自來水來自", "\xb5\xb7上"} FAIL exit status 1
for name,tc := range tests{ t.Run(name, func(t *testing.T) { got := Split(tc.input,tc.sep) if !reflect.DeepEqual(got,tc.want){ t.Errorf("測試名:%#v,期待:%#v,得到:%#v",name,tc.want,got) } }) }
執行結果:
$go test -v === RUN TestSplit === RUN TestSplit/simple2 === RUN TestSplit/simple3 === RUN TestSplit/simple === RUN TestSplit/simple1 --- FAIL: TestSplit (0.00s) --- FAIL: TestSplit/simple2 (0.00s) split_test.go:71: 測試名:"simple2",期待:[]string{"a", "d", "e"},得到:[]string{"a", "cd", "ce"} --- FAIL: TestSplit/simple3 (0.00s) split_test.go:71: 測試名:"simple3",期待:[]string{"上", "自來水來自", "上"},得到:[]string{"上", "\xb5\xb7自來水來自", "\xb5\xb7上"} --- PASS: TestSplit/simple (0.00s) --- FAIL: TestSplit/simple1 (0.00s) split_test.go:71: 測試名:"simple1",期待:[]string{"a", "b", "c"},得到:[]string{"a:b:c"} FAIL exit status 1
能夠經過-run=RegExp來指定運行的測試用例,還能夠經過/來指定要運行的子測試用例,只測試simple:
$go test -v -run=Split/simple
在測試中至少被運行一次的代碼佔總代碼的比例。Go提供內置功能來檢查你的代碼覆蓋率。咱們可使用go test -cover來查看測試覆蓋率。
$go test -cover --- FAIL: TestSplit (0.00s) --- FAIL: TestSplit/simple2 (0.00s) split_test.go:71: 測試名:"simple2",期待:[]string{"a", "d", "e"},得到:[]string{"a", "cd", "ce"} --- FAIL: TestSplit/simple3 (0.00s) split_test.go:71: 測試名:"simple3",期待:[]string{"上", "自來水來自", "上"},得到:[]string{"上", "\xb5\xb7自來水來自", "\xb5\xb7上"} --- FAIL: TestSplit/simple1 (0.00s) split_test.go:71: 測試名:"simple1",期待:[]string{"a", "b", "c"},得到:[]string{"a:b:c"} FAIL coverage: 100.0% of statements exit status 1
Go還提供了一個額外的-coverprofile參數,用來將覆蓋率相關的記錄信息輸出到一個文件。
$ go test -cover -coverprofile=c.out
執行go tool cover -html=c.out,使用cover工具來處理生成的記錄信息,該命令會打開本地的瀏覽器窗口生成一個HTML報告。

用綠色標記的語句塊表示被覆蓋了,而紅色的表示沒有被覆蓋。
基準測試就是在必定的工做負載之下檢測程序性能的一種方法。
格式:
func BenchmarkName(b *testing.B){ // ... }
基準測試以Benchmark爲前綴,須要一個'*testing.B'類型的參數b,基準測試必需要執行b.N次,這樣的測試纔有對照性,b.N的值是系統根據實際狀況去調整的,從而保證測試的穩定性。 testing.B擁有的方法以下:
func (c *B) Error(args ...interface{}) func (c *B) Errorf(format string, args ...interface{}) func (c *B) Fail() func (c *B) FailNow() func (c *B) Failed() bool func (c *B) Fatal(args ...interface{}) func (c *B) Fatalf(format string, args ...interface{}) func (c *B) Log(args ...interface{}) func (c *B) Logf(format string, args ...interface{}) func (c *B) Name() string func (b *B) ReportAllocs() func (b *B) ResetTimer() func (b *B) Run(name string, f func(b *B)) bool func (b *B) RunParallel(body func(*PB)) func (b *B) SetBytes(n int64) func (b *B) SetParallelism(p int) func (c *B) Skip(args ...interface{}) func (c *B) SkipNow() func (c *B) Skipf(format string, args ...interface{}) func (c *B) Skipped() bool func (b *B) StartTimer() func (b *B) StopTimer()
爲split編寫基準測試:
//基準測試 func BenchmarkSplit(b *testing.B) { for i:=0;i<b.N;i++{ Split("a:b:c",":") } }
經過執行go test -bench=Split命令執行基準測試,輸出結果以下:
$go test -bench=Split goos: darwin goarch: amd64 pkg: xxxxxx BenchmarkSplit-8 10000000 203 ns/op PASS
其中BenchmarkSplit-8表示對Split函數進行基準測試,數字8表示GOMAXPROCS的值,這個對於併發基準測試很重要。10000000和203ns/op表示每次調用Split函數耗時203ns,這個結果是10000000次調用的平均值。
默認狀況下,每一個基準測試至少運行1秒!
可爲基準測試添加-benchmem參數,來得到內存分配的統計數據。
$go test -bench=Split -benchmem ... BenchmarkSplit-8 10000000 203 ns/op 112 B/op 3 allocs/op ...
112 B/op表示每次操做內存分配了112字節,3 allocs/op則表示每次操做進行了3次內存分配。
性能比較函數一般是一個帶有參數的函數,被多個不一樣的Benchmark函數傳入不一樣的值來調用。
func benchmark(b *testing.B, size int){/* ... */} func Benchmark10(b *testing.B){ benchmark(b, 10) } func Benchmark100(b *testing.B){ benchmark(b, 100) } func Benchmark1000(b *testing.B){ benchmark(b, 1000) }
使用斐波那契數的函數測試:
// Fib 是一個計算第n個斐波那契數的函數 func Fib(n int) int { if n <2{ return n } return Fib(n-1) + Fib(n+2) }
性能比較函數:
func benchmarkFib(b *testing.B, n int) { for i := 0; i < b.N; i++ { Fib(n) } } func BenchmarkFib1(b *testing.B) { benchmarkFib(b, 1) } func BenchmarkFib2(b *testing.B) { benchmarkFib(b, 2) } func BenchmarkFib3(b *testing.B) { benchmarkFib(b, 3) } func BenchmarkFib10(b *testing.B) { benchmarkFib(b, 10) } func BenchmarkFib20(b *testing.B) { benchmarkFib(b, 20) } func BenchmarkFib40(b *testing.B) { benchmarkFib(b, 40) }
默認狀況下,每一個基準測試至少運行1秒。若是在Benchmark函數返回時沒有到1秒,則b.N的值會按1,2,5,10,20,50,…增長,而且函數再次運行。
還可使用-benchtime標誌增長最小基準時間,以產生更準確的結果。
func (b *B) RunParallel(body func(*PB))會以並行的方式執行給定的基準測試。 RunParallel會建立出多個goroutine,並將b.N分配給這些 goroutine 執行, 其中 goroutine數量的默認值爲GOMAXPROCS。用戶若是想要增長非CPU受限(non-CPU-bound)基準測試的並行性, 那麼能夠在RunParallel以前調用SetParallelism 。RunParallel一般會與-cpu標誌一同使用。
go test -bench=. -cpu 1
來指定使用的CPU數量。