使用Golang的官方mock工具--gomock

4C7AD81C-DECF-4C23-802B-9F02EFF42C48.jpg

在Golang的官方Repo(https://github.com/golang/)中有一個單獨的工程叫"mock"(https://github.com/golang/mock),雖然star不是特別多外恕,但它卻是Golang官方放出來的mock工具膳犹,充這這點我們也需要使用下,雖然并不是官方的就是最好(比如比標準庫http更快的fasthttp)案怯。

不同場景mock的對象互相不同,那么gomock主要是mock哪些內(nèi)容呢舞痰?

mockgen has two modes of operation: source and reflect. Source mode generates mock interfaces from a source file.
Reflect mode generates mock interfaces by building a program that uses reflection to understand interfaces.

通過gomock的輔助工具我們知道腻豌,gomock主要是針對我們go代碼中的接口進行mock的。

安裝

gomock主要包含兩個部分:" gomock庫"和“ 輔助代碼生成工具mockgen”

他們都可以通過go get來獲入鼓啊:

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

如何你設(shè)置過$GOPATH/bin到你的$PATH變量中,那么這里就可以直接運行mockgen命令了员咽,否則需要使用絕對路徑或者相當于$GOPATH的目錄毒涧。

示例

gomock的repo中帶了一個官方的例子,但是這個例子過于強大和豐富,反而不適合嘗鮮贝室,下面我們寫個我們自己的例子(https://www.github.com/cz-it/blog/blog/Go/testing/gomock/example)契讲,一個獲取當前Golang最新版本的例子:

tree .
.
├── go_version.go
├── main.go
└── spider
    └── spider.go

目錄結(jié)構(gòu)如上。這里spider.go作為接口文件滑频,定義了spider包的接口:

package spider

type Spider interface {
    GetBody() string
}

這里假設(shè)接口GetBody直接可以抓取"https://golang.org"首頁的“Build version”字段來得到當前Golang發(fā)布出來的版本捡偏。

這里在go_version.go中對這個接口進行使用:

import (
    "github.com/cz-it/blog/blog/Go/testing/gomock/example/spider"
)

func GetGoVersion(s spider.Spider) string {
    body := s.GetBody()
    return body
}

直接返回表示版本的字符串。正常情況下我們會寫出如下的單元測試代碼:

func TestGetGoVersion(t *testing.T) {
    v := GetGoVersion(spider.CreateGoVersionSpider())
    if v != "go1.8.3" {
        t.Error("Get wrong version %s", v)
    }
}

這里spider.CreateGoVersionSpider()返回一個實現(xiàn)了Spider接口的用來獲得Go版本號的爬蟲峡迷。

這個單元測試其實既測試了函數(shù)GetGoVersion也測試了spider.CreateGoVersionSpider返回的對象银伟。
而有時候,我們可能僅僅想測試下GetGoVersion函數(shù)绘搞,或者我們的spider.CreateGoVersionSpider爬蟲實現(xiàn)還沒有寫好彤避,那該如何是好呢?

此時Mock工具就顯的尤為重要了夯辖。

這里首先用gomock提供的mockgen工具生成要mock的接口的實現(xiàn):

mockgen -destination spider/mock_spider.go -package spider github.com/cz-it/blog/blog/Go/testing/gomock/example/spider Spider

這里生成了文件:

└── spider
    ├── mock_spider.go
    └── spider.go

這里注意的是琉预,要預(yù)先創(chuàng)建好spider/mocks目錄。這樣我們的mock代碼就生成好了蒿褂,在"spider/mocks/mock_spider.go"文件中圆米。具體的內(nèi)容可以先不管。這里先看例子中怎么使用:

import (
    "github.com/cz-it/blog/blog/Go/testing/gomock/example/spider"
    "github.com/golang/mock/gomock"
    "testing"
)

func TestGetGoVersion(t *testing.T) {
    mockCtl := gomock.NewController(t)
    mockSpider := spider.NewMockSpider(mockCtl)
    mockSpider.EXPECT().GetBody().Return("go1.8.3")
    goVer := GetGoVersion(mockSpider)

    if goVer != "go1.8.3" {
        t.Error("Get wrong version %s", goVer)
    }
}

這里在單元測試中再也不用先去實現(xiàn)一個Spider接口了啄栓,而通過gomock為我們直接生成榨咐,然后再集成到我們的單元測試里面∏垂可以看到gomock和testing單元測試框架可以緊密的結(jié)合起來工作。

mockgen工具

在生成mock代碼的時候齿坷,我們用到了mockgen工具桂肌,這個工具是gomock提供的用來為要mock的接口生成實現(xiàn)的数焊。它可以根據(jù)給定的接口,來自動生成代碼崎场。這里給定接口有兩種方式:接口文件和實現(xiàn)文件

接口文件

如果有接口文件佩耳,則可以通過:

  • -source: 指定接口文件
  • -destination: 生成的文件名
  • -package:生成文件的包名
  • -imports: 依賴的需要import的包
  • -aux_files:接口文件不止一個文件時附加文件
  • -build_flags: 傳遞給build工具的參數(shù)

比如mock代碼使用

mockgen -destination spider/mock_spider.go -package spider -source spider/spider.go

就是將接口spider/spider.go中的接口做實現(xiàn)并存在 spider/mock_spider.go文件中,文件的包名為"spider"谭跨。

實現(xiàn)文件

在我們的上面的例子中干厚,并沒有使用"-source",那是如何實現(xiàn)接口的呢?mockgen還支持通過反射的方式來找到對應(yīng)的接口螃宙。只要在所有選項的最后增加一個包名和里面對應(yīng)的類型就可以了蛮瞄。其他參數(shù)和上面的公用。

通過注釋指定mockgen

如上所述谆扎,如果有多個文件挂捅,并且分散在不同的位置,那么我們要生成mock文件的時候堂湖,需要對每個文件執(zhí)行多次mockgen命令(假設(shè)包名不相同)闲先。這樣在真正操作起來的時候非常繁瑣,mockgen還提供了一種通過注釋生成mock文件的方式无蜂,此時需要借助go的"go generate "工具伺糠。

在接口文件的注釋里面增加如下:

//go:generate mockgen -destination mock_spider.go -package spider github.com/cz-it/blog/blog/Go/testing/gomock/example/spider Spider

這樣,只要在spider目錄下執(zhí)行

go generate

命令就可以自動生成mock文件了斥季。

gomock的接口使用

在生成了mock實現(xiàn)代碼之后训桶,我們就可以進行正常使用了。這里假設(shè)結(jié)合testing進行使用(當然你也可考慮使用GoConvey)泻肯。我們就可以
在單元測試代碼里面首先創(chuàng)建一個mock控制器:

mockCtl := gomock.NewController(t)

* testing.T傳遞給gomock生成一個"Controller"對象渊迁,該對象控制了整個Mock的過程。在操作完后還需要進行回收灶挟,所以一般會在New后面defer一個Finish

defer mockCtl.Finish()

然后就是調(diào)用mock生成代碼里面為我們實現(xiàn)的接口對象:

mockSpider := spider.NewMockSpider(mockCtl)

這里的"spider"是mockgen命令里面?zhèn)鬟f的報名琉朽,后面是NewMockXxxx格式的對象創(chuàng)建函數(shù)"Xxx"是接口名。這里需要傳遞控制器對象進去稚铣。返回一個接口的實現(xiàn)對象箱叁。

有了實現(xiàn)對象,我們就可以調(diào)用其斷言方法了:EXPECT()

這里gomock非常牛的采用了鏈式調(diào)用法惕医,和Swfit以及ObjectiveC里面的Masonry庫一樣耕漱,通過"."連接函數(shù)調(diào)用,可以像鏈條一樣連接下去抬伺。

mockSpider.EXPECT().GetBody().Return("go1.8.3")

這里的每個"."調(diào)用都得到一個"Call"對象螟够,該對象有如下方法:

func (c *Call) After(preReq *Call) *Call
func (c *Call) AnyTimes() *Call
func (c *Call) Do(f interface{}) *Call
func (c *Call) MaxTimes(n int) *Call
func (c *Call) MinTimes(n int) *Call
func (c *Call) Return(rets ...interface{}) *Call
func (c *Call) SetArg(n int, value interface{}) *Call
func (c *Call) String() string
func (c *Call) Times(n int) *Call

這里EXPECT()得到實現(xiàn)的對象,然后調(diào)用實現(xiàn)對象的接口方法,接口方法返回第一個"Call"對象妓笙,
然后對其進行條件約束若河。

上面約束都可以在文檔中或者根據(jù)字面意思進行理解,這里列舉幾個例子:

指定返回值

如我們的例子寞宫,調(diào)用Call的Return函數(shù)萧福,可以指定接口的返回值:

mockSpider.EXPECT().GetBody().Return("go1.8.3")

這里我們指定返回接口函數(shù)GetBody()返回"go1.8.3"。

指定執(zhí)行次數(shù)

有時候我們需要指定函數(shù)執(zhí)行多次辈赋,比如接受網(wǎng)絡(luò)請求的函數(shù)鲫忍,計算其執(zhí)行了多少次。

mockSpider.EXPECT().Recv().Return(nil).Times(3)

執(zhí)行三次Recv函數(shù)钥屈,這里還可以有另外幾種限制:

  • AnyTimes() : 0到多次
  • MaxTimes(n int) :最多執(zhí)行n次悟民,如果沒有設(shè)置
  • MinTimes(n int) :最少執(zhí)行n次,如果沒有設(shè)置

指定執(zhí)行順序

有時候我們還要指定執(zhí)行順序焕蹄,比如要先執(zhí)行Init操作逾雄,然后才能執(zhí)行Recv操作。

initCall := mockSpider.EXPECT().Init()
mockSpider.EXPECT().Recv().After(initCall)

再來回望官方Sample

Sample的結(jié)構(gòu)如下:

sample/
├── README.md
├── imp1
│   └── imp1.go
├── imp2
│   └── imp2.go
├── imp3
│   └── imp3.go
├── imp4
│   └── imp4.go
├── mock_user
│   └── mock_user.go
├── user.go
└── user_test.go

這里腻脏,user.go是包含要mock的接口函數(shù)的目標文件鸦泳,而imp1-4是user.go里面接口依賴的文件用來模擬"-imports"和"-aux_files"選項。

user_test.go 文件如同我們的test文件永品,是對gomock的調(diào)用做鹰。

而mock_user是生成mock文件的目錄。里面的mock_user.go是通過mockgen生成的鼎姐。

這里我們看到user.go有g(shù)enerate的注釋:

//go:generate mockgen -destination mock_user/mock_user.go github.com/golang/mock/sample Index,Embed,Embedded

這里指定了同一個包里面的三個接口钾麸。然后定義了三個接口,里面方法有依賴impx四個目錄中的文件:

type Embed interface {
    ...
}

type Embedded interface {
    ...
}

type Index interface {
    ...
    ForeignOne(imp1.Imp1)
    ForeignTwo(renamed2.Imp2)
    ForeignThree(Imp3)
    ForeignFour(imp_four.Imp4)
    ...
}

以及其他函數(shù)炕桨。

最后來看調(diào)用饭尝,在user_test.go中首先創(chuàng)建控制器并調(diào)用其Finish函數(shù):

ctrl := gomock.NewController(t)
defer ctrl.Finish()

然后就是如上面我介紹的,這里分開在幾個不同Test函數(shù)中献宫,流程基本上钥平,依次創(chuàng)建mock對象:

mockIndex := mock_user.NewMockIndex(ctrl)

然后調(diào)用其mock的方法:

mockIndex.EXPECT().Put("a", 1)

boolc := make(chan bool)
mockIndex.EXPECT().ConcreteRet().Return(boolc)

最后運行go test就可以進行測試了。

$ go test
PASS
ok      github.com/golang/mock/sample   0.013s
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末姊途,一起剝皮案震驚了整個濱河市涉瘾,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌捷兰,老刑警劉巖立叛,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異贡茅,居然都是意外死亡秘蛇,警方通過查閱死者的電腦和手機其做,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來赁还,“玉大人庶柿,你說我怎么就攤上這事』嘟剑” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵甚负,是天一觀的道長柬焕。 經(jīng)常有香客問我,道長梭域,這世上最難降的妖魔是什么斑举? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮病涨,結(jié)果婚禮上富玷,老公的妹妹穿的比我還像新娘。我一直安慰自己既穆,他們只是感情好赎懦,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著幻工,像睡著了一般励两。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上囊颅,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天当悔,我揣著相機與錄音,去河邊找鬼踢代。 笑死盲憎,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的胳挎。 我是一名探鬼主播饼疙,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼串远!你這毒婦竟也來了宏多?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤澡罚,失蹤者是張志新(化名)和其女友劉穎伸但,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體留搔,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡更胖,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片却妨。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡饵逐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出彪标,到底是詐尸還是另有隱情倍权,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布捞烟,位于F島的核電站薄声,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏题画。R本人自食惡果不足惜默辨,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望苍息。 院中可真熱鬧缩幸,春花似錦、人聲如沸竞思。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽衙四。三九已至铃肯,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間传蹈,已是汗流浹背押逼。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留惦界,地道東北人挑格。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像沾歪,于是被迫代替她去往敵國和親漂彤。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,077評論 25 707
  • 序言 要寫出好的測試代碼灾搏,必須精通相關(guān)的測試框架挫望。對于Golang的程序員來說,至少需要掌握下面四個測試框架: G...
    Aedan閱讀 2,298評論 0 0
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法狂窑,類相關(guān)的語法媳板,內(nèi)部類的語法,繼承相關(guān)的語法泉哈,異常的語法蛉幸,線程的語...
    子非魚_t_閱讀 31,623評論 18 399
  • 去年的今天(大概是今天把)破讨,招協(xié)換屆了,我如愿拿到了聘書奕纫。今天提陶,看著陪伴了一年的干事變成部長,覺得心里感慨萬分匹层。當...
    Forri閱讀 684評論 1 0
  • 0 系列目錄# WEB請求處理 WEB請求處理一:瀏覽器請求發(fā)起處理 WEB請求處理二:Nginx請求反向代理 W...
    七寸知架構(gòu)閱讀 4,308評論 3 55