引言
gomonkey 是筆者開源的一款 Go 語言 的打樁框架,目標(biāo)是讓用戶在單元測(cè)試中低成本的完成打樁灾部,從而將精力聚焦于業(yè)務(wù)功能的開發(fā)珊佣。gomonkey 接口友好蹋宦,功能強(qiáng)大,目前已被很多項(xiàng)目使用咒锻,用戶遍及世界多個(gè)國(guó)家冷冗。
這幾年陸續(xù)有多個(gè) gopher 線上或線下咨詢筆者 gomonkey 是否可以給私有方法(private method)打樁的問題,他們往往顯得比較焦急惑艇,筆者也感同身受蒿辙,能夠體會(huì)到他們對(duì)該需求的渴望。但那時(shí)那刻,該需求實(shí)現(xiàn)難度很大须板,作者通常會(huì)答復(fù)暫時(shí)無法實(shí)現(xiàn)該需求碰镜,主要原因是 Go 反射的 API 不支持,具體就是 reflect 包有限制习瑰,即提供的 MethodByName 方法實(shí)現(xiàn)中調(diào)用了私有的 exportedMethods (可導(dǎo)出)方法,就是說私有方法(不可導(dǎo)出)在 MethodByName 這個(gè) API 中查不到秽荤。
直到 gomonkey 全面支持 arm64 后甜奄,筆者感覺 gomonkey 已經(jīng)開始引領(lǐng) Go 語言的猴子補(bǔ)丁了。這時(shí)窃款,恰好又有一些 gopher 站出來课兄,希望 gomonkey 能夠使用黑科技實(shí)現(xiàn)對(duì)私有方法打樁的需求。雖然這次與前幾次是同樣的需求晨继,但此時(shí)此刻烟阐,作者突然有了一個(gè)想挑戰(zhàn)一下的念頭冒出來,于是就有了后續(xù)支持該需求的破局行動(dòng)紊扬。
有的讀者可能會(huì)有疑問:public method 是類暴露出來的 API蜒茄,相對(duì)穩(wěn)定,而 private method 類內(nèi)部的具體實(shí)現(xiàn)細(xì)節(jié)餐屎,可能不穩(wěn)定檀葛,同時(shí)給 public method 打樁已經(jīng)足夠,為什么還有給 private method 打樁的需求腹缩?或者說屿聋,給 private method 打樁到底會(huì)有什么價(jià)值?
筆者也同樣思考過這個(gè)問題藏鹊,結(jié)論是至少有一種場(chǎng)景润讥,價(jià)值還是很大的:private method 封裝了多 個(gè)下層操作,這些操作雖然都是 public method盘寡,但是對(duì) private method 打樁只需打一次樁楚殿,而對(duì)多個(gè)下層操作的 public method 打樁需要打多次樁,并且這幾個(gè)樁有關(guān)聯(lián)宴抚。顯然勒魔,這時(shí)對(duì) private method 直接打樁會(huì)更高效,讓開發(fā)者自測(cè)更美好菇曲!
private method 接口設(shè)計(jì)
private method 需求實(shí)現(xiàn)的關(guān)鍵是要穿越 reflect 包的限制冠绢,需要在 gomonkey 內(nèi)實(shí)現(xiàn)一個(gè)特定的反射包來與 Go 語言對(duì)接。但為 gomonkey 定制的反射包 creflect 與 Go 語言標(biāo)準(zhǔn)庫(kù)的反射包 reflect 在內(nèi)部數(shù)據(jù)結(jié)構(gòu)上有一定的耦合常潮,因此需要設(shè)計(jì)獨(dú)立的 API 讓用戶使用弟胀,而不復(fù)用已有的 ApplyMethod,以便 creflect 包的變化僅僅影響 private method 需求。
按照 gomonkey 的慣例孵户,private method 接口應(yīng)該有兩個(gè):一個(gè)是函數(shù)接口萧朝,一個(gè)是方法接口
func ApplyPrivateMethod(target interface{}, methodName string, double interface{}) *Patches
func (this *Patches) ApplyPrivateMethod(target interface{}, methodName string, double interface{}) *Patches
說明:除過方法名字,參數(shù)和 ApplyMethod 一模一樣夏哭,之所以用兩個(gè)方法名检柬,完全是為了隔離變化。
private method 接口使用方法
gomonkey 在測(cè)試目錄增加了測(cè)試文件 apply_private_method_test.go 來引導(dǎo)讀者寫 private method 的測(cè)試竖配。
測(cè)試代碼:
func TestApplyPrivateMethod(t *testing.T) {
Convey("TestApplyPrivateMethod", t, func() {
Convey("patch private pointer method in the different package", func() {
f := new(fake.PrivateMethodStruct)
var s *fake.PrivateMethodStruct
patches := ApplyPrivateMethod(s, "ok", func(_ *fake.PrivateMethodStruct) bool {
return false
})
defer patches.Reset()
result := f.Happy()
So(result, ShouldEqual, "unhappy")
})
Convey("patch private value method in the different package", func() {
s := fake.PrivateMethodStruct{}
patches := ApplyPrivateMethod(s, "haveEaten", func(_ fake.PrivateMethodStruct) bool {
return false
})
defer patches.Reset()
result := s.AreYouHungry()
So(result, ShouldEqual, "I am hungry")
})
})
}
模擬的產(chǎn)品代碼:
type PrivateMethodStruct struct {
}
func (this *PrivateMethodStruct) ok() bool {
return this != nil
}
func (this *PrivateMethodStruct) Happy() string {
if this.ok() {
return "happy"
}
return "unhappy"
}
func (this PrivateMethodStruct) haveEaten() bool {
return this != PrivateMethodStruct{}
}
func (this PrivateMethodStruct) AreYouHungry() string {
if this.haveEaten() {
return "I am full"
}
return "I am hungry"
}
運(yùn)行測(cè)試:
zhangxiaolongdeMacBook-Pro:test zhangxiaolong$ go test -gcflags=all=-l apply_private_method_test.go -v
=== RUN TestApplyPrivateMethod
TestApplyPrivateMethod
patch private pointer method in the different package ?
patch private value method in the different package ?
2 total assertions
--- PASS: TestApplyPrivateMethod (0.00s)
PASS
ok command-line-arguments
新版本說明
作者在 github 上發(fā)布了 gomonkey 的新版本 v2.7.0 來完整支持該特性:
如何獲取 gomonkey 新版本何址?
假設(shè)你使用 go get 命令來獲取 gomonkey v2.7.0:
$ go get github.com/agiledragon/gomonkey/v2@v2.7.0
如何導(dǎo)入 gomonkey 新版本?
import (
"encoding/json"
"testing"
. "github.com/agiledragon/gomonkey/v2"
. "github.com/smartystreets/goconvey/convey"
)
小結(jié)
gomonkey 穿越 reflect 包的限制进胯,終于支持為 private method 打樁的特性了用爪!該特性高效解決了諸多 gopher 這些年寫測(cè)試在某些場(chǎng)景下打樁繁雜的困擾,使得 gomonkey 打樁更貼心胁镐,讓開發(fā)者自測(cè)更美好偎血!
gomonkey 專門為用戶提供了新接口來使用 private method 打樁特性,從而將為 gomonkey 私人定制的反射包 creflect 的影響盡可能的局部化盯漂。
關(guān)于支持 private method sequence 的特性颇玷,暫不考慮開發(fā)計(jì)劃。