????Go 单元测试git
????1.单测工具github
????2.单测golang
????2.1 调本身框架
????2.2 直接mock远程调用接口ide
????2.3 monkey函数
????3.优雅的单测工具
// go mock相关: go get github.com/golang/mock/gomock go get github.com/golang/mock/mockgen //stub相关: go get github.com/prashantv/gostub // monkey go get github.com/bouk/monkey // goconvey go get github.com/smartystreets/goconvey
在单元测试过程当中,遇到了两个问题,第一个:单元测试
func Target() { A() } func A() { // call rpc interface }
如今咱们想测试Target函数,可是因为调用的A函数依赖于本身的某个函数,这里就是A调用了rpc接口拉别人接口数据,咱们想mockA接口的目标是,想直接拿到A返回的数据便可,直接采用gomock方式,行不通,本身测试了一下,发现要不断的mock 别人接口所依赖的其余接口,很是麻烦,经过注入代码或者后面第三种方式替换函数便可解决。测试
实践中只须要按照下面方法来,注入一个getHook函数,在test里面才去赋值:code
var getHook func([]string) ([]Info, error) func Target() { if getHook != nil { info, err = getHook("xxx") } else { info, err = handler.getInfo("xxx") } }
其中返回的数据时Info:
type Info struct { ID string, }
test文件中,这样写:
func mockGetInfo(x []string) ([]Info, error) { res := []Info{ { ID: "xxx" }, } return res, nil } func Test_xxx(t *testing.T) { // 注入屏蔽测试 getHook = mockGetInfo // inject getHook("xxx") }
这样,就能够把这个接口给mock掉了。
在代码中,还会有调别人的服务,例如:双方约定Pb rpc协议来调用拉取数据,现有下面这个接口:
type Service interface { GetSerData(req *SerReq) (rsp *SerRsp, err error) }
主函数调用以下
// 请求渲染后台 som := NewServiceClientProxy(opts...) // 发起rpc调用 rsp, _ := som.GetSerData(&req)
这个就比较简单了,直接采用gomock+gostub便可解决,不须要注入代码及主逻辑,很是方便!
首先,使用mockgen生成相应mock_service.go
mockgen -destination=mocks/mock_service.go -package=mocks com.gcx Service
该命令中解释以下:
destination表示生成的目标文件
package表示上述文件的包名
com.gcx表示mock的接口包名
Service表示接口名
使用gostub对proxy进行打桩,能够简单理解位用本身的替换代码中想mock的接口。
ctrl := gomock.NewController(t) mockedService := mocks.NewMockServiceClientProxy(ctrl) serStubs := gostub.Stub(&NewServiceClientProxy, func(opts ...client.Option) Service { return mockedService }) defer serStubs.Reset()
随后,咱们想经过本身的mock本身想要的数据,只须要下面这样描述预期行为便可:
mockedService.EXPECT().GetSerData(gomock.Any(), gomock.Any(), gomock.Any()).Return(&SerRsp{ // 填充字段 }, nil).AnyTimes()
使用monkey测试,算是最简单的一种方式了,不用本身去打桩,而后替换,也不用像方法1同样进行主逻辑的函数注入,mock谁,咱们就替换掉这个方法或者函数就好了,而mockey就是这么直接的。
首先看一下安装问题:正常的 方式为:
import "github.com/bouk/monkey"
源码指定了 import
方式,所以实际单测中应该:
import "bou.ke/monkey"
此时,须要进入gopath里面:go/pkg/mod/github.com/bouk
,重命名文件夹:mv github.com/bouk bou.ke
如何去使用呢,下面举个例子:
假设要测试getNum:
func getNum() { // dosomething handler := &ProcessHandler{} unionInfo, err = handler.GetSerData("xxx") // dosomething } // 咱们想mock掉该接口,该接口具体实现以下: func (s *ProcessHandler) GetSerData(c []string) ([], error) { // dosomething return info, err }
此时咱们直接使用
patch
进行替换便可:
var s *ProcessHandler monkey.PatchInstanceMethod(reflect.TypeOf(s), "GetSerData", func(o *ProcessHandler, x []string) ([]union.CoverInfo, error) { res := []Info{ { ID: "xxx" }, } return res, nil })
mockey是有一些坑的,例如:你要测是的函数或者方法不可导出,就会报下面错误:
这里以GetSerData不可导出为例:panic: unknown method GetSerData反射机制的这种差别致使了Monkey框架的缺陷:在go1.6版本中能够成功打桩的首字母小写的方法,当go版本升级后Monkey框架会显式触发panic,表示
unknown method:
具体patch的原理见后面参考。
vscode生成的单测,以下:
func Test_getNum(t *testing.T) { tests := []struct { name string args args wantErr bool }{ { name: "TestGetNum", args: args{ req: &SerReq{ }, }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { if err := getNum(tt.args.req); (err != nil) != tt.wantErr { t.Errorf("getNum error = %v, wantErr %v", err, tt.wantErr) } }) } }
太挫了,来看一下高富帅的:
convey.Convey("getNum invoke test", t, func() { type args struct { req *SerReq } input := args{ ctx: ctx, req: &SerReq{ }, } err := getNum(req) convey.So(err, convey.ShouldBeNil) })
这里引入convey,具体安装见前面。