gostub 源碼分析

打樁過程比較重

既有代碼必須適配新增的抽象

那麼有沒有輕量級的打樁方法呢?答案是 gostub

全局變量可通過 GoStub 框架打樁

過程可通過 GoStub 框架打樁

函數可通過 GoStub 框架打樁

1,gostub 的用法

變量打樁

函數打樁

這種函數沒法直接用 gostub 的,Golang 支持閉包,這使得函數可以作爲另一個函數的參數或返回值,而且可以賦值給一個變量。

需要改成

現在我們可以對 Exec 函數打樁了,代碼如下所示:

其實 GoStub 框架專門提供了 StubFunc 函數用於函數打樁

1stubs := StubFunc(&Exec,"xxx-vethName100-yyy", nil)
2defer stubs.Reset()
3

對於 Golang 的庫函數或第三方的庫函數,可以定義庫函數的變量作爲適配層:

1package adaptervar
2Stat = os.Statvar
3

2,gostub 的源碼分析

gostub 源碼很簡單,只有 gostub.go 一個源文件

核心原理就是利用反射進行值的替換

以函數打樁的接口函數爲例:

1func StubFunc(funcVarToStub interface{}, stubVal ...interface{}) *Stubs {
2  return New().StubFunc(funcVarToStub, stubVal...)
3}
4

其實底層和變量替換的實現是差不多的。

1type Stubs struct {
2  // stubs is a map from the variable pointer (being stubbed) to the original value.
3  stubs   map[reflect.Value]reflect.Value
4  origEnv map[string]envVal
5}
6

1,先通過反射獲取被替換變量的值,和即將替代的值

2,把原始的值用剛剛講的 map 存起來

3,修改被替換變量的值

4,用到了反射的核心函數有

reflect.ValueOf // 獲取 interface 的值

v.Elem().Interface()// 獲取 interface 包含的值的接口

v.Elem().Set(stub) // 修改 interface 包含的值

 1func (s *Stubs) Stub(varToStub interface{}, stubVal interface{}) *Stubs {
 2  v := reflect.ValueOf(varToStub)
 3  stub := reflect.ValueOf(stubVal)
 4    if _, ok := s.stubs[v]; !ok {
 5    // Store the original value if this is the first time varPtr is being stubbed.
 6    s.stubs[v] = reflect.ValueOf(v.Elem().Interface())
 7  }
 8  // *varToStub = stubVal
 9  v.Elem().Set(stub)
10  return s
11}
12

恢復最初定義變量或者函數

1func (s *Stubs) Reset() {
2  for v, originalVal := range s.stubs {
3    v.Elem().Set(originalVal)
4  }
5}
6

對於函數替換過程稍微有點複雜,需要對函數的內容進行重新構造,具體代碼如下

1func FuncReturning(funcType reflect.Type, results ...interface{}) reflect.Value {
2  return reflect.MakeFunc(funcType, func(_ []reflect.Value) []reflect.Value {
3    return resultValues
4  })
5}
6

構造函數的過程用到了反射的 MakeFunc 方法

reflect.MakeFunc // 用給定函數來構造出 funcType 類型的函數,底層和 c 函數的轉換類似,就是函數指針的轉換。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/I6urCBHbcfZCNaWw1iZmnA