引入
go get github.com/agiledragon/gomonkey/v2
gomoneky方法一览
// 函数打桩
func ApplyFunc(target, double interface{}) *Patches {}
// 方法打桩
func ApplyMethod(target reflect.Type, methodName string, double interface{}) *Patches {}
// 全局变量打桩
func ApplyGlobalVar(target, double interface{}) *Patches {}
// 函数变量打桩
func ApplyFuncVar(target, double interface{}) *Patches {}
// 函数序列打桩
func ApplyFuncSeq(target interface{}, outputs []OutputCell) *Patches {}
// 方法序列打桩
func ApplyMethodSeq(target reflect.Type, methodName string, outputs []OutputCell) *Patches {}
// 函数变量序列打桩
func ApplyFuncVarSeq(target interface{}, outputs []OutputCell) *Patches {}
打桩一个函数
这是我们使用较多的情况,举个栗子:
某openApi的http client,其初始化函数为:
// NewOpenApiClient 根据客户id生成相应的openapi client
func NewOpenApiClient(accountId string) (*client.EcxAPIGoClient, error) {
.................................
openApiClient := client.New(rt, nil)
return openApiClient, nil
}
gomonkey可以灵活的在单测中patch任何一个函数,这个函数可能是一个工具函数,对于它们我们早已经验证了其正确性,还是以上述NewOpenApiClient为例,我们只需要调用gomonkey的applyFunc方法来是的运行test.go文件时替换掉本函数即可,示例:
func Test(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
backendGroupClient := backend_group.NewMockClientService(ctrl)
patches := gomonkey.ApplyFunc(openapi.NewOpenApiClient, func(accountId string) (goClient *client.EcxAPIGoClient, err error) {
return &client.EcxAPIGoClient{
BackendGroup: backendGroupClient,
}, nil
})
defer patches.Reset()
}
打桩一个方法
gomonkey可以为一个对象方法打桩,还是以openApiClient为例,我们创建一个struct,让它实现newClient方法:
type client struct{}
func (c *client) NewOpenApiClient(accountId string) (*client.EcxAPIGoClient, error) {
......
}
测试方法:
func Test(t *testing.T) {
var cli *cli
patches := gomonkey.ApplyMethod(reflect.TypeOf(cli),"NewOpenApiClient", func (_ *cli, accountId string) (*client.EcxAPIGoClient, error) {
return ....
})
defer patches.Reset()
}
打桩一个变量
var num = 10
func Test(t *testing.T){
patches := gomonkey.ApplyGlobalVar(&num, 12)
defer patches.Reset()
}
打桩一个函数返回序列
gomonkey可以模拟一个函数被请求多次的返回序列,不太恰当的,我们模拟请求上述newOpenApi函数的三次返回,分别返回三个不同的client:
func Test(t *testing.T) {
outputs := []gomonkey.OutputCell{
{Values: gomonkey.Params{client1, nil}}, // 第一次请求返回client1
{Values: gomonkey.Params{client2, nil}}, // 第二次请求返回client2
{Values: gomonkey.Params{client3, nil}}, // 第三次请求返回client3
}
patches := gomonkey.ApplyFuncSeq(openapi.NewOpsOpenApiClient, outputs)
defer patches.Reset()
}
打桩方法序列则与打桩方法类似,首先需要声明方法所属对象的类型,指定打桩方法名,最后与函数返回序列一致,定义期望的多次返回即可。
特别地,经测试如果我们需要模拟一个函数或者方法被多次请求,但是每次返回一致,那么只需要patch该函数或者方法即可,不需要使用序列相关的方法。
一些限制
1.gomonkey极少数情况会因为内联的原因导致失效,解决方法:
go test -v -gcflags=-l
2.gomonkey提供的现有方法应该不能实现链式调用的打桩,例如k8sClient get node,所以按照之前的等待job结束方法单测,还是需要通过fake实现。