單元測試通常用來在日常開發中檢查代碼中存在的問題,是提升代碼質量一種有效手段。在保證代碼功能沒有問題的同時,可以得到預期結果。Golang有許多優秀的框架支持UT,下面列舉日常開發中不同框架對應的UT情況,以便后來人實踐UT。
1、Goland提供的簡單UT模板
用途:對其中一個函數、方法生成UT
介紹:在新手不知道如何寫UT,按照什么規范去編寫UT的時候,不妨采用Goland自帶的模板。
tips:在Goland選中要測試的方法、函數--“generate”--"Test for selection",然后在// TODO: Add test cases中加入自己的測試用例數據即可。
缺點:通用性一般,適合邏輯簡單的函數。
2、Convey
用途:較為好用的UT框架,斷言多樣,提供UT的web界面,較為常用
官方文檔:link
tips:Testify: 斷言庫,斷言功能豐富,簡單易用,通常和其他框架搭配使用
code example:

1 // TestFunc 2 func CheckVal(val string) bool { 3 valList := []string{"AAA", "BBB"} 4 for index := range valList { 5 if valList[index] == val { 6 return true 7 } 8 } 9 return false 10 } 11 12 13 // Convey特點:斷言函數豐富,提供UT的web界面,較為常用 14 // Convey website: https://github.com/smartystreets/goconvey/wiki/Documentation 15 16 // Testify: 斷言庫,多用它提供的豐富的斷言功能 17 func TestCheckVal(t *testing.T) { 18 convey.Convey("TestCheckVal happy path", t, func(){ 19 res := CheckVal("AAA") 20 convey.So(res, convey.ShouldBeTrue) 21 assert.True(t, res) 22 }) 23 24 convey.Convey("TestCheckVal unhappy path", t, func(){ 25 res := CheckVal("CCC") 26 convey.So(res, convey.ShouldBeFalse) 27 assert.False(t, res) 28 }) 29 }
3、Gomonkey
tips:
mac電腦在用Gomonkey打樁的時候時常會遇到syscall的問題,是該框架的一個bug,需要配置插件來解決這個問題,具體可以參照國外作者的配置 link
code example:

1 // Gomonkey: 十分常用的打樁工具,是在monkey框架基礎上個人改進的一款UT框架,比較好奇公司喜歡用這類個人庫而不是monkey這種偏官方的庫寫UT 2 // Gomonkey支持多種打樁特性,但在平常開發中多用來對全局變量打樁;對函數打樁;對方法打樁;對函數變量進行打樁 3 4 // 1、為函數打樁 ApplyFunc(origin func, stub func) 5 func CalVal(a, b int) int { 6 return a + b 7 } 8 9 func TestCalVal(t *testing.T) { 10 convey.Convey("happy path", t, func() { 11 patch := gomonkey.ApplyFunc(CalVal, func(a, b int) int { 12 return 3 13 }) 14 defer patch.Reset() 15 16 res := CalVal(1, 2) 17 convey.So(res, convey.ShouldEqual, 3) // or use assert 18 }) 19 } 20 21 // 2、為全局變量打樁 ApplyGlobalVar(&var, stubvalue) 22 23 var temp = 10 24 func TestGlobal(t *testing.T) { 25 convey.Convey("happy path", t, func() { 26 patch := gomonkey.ApplyGlobalVar(&temp, 15) 27 defer patch.Reset() 28 convey.So(temp, convey.ShouldEqual, 15) 29 }) 30 } 31 32 // 3、對函數變量打樁 ApplyFuncVar (&func, stubfunc) equal to ApplyFunc, but it is the address of func var 33 34 // 4、對方法打樁 ApplyMethod(reflect.TypeOf, methodName, stubfunc) 35 36 type User struct { 37 money int 38 } 39 40 func(u *User) AddMoney(temp int) int{ 41 u.money += temp 42 return u.money 43 } 44 45 func TestAddMoney(t *testing.T) { 46 temp := User{} 47 patch := gomonkey.ApplyMethod(reflect.TypeOf(&temp), "AddMoney", func(_ *User, temp int) int{ 48 return 100 49 }) 50 defer patch.Reset() 51 52 convey.Convey("happy path", t, func() { 53 Bob := &User{ 54 money: 20, 55 } 56 convey.So(Bob.AddMoney(80),convey.ShouldEqual, 100) 57 }) 58 59 }
4、Gomock
用途:對接口進行mock,提前定義好返回內容
介紹:go官方提供的框架,同時還可以使用mockGen工具來生成接口的mock代碼
tips:
使用mockgen生成接口的mock代碼
mockgen -source= A.go -destination= A_mock.go -package=main
code example:

1 // gomock 多用於接口打樁,結合mockgen工具自動生成打樁代碼,方便調用 2 3 type Repo interface { 4 GetName(id int) string 5 } 6 7 func TestGetName(t *testing.T) { 8 // new mockController 9 ctrl := gomock.NewController(t) 10 defer ctrl.Finish() 11 12 // mock Repo interface 13 mock := mock_go_UT_learn.NewMockRepo(ctrl) 14 mock.EXPECT().GetName(gomock.Eq(1)).Return("JD_1") 15 if val := mock.GetName(1); val != "JD_1" { 16 t.Fatal("expected JD_1, but got", val) 17 } else { 18 fmt.Println("the result is ", val) 19 } 20 21 } 22 23 //go:generate mockgen -source=./gomock_test.go -destination=./mock/mocktest_mock.go
5、SqlMock
用途:模擬數據庫請求,無需關注數據庫連接
介紹:實際遇到的情況是SqlMock和GORM的的測試
tips: mock.ExpectQuery("content").WillReturnRows(sqlmock.NewRows([]string{"id"})) 在WillReturnRows里不addRow就可以返回空行了
code example:

1 type Goods struct { 2 name string 3 } 4 type Repository struct { 5 db *gorm.DB 6 } 7 8 9 func(p *Repository)ListAll()([]*Goods, error) { 10 var goods []*Goods 11 err := p.db.Find(&goods).Error 12 return goods, err 13 } 14 15 16 func TestSql(t *testing.T) { 17 // 使用 sqlmock.New() 創建 *sql.DB 的模擬實例和模擬控制器 18 db, mock, err := sqlmock.New() 19 if nil != err { 20 log.Fatalf("Init sqlmock failed, err %v", err) 21 } 22 // create gorm link with sqlmock 23 dbLink, err := gorm.Open(mysql.New(mysql.Config{ 24 SkipInitializeWithVersion: true, 25 Conn: db, 26 }), &gorm.Config{}) 27 if nil != err { 28 log.Fatalf("Init DB with sqlmock failed, err %v", err) 29 } 30 31 repo := Repository{ 32 db: dbLink, 33 } 34 35 // create mock record 36 const sqlSelectAll = `SELECT * FROM "blogs"` 37 mock.ExpectQuery(sqlSelectAll).WillReturnRows(sqlmock.NewRows(nil)) 38 39 goods, err := repo.ListAll() 40 assert.Nil(t, err) 41 assert.Nil(t, goods) 42 }
所有UT代碼地址:this