gomock 使用
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 # 代码生成工具
基本使用
基本使用步骤
-
使用 mockgen 为你想要 mock 的接口生成 mock 代码。
-
在你的测试代码中,创建一个 gomock.Controller 实例并把它作为参数传递给 mock 对象的构造函数来创建一个 mock 对象。
-
调用 EXPECT() 为你的 mock 对象设置各种期望和返回值。
-
调用 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 用两种使用方式,源代码形式和反射形式。
- 源码形式
mockgen -source=foo.go [other options]
options 可以使用 imports 和 aux_files。
- 反射形式
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.go
,bar/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
最少执行次数