GoMock非常优秀,但是对于普通的函数打桩来说也有一些缺点:
必须引入额外的抽象(interface)
打桩过程比较重
既有代码必须适配新增的抽象
那么有没有轻量级的打桩方法呢?答案是gostub
全局变量可通过GoStub框架打桩
过程可通过GoStub框架打桩
函数可通过GoStub框架打桩
1,gostub 的用法
变量打桩
代码语言:javascript复制var counter = 100
stubs := gostub.Stub(&counter, 200)
函数打桩
代码语言:javascript复制func Exec(cmd string, args ...string) (string, error) {}
这种函数没法直接用gostub的,Golang支持闭包,这使得函数可以作为另一个函数的参数或返回值,而且可以赋值给一个变量。
需要改成
代码语言:javascript复制var Exec = func(cmd string, args ...string) (string, error) {}
现在我们可以对Exec函数打桩了,代码如下所示:
代码语言:javascript复制stubs := Stub(&Exec, func(cmd string, args ...string) (string, error) {
return "xxx-vethName100-yyy", nil
})
defer stubs.Reset()
其实GoStub框架专门提供了StubFunc函数用于函数打桩
代码语言:javascript复制stubs := StubFunc(&Exec,"xxx-vethName100-yyy", nil)
defer stubs.Reset()
对于Golang的库函数或第三方的库函数,可以定义库函数的变量作为适配层:
代码语言:javascript复制package adaptervar
Stat = os.Statvar
2,gostub的源码分析
gostub 源码很简单,只有gostub.go一个源文件
核心原理就是利用反射进行值的替换
以函数打桩的接口函数为例:
代码语言:javascript复制func StubFunc(funcVarToStub interface{}, stubVal ...interface{}) *Stubs {
return New().StubFunc(funcVarToStub, stubVal...)
}
其实底层和变量替换的实现是差不多的。
核心的数据如下,存储了最初的原始变量,便于在stub结束后数据的恢复,用stubs 变量存储了被替换的变量和替换后变量的映射。
代码语言:javascript复制type Stubs struct {
// stubs is a map from the variable pointer (being stubbed) to the original value.
stubs map[reflect.Value]reflect.Value
origEnv map[string]envVal
}
核心的stub函数如下:
1,先通过反射获取被替换变量的值,和即将替代的值
2,把原始的值用刚刚讲的map存起来
3,修改被替换变量的值
4,用到了反射的核心函数有
reflect.ValueOf //获取interface的值
v.Elem().Interface()//获取interface 包含的值的接口
v.Elem().Set(stub) //修改interface 包含的值
代码语言:javascript复制func (s *Stubs) Stub(varToStub interface{}, stubVal interface{}) *Stubs {
v := reflect.ValueOf(varToStub)
stub := reflect.ValueOf(stubVal)
if _, ok := s.stubs[v]; !ok {
// Store the original value if this is the first time varPtr is being stubbed.
s.stubs[v] = reflect.ValueOf(v.Elem().Interface())
}
// *varToStub = stubVal
v.Elem().Set(stub)
return s
}
恢复最初定义变量或者函数
代码语言:javascript复制func (s *Stubs) Reset() {
for v, originalVal := range s.stubs {
v.Elem().Set(originalVal)
}
}
对于函数替换过程稍微有点复杂,需要对函数的内容进行重新构造,具体代码如下
代码语言:javascript复制func FuncReturning(funcType reflect.Type, results ...interface{}) reflect.Value {
return reflect.MakeFunc(funcType, func(_ []reflect.Value) []reflect.Value {
return resultValues
})
}
构造函数的过程用到了反射的MakeFunc方法
reflect.MakeFunc //用给定函数来构造出funcType类型的函数,底层和c函数的转换类似,就是函数指针的转换。