網站首頁 編程語言 正文
安裝
GoMock
是一個Go框架。它與內置的測試包整合得很好,并在單元測試時提供了靈活性。正如我們所知,對具有外部資源(數據庫、網絡和文件)或依賴關系的代碼進行單元測試總是很麻煩。
為了使用GoMock,我們需要安裝gomock
包github.com/golang/mock/gomock
和mockgen
代碼生成工具github.com/golang/mock/mockgen
。使用這個命令行:
go get github.com/golang/mock/gomock go get github.com/golang/mock/mockgen
GoMock的使用遵循四個基本步驟:
- 使用
mockgen
為你想模擬的接口生成一個模擬對象。 - 在測試部分,創建一個
gomock.Controller
的實例,并把它傳遞給你的mock
對象的構造函數以獲得一個mock
對象。 - 在mock上調用
EXPECT()
來設置期望值和返回值。 - 在模擬控制器上調用
Finish()
來斷言模擬對象的期望。
開始
讓我們創建一個這樣的文件夾(本代碼在 go1.16.15 版本下執行)
gomock_test ├── doer │ └── doer.go ├── mocks │ └── mock_doer.go └── user ├── user.go └── user_test.go
doer/doer.go
package doer type Doer interface { DoSomething(int, string) error SaySomething(string) string }
那么這里是我們在模擬Doer接口時要測試的代碼。
user/user.go
package user import "gomock_test/doer" const ( filtered = "OK" unfiltered = "spam" Nice = "nice" Bad = "bad" ) type User struct { // struct while mocking the doer interface Doer doer.Doer } // method Use using it func (u *User) Use() error { return u.Doer.DoSomething(123, "Hello GoMock") } func (u *User) SaySomething(num int) string { if num == 3 { return u.Doer.SaySomething(unfiltered) } return u.Doer.SaySomething(filtered) } type Student struct{} func (s *Student) DoSomething(_ int, _ string) error { panic("not implemented") // TODO: Implement } func (s *Student) SaySomething(kata string) string { if kata == filtered { return Nice } return Bad }
我們將把Doer
的模擬放在一個包mocks
中。我們首先創建一個包含我們的模擬實現的目錄mocks
,然后在doer
包上運行mockgen
:
mockgen -destination=../mocks/mock_doer.go -package=mocks gomock_test/doer Doer
NOTE: 在執行這步的時候,會報錯:Failed to format generated source code: mocks/mock_doer.go:5:15: expected ';', found '.’ 這個時候,我們只需要將打印出來的代碼復制到我們對應的文件中即可。
當有大量的接口/包需要模擬時,為每個包和接口運行mockgen是一種烏托邦。為了緩解這個問題,可以將mockgen
命令與go:generate
放在一起。
go:generate mockgen -destination=../mocks/mock_doer.go -package=mocks gomock_test/doer Doer
我們必須自己創建目錄模擬,因為GoMock不會為我們這樣做,而是會以錯誤退出。
- destination=.../mocks/mock_doer.go : 把生成的mocks放在這個路徑下。
- -package=mocks : 把生成的mocks放在這個包里
- gomock_test/doer : 為這個包生成mocks。
- Doer : 為這個接口生成mocks(必填),因為我們需要指定哪個接口來生成mocks。(如果需要的話,可以用逗號分隔的列表來指定多個接口。例如,Doer1, Doer2)
因為我們對mockgen
的調用在我們的項目中放置了一個文件mocks/mock_doer.go
。這就是這樣一個生成的mock
實現的樣子:
// Code generated by MockGen. DO NOT EDIT. // Source: github.com/timliudream/go-test/gomock_test/doer (interfaces: Doer) // Package github.com/timliudream/go-test/gomock_test/mocks is a generated GoMock package. package mocks import ( gomock "github.com/golang/mock/gomock" reflect "reflect" ) // MockDoer is a mock of Doer interface. type MockDoer struct { ctrl *gomock.Controller recorder *MockDoerMockRecorder } // MockDoerMockRecorder is the mock recorder for MockDoer. type MockDoerMockRecorder struct { mock *MockDoer } // NewMockDoer creates a new mock instance. func NewMockDoer(ctrl *gomock.Controller) *MockDoer { mock := &MockDoer{ctrl: ctrl} mock.recorder = &MockDoerMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockDoer) EXPECT() *MockDoerMockRecorder { return m.recorder } // DoSomething mocks base method. func (m *MockDoer) DoSomething(arg0 int, arg1 string) error { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "DoSomething", arg0, arg1) ret0, _ := ret[0].(error) return ret0 } // DoSomething indicates an expected call of DoSomething. func (mr *MockDoerMockRecorder) DoSomething(arg0, arg1 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DoSomething", reflect.TypeOf((*MockDoer)(nil).DoSomething), arg0, arg1) } // SaySomething mocks base method. func (m *MockDoer) SaySomething(arg0 string) string { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "SaySomething", arg0) ret0, _ := ret[0].(string) return ret0 } // SaySomething indicates an expected call of SaySomething. func (mr *MockDoerMockRecorder) SaySomething(arg0 interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SaySomething", reflect.TypeOf((*MockDoer)(nil).SaySomething), arg0) }
接下來,我們在測試中定義一個模擬控制器。一個模擬控制器負責跟蹤和斷言其相關模擬對象的期望。我們可以通過傳遞一個*testing.T
類型的值給它的構造函數來獲得一個模擬控制器,并使用它來構造一個Doer
接口的模擬對象。
//core of gomock mockCtrl := gomock.NewController(t) //used to trigger final assertions. if its ignored, mocking assertions will never fail defer mockCtrl.Finish() // create a new mock object, passing the controller instance as parameter // for a newly created mock object it will accept any input and outpuite // need to define its behavior with the method expect mockDoer := mocks.NewMockDoer(mockCtrl)
使用參數匹配器
在GoMock中,一個參數可以被期望有一個固定的值,也可以被期望與一個謂詞(稱為匹配器)相匹配。匹配器用于表示被模擬方法的預期參數范圍。下列匹配器在Gomock中被預先定義了:
- gomock.Any() : 匹配任何值(任何類型)。
- gomock.Eq(x) : 使用反射來匹配與x深度相等的值。
- gomock.Nil() : 匹配nil
user/user_test.go
package user import ( "fmt" "github.com/golang/mock/gomock" "gomock_test/mocks" "testing" ) func TestUse(t *testing.T) { //core of gomock mockCtrl := gomock.NewController(t) //used to trigger final assertions. if its ignored, mocking assertions will never fail defer mockCtrl.Finish() // create a new mock object, passing the controller instance as parameter // for a newly created mock object it will accept any input and outpuite // need to define its behavior with the method expect mockDoer := mocks.NewMockDoer(mockCtrl) testUser := &User{Doer: mockDoer} // // Expect Do to be called once with 123 and "Hello GoMock" as parameters, and return nil from the mocked call. mockDoer.EXPECT().DoSomething(123, "Hello GoMock").Return(nil).Times(1) fmt.Println(testUser.Use()) fmt.Println() } func TestUser_SaySomething(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() mockDoer := mocks.NewMockDoer(mockCtrl) user := User{ Doer: mockDoer, } type args struct { num int } tests := []struct { name string args args want string expect func() wantErr bool }{ { name: "Positive test case 1", expect: func() { mockDoer.EXPECT().SaySomething("spam").Return("bad") }, args: args{num: 3}, wantErr: false, want: "bad", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { tt.expect() if got := user.SaySomething(tt.args.num); (got != tt.want) != tt.wantErr { fmt.Println("gott :", got) t.Errorf("User.SaySomething() = %v, want %v", got, tt.want) } }) } }
而單元測試的結果將是這樣的:
=== RUN ? TestUser_SaySomething
=== RUN ? TestUser_SaySomething/Positive_test_case_1
--- PASS: TestUser_SaySomething (0.00s)
? ? --- PASS: TestUser_SaySomething/Positive_test_case_1 (0.00s)
PASS
ok ? ? ?github.com/tokopedia/go_learning/udemy/pzn/gomock_test/user ? ? 1.100s
經驗之談
可以在不調用外部依賴的情況下進行單元測試。使用mocking
框架可以在很多方面幫助我們,建立干凈和輕量級的單元測試。與接口和依賴注入相結合。
原文鏈接:https://juejin.cn/post/7169014973287366693
相關推薦
- 2022-09-02 使用Docker配置redis?sentinel哨兵的方法步驟_docker
- 2022-03-30 用C語言實現二分查找算法_C 語言
- 2022-07-28 Golang配置管理庫?Viper的教程詳解_Golang
- 2023-02-25 C和C++如何實現互相調用詳解_C 語言
- 2022-10-02 react如何獲取state的值并更新使用_React
- 2022-07-06 QT5實現簡單的TCP通信的實現_C 語言
- 2022-04-30 C#操作DataGridView獲取或設置當前單元格的內容_C#教程
- 2022-11-17 Android對話框AlertDialog與DatePickerDialog及TimePickerD
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支