GoCommunity.png
How to write test with golang
- TDD(Test-Driven development) 測(cè)試驅(qū)動(dòng)開(kāi)發(fā)
- 內(nèi)置的 testing 庫(kù) 佃蚜、 表格驅(qū)動(dòng)髓需、樣本測(cè)試置吓、TestMain
- 第三方:goconvey
- Monkey 猴子補(bǔ)丁
- 數(shù)據(jù)庫(kù) mock
- travisCI
- 代碼覆蓋率
TDD
- 快速實(shí)現(xiàn)功能
- 再設(shè)計(jì)和重構(gòu)
軟件測(cè)試
在指定的條件下蝗砾,操作程序贫母,發(fā)現(xiàn)程序錯(cuò)誤
單元測(cè)試
對(duì)軟件的組成單元進(jìn)行測(cè)試陈辱,最小單位:函數(shù)
包含三個(gè)步驟:
- 指定輸入
- 指定預(yù)期
- 函數(shù)結(jié)果和指定的預(yù)期比較
指標(biāo):
- 代碼覆蓋率:運(yùn)行測(cè)試執(zhí)行的代碼占總代碼的行數(shù)
testing 庫(kù)的使用
// Hello ...
func Hello() string {
return "Hello World"
}
// 傳統(tǒng)測(cè)試
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)
}
}
// 表格驅(qū)動(dòng)測(cè)試: 使用匿名結(jié)構(gòu)體故黑,邏輯更清晰
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)
}
})
}
}
運(yùn)行:
// mode one
go test // equal to : go test . 執(zhí)行當(dāng)前目錄下的測(cè)試文件
// mode two
go test ./.. // 加上路徑參數(shù)儿咱,可以執(zhí)行指定目錄下的測(cè)試文件
樣本測(cè)試:
func ExampleHello() {
fmt.Println(Hello())
// Output:
// Hello World
}
TestMain:
包的測(cè)試運(yùn)行之前執(zhí)行
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 結(jié)尾
- 測(cè)試函數(shù)必須以 TestX... 開(kāi)頭,
X
可以是_
或者大寫(xiě)字母庭砍,不可以是小寫(xiě)字母或數(shù)字 - 參數(shù):*testing.T
- 樣本測(cè)試必須以 Example... 開(kāi)頭,輸入使用注釋的形式
- TestMain 每個(gè)包只有一個(gè)混埠,參數(shù)為 *testing.M
覆蓋率:
go test -cover
go test -coverprofile=cover.out
go tool cover -html=cover.out -o coverage.html
第三方:goconvey
- 支持?jǐn)嘌?/li>
- 支持嵌套
- 完全兼容內(nèi)置 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 // 啟動(dòng) web 界面
Monkey 猴子補(bǔ)丁
- 函數(shù)打樁
- 過(guò)程打樁
- 方法打樁
// 函數(shù)
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
}
// 過(guò)程
guard := Patch(DestroyResource, func(_ string) {
})
defer guard.Unpatch()
使用思路怠缸,被測(cè)函數(shù)中需要使用的其他依賴(lài)函數(shù),進(jìn)行打樁處理钳宪。
sqlmock
對(duì) sql 的執(zhí)行過(guò)程進(jìn)行打樁揭北。
- 創(chuàng)建模擬連接
- 編寫(xiě) 原生 sql 語(yǔ)句
- 編寫(xiě) 返回值 或者 錯(cuò)誤信息
- 判斷執(zhí)行結(jié)果和預(yù)設(shè)的返回值
Reference
- gotests 自動(dòng)生成測(cè)試代碼,只需填寫(xiě)測(cè)試數(shù)據(jù)即可
- goconvey 第三方測(cè)試庫(kù)吏颖,兼容 testing 庫(kù)
- httpmock 接口模擬
- how to test with Go 參考文檔
- monkey 猴子補(bǔ)丁
- sqlmock sqlmock
- how to test with Go 參考文檔