## 使用Mockgen包
前面的小節我們使用了自行模擬的方式。當你需要面對很多的接口時,這么干會變得極為麻煩且極易發生錯誤。這是自動化測試的意義所在。本節我們使用 github.com/golang/mock/gomock ,該庫提供了一組模擬對象,可以與接口測試結合使用。
### 實踐
1.獲取第三方庫:
```
go get github.com/golang/mock/
```
2. 建立 interface.go:
```
package mockgen
type GetSetter interface {
Set(key, val string) error
Get(key string) (string, error)
}
```
3. 運行命令行建立 mocks.go:
```
mockgen -destination internal/mocks.go -package internal
github.com/agtorre/go-cookbook/chapter8/mockgen GetSetter
```
```
// Automatically generated by MockGen. DO NOT EDIT!
// Source: github.com/agtorre/go-cookbook/chapter8/mockgen (interfaces: GetSetter)
package internal
import (
gomock "github.com/golang/mock/gomock"
)
// Mock of GetSetter interface
type MockGetSetter struct {
ctrl *gomock.Controller
recorder *_MockGetSetterRecorder
}
// Recorder for MockGetSetter (not exported)
type _MockGetSetterRecorder struct {
mock *MockGetSetter
}
func NewMockGetSetter(ctrl *gomock.Controller) *MockGetSetter {
mock := &MockGetSetter{ctrl: ctrl}
mock.recorder = &_MockGetSetterRecorder{mock}
return mock
}
func (_m *MockGetSetter) EXPECT() *_MockGetSetterRecorder {
return _m.recorder
}
func (_m *MockGetSetter) Get(_param0 string) (string, error) {
ret := _m.ctrl.Call(_m, "Get", _param0)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
func (_mr *_MockGetSetterRecorder) Get(arg0 interface{}) *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "Get", arg0)
}
func (_m *MockGetSetter) Set(_param0 string, _param1 string) error {
ret := _m.ctrl.Call(_m, "Set", _param0, _param1)
ret0, _ := ret[0].(error)
return ret0
}
func (_mr *_MockGetSetterRecorder) Set(arg0, arg1 interface{}) *gomock.Call {
return _mr.mock.ctrl.RecordCall(_mr.mock, "Set", arg0, arg1)
}
```
4. 建立 exec.go:
```
package mockgen
// Controller 這個結構體演示了一種初始化接口的方式
type Controller struct {
GetSetter
}
// GetThenSet 檢查值是否已設置。如果沒有設置就將其設置
func (c *Controller) GetThenSet(key, value string) error {
val, err := c.Get(key)
if err != nil {
return err
}
if val != value {
return c.Set(key, value)
}
return nil
}
```
5. 建立 interface_test.go:
```
package mockgen
import (
"errors"
"testing"
"github.com/agtorre/go-cookbook/chapter8/mockgen/internal"
"github.com/golang/mock/gomock"
)
func TestExample(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockGetSetter := internal.NewMockGetSetter(ctrl)
var k string
mockGetSetter.EXPECT().Get("we can put anything here!").Do(func(key string) {
k = key
}).Return("", nil)
customError := errors.New("failed this time")
mockGetSetter.EXPECT().Get(gomock.Any()).Return("", customError)
if _, err := mockGetSetter.Get("we can put anything here!"); err != nil {
t.Errorf("got %#v; want %#v", err, nil)
}
if k != "we can put anything here!" {
t.Errorf("bad key")
}
if _, err := mockGetSetter.Get("key"); err == nil {
t.Errorf("got %#v; want %#v", err, customError)
}
}
```
6. 建立 exec_test.go:
```
package mockgen
import (
"errors"
"testing"
"github.com/agtorre/go-cookbook/chapter8/mockgen/internal"
"github.com/golang/mock/gomock"
)
func TestController_Set(t *testing.T) {
tests := []struct {
name string
getReturnVal string
getReturnErr error
setReturnErr error
wantErr bool
}{
{"get error", "value", errors.New("failed"), nil, true},
{"value match", "value", nil, nil, false},
{"no errors", "not set", nil, nil, false},
{"set error", "not set", nil, errors.New("failed"), true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockGetSetter := internal.NewMockGetSetter(ctrl)
mockGetSetter.EXPECT().Get("key").AnyTimes().Return(tt.getReturnVal, tt.getReturnErr)
mockGetSetter.EXPECT().Set("key", gomock.Any()).AnyTimes().Return(tt.setReturnErr)
c := &Controller{
GetSetter: mockGetSetter,
}
if err := c.GetThenSet("key", "value"); (err != nil) != tt.wantErr {
t.Errorf("Controller.Set() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
```
### 說明
生成的模擬對象允許測試預定的參數,調用函數的次數以及返回的內容,并且允許我們設置其他工作流程。interface_test.go文件展示了在線調用它們時使用模擬對象的一些示例。 通常,測試看起來更像exec_test.go,我們希望攔截由實際代碼執行的接口函數調用,并在測試時更改它們的行為。
exec_test.go文件還展示了如何在表驅動的測試環境中使用模擬對象。Any()函數意味著模擬函數可以被調用零次或多次,這對于代碼提前終止的情況非常有用。
示例演示的最后一個技巧是將模擬對象粘貼到內部包中。當需要模擬在自己之外的包中聲明的函數時,這非常有用。 這允許在非_test.go文件中定義這些方法,但不允許將它們導出到庫的情況。通常,使用與當前編寫的測試相同的包名稱將模擬對象粘貼到_test.go文件中更容易。
* * * *
學識淺薄,錯誤在所難免。歡迎在群中就本書提出修改意見,以饗后來者,長風拜謝。
Golang中國(211938256)
beego實戰(258969317)
Go實踐(386056972)
- 前言
- 第一章 I/O和文件系統
- 常見 I/O 接口
- 使用bytes和strings包
- 操作文件夾和文件
- 使用CSV格式化數據
- 操作臨時文件
- 使用 text/template和HTML/templates包
- 第二章 命令行工具
- 解析命令行flag標識
- 解析命令行參數
- 讀取和設置環境變量
- 操作TOML,YAML和JSON配置文件
- 操做Unix系統下的pipe管道
- 處理信號量
- ANSI命令行著色
- 第三章 數據類型轉換和解析
- 數據類型和接口轉換
- 使用math包和math/big包處理數字類型
- 貨幣轉換和float64注意事項
- 使用指針和SQL Null類型進行編碼和解碼
- 對Go數據編碼和解碼
- Go中的結構體標簽和反射
- 通過閉包實現集合操作
- 第四章 錯誤處理
- 錯誤接口
- 使用第三方errors包
- 使用log包記錄錯誤
- 結構化日志記錄
- 使用context包進行日志記錄
- 使用包級全局變量
- 處理恐慌
- 第五章 數據存儲
- 使用database/sql包操作MySQL
- 執行數據庫事務接口
- SQL的連接池速率限制和超時
- 操作Redis
- 操作MongoDB
- 創建存儲接口以實現數據可移植性
- 第六章 Web客戶端和APIs
- 使用http.Client
- 調用REST API
- 并發操作客戶端請求
- 使用OAuth2
- 實現OAuth2令牌存儲接口
- 封裝http請求客戶端
- 理解GRPC的使用
- 第七章 網絡服務
- 處理Web請求
- 使用閉包進行狀態處理
- 請求參數驗證
- 內容渲染
- 使用中間件
- 構建反向代理
- 將GRPC導出為JSON API
- 第八章 測試
- 使用標準庫進行模擬
- 使用Mockgen包
- 使用表驅動測試
- 使用第三方測試工具
- 模糊測試
- 行為驅動測試
- 第九章 并發和并行
- 第十章 分布式系統
- 第十一章 響應式編程和數據流
- 第十二章 無服務器編程
- 第十三章 性能改進