工作中,很多公司都要求效能,要求自动化测试。
实际落地的过程中发现,要做单元测试,自动化测试,可能当前这个服务会依赖其他服务的数据,接口等等。
那么单测或者自动化的过程中,就可能会由于其他服务的原因或者环境因素导致测试失败,或者阻塞测试。
这是一个问题,必须得解决,我们可以采用 golang 自带的 mock 工具来完成,可以在一些必要的地方进行数据打桩,mock 数据。
gomock 是什么?
是官方提供的 一个 mock 数据的 框架
官方还提供了 mockgen 工具用来帮助 我们 生成测试代码
github 上项目地址是:https://github.com/golang/mock
官方是这样介绍 gomock 的:
gomock 是一个用于Go 编程语言的 mocking 框架。它与 Go 的内置测试包集成得很好,但也可以在其他环境中使用。
如何使用 gomock?
使用 gomock 也是非常简单的,先 go get 对应的 工具 gomock 和 mockgen
goget-ugithub.com/golang/mock/gomockgoget-ugithub.com/golang/mock/mockgen
可以写一个 demo 来进行实践
目录结构是这样的
gomock_test├──go.mod├──go.sum├──main.go└──myfunc├──mock_myfunc.go├──myfunc.go├──myuser.go└──myuser_test.go
mock_myfunc.go 是使用 mockgen 工具生成的
myfunc.go 主要是用于模拟调用的底层实现
myuser.go 主要是去调用 myfunc.go 里面的接口
myuser_test.go 是 对应的单测文件
myfunc.go
编写一个 接口,里面有一个 GetInfo() string
方法,模拟获取信息
packagemyfunctypeMyFuncinterface{GetInfo()string}
myuser.go
调用 myfunc.go 中的方法,调用接口获取信息
packagemyfuncfuncgetUser(mMyFunc)string{user:=m.GetInfo()returnuser}
mock 文件的生成
mock_myfunc.go
这个文件不是我们自己写的,是通过 mockgen 工具生成的 ,生成方式如下:
在 myfunc.go 的同级目录下执行如下语句,填入 source 源文件 和 目标文件即可生成新的 mock 文件
mockgen-source=myfunc.go-destination=mock_myfunc.go
我们可以看一下 mockgen 的帮助文档,还有其他的参数供我们使用
#mockgenmockgenhastwomodesofoperation:sourceandreflect.Sourcemodegeneratesmockinterfacesfromasourcefile.Itisenabledbyusingthe-sourceflag.Otherflagsthatmaybeusefulinthismodeare-importsand-aux_files.Example:mockgen-source=foo.go[otheroptions]Reflectmodegeneratesmockinterfacesbybuildingaprogramthatusesreflectiontounderstandinterfaces.Itisenabledbypassingtwonon-flagarguments:animportpath,andacomma-separatedlistofsymbols.Example:mockgendatabase/sql/driverConn,Driver-aux_filesstring(sourcemode)Comma-separatedpkg=pathpairsofauxiliaryGosourcefiles.-build_flagsstring(reflectmode)Additionalflagsforgobuild.-copyright_filestringCopyrightfileusedtoaddcopyrightheader-debug_parserPrintoutparserresultsonly.-destinationstringOutputfile;defaultstostdout.-exec_onlystring(reflectmode)Ifset,executethisreflectionprogram.-importsstring(sourcemode)Comma-separatedname=pathpairsofexplicitimportstouse.-mock_namesstringComma-separatedinterfaceName=mockNamepairsofexplicitmocknamestouse.Mocknamesdefaultto'Mock'+interfaceNamesuffix.-packagestringPackageofthegeneratedcode;defaultstothepackageoftheinputwitha'mock_'prefix.-prog_only(reflectmode)Onlygeneratethereflectionprogram;writeittostdoutandexit.-self_packagestringThefullpackageimportpathforthegeneratedcode.Thepurposeofthisflagistopreventimportcyclesinthegeneratedcodebytryingtoincludeitsownpackage.Thiscanhappenifthemock'spackageissettooneofitsinputs(usuallythemainone)andtheoutputisstdiosomockgencannotdetectthefinaloutputpackage.Settingthisflagwillthentellmockgenwhichimporttoexclude.-sourcestring(sourcemode)InputGosourcefile;enablessourcemode.-versionPrintversion.-write_package_commentWritespackagedocumentationcomment(godoc)iftrue.(defaulttrue)2021/10/3016:43:25Expectedexactlytwoarguments
一般用的比较多的就是
-source
源文件
-destination
目标文件
-imports
依赖的需要 import 的包
-build_flags
传递给build工具的参数
-aux_files
接口文件不止一个文件时附加文件
-package
设置 mock 文件的包名,不设置的话,mock 文件的包名默认是 mock_输入文件的包名
通过上述指令生成的 mock 文件如下:
NewMockMyFunc
创建一个新的 mock 实例
EXPECT
允许调用者指示预期用途的对象
GetInfo
mock 的基础方法,也就是我们需要 mock 的方法
具体的如何使用
myuser_test.go
myuser.go 对应的单测文件 , 使用了 mock 的方式
packagemyfuncimport("fmt""testing"gomock"github.com/golang/mock/gomock")funcTest_getUser(t*testing.T){mockCtl:=gomock.NewController(t)mockMyFunc:=NewMockMyFunc(mockCtl)mockMyFunc.EXPECT().GetInfo().Return("xiaomotong")v:=getUser(mockMyFunc)ifv=="xiaomotong"{fmt.Println("getuserright!")}else{t.Error("geterroruser")}}
看到上述单测文件,可以还不是特别明白区别,我们来看看不用 mock 的时候,我们会是如何去写单测呢
packagemyfuncimport("fmt""testing"gomock"github.com/golang/mock/gomock")funcTest_getUser(t*testing.T){m:=myfunc.CreateMyFunc()//也就是说需要自己创建一个对象v:=getUser(m)ifv=="xiaomotong"{fmt.Println("getuserright!")}else{t.Error("geterroruser")}}
m := myfunc.CreateMyFunc()
看到上述这一句话,是创建对应的对象,再将该对象作为参数传入到 getUser 函数中,正常情况下这样做单测没有问题
但是如果这个时候创建 MyFunc 对象由于对外部还有依赖导致还没有编码好,可是也不能阻塞我们的单元测试
这个时候使用最上面的 mock 方案就显得尤为重要,可以使用 mock 的方式,mock 一个 MyFunc 对象,并设置好返回值即可完成,如:
mockCtl:=gomock.NewController(t)mockMyFunc:=NewMockMyFunc(mockCtl)mockMyFunc.EXPECT().GetInfo().Return("xiaomotong")
执行上述代码结果如下:
>gotestgetuserright!PASSokmygomock/myfunc0.427s
感兴趣的朋友可以使用起来,用的多了就会更加熟悉
使用 gomock 的好处?
gomock 实现了较为完整的基于 interface 的 Mock 功能,能够与 Golang 内置的 testing包良好集成,也能用于其它的测试环境中
学习成本低,很快就能上手
工具需要用起来,才能发挥他的价值,需要的可以用起来吧~
作者:小魔童哪吒