单元测试一般不允许有任何外部依赖(文件依赖/网络依赖/数据库依赖等),不会(or不能)在测试代码中去真正 连接数据库/调用api等。这些外部依赖在执行测试时需被模拟(mock/stub)。在测试时,使用模拟的对象来模拟真实依赖下的各种行为。如何运用mock/stub来模拟系统真实行为,算是单元测试道路上的一只拦路虎
Mock(模拟)和Stub(桩)是在测试过程中,模拟外部依赖行为的两种常用的技术手段。 通过Mock和Stub我们不仅可以让测试环境没有外部依赖,而且还可以模拟一些异常行为,如数据库服务不可用,没有文件的访问权限等等。
–搞定Go单元测试(一)——基础原理 之利用mock/stub 技术破除外部依赖
单元测试中只是针对单个函数的测试,关注其内部的逻辑,对于网络/数据库访问等,需要通过相应的手段进行 mock
gomock基础用法
gomock 是Go官方开源的golang测试框架:“GoMock is a mocking framework for the Go programming language”。
通过mockgen命令生成包含mock对象的.go文件,其生成的mock对象具备mock+stub的强大功能
安装:
go install github.com/golang/mock/mockgen@v1.6.0
mockgen has two modes of operation: source and reflect.
Source mode generates mock interfaces from a source file.
It is enabled by using the -source flag. Other flags that
may be useful in this mode are -imports and -aux_files.
Example:
mockgen -source=foo.go [other options]
Reflect mode generates mock interfaces by building a program
that uses reflection to understand interfaces. It is enabled
by passing two non-flag arguments: an import path, and a
comma-separated list of symbols.
Example:
mockgen database/sql/driver Conn,Driver
-aux_files string
(source mode) Comma-separated pkg=path pairs of auxiliary Go source files.
-build_flags string
(reflect mode) Additional flags for go build.
-copyright_file string
Copyright file used to add copyright header
-debug_parser
Print out parser results only.
-destination string
Output file; defaults to stdout.
-exec_only string
(reflect mode) If set, execute this reflection program.
-imports string
(source mode) Comma-separated name=path pairs of explicit imports to use.
-mock_names string
Comma-separated interfaceName=mockName pairs of explicit mock names to use. Mock names default to ‘Mock’+ interfaceName suffix.
-package string
Package of the generated code; defaults to the package of the input with a ‘mock_’ prefix.
-prog_only
(reflect mode) Only generate the reflection program; write it to stdout and exit.
-self_package string
The full package import path for the generated code. The purpose of this flag is to prevent import cycles in the generated code by trying to include its own package. This can happen if the mock’s package is set to one of its inputs (usually the main one) and the output is stdio so mockgen cannot detect the final output package. Setting this flag will then tell mockgen which import to exclude.
-source string
(source mode) Input Go source file; enables source mode.
-version
Print version.
-write_package_comment
Writes package documentation comment (godoc) if true. (default true)
2017/02/15 10:56:34 Expected exactly two arguments
mockgen 有两种操作模式:source 和 reflect。
源模式从源文件生成模拟接口。
它通过使用 -source 标志启用。其他标志
在这种模式下可能有用的是 -imports 和 -aux_files。
例子:
mockgen -source=foo.go [其他选项]
Reflect模式通过构建程序生成mock接口
使用反射来理解接口。它已启用
通过传递两个非标志参数:一个导入路径和一个
逗号分隔的符号列表。
例子:
mockgen 数据库/sql/driver Conn,Driver
aux_files 字符串
(源模式)逗号分隔的 pkg=辅助 Go 源文件的路径对。build_flags 字符串
(反射模式)go build 的附加标志。copyright_file 字符串
用于添加版权标题的版权文件debug_parser
仅打印解析器结果。目标字符串
输出文件;默认为标准输出。exec_only 字符串
(反射模式)如果设置,则执行此反射程序。导入字符串
(源模式)逗号分隔的名称=要使用的显式导入的路径对。mock_names 字符串
逗号分隔的 interfaceName=mockName 要使用的显式模拟名称对。模拟名称默认为 ‘Mock’+ interfaceName 后缀。包字符串
生成的代码包;默认为带有“mock_”前缀的输入包。prog_only
(反射模式)只生成反射程序;将其写入标准输出并退出。self_package 字符串
生成代码的完整包导入路径。此标志的目的是通过尝试包含自己的包来防止生成代码中的导入循环。如果将 mock 的包设置为其输入之一(通常是主包)并且输出是 stdio,则可能会发生这种情况,因此 mockgen 无法检测到最终输出包。设置此标志将告诉 mockgen 要排除哪个导入。源字符串
(源模式)输入Go源文件;启用源模式。版本
印刷版。write_package_comment
如果为真,则写入包文档注释 (godoc)。 (默认为真)
如对于user.go:
1 | package user |
执行mockgen -source user.go -destination user_mock.go -package user,
source:指定需要模拟(mock)的接口文件
destination:设置生成的mock文件名。若不设置则打印到标准输出中
package:设置mock文件的报名。若不设置,则为 mock_前缀加文件名
之后会在同目录下,生成一个user_mock.go:
1 | // Code generated by MockGen. DO NOT EDIT. |
在该目录下新建user_test.go文件,来写测试函数
设置函数的返回值
user_test.go:
1 | package user |
基本使用:
首先要把需要 mock 的地方,写成接口(mock 作用的是接口,因此将依赖抽象为接口,而不是直接依赖具体的类)
执行 mockgen 生成xx_mock.go代码
在单元测试中(xx_test.go),先执行 ctrl := gomock.NewController(t), 然后 defer ctrl.Finish()
使用 NewMockXXX 生成 mock 对象
调用 EXPECT() 方法,开始设置断言,对于参数,如果输入具体类型,则执行时就要传入相应类型; 如输入 gomock.Any(),则会忽略参数类型(还有 gomock.Eq, gomock.Len, gomock.All等..)
可以通过 Return 设置返回结果
可以通过 Times 设置调用次数( 另外还有 AnyTimes 不限次数,MaxTimes 最多执行次数,MinTimes 最少执行次数)
可以通过 After或InOrder 设置执行顺序
调用次数检测
user_test.go:
1 | func TestTimes(t *testing.T) { |
调顺序数检测
user_test.go:
1 | func TestOrder(t *testing.T) { |
如果不按设置的顺序调用,则会报错:
doesn’t match the argument at index 0.
doesn’t have a prerequisite call satisfied:
内容参考自
更多使用技巧,参考
gomock其他用法
除去使用mockgen -source=xxx.go,指明从哪个源文件生成,还可以通过mockgen database/sql/driver Conn,Driver指明mock的目标是哪个包的哪些接口
另外,还可以 以 go:generate 的方式写,然后执行 go generate
generate.go:
1 | package user |
执行go generate后会生成mock_generate.go:
1 | // Code generated by MockGen. DO NOT EDIT. |
编写可mock的代码
mock 作用的是接口,因此将依赖抽象为接口,而不是直接依赖具体的类。
不直接依赖实例,而是使用依赖注入降低耦合。
如果这样写,是无法mock的
1 | func GetFromDB(key string) int { |
而如果将接口 db DB 通过参数传递到 GetFromDB(),那么就可以轻而易举地传入 Mock 对象
1 | // db.go |
参考自:
原文链接: https://dashen.tech/2016/01/23/Go实用mock工具/
版权声明: 转载请注明出处.