go test 测试用例那些事(二) mock
关于go
的单元测试,之前有写过一篇帖子go test测试用例那些事,但是没有说go官方的库mock,很有必要单独说一下这个库,和他的实现原理。
mock
主要的功能是对接口的模拟,需要在写代码的时候定义抽象很多接口,有时为了能方便go test
可能会多写一些冗余代码,但这些工作会让你的单元测试更灵活。特别是逻辑比较复杂的时候,上层要调用其他层的方法进行单元测试,会让单元测试越写越麻烦,越写越复杂,这也是很多人不喜欢写单元测试的原因。使用mock
模拟底层的接口,能让你只关注上层需要测试的逻辑,而不用为了测试一个功能,写一堆调用的底层的相关的测试逻辑。
使用
mockgen
就是mock的可执行命令。使用也很简单
mockgen -source=src.go [other options]
比如我们有一个接口
package d1
type User interface {
Name() string
SetAge(age int) bool
V(idx int, name string) (string, error)
}
执行mockgen
命令
mockgen -source=user.go
这里只指写了-source
会直接在控制台输出。也可以指定输出目录和输出包名称
mockgen -source=user.go -destination ./dao/u_mock.go -package mock_data
或者使用 go generate
来生成,需要在包名字上面加上下面这句。
//go:generate mockgen -destination ./dao/u_mock.go -package mock_data -source user.go
然后执行go generate ./...
和上面是一样的效果。
虽然go generate
很方便,但如果目标文件或者包名字有变动里,就需要修改所有文件。不如用命令来的快,直接写一个Makefile
进行指处理,下面是一个小例子,实现mock
目录dao
和service
下的go
文件,去掉了*_test.go
和一些指定的文件。
DAO_DIR=./dao
DAO_MOCK_DIR=$(DAO_DIR)/mock_dao
DAO_FILES=$(shell find $(DAO_DIR) -not -path "$(DAO_MOCK_DIR)/*" -type f -name "*.go" -not -name "*_test.go" -not -name "dao_init.go" -not -name "dao.go")
SERVICE_DIR=./service
SERVICE_MOCK_DIR=$(SERVICE_DIR)/mock_srv
SERVICE_FILES=$(shell find $(SERVICE_DIR) -not -path "$(SERVICE_MOCK_DIR)/*" -type f -name "*.go" -not -name "*_test.go" -not -name "service.go" -not -name "system_filter.go")
define gen-mock-file
@for f in $(3); do \
eval t=`echo $$f | sed 's#$(1)#$(2)#'` ; \
mockgen -source=$$f -destination=$$t ; \
done
endef
.PHONY: gen-mock-dao
gen-mock-dao:
$(call gen-mock-file,$(DAO_DIR),$(DAO_MOCK_DIR),$(DAO_FILES))
.PHONY: gen-mock-service
gen-mock-service:
$(call gen-mock-file,$(SERVICE_DIR),$(SERVICE_MOCK_DIR),$(SERVICE_FILES))
gen-mock-all:
@echo begin gen code
@$(MAKE) gen-mock-dao
@$(MAKE) gen-mock-service
@echo done
使用
使用也很简单直接调用EXPECT()
然后给具体的方法指定参数,参数可以是任意的如下面的V
方法的第一个参数gomock.Any()
,参数可以是具体的值比如下面的2
,然后调用Return
指写返回指定的值。最后指定这个方法调用多少次,下面是调用的AnyTimes()
,当然也可以调用MinTimes
或者MaxTimes
指定次数
func TestUser1(t *testing.T) {
mockUser := mock_data.NewMockUser(gomock.NewController(t))
mockUser.EXPECT().V(gomock.Any(), "2").Return("a", nil).AnyTimes()
var u User = mockUser
a, err := u.V(1, "2")
t.Log(a, err)
}
Return
如果不调用会返回参数的默认值,上面的方法不如果不调用Return
会返回 "", nil
。
对于简单的逻辑可以直接调用Return
方法,返回指定的结果。但实际情况可能需要进行一些逻辑处理,返回动态的数据,可能通过DoAndReturn
mockUser := mock_data.NewMockUser(gomock.NewController(t))
mockUser.EXPECT().V(1, "2").DoAndReturn(func(idx int, n string) (string, error) {
t.Log(idx, " ", n)
return "1", nil
})
可以有多个DoAndReturn
,但只有最后一个的 return
会生效。
如果只想对传入的参数进行逻辑处理,可以调用Do
方法。
mockUser.EXPECT().V(1, "2").Do(func(id int, name string) {
t.Log(id, " ", name)
}).Do(func(id int, name string) {
t.Log("do2 ", id)
}).Return("a", nil)
当然根据自己的需要可以有多个Do
方法的处理。
mock
实现原理
实现的原理是根据go
强大的抽象语法树
实现的,说一个题外话除了mock库,还有一个依赖注入的库wire也是依赖抽象语法树实现的。
抽象语法树分析-source
传入的文件,把提取文件内所有的import
和interface
,然后遍历所有的接口方法,判断参数属于哪个import
,组织成结构,生成模拟结构实现提取的接口。
看一下生成的两个struct
// MockUser is a mock of User interface
type MockUser struct {
ctrl *gomock.Controller
recorder *MockUserMockRecorder
}
// MockUserMockRecorder is the mock recorder for MockUser
type MockUserMockRecorder struct {
mock *MockUser
}
上面的MockUser
具体实现了我们的接口User
。下面的MockUserMockRecorder
才是重头戏,保存着我们传入的的指定参数传Do
方法Return
方法等。
// NewMockUser creates a new mock instance
func NewMockUser(ctrl *gomock.Controller) *MockUser {
mock := &MockUser{ctrl: ctrl}
mock.recorder = &MockUserMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockUser) EXPECT() *MockUserMockRecorder {
return m.recorder
}
EXPECT()
方法返回的就是MockUserMockRecorder
看一下我们的例子方法V
// V mocks base method
func (m *MockUser) V(idx int, name string) (string, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "V", idx, name)
ret0, _ := ret[0].(string)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// V indicates an expected call of V
func (mr *MockUserMockRecorder) V(idx, name interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "V", reflect.TypeOf((*MockUser)(nil).V), idx, name)
}
返回的*gomock.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{}
}
method``methodType
保存的方法的信息,mock
是从反射字段methodType
知道传入参数和返回结果的信息。args
用于保存指定的参数, 是gomock.Any()
还是gomock.Eq()
等,进行传入参数匹配。minCalls maxCalls
用于保存调用次数的限制actions
用于保存我们的方法自定义方法Do
Return
DoReturn
等。
go test 测试用例那些事(二) mock的更多相关文章
- [Tommas] 测试用例覆盖率(二)
二.详细用例的设计 划分好了测试项,接着就是针对各个测试项,考虑具体的测试用例了.根据测试项的特点,测试用例的设计角度也有所不同.下面我们就来看看通常的功能点测试用例,该从哪些角度出发来进行设计: 1 ...
- .net到Java那些事儿--structs做了那些事(二)
一.跟着项目先来看下structs怎么执行的 首先看下web.xml配置文件,下面有如下代码 <filter> <filter-name>struts2</fi ...
- SharePoint咨询师之路:设计之前的那些事二:规模
提示:本系列只是一个学习笔记系列,大部分内容都可以从微软官方网站找到,本人只是按照自己的学习路径来学习和呈现这些知识. 有些内容是自己的经验和积 累,如果有不当之处,请指正. 咨询师更多的时候是解决方 ...
- app 性能优化的那些事(二)
来源:树下的老男孩 链接:http://www.jianshu.com/p/2a01e5e2141f 这次我们来说说iOS app中滑动的那些事.iOS为了提高滑动的流畅感,特意在滑动的时候将runl ...
- Dynamics 365-关于Solution的那些事(二)
接着上一篇的说,现在有一个已知前提:Solution的增量特性.然后我们再思考这么一个场景,项目开发过程中,存在多次迭代的情况,每次迭代可能涉及到的solution是同一个,唯一区别的,就是solut ...
- go test 测试用例那些事
go test命令,相信大家都不陌生,常见的情况会使用这个命令做单测试.基准测试和http测试.go test还是有很多flag 可以帮助我们做更多的分析,比如测试覆盖率,cpu分析,内存分析,也有很 ...
- app测试自动化之测试套框架构造之公共部分以及测试用例导包二
封装的公共部分:commonfrom time import sleepdef com(dr): #点击backup dr.find_element_by_android_uiautomator\ ( ...
- Java 编码那些事(二)
建议先阅读:Java 编码那些事(一) 现在说说编码在Java中的实际运用.在使用tomcat的时候,绝大部分同学都会遇到乱码的问题,查查文档,google一下解决方案啥的,都是设置这里,设置那里,或 ...
- 超链接的那些事(二): 属性href
a标签的属性之一 href 1. 定义 href 属性用于指定超链接目标的 URL. 2. 用法 ①. 锚点 同一页面添加锚点 (1)<a href="#test"& ...
随机推荐
- lin-cms-dotnetcore功能模块的设计
lin-cms-dotnetcore功能模块的设计 先来回答以下问题. 1.什么是cms? Content Management System,内容管理系统. 2.dotnetcore是什么? .NE ...
- Jmeter之仿真高并发测试-集合点
场景: 大家在使用Jmeter测试的时候应该发现了, (1)线程启动了就会直接发送测试请求:--如果要模拟在一瞬间高并发量测试的时候,需要调高线程数量,这很耗测试机器的性能,往往无法支持较大的并发数, ...
- netty解决TCP的拆包和粘包的解决办法
TCP粘包.拆包问题 熟悉tcp编程的可能知道,无论是服务端还是客户端,当我们读取或者发送数据的时候,都需要考虑TCP底层的粘包个拆包机制. tcp是一个“流”协议,所谓流就是没有界限的传输数据,在业 ...
- java 虚拟机指令重新排序
指令重排序是JVM为了优化指令,提高程序运行效率,在不影响单线程程序执行结果的前提下,尽可能地提高并行度.编译器.处理器也遵循这样一个目标.注意是单线程.多线程的情况下指令重排序就会给程序员带来问题. ...
- Redhat FreeIPA Server 安装服务端和客户端 (实验:VMware环境下)
实验环境:windows7 + vmware 15 + redhat 71:准备2台虚拟机: 虚拟机VMnet8,Subnet IP:192.168.145.0 Redhat 7( ...
- 入门大数据---HiveCLI和Beeline命令行的基本使用
一.Hive CLI 1.1 Help 使用 hive -H 或者 hive --help 命令可以查看所有命令的帮助,显示如下: usage: hive -d,--define <key=va ...
- 入门大数据---Flume 简介及基本使用
一.Flume简介 Apache Flume 是一个分布式,高可用的数据收集系统.它可以从不同的数据源收集数据,经过聚合后发送到存储系统中,通常用于日志数据的收集.Flume 分为 NG 和 OG ( ...
- iptables failed: iptables --wait -t nat -A DOCKER -p tcp -d 0/0 --dport 3306 -j DNAT --to-destination 172.17.0.2:3306 ! -i docker0: iptables: No chain/target/match by that name
今天使用docker运行mysql时报错, 执行命令: docker run --restart=always --name mysql5.7 -p 3306:3306 -v /data/mysql/ ...
- Apache Dubbo Provider默认反序列漏洞复现(CVE-2020-1948)
Apache Dubbo Provider默认反序列漏洞(CVE-2020-1948) 0x01 搭建漏洞环境 漏洞介绍 2020年06月23日, 360CERT监测发现Apache Dubbo 官方 ...
- 【2003、2004 NOIp 入门组错题报告】
2003: T4: 题目大意: 讲这么多话,其实就是求比当前序列大的序列中第m小的一个.可以每次找出比当前序列大的最小的一个序列.我们可以从后往前扫描,当当前这个数比后一个数小时,我们把它与它后面的 ...