// 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里面才去赋值:
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
该命令中解释如下:
使用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,具体安装见前面。