Go Mock 是 Go 官方开源的一套 Go 测试框架。

安装

我们需要安装 gomock 包 http://github.com/golang/mock/gomock 和代码生成工具 mockgen http://github.com/golang/mock/mockgen

go get github.com/golang/mock/gomock # go mock 代码项目依赖
go get github.com/golang/mock/mockgen # 代码生成工具

基本使用

基本使用步骤

  1. 使用 mockgen 为你想要 mock 的接口生成 mock 代码。

  2. 在你的测试代码中,创建一个 gomock.Controller 实例并把它作为参数传递给 mock 对象的构造函数来创建一个 mock 对象。

  3. 调用 EXPECT() 为你的 mock 对象设置各种期望和返回值。

  4. 调用 mock 控制器的 Finish() 以验证 mock 的期望行为。

接下来做个测试,比如我们想要 mock 的接口是

type ITestInterface interface {
        Get(name string) string
}

生成 mock 代码

首先用 mockgen 生成 mock 代码

mockgen -source=gomock.go -destination=mocks/gomock_mock.go -package=mocks

参数含义如下

  • -destination=mocks/gomock_mock.go:将自动生成的 mock 代码存储到文件mocks/gomock_mock.go中。

  • -package=mocks:将生成的 mock 代码放置到 mocks 包中。

  • -source=gomock.go 为这个文件里的接口生成 mock 代码

  • http://github.com/sgreben/testing-with-gomock/doer:为这个包生成mock代码。

生成的代码如下(我生成用的命令和上面的例子有点区别,不是在 mocks 包里)

https://github.com/hilaily/test_example/blob/master/gomock_mock.go

写一个测试用例

func TestGet(t *testing.T) {
        ctrl := gomock.NewController(t) // 创建一个 gomock Controller 实例
        defer ctrl.Finish() // 用于保证 Get 方法被调用了

        m := NewMockITestInterface(ctrl) // 通过 NewMockXXX 使用生成的代码创建一个接口实现
        m.EXPECT().Get("test").DoAndReturn(func(name string) string {
                return name + "_mock"
        }).Times(1) // 设置 Get 方法被调用后的期望

        r := m.Get("test")
        assert.Equal(t, r, "test_mock")
}

常用 mock 方法

  • Return:模拟返回值

  • Do:在方法被调用是会执行 Do 方法,但是会忽略 Do 的返回值。

  • DoAndReturn:执行 Do 方法并且返回 Do 的返回值。

生成 mock 代码

mockgen 生成代码

mockgen 用两种使用方式,源代码形式和反射形式。

  1. 源码形式
mockgen -source=foo.go [other options]

options 可以使用 imports 和 aux_files。

  1. 反射形式
mocken <import_path> <interface name list>

# mockgen database/sql/driver Conn,Driver

参数说明

  • -source: 一个包含期望被 mock 的接口的文件。

  • -destination: 生成的 mock 代码写入的文件路径。不设置这个值,生成的代码将会打印到终端。

  • -package: 生成的 mock 代码所在的包名。默认是 mock_ 加上文件的名称。

  • -imports: 一组需要在生产的 mock 代码里写入的引入包信息。形式如 foo=bar/baz,bar/baz 是被引入的包, foo 是包的是有标识。

  • -aux_files: 一组额外的需要被引入的文件。比如接口内嵌的其他接口所在的文件,形式如 foo=bar/baz.gobar/baz.go 是原文件。foo 是包名。

  • -build_flags: 传递给 go build 用的参数,只有反射模式可以用。

  • -mock_names: 为生成的 mock 接口定义的名称。形式如Repository=MockSensorRepository,Endpoint=MockSensorEndpoint, Repository 是接口名,MockSensorRepository 是期望在生成的 mock 代码里使用的名字。

  • -self_package: 生成 mock 代码的包路径。比如生成的代码正好引入了放生成代码的包就会出现循环引用,这里加上这个参数,生成的代码发现是自身的包,就不会引入了。

  • -copyright_file: 添加版权信息在生成的文件头。

  • -debug_parser: 打印分析结果。

  • -exec_only: 在反射模式下使用,执行反射程序。

  • -prog_only: 在反射模式下使用,只是生成反射程序,将结果写入到标准输出。

  • -write_package_comment: 写入包文档,默认为 true。

结合 go:generate 使用 GoMock

为了不每次单独执行 mockgen 命令,可以结合 go:generate 自动生成。

在需要生产 mock 代码的接口上加上这个注释,然后每次执行 go generate ./… 就会生成所有接口的 mock 代码。

_//go:generate mockgen -destination=../mocks/mock_doer.go -package=mocks github.com/sgreben/testing-with-gomock/doer Doer
_
type Doer interface {DoSomething(int, string) error
}

Except 高级使用

使用参数匹配

m.EXPECT().Get("test").DoAndReturn(func(name string) string {
                return name + "_mock"
        }).Times(1) // 设置 Get 方法被调用后的期望

这里 Get 方法里的 “test” 表示要求调用 Get 方法是传入的参数是 test。如果不是测试就会失败。

这个等价与 `m.EXPECT().Get(gomock.Eq(“test”)).DoAndReturn()

目前 gomock 默认有以下参数匹配方法

  • gomock.Any():匹配任何类型的任何值

  • gomock.Eq(x):匹配使用反射reflect.DeepEqual与x相等的值,Eq 的可以简单只写 x 在参数里。

  • gomock.Nil():匹配等于nil的值

  • gomock.Not(m):(这里的m是一个Matcher)匹配同m不匹配的值

  • gomock.Not(x):(这里的x不是Matcher)匹配使用反射reflect.DeepEqual与x不相等的值

  • gomock.Len(x): 匹配参数的长度,如果参数类型不是 array,slice,map 等就是不匹配。

断言调用顺序

如果一个方法会被调用多次,每次调用期望不一样,可以用如下的写法

callFirst := mockDoer.EXPECT().DoSomething(1, "first this")
callA := mockDoer.EXPECT().DoSomething(2, "then this").After(callFirst)
callB := mockDoer.EXPECT().DoSomething(2, "or this").After(callFirst)

或者

gomock.InOrder(
    mockDoer.EXPECT().DoSomething(1, "first this"),
    mockDoer.EXPECT().DoSomething(2, "then this"),
    mockDoer.EXPECT().DoSomething(3, "then this"),
    mockDoer.EXPECT().DoSomething(4, "finally this"),
)

控制调用次数

在一次测试执行中,可能需要确定这个方法被调用了几次。

m.EXPECT().Get("test").DoAndReturn(func(name string) string {
                return name + "_mock"
        }).Times(1)

Times 方法用来断言方法被调用的次数。对应还有如下方法。

  • AnyTimes 不限次数

  • MaxTimes 最多执行次数

  • MinTimes 最少执行次数

参考