Source From Here
Preface在 Golang 的官方 Repo ( https://github.com/golang/ ) 中有一個單獨的工程叫 "mock" ( https://github.com/golang/mock ), 雖然 star 不是特別多,但它卻是 Golang 官方放出來的 mock 工具,充這這點我們也需要使用下,雖然並不是官方的就是最好(比如比標準庫http更快的fasthttp)。
不同場景 mock 的對象互相不同,那麼 gomock 主要是 mock 哪些內容呢?
通過 gomock 的輔助工具我們知道,gomock 主要是針對我們 go 代碼中的接口進行 mock 的。
安裝
gomock 主要包含兩個部分:" gomock 庫"和“ 輔助代碼生成工具 mockgen”. 他們都可以通過 go get 來獲取:
假設你已經設置過 $GOPATH/bin 到你的 $PATH 變量中,那麼這裡就可以直接運行 mockgen 命令了,否則需要使用絕對路徑或者相當於 $GOPATH 的目錄。
範例
gomock 的 repo中帶了一個 官方的例子,但是這個例子過於強大和豐富,反而不適合嚐鮮,下面我們寫個我們自己的例子, 一個獲取當前Golang最新版本的例子:
- root@localhost:src# tree mock_ex/
- mock_ex/
- ├── main.go
- ├── mock_ex
- ├── mocks
- │ └── mock_spider.go
- ├── spider
- │ ├── mocks
- │ │ └── mock_spider.go
- │ └── spider.go
- └── utils
- ├── go_version.go
- ├── go_version_test.go
- ├── mock_ex
- └── my_lib.go
- 4 directories, 9 files
- spider.go
- package spider
- type Spider interface {
- GetBody() string
- }
- package utils
- import (
- "mock_ex/spider"
- )
- func GetGoVersion(s spider.Spider) string {
- show()
- 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)
- }
- }
此時 Mock 工具就顯的尤為重要了。這里首先用 gomock 提供的 mockgen 工俱生成要 mock 的接口的實現:
這裡生成了文件:
- # tree mocks/
- mocks/
- └── mock_spider.go
- utils/go_version_test.go
- package utils_test
- import (
- "fmt"
- "mock_ex/mocks"
- "mock_ex/utils"
- "github.com/golang/mock/gomock"
- "testing"
- )
- func TestGetGoVersion(t *testing.T) {
- mockCtl := gomock.NewController(t)
- mockSpider := mocks.NewMockSpider(mockCtl)
- mockSpider.EXPECT().GetBody().Return("go1.8.3")
- goVer := utils.GetGoVersion(mockSpider)
- if goVer != "go1.8.3" {
- t.Error(fmt.Sprintf("Get wrong version %s", goVer))
- }
- }
mockgen 工具
在生成 mock 代碼的時候,我們用到了 mockgen 工具,這個工具是 gomock 提供的用來為要 mock 的接口生成實現的。它可以根據給定的接口,來自動生成代碼。這裡給定接口有兩種方式:接口文件和實現文件
接口文件
如果有接口文件,則可以通過:
一個使用範例如下:
就是將檔案 spider/spider.go 中的介面 Spider (多個介面名稱使用 "," 分開) 做實現並存在 mocks/mock_spider.go 文件中,文件的套件名為 "mocks"。
通過註釋指定 mockgen
如上所述,如果有多個文件,並且分散在不同的位置,那麼我們要生成mock文件的時候,需要對每個文件執行多次mockgen命令(假設套件名稱不相同)。這樣在真正操作起來的時候非常繁瑣,mockgen 還提供了一種通過註釋生成 mock 文件的方式,此時需要藉助 go 的 "go generate" 工具。
在接口文件的註釋裡面增加如下:
- //go:generate mockgen -destination mocks/mock_spider.go -package mocks mock_ex/spider Spider
- type Spider interface {
- GetBody() string
- }
就可以自動生成 mock 文件了。
gomock 的接口使用
在生成了mock實現代碼之後,我們就可以進行正常使用了。這裡假設結合testing進行使用(當然你也可考慮使用GoConvey)。我們就可以
在單元測試代碼裡面首先創建一個mock控制器:
- mockCtl := gomock.NewController(t)
將 * testing.T 傳遞給 gomock 生成一個 "Controller" 對象,該對象控制了整個Mock的過程。在操作完後還需要進行回收,所以一般會在 New 後面 defer 一個 Finish
- defer mockCtl.Finish()
- mockSpider := mocks.NewMockSpider(mockCtl)
- mockSpider.EXPECT().GetBody().Return("go1.8.3")
- 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
指定返回值
如我們的例子,調用Call的Return函數,可以指定接口的返回值:
- mockSpider.EXPECT().GetBody().Return("go1.8.3")
指定執行次數
有時候我們需要指定函數執行多次,比如接受網絡請求的函數,計算其執行了多少次。
- mockSpider.EXPECT().Recv().Return(nil).Times(3)
指定執行順序
有時候我們還要指定執行順序,比如要先執行Init操作,然後才能執行Recv操作。
- initCall := mockSpider.EXPECT().Init()
- mockSpider.EXPECT().Recv().After(initCall)
最後你可以如下執行測試:
Supplement
* How to mock in your Go tests - Golang Tutorial (Youtube)
* Medium - Go Test Your Code: An introduction to testing in Go
沒有留言:
張貼留言