原文連接:Go 語言單元測試實踐html
什麼是軟件測試?git
軟件測試是一個過程,該過程對軟件(計算機程序)進行各類操做來發現軟件錯誤。github
爲何要進行軟件測試?golang
進行軟件測試能夠幫助咱們驗證軟件的各類功能正常,保證軟件的正常工做從而提升軟件質量。而且在實踐中已被證實是很有成效的。編程
測試驅動開發的由來:瀏覽器
一個從大量實踐中得出的結論:人們發如今軟件開發週期中,軟件錯誤每進入到下一個階段要修正它所付出的時間和人力會出人意表的翻上十倍。因此更早地進行軟件測試能夠更早地發現軟件錯誤,從而大大減小後期修正的成本。後來又有人提出了測試驅動開發(TDD: Test-driven development),主體思想就是先編寫測試程序,再實現程序功能。框架
下面就來介紹如何在 Go 語言中進行軟件測試中較爲重要的一環:單元測試。函數
<!--more-->post
單元測試就是針對程序最小單元的測試。單元測試
最小單元在過程化編程中指的是函數;在面向對象編程中指的是方法。
go tool
中一個按照約定和組織的測試代碼的命令。_test.go
結尾的文件纔會被視做是測試文件。如:swap.go
。Test
開頭,然後緊接着的第一個字母必須大寫。如:TestSwap
。testing.T
或 testing.B
(基準測試)的指針做爲參數。如:func TestSwap(t *testing.T)
,且沒有返回值。package xxx
寫成相同的。要測試的代碼文件名_test.go
。如:util.go
的測試文件名應爲 util_test.go
。Test要測試的函數名稱
。如:Swap
函數的測試函數命名應爲 TestSwap
,這個函數應只包含測試 Swap
函數的內容。這裏有一個用 Go 語言編寫的 Swap
函數(交換切片中的兩個值):
swap.go
。swap
。swap_test.go
。swap
。這兩個文件都放在 $GOPATH/src/swap/
目錄下。
go test swap
。swap
爲基於 $GOPATH/src/
的相對目錄。go test swap -run TestSwap
。swap
一樣爲相對目錄,TestSwap
爲測試函數名。下面會展現如何爲這個 Swap
函數編寫單元測試、將單元測試改寫成表驅動測試的形式並顯示代碼的測試覆蓋率。
package swap import ( "errors" ) // Swap exchanges s[i] and s[j]. func Swap(s []interface{}, i, j int) error { if s == nil { return errors.New("slice can't be nil") } if (i < 0 || i >= len(s)) || (j < 0 || j >= len(s)) { return errors.New("illegal index") } s[i], s[j] = s[j], s[i] return nil } // IsSameSlice determines two slice is it the same. func IsSameSlice(a, b []interface{}) bool { if len(a) != len(b) { return false } if (a == nil) != (b == nil) { return false } for i, v := range a { if b[i] != v { return false } } return true }
package swap import ( "testing" ) func TestSwap(t *testing.T) { input1 := []interface{}{1, 2} i1 := 0 j1 := 1 want1 := []interface{}{2, 1} if err := Swap(input1, i1, j1); err != nil { t.Error(err) } if !IsSameSlice(input1, want1) { t.Errorf("got %v, want %v", input1, want1) } input2 := []interface{}{1, 'a', "aa"} i2 := 0 j2 := 2 want2 := []interface{}{"aa", 'a', 1} if err := Swap(input2, i2, j2); err != nil { t.Error(err) } if !IsSameSlice(input2, want2) { t.Errorf("got %v, want %v", input2, want2) } }
這段代碼就是爲 Swap
編寫的簡單單元測試,能夠看出,若是測試數據變多,代碼就會有不少冗餘。這種問題的一個有效地解決方法就是用表驅動測試來實現單元測試。
表驅動測試是單元測試的一種形式,經過把測試條件都寫在一張表裏,就能夠動態地添加測試數據而不用改動太多代碼。
package swap import ( "testing" ) func TestSwap(t *testing.T) { tests := []struct { input []interface{} i int j int want []interface{} }{ {[]interface{}{1, 2}, 0, 1, []interface{}{2, 1}}, {[]interface{}{1, 'a', "aa"}, 0, 2, []interface{}{"aa", 'a', 1}}, } for i, tt := range tests { if err := Swap(tt.input, tt.i, tt.j); err != nil { t.Error(err) } if !IsSameSlice(tt.input, tt.want) { t.Errorf("%v. got %v, want %v", i, tt.input, tt.want) } } }
測試覆蓋率就是測試運行到的被測試代碼的代碼數目。其中以語句的覆蓋率最爲簡單和普遍,語句的覆蓋率指的是在測試中至少被運行一次的代碼佔總代碼數的比例。
使用 go tool cover
命令來查看有關測試覆蓋率命令行的幫助。
注意:首先必須保證測試是可以經過的。
go test -cover=true swap
。go test -cover=true swap -run TestSwap
。-cover=true
選項會開啓覆蓋率說明。
前面展現的都是命令行下的報告,不夠直觀。
其實 go tool cover
支持更友好的輸出,如:HTML 報告。
go test -cover=true swap -coverprofile=out.out
將在當前目錄生成覆蓋率數據。
配合 go tool cover -html=out.out
在瀏覽器中打開 HTML 報告。
或者使用 go tool cover -html=out.out -o=out.html
生成 HTML 文件。
通常狀況下,Go 語言標準庫對測試的支持已足夠強大。可是也有許多第三方的庫提供了各類各樣的功能。這裏只介紹兩個使用普遍的庫和其主要功能。
安裝:go get -u github.com/stretchr/testify
。
testify
提供 assert
、mock
、http
三個包進行更多樣的測試。下面用其中的 assert
包改寫前面的例子。
package swap import ( "github.com/stretchr/testify/assert" "testing" ) func TestSwap(t *testing.T) { input1 := []interface{}{1, 2} i1 := 0 j1 := 1 want1 := []interface{}{2, 1} if assert.Nil(t, Swap(input1, i1, j1)) { t.FailNow() } assert.Equal(t, input1, want1) input2 := []interface{}{1, 'a', "aa"} i2 := 0 j2 := 2 want2 := []interface{}{"aa", 'a', 1} if assert.Nil(t, Swap(input2, i2, j2)) { t.FailNow() } assert.Equal(t, input2, want2) }
安裝:go get -u github.com/cweill/gotests
。
gotests
能夠爲程序生成表驅動測試,在生成以後再添加測試數據便可完成測試代碼的編寫。
使用命令:gotests -all -w go/src/swap/swap.go
便可生成關於 swap.go
文件中全部函數的表驅動測試形式的單元測試,包含在與 swap.go
同目錄下的 swap_test.go
文件中。
package swap import "testing" func TestSwap(t *testing.T) { type args struct { s []interface{} i int j int } tests := []struct { name string args args wantErr bool }{ // TODO: Add test cases. } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := Swap(tt.args.s, tt.args.i, tt.args.j); (err != nil) != tt.wantErr { t.Errorf("Swap() error = %v, wantErr %v", err, tt.wantErr) } }) } }
上面就是運行命令以後生成的代碼。在 TODO
中添加測試數據便可。
package swap import "testing" func TestSwap(t *testing.T) { type args struct { s []interface{} i int j int } tests := []struct { name string args args wantErr bool }{ {"1", args{[]interface{}{1, 2}, 0, 1}, false}, {"2", args{[]interface{}{1, 'a', "aa"}, 0, 2}, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := Swap(tt.args.s, tt.args.i, tt.args.j); (err != nil) != tt.wantErr { t.Errorf("Swap() error = %v, wantErr %v", err, tt.wantErr) } }) } }
能夠看出這裏並無測試交換後的值是否正確,說明 gotests
的代碼會根據返回值生成,也說明了 gotests
對某些函數來講是不適用的或者說是須要手動修改的。