## 使用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包
- 使用表驱动测试
- 使用第三方测试工具
- 模糊测试
- 行为驱动测试
- 第九章 并发和并行
- 第十章 分布式系统
- 第十一章 响应式编程和数据流
- 第十二章 无服务器编程
- 第十三章 性能改进