【go】gomock的使用方法及源码解析

gomock是官方提供的mock框架,用于解决单元测试中遇到的外部依赖问题,并且还有mockgen工具用来辅助生成相关的mock代码。

gomock的原理是:针对 interface 生成对应的Mock代码文件,其中包含了一个实现该接口的结构,并提供了操作该结构行为的方法。使用该结构代替真实的依赖,可以控制下游按我们想要的方式进行某些操作和返回结果,以此达到解除外部依赖的目的。

1. 安装gomock和mockgen

# 安装gomock和mockgen
go get -u github.com/golang/mock/gomock
go install github.com/golang/mock/mockgen

安装成功后可以在命令行运行mockgen命令查看是否安装成功。

2. 具体使用方式

# 演示用例的目录结构
.
├── dao
│  └── dao.go
├── go.mod
├── go.sum
├── mock_dao
│  └── mock_dao.go
└── service
  ├── service.go
  └── service_test.go

演示用例的目录结构如上,去除UT相关的内容,该项目最基本的结构包含dao和service。其中dao代表的是数据层,其中定义了一个Search接口,具体实现应该是要去mysql中查找数据;service代表的是业务逻辑层,会调用dao层的接口进一步实现具体业务。

这是一个比较典型的依赖数据库的例子,可以使用gomock对dao中的接口进行mock。

//dao和service中具体的代码
// dao.dao.go
type Search interface{
   GetNameByID(id int64) (string, error)
}

// service.service.go
type FindService struct {
   DB dao.Search
}

func (f *FindService) FindUser(id int64) string {
   name, _ := f.DB.GetNameByID(id)
   return name
}
  • 首先,运行以下命令,在mock_dao目录下生成mock_dao.go文件。
mockgen -source=dao.go -destination=../mock_dao/mock_dao.go -package=mock_dao
  • 其次,编写service_test.go文件,具体如下。
package service

import (
   "github.com/golang/mock/gomock"
   "gomock/mock_dao"
   "testing"
)

func TestFindUser(t *testing.T) {
    // 首先生成一个controller对象,然后注册到defer中
   ctl := gomock.NewController(t)
   defer ctl.Finish()
    
    // 然后生成一个mockSearch对象,用来替代Search接口
   mockSearch := mock_dao.NewMockSearch(ctl)
   service := FindService{DB: mockSearch}
   // 操纵mockSearch对象的行为
   mockSearch.EXPECT().GetNameByID(int64(10)).Return("liangPin", nil)
   mockSearch.EXPECT().GetNameByID(int64(20)).Return("zhangHeng", nil)
    
    // 测试过程
   if name1 := service.FindUser(10); name1 != "liangPin"{
      t.Error(name1)
   }

   if name2 := service.FindUser(20); name2 != "zhangHeng"{
      t.Error(name2)
   }
}

3. gomock源代码解析

  • Controller对象—mock对象的核心
    Controller控制着mock对象的作用域和生命周期,我们操纵mock对象的行为也是将其加入到expectedCalls中,所以Controller是非常核心的一个对象。每个测试都应该创建一个controller对象将其注册到defer中。通常通过NewController函数来创建controller,1.14版本以后通过NewController创建的controller可以不必显式地调用ctrl.Finish()。
// Controller对象的定义
type Controller struct {
    // 传入的test对象
   T             TestHelper
   // 保证多协程安全的
   mu            sync.Mutex
   // mock对象的期望行为
   expectedCalls *callSet
   finished      bool
}

// 使用NewController来得到Controller
func NewController(t TestReporter) *Controller {
   h, ok := t.(TestHelper)
   if !ok {
      h = &nopTestHelper{t}
   }
   ctrl := &Controller{
      T:             h,
      expectedCalls: newCallSet(),
   }
   if c, ok := isCleanuper(ctrl.T); ok {
      c.Cleanup(func() {
         ctrl.T.Helper()
         ctrl.finish(true, nil)
      })
   }

   return ctrl
}
  • Call对象—预期的mock对象行为
    Call对象是我们预期的mock对象的行为。UT代码中我们预期的行为会创建一个Call对象传入到Controller中,测试过程调用时会去Controller的excepedCalls中匹配找到对应的Call对象,并返回结果。
// Call对象的定义
type Call struct {
   t TestHelper // for triggering test failures on invalid call setup

   receiver   interface{}  // the receiver of the method call
   method     string       // the name of the method
   methodType reflect.Type // the type of the method
   args       []Matcher    // the args
   origin     string       // file and line number of call setup

   preReqs []*Call // prerequisite calls

   // Expectations
   minCalls, maxCalls int

   numCalls int // actual number made

   // actions are called when this Call is called. Each action gets the args and
   // can set the return values by returning a non-nil slice. Actions run in the
   // order they are created.
   actions []func([]interface{}) []interface{}
}

4. gomock关系图

在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值