gostub源码分析

2022-08-02 14:46:26 浏览数 (1)

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函数的转换类似,就是函数指针的转换。

0 人点赞