gomonkey 是 Go 生态中的一个测试打桩框架,它能在单元测试中给函数,导出方法,私有方法,接口,函数参数,全局变量等进行打桩,覆盖的场景很全。
这个库由国人张晓龙开发,注意哦,不是做微信那个,那个叫张小龙。嘿嘿,看来名字叫小龙、晓龙….等等同音字的容易出牛人。
今天我们就来一起看几个典型的例子熟悉它的用法
常用打桩案例
使用前我们还是用 go get 在项目中添加一下依赖:
go get github.com/agiledragon/gomonkey/v2@v2.11.0
为了防止Go编译时发生函数内联,导致打的桩不起作用了。所以保险起见在执行测试 case 像下面这样加上gcflags
go test -gcflags=all=-l
函数打桩
给函数打桩是最常见的场景,ApplyFunc 接口定义如下:
func ApplyFunc(target, double interface{}) *Patches
func (this *Patches) ApplyFunc(target, double interface{}) *Patches
ApplyFunc第一个参数是函数名,第二个参数是桩函数。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。
这里我们看一个最简单的示例,比如我们希望 time.Now() 返回固定时间,而不是当前的系统时间,就可以插桩来指定。最后通过 defer 来 Reset 。
now := time.Now()
var p = gomonkey.ApplyFunc(time.Now, func() time.Time {
return now
})
defer p.Reset()
给函数打桩时,一定要与函数的声明匹配上,即参数类型和返回值类型都不能有差别。再看一个例子假设我们在 cache 包下有一个 Get 函数:
func Get(ctx context.Context, key string) (string, error)
那么同样,对其插桩的时候也要用一样的函数声明:
returnValue := ""
patches := gomonkey.ApplyFunc(cache.Get, func(ctx context.Context, key string) (string, error) {
return returnValue, nil
})
defer patches.Reset()
gomonkey 还支持对函数打一个特定的桩序列,如果程序里有对这个函数的多次调用的场景可以看一下 gomonkey.ApplyFuncSeq。
方法打桩
除了给函数打桩,我们还能用 ApplyMethod 给方法打桩。
func ApplyMethod(target interface{}, methodName string, double interface{}) *Patches
func (this *Patches) ApplyMethod(target interface{}, methodName string, double interface{}) *Patches
第一个参数是目标类的变量。第二个参数是字符串形式的方法名,第三个参数是桩函数。测试完成后,patches 对象通过 Reset 成员方法删除所有测试桩。
假设我们在 util 包有如下代码:
package util
type Slice []int
func NewSlice() Slice {
return make(Slice, 0)
}
func (s* Slice) Add(elem int) error {}
func (s* Slice) Remove(elem int) error {}
func (s *Slice) Append(elems ...int) int {}
一个类型 Slice,以及下面的 Add, Remove, Append 三个方法。具体的实现省略。
现在我们要针对 Add 方法来打桩,就可以这样:
slice := util.NewSlice()
var s *util.Slice
patches := ApplyMethod(s, "Add", func(_ *util.Slice, _ int) error {
return nil
})
defer patches.Reset()
注意使用ApplyMethod时,第一个参数 target 的入参和桩函数第一个参数 caller的入参必须和原方法一致,即原方法是结构体的方法,那么它们就必须为结构体,如果是结构体指针的方法,那么它们就必须都得是结构体指针,就像咱们这个例子里一样。
如果用的是gomonkey的老版本,target 和 caller 的类型都是目标类的反射类型,比如reflect.TypeOf(s),我们用的新版本不用这样写,直接传s即可,假如有历史代码按照老版本的方式传的参,在升级还是可以继续使用的。
gomonkey还支持给包内未导出的函数和方法打桩,这就让单元测试变的很方便,不需要为了写测试把包的所有函数和方法都定义成大写开头的公共方法。
给私有方法打桩使用的是 ApplyPrivateMethod ,使用方式与这里的 ApplyMethod 类似
func ApplyPrivateMethod(target interface{}, methodName string, double interface{}) *Patches
func (this *Patches) ApplyPrivateMethod(target interface{}, methodName string, double interface{}) *Patches
全局变量打桩
这个很简单,直接用 ApplyGlobalVar。
var num = 10// 项目的全局变量是10
// 测试的时候用打桩把它变成150 来测试具体行为
func TestApplyGlobalVar(t *testing.T) {
patches := ApplyGlobalVar(&num, 150)
defer patches.Reset()
assert.Equal(t, num, 150)
}