GoMock使用

安裝

安裝gomock軟件包和mockgen代碼生成工具

go get github.com/golang/mock/gomock 
go github.com/golang/mock/mockgen

基本用法

1.用mockgen為要模擬的接口生成模擬。
2.在測(cè)試中創(chuàng)建一個(gè)市里gomock.Controller并將其傳遞給模擬對(duì)象構(gòu)造函數(shù)以獲取模擬對(duì)象。
3.調(diào)用EXPECT()為你的模擬設(shè)置他們的期望值和返回值
4.調(diào)用Finish()模擬控制器來斷言模擬的期望。
5.舉個(gè)簡單的例子:

//位置:doer/doer.go
package doer
type Doer interface {
    DoSomething(int, string) error
}

我們?cè)谀MDoer時(shí)要的測(cè)試代碼

//位置:user/user.go
package user

import "cold/stu1/doer"

type User struct {
    Doer doer.Doer
}

func (u *User) Use() error {
    return u.Doer.DoSomething(123, "Hello Gomock")
}

生成Doer的模擬代碼:

mkdir -p mocks

mockgen -destination=mocks/mock_doer.go -package=mocks cold/stu1/doer Doer

注釋:

  • -destination=mocks/mock_doer.go : 將生成的模擬接口放入文件中mocks/mock_doer.go
  • -package=mocks : 將生成的模擬接口放入包mocks中
  • cold/stu1/doer:為此包生成模擬,注意:這里不要加入絕對(duì)路徑,加入$GOPATH的相對(duì)路徑,否則會(huì)報(bào)錯(cuò)
  • Doer : 為此接口生成模擬。這個(gè)參數(shù)是必需的 - 我們需要指定接口來顯式生成模擬。但是,我們 可以在此指定多個(gè)接口作為逗號(hào)分隔列表(例如 Doer1,Doer2)
    這樣在mocks下面就會(huì)自動(dòng)生成一個(gè)mocks/mock_doer.go的文件,如下內(nèi)容:
// Code generated by MockGen. DO NOT EDIT.
// Source: cold/stu1/doer (interfaces: Doer)

// Package 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 {
    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 {
    return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DoSomething", reflect.TypeOf((*MockDoer)(nil).DoSomething), arg0, arg1)
}

請(qǐng)注意,生成的 EXPECT() 方法在與mock方法相同的對(duì)象上定義(在本例中 DoSomething) - 避免此處的名稱沖突可能是非標(biāo)準(zhǔn)全大寫名稱的原因

接下來,我們 在測(cè)試中定義一個(gè) 模擬控制器。模擬控制器負(fù)責(zé)跟蹤和斷言其關(guān)聯(lián)模擬對(duì)象的期望。

我們可以通過將t 類型 的值傳遞*testing.T 給它的構(gòu)造函數(shù)來獲得模擬控制器 ,然后使用它來構(gòu)造Doer 接口的模擬 。我們還有 defer 它的 Finish 方法。

假設(shè)我們要斷言 mockerDoer的 Do 方法將被調(diào)用 一次,以 123 和 "Hello GoMock" 為參數(shù),將返回 nil。

要做到這一點(diǎn),我們就可以調(diào)用 EXPECT() 上 mockDoer 建立其期望在我們的測(cè)試。調(diào)用 EXPECT()返回一個(gè)對(duì)象(稱為模擬 記錄器),提供與真實(shí)對(duì)象相同名稱的方法。

調(diào)用模擬記錄器上的方法之一指定具有給定參數(shù)的預(yù)期調(diào)用。然后,您可以將其他屬性鏈接到呼叫上,例如:

返回值(通過 .Return(...))
此呼叫預(yù)計(jì)發(fā)生的次數(shù)(通過 .Times(number)或通過 .MaxTimes(number) 和 .MinTimes(number))

代碼如下:


func TestUse(t *testing.T) {
    mockCtrl := gomock.NewController(t)
    defer mockCtrl.Finish()

    mockDoer := mocks.NewMockDoer(mockCtrl)
    testUser := &user.User{Doer: mockDoer}

    mockDoer.EXPECT().DoSomething(123, "Hello Gomock").Return(nil).Times(1)

    testUser.Use()
}

最后,我們來運(yùn)行一下我們的測(cè)試:

$ go test -v cold/stu1/user
=== RUN TestUse
--- PASS: TestUse (0.00s)
=== RUN TestUseReturnsErrorFromDo
--- PASS: TestUseReturnsErrorFromDo (0.00s)
PASS
ok cold/stu1/user 0.002s

我們可能還想聲明Use 方法返回的值確實(shí)是由DoSomething返回的值。我們可以編寫另一個(gè)測(cè)試,創(chuàng)建一個(gè)虛擬錯(cuò)誤,然后將其指定為返回值

func TestUseReturnsErrorFromDo(t *testing.T) {
    mockCtr := gomock.NewController(t)
    defer mockCtr.Finish()

    dummyError := errors.New("dummy error")
    mockDoer := mocks.NewMockDoer(mockCtr)
    testUser := &user.User{Doer: mockDoer}

    mockDoer.EXPECT().DoSomething(123, "Hello Gomock").Return(dummyError).Times(1)

    err := testUser.Use()

    if err != dummyError {
        t.Fail()
    }
}

使用GoMock 的 go:generate

mockgen 當(dāng)存在大量要模擬的接口/包時(shí),單獨(dú)運(yùn)行 每個(gè)包和接口是很麻煩的。為了緩解此問題, mockgen 可以將命令放在特殊 go:generate 注釋中
在我們的示例中,我們可以在我們go:generate 的package 聲明 下方添加 注釋 doer.go:

package doer

//go:generate mockgen -destination=../mocks/mock_doer.go -package=mocks cold/stu1/doer Doer

type Doer interface {
    DoSomething(int, string) error
}

注意:由于當(dāng)前的包doer的工作路徑是doer,所以在destination要寫成 ../mocks/,而不是直接是mocks,執(zhí)行命令:

go generate ./...

那么我們現(xiàn)在可以運(yùn)行所有有g(shù)o:generate ./... 注釋的mocks,注意:注釋符 //與go:generate之間沒有空格。

關(guān)于在何處放置go:generate 注釋以及要包括哪些接口的合理策略如下:

  • go:generate 每個(gè)文件一條注釋,包含要模擬的接口
  • 一調(diào)用mockgen,就會(huì)為所有的接口生成mocks
  • 將模擬放入包中 mocks 并將文件的模擬 X.go 寫入 mocks/mock_X.go。
    這樣, mockgen 調(diào)用接近實(shí)際接口,同時(shí)避免了每個(gè)接口的單獨(dú)調(diào)用和目標(biāo)文件的開銷。

使用參數(shù)匹配器

有時(shí),您不關(guān)心調(diào)用mock的特定參數(shù)。使用 GoMock,可以預(yù)期參數(shù)具有固定值(通過指定預(yù)期調(diào)用中的值),或者可以預(yù)期它與謂詞匹配,稱為 匹配器。匹配器用于表示模擬方法的預(yù)期參數(shù)范圍。以下匹配器在GoMock中預(yù)定義 :
gomock.Any():匹配任何值(任何類型)
gomock.Eq(x):使用反射來匹配是值DeepEqual 到 x
gomock.Nil(): 火柴 nil
gomock.Not(m):( m 匹配器在哪里 )匹配匹配器不匹配的值 m
gomock.Not(x)(式中, x 是 不 一個(gè)Matcher)匹配的值不 DeepEqual 至 x

示例:
如果我們不關(guān)心第一個(gè)參數(shù)的值 Do,我們可以寫:

mockDoer.EXPECT().DoSomething(gomock.Any(), "Hello GoMock")

GoMock 自動(dòng)將實(shí)際的參數(shù)轉(zhuǎn)換 Matcher 為 Eq 匹配器,因此上述調(diào)用等效于:

mockDoer.EXPECT().DoSomething(gomock.Any(), gomock.Eq("Hello GoMock"))
您可以通過實(shí)現(xiàn)gomock.Matcher 界面來定義自己的匹配器 :
//位置:gomock/matchers.go
type Matcher interface {
    Matches(x interface{}) bool
    String() string
}

該 Matches 方法是實(shí)際匹配發(fā)生的地方,同時(shí) String 用于為失敗的測(cè)試生成人類可讀的輸出。例如,檢查參數(shù)類型的匹配器可以實(shí)現(xiàn)如下:

//位置:match/oftype.go
package match

import (
    "reflect"
    "github.com/golang/mock/gomock"
)

type ofType struct{ t string }

func OfType(t string) gomock.Matcher {
    return &ofType{t}
}

func (o *ofType) Matches(x interface{}) bool {
    return reflect.TypeOf(x).String() == o.t
}

func (o *ofType) String() string {
    return "is of type " + o.t
}

我們可是使用自定義的matcher如下:

// Expect Do to be called once with 123 and any string as parameters, and return nil from the mocked call.
mockDoer.EXPECT().
   DoSomething(123, match.OfType("string")).
   Return(nil).
   Times(1)

請(qǐng)注意,在Go中,我們必須 在一系列鏈?zhǔn)秸{(diào)用中將點(diǎn)放在每一行的 末尾

調(diào)用對(duì)象的順序通常很重要。 GoMock 提供了一種斷言一個(gè)調(diào)用必須在另一個(gè)調(diào)用之后發(fā)生的.After 方法,即 方法。例如,

callFirst := mockDoer.EXPECT().DoSomething(1, "first this")
callA := mockDoer.EXPECT().DoSomething(2, "then this").After(callFirst)
callB := mockDoer.EXPECT().DoSomething(2, "or this").After(callFirst)

GoMock 還提供了一個(gè)便利功能, gomock.InOrder 用于指定必須按照給定的確切順序執(zhí)行調(diào)用。這比.After 直接使用靈活性要差 ,但可以使您的測(cè)試對(duì)于更長的調(diào)用序列更具可讀性:

gomock.InOrder(
    mockDoer.EXPECT().DoSomething(1, "first this"),
    mockDoer.EXPECT().DoSomething(2, "then this"),
    mockDoer.EXPECT().DoSomething(3, "then this"),
    mockDoer.EXPECT().DoSomething(4, "finally this"),
)
指定模擬操作

模擬對(duì)象與實(shí)際實(shí)現(xiàn)的不同之處在于它們不實(shí)現(xiàn)任何行為 - 它們所做的只是在適當(dāng)?shù)臅r(shí)刻提供預(yù)設(shè)響應(yīng)并記錄其調(diào)用。但是,有時(shí)你需要你的mock才能做更多的事情。在這里, GoMock的 Do 行動(dòng)派上用場。任何調(diào)用都可以通過調(diào)用一個(gè)動(dòng)作進(jìn)行修飾, .Do 每當(dāng)調(diào)用匹配時(shí),都會(huì)執(zhí)行一個(gè)函數(shù):

mockDoer.EXPECT().
    DoSomething(gomock.Any(), gomock.Any()).
    Return(nil).
    Do(func(x int, y string) {
        fmt.Println("Called with x =",x,"and y =", y)
    })

關(guān)于調(diào)用參數(shù)的復(fù)雜斷言可以寫在 Do 操作中。例如,如果DoSomething第一個(gè)(int)參數(shù) 應(yīng)小于或等于second(string)參數(shù)的長度,我們可以編寫:

mockDoer.EXPECT().
    DoSomething(gomock.Any(), gomock.Any()).
    Return(nil).
    Do(func(x int, y string) {
        if x > len(y) {
            t.Fail()
        }
    })
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,602評(píng)論 19 139
  • 原文地址:使用 Gomock 進(jìn)行單元測(cè)試 前言 在實(shí)際項(xiàng)目中,需要進(jìn)行單元測(cè)試的時(shí)候。卻往往發(fā)現(xiàn)有一大堆依賴項(xiàng)。...
    EDDYCJY閱讀 9,056評(píng)論 0 5
  • 1、通過CocoaPods安裝項(xiàng)目名稱項(xiàng)目信息 AFNetworking網(wǎng)絡(luò)請(qǐng)求組件 FMDB本地?cái)?shù)據(jù)庫組件 SD...
    陽明AI閱讀 16,211評(píng)論 3 119
  • ORA-00001: 違反唯一約束條件 (.) 錯(cuò)誤說明:當(dāng)在唯一索引所對(duì)應(yīng)的列上鍵入重復(fù)值時(shí),會(huì)觸發(fā)此異常。 O...
    我想起個(gè)好名字閱讀 5,981評(píng)論 0 9
  • 當(dāng)自己的孩子來到這個(gè)世界的時(shí)候,每一個(gè)初為人父母的家長都想把這個(gè)世界上最好的東西,在自己的能力范圍之內(nèi)給予孩子,而...
    釋然andy閱讀 392評(píng)論 0 0

友情鏈接更多精彩內(nèi)容