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

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

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

不同情境mock的對象互相不同,那麼gomock主要是mock哪些內容呢?

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/gomockgo get github.com/golang/mock/mockgen

如何你設定過$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

目錄結構如上。這裡spider.go作為介面檔案,定義了spider包的介面:

package spidertype Spider interface {    GetBody() string}

這裡假設介面GetBody直接可以抓取"https://golang.org"首頁的“Build version”欄位來得到當前Golang發布出來的版本。

這裡在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()返回一個實現了Spider介面的用來獲得Go版本號碼的爬蟲。

這個單元測試其實既測試了函數GetGoVersion也測試了spider.CreateGoVersionSpider返回的對象。
而有時候,我們可能僅僅想測試下GetGoVersion函數,或者我們的spider.CreateGoVersionSpider爬蟲實現還沒有寫好,那該如何是好呢?

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

這裡首先用gomock提供的mockgen工具產生要mock的介面的實現:

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

這裡注意的是,要預先建立好spider/mocks目錄。這樣我們的mock代碼就產生好了,在"spider/mocks/mock_spider.go"檔案中。具體的內容可以先不管。這裡先看例子中怎麼使用:

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)    }}

這裡在單元測試中再也不用先去實現一個Spider介面了,而通過gomock為我們直接產生,然後再整合到我們的單元測試裡面。可以看到gomock和testing單元測試架構可以緊密的結合起來工作。

mockgen工具

在產生mock代碼的時候,我們用到了mockgen工具,這個工具是gomock提供的用來為要mock的介面產生實現的。它可以根據給定的介面,來自動產生代碼。這裡給定介面有兩種方式:介面檔案和實現檔案

介面檔案

如果有介面檔案,則可以通過:

  • -source: 指定介面檔案
  • -destination: 產生的檔案名稱
  • -package:組建檔案的包名
  • -imports: 依賴的需要import的包
  • -aux_files:介面檔案不止一個檔案時附加檔案
  • -build_flags: 傳遞給build工具的參數

比如mock代碼使用

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

就是將介面spider/spider.go中的介面做實現並存在 spider/mock_spider.go檔案中,檔案的包名為"spider"。

實現檔案

在我們的上面的例子中,並沒有使用"-source",那是如何?介面的呢?mockgen還支援通過反射的方式來找到對應的介面。只要在所有選項的最後增加一個包名和裡面對應的類型就可以了。其他參數和上面的公用。

通過注釋指定mockgen

如上所述,如果有多個檔案,並且分散在不同的位置,那麼我們要產生mock檔案的時候,需要對每個檔案執行多次mockgen命令(假設包名不相同)。這樣在真正操作起來的時候非常繁瑣,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目錄下執行

go generate

命令就可以自動產生mock檔案了。

gomock的介面使用

在產生了mock實現代碼之後,我們就可以進行正常使用了。這裡假設結合testing進行使用(當然你也可考慮使用GoConvey)。我們就可以
在單元測試代碼裡面首先建立一個mock控制器:

mockCtl := gomock.NewController(t)

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

defer mockCtl.Finish()

然後就是調用mock產生代碼裡面為我們實現的介面對象:

mockSpider := spider.NewMockSpider(mockCtl)

這裡的"spider"是mockgen命令裡面傳遞的報名,後面是NewMockXxxx格式的對象建立函數"Xxx"是介面名。這裡需要傳遞控制器對象進去。返回一個介面的實現對象。

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

這裡gomock非常牛的採用了鏈式調用法,和Swfit以及ObjectiveC裡面的Masonry庫一樣,通過"."串連函數調用,可以像鏈條一樣串連下去。

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

這裡的每個"."調用都得到一個"Call"對象,該對象有如下方法:

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

這裡EXPECT()得到實現的對象,然後調用實現對象的介面方法,介面方法返回第一個"Call"對象,
然後對其進行條件約束。

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

指定傳回值

如我們的例子,調用Call的Return函數,可以指定介面的傳回值:

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

這裡我們指定返回介面函數GetBody()返回"go1.8.3"。

指定執行次數

有時候我們需要指定函數執行多次,比如接受網路請求的函數,計算其執行了多少次。

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

執行三次Recv函數,這裡還可以有另外幾種限制:

  • AnyTimes() : 0到多次
  • MaxTimes(n int) :最多執行n次,如果沒有設定
  • MinTimes(n int) :最少執行n次,如果沒有設定

指定執行順序

有時候我們還要指定執行順序,比如要先執行Init操作,然後才能執行Recv操作。

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

再來回望官方Sample

Sample的結構如下:

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的介面函數的目標檔案,而imp1-4是user.go裡面介面依賴的檔案用來類比"-imports"和"-aux_files"選項。

user_test.go 檔案如同我們的test檔案,是對gomock的調用。

而mock_user是產生mock檔案的目錄。裡面的mock_user.go是通過mockgen產生的。

這裡我們看到user.go有generate的注釋:

//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)    ...}

以及其他函數。

最後來看調用,在user_test.go中首先建立控制器並調用其Finish函數:

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

然後就是如上面我介紹的,這裡分開在幾個不同Test函數中,流程基本上,依次建立mock對象:

mockIndex := mock_user.NewMockIndex(ctrl)

然後調用其mock的方法:

mockIndex.EXPECT().Put("a", 1)boolc := make(chan bool)mockIndex.EXPECT().ConcreteRet().Return(boolc)

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

$ go testPASSok      github.com/golang/mock/sample   0.013s
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.