go-mock初探
go mock 用于虚拟接口。一般只要提供interface,无论有没有实现该interface的类型,gomock会根据接口的输入输出提供一个实现了该interface的mock实例,同时在自定义mock实例的输入输出前,可以根据期望过滤输入,产生想要的输出或调用某些函数。说的很晦涩,看点代码吧。
目录结构:
trymock/
/db
|--db.go
|--db_test.go
/mock
|--db_mock.go // generated by mockgen
//db.go
type DB interface {
Get(key string) (int, error)
}
func GetFromDB(db DB, key string) int {
if value, err := db.Get(key); err == nil {
return value
}
return -1
}
gomock中,DB实例只能通过传参传入生成mock文件
mockgen -source=db.go -destination=db_mock.go -package=mock
gomock生成的db_mock.go如下
//db_mock.go
// Code generated by MockGen. DO NOT EDIT.
// Source: db.go
// Package mock_db is a generated GoMock package.
package mock
import (
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockDB is a mock of DB interface
type MockDB struct {
ctrl *gomock.Controller
recorder *MockDBMockRecorder
}
// MockDBMockRecorder is the mock recorder for MockDB
type MockDBMockRecorder struct {
mock *MockDB
}
// NewMockDB creates a new mock instance
func NewMockDB(ctrl *gomock.Controller) *MockDB {
mock := &MockDB{ctrl: ctrl}
mock.recorder = &MockDBMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockDB) EXPECT() *MockDBMockRecorder {
return m.recorder
}
// Get mocks base method
func (m *MockDB) Get(key string) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Get", key)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Get indicates an expected call of Get
func (mr *MockDBMockRecorder) Get(key interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockDB)(nil).Get), key)
}
测试
// db_test.go
func TestGetFromDB(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish() // 断言 DB.Get() 方法是否被调用
m := mock.NewMockDB(ctrl)
m.EXPECT().Get(gomock.Eq("Tom")).Return(100, errors.New("not exist"))
if v := GetFromDB(m, "Tom"); v != -1 {
t.Fatal("expected -1, but got", v)
}
}
go mock有个限制,生成的mock_db实例和真正的db实例是两个不一样的实例(废话),因此在写测试时需要使用mock实例,而开发/生产环境中需要用真正的db实例,导致在设计GetFromDB一类的方法时,需要将db实例作为参数传入方法中,这样有碍结构设计的灵活性。 当然有方法能越过这层阻碍重点
//db.go
func GetFromDB(key string){
db := db.getDBClient()
db.Get(key)
...
}
var mockDBClient DB
func getDBClient(){
if mockDBClient != nil{
return mockDBClient
}
// 懒加载获得真实的DB连接并返回
return actual.GetDBClient()
}
func newMockDBClient(t *testing.T){
gomock.NewController(t)
m := mock.NewMockDB(ctrl)
mockDBClient = m
}
以上将获取真实DBClient之前加上了一层判断,判断是否创建了mockDB,创建了则返回mockDB, 没创建就懒加载DB连接。 gomock另一个缺陷是他不支持自动生成响应,也就是说不能配置mock server,或许gomock是专为测试设计的,server这一块用于前端调用模拟,一般也是给个接口就能创建,应该有其他技术覆盖了这一块。