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