助力软件开发企业降本增效 PHP / java源码系统,只需一次付费,代码终身使用! 广告
[TOC] ## <span style="font-size:15px">**一、说明**</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;GoMock是由Golang官方开发维护的测试框架,实现了较为完整的基于interface的Mock功能,能够与Golang内置的testing包良好集成,也能用于其它的测试环境中。GoMock测试框架包含了GoMock包和mockgen工具两部分,其中GoMock包完成对桩对象生命周期的管理,mockgen工具用来生成interface对应的Mock类源文件。 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;GoMock官网:`https://github.com/golang/mock` &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;GoMock文档:`go doc github.com/golang/mock/gomock` ## <span style="font-size:15px">**二、安装**</span> ``` go get -u github.com/golang/mock/gomock go get -u github.com/golang/mock/mockgen ``` ## <span style="font-size:15px">**三、mockgen工具**</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mockgen有两种操作模式:源文件模式和反射模式。 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;源文件模式通过一个包含interface定义的源文件生成mock类文件,通过-source标识开启,-imports和-aux_files标识在源文件模式下是有用的。mockgen源文件模式的命令格式如下: ``` mockgen -source=xxxx.go [other options] ``` &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;反射模式通过构建一个程序用反射理解接口生成一个mock类文件,通过两个非标志参数开启:导入路径和用逗号分隔的符号列表(多个interface)。mockgen反射模式的命令格式如下: ``` // 第一个参数 packagepath 是基于GOPATH的相对路径 // 第二个参数可以为多个interface,并且interface之间只能用逗号分隔,不能有空格。 mockgen packagepath Interface1,Interface2... ``` &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mockgen工作模式适用场景如下: * 对于简单场景,只需使用-source选项。 * 对于复杂场景,如一个源文件定义了多个interface而只想对部分interface进行mock,或者interface存在嵌套,则需要使用反射模式。 ## <span style="font-size:15px">**四、GoMock常用方法**</span> ### <span style="font-size:15px">**1、gomock.Call**</span> ``` 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{} } ``` | 函数 | 说明 | | --- | --- | | func (c \*Call) After(preReq \*Call) \*Call | After声明调用在preReq完成后执行 | | func (c *Call) AnyTimes() *Call | 允许调用0次或多次 | | func (c *Call) MinTimes(n int) *Call | 设置最小的调用次数为n次 | | func (c *Call) MaxTimes(n int) *Call | 设置最大的调用次数为n次 | | func (c *Call) DoAndReturn(f interface{}) *Call | 声明在匹配时要运行的操作,并设置mock函数所需要的返回值 | | func (c *Call) Do(f interface{}) *Call | 声明在匹配时要运行的操作 | | func (c *Call) Return(rets ...interface{}) *Call | 设置mock函数调用要返回值 | | func (c *Call) Times(n int) *Call | 设置调用的次数为n次 | | func (c *Call) SetArg(n int, value interface{}) *Call | 设置参数 | ### <span style="font-size:15px">**2、gomock.Controller**</span> | 函数 | 说明 | | --- | --- | | func NewController(t TestReporter) \*Controller | 返回一个实例化控制器 | | func (ctrl \*Controller) Call(receiver interface{}, method string, args ...interface{}) \[\]interface{} | 模拟 Mock 对象的方法调用,并返回预先设定的结果 | | func (ctrl *Controller) Finish() | 检查是否调用了所有预期调用的方法 | | func (ctrl *Controller) RecordCall(receiver interface{}, method string, args ...interface{}) *Call| 用于记录 Mock 对象的方法调用。当你希望在测试中验证某个方法是否被调用,你可以使用 RecordCall 函数来记录这个调用,以便后续进行断言 | | func (ctrl *Controller) RecordCallWithMethodType(receiver interface{}, method string, methodType reflect.Type, args ...interface{}) *Call | 与 RecordCall 类似,不同之处在于它还需要传入方法的类型信息。这个函数通常用于记录接口类型的方法调用,以便在后续的断言中进行类型匹配 | ## <span style="font-size:15px">**五、使用 mock 对象进行打桩测试**</span> ### <span style="font-size:15px">**1、导入 mock 相关的包**</span> ``` import (     . "github.com/golang/mock/gomock" ) ``` ### <span style="font-size:15px">**2、生成 mock 类文件**</span> calculator.go文件内容如下 ``` package util type Calculator interface { Add(a, b int) int Subtract(a, b int) int } type MyCalculator struct{} func (c *MyCalculator) Add(a, b int) int { return a + b } func (c *MyCalculator) Subtract(a, b int) int { return a - b } ``` 执行mockgen命令生成mock类文件 ``` mockgen -source=calculator.go -destination=calculator_mock.go -package=util ``` ``` // Code generated by MockGen. DO NOT EDIT. // Source: calculator.go // Package util is a generated GoMock package. package util import ( reflect "reflect" gomock "github.com/golang/mock/gomock" ) // MockCalculator is a mock of Calculator interface. type MockCalculator struct { ctrl *gomock.Controller recorder *MockCalculatorMockRecorder } // MockCalculatorMockRecorder is the mock recorder for MockCalculator. type MockCalculatorMockRecorder struct { mock *MockCalculator } // NewMockCalculator creates a new mock instance. func NewMockCalculator(ctrl *gomock.Controller) *MockCalculator { mock := &MockCalculator{ctrl: ctrl} mock.recorder = &MockCalculatorMockRecorder{mock} return mock } // EXPECT returns an object that allows the caller to indicate expected use. func (m *MockCalculator) EXPECT() *MockCalculatorMockRecorder { return m.recorder } // Add mocks base method. func (m *MockCalculator) Add(a, b int) int { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Add", a, b) ret0, _ := ret[0].(int) return ret0 } // Add indicates an expected call of Add. func (mr *MockCalculatorMockRecorder) Add(a, b interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockCalculator)(nil).Add), a, b) } // Subtract mocks base method. func (m *MockCalculator) Subtract(a, b int) int { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "Subtract", a, b) ret0, _ := ret[0].(int) return ret0 } // Subtract indicates an expected call of Subtract. func (mr *MockCalculatorMockRecorder) Subtract(a, b interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Subtract", reflect.TypeOf((*MockCalculator)(nil).Subtract), a, b) } ``` ### <span style="font-size:15px">**3、mock控制器定义**</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mock 控制器通过 NewController 接口生成,是 mock 生态系统的顶层控制,它定义了 mock 对象的作用域和生命周期,以及它们的期望。多个协程同时调用控制器的方法是安全的。当用例结束后,控制器会检查所有剩余期望的调用是否满足条件。 ``` ctrl := NewController(t) defer ctrl.Finish() ``` &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mock 对象创建时需要注入控制器,如果有多个 mock 对象则注入同一个控制器,如下所示: ``` ctl := gomock.NewController(t) defer ctl.Finish() mockCalculator := NewMockCalculator(ctl) ``` ### <span style="font-size:15px">**4、mock 对象的行为注入**</span> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;对于 mock 对象的行为注入,控制器是通过 map 来维护的,一个方法对应 map 的一项。因为一个方法在一个用例中可能调用多次,所以 map 的值类型是数组切片。当 mock 对象进行行为注入时,控制器会将行为 Add。当该方法被调用时,控制器会将该行为 Remove。 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;通过生成的mock类提供的EXPECT方法进行mock方法、参数和期望返回行为进行注入 ``` mockCalculator.EXPECT().Add(1, 2).Return(4).MaxTimes(1) ``` ### <span style="font-size:15px">**5、单测完整示例**</span> ``` package util import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/assert" "testing" ) func TestAddByMock(t *testing.T) { ctl := gomock.NewController(t) defer ctl.Finish() mockCalculator := NewMockCalculator(ctl) mockCalculator.EXPECT().Add(1, 2).Return(4).MaxTimes(1) m := &MyCalculator{} if !assert.EqualValues(t, 3, m.Add(1, 2)) { // mock设置方法返回值为4,因此断言失败 t.Fail() } } ```