Go 代碼測試時(shí)怎么打樁?給大家寫了幾個(gè)常用案例
gomonkey 是 Go 生態(tài)中的一個(gè)測試打樁框架,它能在單元測試中給函數(shù),導(dǎo)出方法,私有方法,接口,函數(shù)參數(shù),全局變量等進(jìn)行打樁,覆蓋的場景很全。
這個(gè)庫由國人張曉龍開發(fā),注意哦,不是做微信那個(gè),那個(gè)叫張小龍。嘿嘿,看來名字叫小龍、曉龍….等等同音字的容易出牛人。
今天我們就來一起看幾個(gè)典型的例子熟悉它的用法
常用打樁案例
使用前我們還是用 go get 在項(xiàng)目中添加一下依賴:
go get github.com/agiledragon/gomonkey/v2@v2.11.0
為了防止Go編譯時(shí)發(fā)生函數(shù)內(nèi)聯(lián),導(dǎo)致打的樁不起作用了。所以保險(xiǎn)起見在執(zhí)行測試 case 像下面這樣加上gcflags
go test -gcflags=all=-l
函數(shù)打樁
給函數(shù)打樁是最常見的場景,ApplyFunc 接口定義如下:
func ApplyFunc(target, double interface{}) *Patches
func (this *Patches) ApplyFunc(target, double interface{}) *Patches
ApplyFunc第一個(gè)參數(shù)是函數(shù)名,第二個(gè)參數(shù)是樁函數(shù)。測試完成后,patches 對象通過 Reset 成員方法刪除所有測試樁。
這里我們看一個(gè)最簡單的示例,比如我們希望 time.Now() 返回固定時(shí)間,而不是當(dāng)前的系統(tǒng)時(shí)間,就可以插樁來指定。最后通過 defer 來 Reset 。
now := time.Now()
var p = gomonkey.ApplyFunc(time.Now, func() time.Time {
return now
})
defer p.Reset()
給函數(shù)打樁時(shí),一定要與函數(shù)的聲明匹配上,即參數(shù)類型和返回值類型都不能有差別。再看一個(gè)例子假設(shè)我們在 cache 包下有一個(gè) Get 函數(shù):
func Get(ctx context.Context, key string) (string, error)
那么同樣,對其插樁的時(shí)候也要用一樣的函數(shù)聲明:
returnValue := ""
patches := gomonkey.ApplyFunc(cache.Get, func(ctx context.Context, key string) (string, error) {
return returnValue, nil
})
defer patches.Reset()
gomonkey 還支持對函數(shù)打一個(gè)特定的樁序列,如果程序里有對這個(gè)函數(shù)的多次調(diào)用的場景可以看一下 gomonkey.ApplyFuncSeq。
方法打樁
除了給函數(shù)打樁,我們還能用 ApplyMethod 給方法打樁。
func ApplyMethod(target interface{}, methodName string, double interface{}) *Patches
func (this *Patches) ApplyMethod(target interface{}, methodName string, double interface{}) *Patches
第一個(gè)參數(shù)是目標(biāo)類的變量。第二個(gè)參數(shù)是字符串形式的方法名,第三個(gè)參數(shù)是樁函數(shù)。測試完成后,patches 對象通過 Reset 成員方法刪除所有測試樁。
假設(shè)我們在 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 {}
一個(gè)類型 Slice,以及下面的 Add, Remove, Append 三個(gè)方法。具體的實(shí)現(xiàn)省略。
現(xiàn)在我們要針對 Add 方法來打樁,就可以這樣:
slice := util.NewSlice()
var s *util.Slice
patches := ApplyMethod(s, "Add", func(_ *util.Slice, _ int) error {
return nil
})
defer patches.Reset()
注意使用ApplyMethod時(shí),第一個(gè)參數(shù) target 的入?yún)⒑蜆逗瘮?shù)第一個(gè)參數(shù) caller的入?yún)⒈仨毢驮椒ㄒ恢?,即原方法是結(jié)構(gòu)體的方法,那么它們就必須為結(jié)構(gòu)體,如果是結(jié)構(gòu)體指針的方法,那么它們就必須都得是結(jié)構(gòu)體指針,就像咱們這個(gè)例子里一樣。
如果用的是gomonkey的老版本,target 和 caller 的類型都是目標(biāo)類的反射類型,比如reflect.TypeOf(s),我們用的新版本不用這樣寫,直接傳s即可,假如有歷史代碼按照老版本的方式傳的參,在升級還是可以繼續(xù)使用的。
gomonkey還支持給包內(nèi)未導(dǎo)出的函數(shù)和方法打樁,這就讓單元測試變的很方便,不需要為了寫測試把包的所有函數(shù)和方法都定義成大寫開頭的公共方法。
給私有方法打樁使用的是 ApplyPrivateMethod ,使用方式與這里的 ApplyMethod 類似
func ApplyPrivateMethod(target interface{}, methodName string, double interface{}) *Patches
func (this *Patches) ApplyPrivateMethod(target interface{}, methodName string, double interface{}) *Patches
全局變量打樁
這個(gè)很簡單,直接用 ApplyGlobalVar。
var num = 10// 項(xiàng)目的全局變量是10
// 測試的時(shí)候用打樁把它變成150 來測試具體行為
func TestApplyGlobalVar(t *testing.T) {
patches := ApplyGlobalVar(&num, 150)
defer patches.Reset()
assert.Equal(t, num, 150)
}