經過閱讀上一篇文章,相信你對怎麼作單元測試已經有了初步的概念,能夠着手對現有的項目進行改造並開展測試了。學會了走路,咱們嘗試跑起來,本篇主要介紹gomock測試框架,讓咱們的單元測試更加有效率。git
當針對某方法進行單元測試的時候,一般不止寫一個測試用例,咱們須要測試該方法在多種入參條件下是否都能正常工做,特別是要針對邊界值進行測試。一般這個時候表格驅動測試就派上用場了——當你發現你在寫測試方法的時候用上了複製粘貼,這就說明你須要考慮使用表格驅動測試來構建你的測試方法了。咱們依舊來舉個例子:github
func TestTime(t *testing.T) {
testCases := []struct { // 設計咱們的測試用例
gmt string
loc string
want string
}{
{"12:31", "Europe/Zuri", "13:31"}, // incorrect location name
{"12:31", "America/New_York", "7:31"}, // should be 07:31
{"08:08", "Australia/Sydney", "18:08"},
}
for _, tc := range testCases { // 循環執行測試用例
loc, err := time.LoadLocation(tc.loc)
if err != nil {
t.Fatalf("could not load location %q", tc.loc)
}
gmt, _ := time.Parse("15:04", tc.gmt)
if got := gmt.In(loc).Format("15:04"); got != tc.want {
t.Errorf("In(%s, %s) = %s; want %s", tc.gmt, tc.loc, got, tc.want)
}
}
}
複製代碼
表格驅動測試方法讓咱們的測試方法更加清晰和簡練,減小了複製粘貼,並大大提升的測試代碼的可讀性。golang
還記得上文說單元測試也是須要維護的嗎?單元測試也是代碼的一部分,也應當被認真對待。記得要用表格驅動測試的方法來組織你的測試用例,同時別忘了像正式代碼那樣,寫上相應的註釋。 更多參考: github.com/golang/go/w… blog.golang.org/subtestsbash
gomock是Google開源的golang測試框架。或者引用官方的話來講:「GoMock is a mocking framework for the Go programming language」。框架
上篇文章末尾介紹了mock和stub相結合的測試方法,能夠感覺到mock與stub結合起來功能當然強大——調用順序檢測,調用次數檢測,動態控制函數的返回值等等,但同時,其帶來的維護成本和複雜度缺是不可忽視的,手動維護這樣一套測試代碼那將是一場災難。咱們指望能用一套框架或者工具,在提供強大的測試功能的同時幫咱們維護複雜的mock代碼。工具
gomock經過mockgen
命令生成包含mock對象的.go
文件,其生成的mock對象具有mock+stub的強大功能,並將咱們從寫mock對象中解放了出來:單元測試
mockgen -destination foo_mock.go -source foo.go -package foo //mock foo.go裏面全部的接口,將mock結果保存到foo_mock.go
複製代碼
gomock讓咱們既能使用mock與stub結合的強大功能,又不須要手動維護這些mock對象,豈不美哉?測試
在這裏咱們對gomock的基本功能作一個簡單演示: 假設咱們的接口定義在user.go
:優化
// user.go
package user
// User 表示一個用戶
type User struct {
Name string
}
// UserRepository 用戶倉庫
type UserRepository interface {
// 根據用戶id查詢獲得一個用戶或是錯誤信息
FindOne(id int) (*User,error)
}
複製代碼
經過mockgen在同目錄下生成mock文件user_mock.go
mockgen -source user.go -destination user_mock.go -package user
複製代碼
而後在該目錄下新建user_test.go
來寫咱們的測試函數,上述步驟完成以後,咱們的目錄結構以下:
└── user
├── user.go
├── user_mock.go
└── user_test.go
複製代碼
// 靜態設置返回值
func TestReturn(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
repo := NewMockUserRepository(ctrl)
// 指望FindOne(1)返回張三用戶
repo.EXPECT().FindOne(1).Return(&User{Name: "張三"}, nil)
// 指望FindOne(2)返回李四用戶
repo.EXPECT().FindOne(2).Return(&User{Name: "李四"}, nil)
// 指望給FindOne(3)返回找不到用戶的錯誤
repo.EXPECT().FindOne(3).Return(nil, errors.New("user not found"))
// 驗證一下結果
log.Println(repo.FindOne(1)) // 這是張三
log.Println(repo.FindOne(2)) // 這是李四
log.Println(repo.FindOne(3)) // user not found
log.Println(repo.FindOne(4)) //沒有設置4的返回值,卻執行了調用,測試不經過
}
// 動態設置返回值
func TestReturnDynamic(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
repo := NewMockUserRepository(ctrl)
// 經常使用方法之一:DoAndReturn(),動態設置返回值
repo.EXPECT().FindOne(gomock.Any()).DoAndReturn(func(i int) (*User,error) {
if i == 0 {
return nil, errors.New("user not found")
}
if i < 100 {
return &User{
Name:"小於100",
}, nil
} else {
return &User{
Name:"大於等於100",
}, nil
}
})
log.Println(repo.FindOne(120))
//log.Println(repo.FindOne(66))
//log.Println(repo.FindOne(0))
}
複製代碼
func TestTimes(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
repo := NewMockUserRepository(ctrl)
// 默認指望調用一次
repo.EXPECT().FindOne(1).Return(&User{Name: "張三"}, nil)
// 指望調用2次
repo.EXPECT().FindOne(2).Return(&User{Name: "李四"}, nil).Times(2)
// 調用多少次能夠,包括0次
repo.EXPECT().FindOne(3).Return(nil, errors.New("user not found")).AnyTimes()
// 驗證一下結果
log.Println(repo.FindOne(1)) // 這是張三
log.Println(repo.FindOne(2)) // 這是李四
log.Println(repo.FindOne(2)) // FindOne(2) 需調用兩次,註釋本行代碼將致使測試不經過
log.Println(repo.FindOne(3)) // user not found, 不限調用次數,註釋掉本行也能經過測試
}
複製代碼
func TestOrder(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
repo := NewMockUserRepository(ctrl)
o1 := repo.EXPECT().FindOne(1).Return(&User{Name: "張三"}, nil)
o2 := repo.EXPECT().FindOne(2).Return(&User{Name: "李四"}, nil)
o3 := repo.EXPECT().FindOne(3).Return(nil, errors.New("user not found"))
gomock.InOrder(o1, o2, o3) //設置調用順序
// 按順序調用,驗證一下結果
log.Println(repo.FindOne(1)) // 這是張三
log.Println(repo.FindOne(2)) // 這是李四
log.Println(repo.FindOne(3)) // user not found
// 若是咱們調整了調用順序,將致使測試不經過:
// log.Println(repo.FindOne(2)) // 這是李四
// log.Println(repo.FindOne(1)) // 這是張三
// log.Println(repo.FindOne(3)) // user not found
}
複製代碼
上面的示例只展示了gomock
功能的冰山一角,在本篇中再也不深刻討論,更多用法請參考文檔。
更多官方示例:github.com/golang/mock…
若是你完成了上一章的小練習,嘗試動手使用gomock改造一下吧!
本篇介紹了表格驅動測試與gomock測試框架。運用表格驅動測試方法不只能使測試代碼更精簡易讀,還能提升咱們測試用例的編寫能力,無形中提高了單元測試的質量。gomock的功能十分豐富,想掌握各類騷操做仍是要細心閱讀一下官方示例,但一般20%的常規功能也足夠覆蓋80%的測試場景了。
表格驅動單元測試和gomock將咱們的單元測試效率與質量提高了一個檔次。在下一篇文章中,將介紹testify
斷言庫,繼續優化咱們的單元測試。