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这一块用于前端调用模拟,一般也是给个接口就能创建,应该有其他技术覆盖了这一块。


golang

293 Words

2021-01-21 17:41 +0000