go語言中GoMock安裝使用詳解
安裝
GoMock是一個Go框架。它與內(nèi)置的測試包整合得很好,并在單元測試時提供了靈活性。正如我們所知,對具有外部資源(數(shù)據(jù)庫、網(wǎng)絡(luò)和文件)或依賴關(guān)系的代碼進行單元測試總是很麻煩。
為了使用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為你想模擬的接口生成一個模擬對象。 - 在測試部分,創(chuàng)建一個
gomock.Controller的實例,并把它傳遞給你的mock對象的構(gòu)造函數(shù)以獲得一個mock對象。 - 在mock上調(diào)用
EXPECT()來設(shè)置期望值和返回值。 - 在模擬控制器上調(diào)用
Finish()來斷言模擬對象的期望。
開始
讓我們創(chuàng)建一個這樣的文件夾(本代碼在 go1.16.15 版本下執(zhí)行)
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中。我們首先創(chuàng)建一個包含我們的模擬實現(xiàn)的目錄mocks,然后在doer包上運行mockgen:
mockgen -destination=../mocks/mock_doer.go -package=mocks gomock_test/doer Doer
NOTE: 在執(zhí)行這步的時候,會報錯:Failed to format generated source code: mocks/mock_doer.go:5:15: expected ';', found '.’ 這個時候,我們只需要將打印出來的代碼復(fù)制到我們對應(yīng)的文件中即可。
當有大量的接口/包需要模擬時,為每個包和接口運行mockgen是一種烏托邦。為了緩解這個問題,可以將mockgen命令與go:generate放在一起。
go:generate mockgen -destination=../mocks/mock_doer.go -package=mocks gomock_test/doer Doer
我們必須自己創(chuàng)建目錄模擬,因為GoMock不會為我們這樣做,而是會以錯誤退出。
- destination=.../mocks/mock_doer.go : 把生成的mocks放在這個路徑下。
- -package=mocks : 把生成的mocks放在這個包里
- gomock_test/doer : 為這個包生成mocks。
- Doer : 為這個接口生成mocks(必填),因為我們需要指定哪個接口來生成mocks。(如果需要的話,可以用逗號分隔的列表來指定多個接口。例如,Doer1, Doer2)
因為我們對mockgen的調(diào)用在我們的項目中放置了一個文件mocks/mock_doer.go。這就是這樣一個生成的mock實現(xiàn)的樣子:
// 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)
}
接下來,我們在測試中定義一個模擬控制器。一個模擬控制器負責(zé)跟蹤和斷言其相關(guān)模擬對象的期望。我們可以通過傳遞一個*testing.T類型的值給它的構(gòu)造函數(shù)來獲得一個模擬控制器,并使用它來構(gòu)造一個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)
使用參數(shù)匹配器
在GoMock中,一個參數(shù)可以被期望有一個固定的值,也可以被期望與一個謂詞(稱為匹配器)相匹配。匹配器用于表示被模擬方法的預(yù)期參數(shù)范圍。下列匹配器在Gomock中被預(yù)先定義了:
- 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)
}
})
}
}
而單元測試的結(jié)果將是這樣的:
=== 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
經(jīng)驗之談
可以在不調(diào)用外部依賴的情況下進行單元測試。使用mocking框架可以在很多方面幫助我們,建立干凈和輕量級的單元測試。與接口和依賴注入相結(jié)合。
以上就是go語言中GoMock安裝使用詳解的詳細內(nèi)容,更多關(guān)于go語言GoMock安裝的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
go local history本地歷史恢復(fù)代碼神器
這篇文章主要為大家介紹了go local history本地歷史恢復(fù)代碼神器的使用功能詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2024-01-01
golang使用sync.singleflight解決熱點緩存穿透問題
在go的sync包中,有一個singleflight包,里面有一個?singleflight.go文件,代碼加注釋,一共200行出頭,通過?singleflight可以很容易實現(xiàn)緩存和去重的效果,避免重復(fù)計算,接下來我們就給大家詳細介紹一下sync.singleflight如何解決熱點緩存穿透問題2023-07-07
利用Go實現(xiàn)一個簡易DAG服務(wù)的示例代碼
DAG的全稱是Directed Acyclic Graph,即有向無環(huán)圖,DAG廣泛應(yīng)用于表示具有方向性依賴關(guān)系的數(shù)據(jù),如任務(wù)調(diào)度、數(shù)據(jù)處理流程、項目管理以及許多其他領(lǐng)域,下面,我將用Go語言示范如何實現(xiàn)一個簡單的DAG服務(wù),需要的朋友可以參考下2024-03-03

