Golang 編寫測試教程
- TDD(Test-Driven development) 測試驅動開發
- 內建的 testing 庫 、 表格驅動、樣本測試、TestMain
- 第三方:goconvey
- Monkey 猴子補丁
- 資料庫 mock
- travisCI
- 程式碼覆蓋率
TDD
- 快速實現功能
- 再設計和重構
軟體測試
在指定的條件下,操作程式,發現程式錯誤
單元測試
對軟體的組成單元進行測試,最小單位:函式
包含三個步驟:
- 指定輸入
- 指定預期
- 函式結果和指定的預期比較
指標:
- 程式碼覆蓋率:執行測試執行的程式碼佔總程式碼的行數
testing 庫的使用
// Hello ... func Hello() string { return "Hello World" } 複製程式碼
// 傳統測試 func TestHello(t *testing.T) { result := Hello() want := "Hello World" if result == want { t.Logf("Hello() = %v, want %v", result, want) } else { t.Errorf("Hello() = %v, want %v", result, want) } want2 := "Hello world" if result == want2 { t.Logf("Hello() = %v, want %v", result, want) } else { t.Errorf("Hello() = %v, want %v", result, want) } } // 表格驅動測試: 使用匿名結構體,邏輯更清晰 func TestHelloWithTable(t *testing.T) { tests := []struct { name string want string }{ // TODO: Add test cases. { name: "test for hello", want: "Hello World", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if got := Hello(); got != tt.want { t.Errorf("Hello() = %v, want %v", got, tt.want) } }) } } 複製程式碼
執行:
// mode one go test//equal to : go test .執行當前目錄下的測試檔案 // mode two go test ./..// 加上路徑引數,可以執行指定目錄下的測試檔案 複製程式碼
樣本測試:
func ExampleHello() { fmt.Println(Hello()) // Output: // Hello World } 複製程式碼
TestMain:
包的測試執行之前執行
func TestMain(m *testing.M) { fmt.Println("Before ====================") code := m.Run() fmt.Println("End ====================") os.Exit(code) } 複製程式碼
testing 包含下面幾種方法:
- Log | Logf
- Error | ErrorF
- Fatal | FatalF
備註:
- 檔案必須以 ...test.go 結尾
-
測試函式必須以 TestX... 開頭,
X
可以是_
或者大寫字母,不可以是小寫字母或數字 - 引數:*testing.T
- 樣本測試必須以 Example... 開頭,輸入使用註釋的形式
- TestMain 每個包只有一個,引數為 *testing.M
覆蓋率:
go test -cover go test -coverprofile=cover.out go tool cover -html=cover.out -o coverage.html 複製程式碼
第三方:goconvey
- 支援斷言
- 支援巢狀
- 完全相容內建 testing
- 提供 web UI
func TestAdd_Two(t *testing.T) { Convey("test add", t, func() { Convey("0 + 0", func() { So(Add(0, 0), ShouldEqual, 0) }) Convey("-1 + 0", func() { So(Add(-1, 0), ShouldEqual, -1) }) }) } func TestFloatToString_Two(t *testing.T) { Convey("test float to string", t, func() { Convey("1.0/3.0", func() { result := FloatToString(1.0, 3.0) So(result, ShouldContainSubstring, "%") So(len(result), ShouldEqual, 6) So(result, ShouldEqual, "33.33%") }) }) } 複製程式碼
goconvey // 啟動 web 介面 複製程式碼
Monkey 猴子補丁
- 函式打樁
- 過程打樁
- 方法打樁
// 函式 func main() { monkey.Patch(fmt.Println, func(a ...interface{}) (n int, err error) { s := make([]interface{}, len(a)) for i, v := range a { s[i] = strings.Replace(fmt.Sprint(v), "hell", "*bleep*", -1) } return fmt.Fprintln(os.Stdout, s...) }) fmt.Println("what the hell?") // what the *bleep*? } 複製程式碼
// 方法 func main() { var d *net.Dialer // Has to be a pointer to because `Dial` has a pointer receiver monkey.PatchInstanceMethod(reflect.TypeOf(d), "Dial", func(_ *net.Dialer, _, _ string) (net.Conn, error) { return nil, fmt.Errorf("no dialing allowed") }) _, err := http.Get("http://google.com") fmt.Println(err) // Get http://google.com: no dialing allowed } 複製程式碼
// 過程 guard := Patch(DestroyResource, func(_ string) { }) defer guard.Unpatch() 複製程式碼
使用思路,被測函式中需要使用的其他依賴函式,進行打樁處理。