GoStub是一款輕量級的單元測試框架,接口友好,能夠對全局變量、函數或過程進行打樁。
GoStub安裝:go get github.com/prashantv/gostub
git
gostub用於在測試時打樁變量,一旦測試運行時,重置原來的值。github
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 }
Stubs表明一系列能夠重置的打樁變量。數據庫
func Stub(varToStub interface{}, stubVal interface{}) *Stubs { return New().Stub(varToStub, stubVal) }
Stub使用stubVal替代存儲在varToStub變量的值,返回*Stubs
類型變量
varToStub必須是指向變量的指針。
stubVal是可賦值到變量的類型json
func StubFunc(funcVarToStub interface{}, stubVal ...interface{}) *Stubs { return New().StubFunc(funcVarToStub, stubVal...) }
StubFunc用返回stubval值的函數替換函數變量,返回*Stubs
類型變量
funcVarToStub是指向函數變量的指針。若是函數返回多個值,返回的多個值被傳遞給StubFunc。func New() *Stubs
New返回用於打樁變量的*Stubs
變量func (s *Stubs) Reset()
Reset重置打樁的全部變量到其原始值func (s *Stubs) ResetSingle(varToStub interface{})
ResetSingle重置打樁的單個變量到其原始值func (s *Stubs) SetEnv(k, v string) *Stubs
SetEnv設置指定的環境變量到指定值func (s *Stubs) UnsetEnv(k string) *Stubs
UnsetEnv還原指定環境變量的值func (s *Stubs) Stub(varToStub interface{}, stubVal interface{}) *Stubs
Stub使用stubVal替代存儲在varToStub變量的值
varToStub必須是指向變量的指針。
stubVal是可賦值到變量的類型func (s *Stubs) StubFunc(funcVarToStub interface{}, stubVal ...interface{}) *Stubs
StubFunc用返回stubval值的函數替換函數變量,返回*Stubs
類型變量
funcVarToStub是指向函數變量的指針。若是函數返回多個值,返回的多個值被傳遞給StubFunc。框架
GoStub框架的使用場景以下:
A、爲一個全局變量打樁
B、爲一個函數打樁
C、爲一個過程打樁
D、由任意相同或不一樣的基本場景組合而成ide
假設counter爲被測函數中使用的一個全局整型變量,當前測試用例中假定counter的值爲200,則打樁的代碼以下:函數
package main import ( "fmt" "github.com/prashantv/gostub" ) var counter = 100 func stubGlobalVariable() { stubs := gostub.Stub(&counter, 200) defer stubs.Reset() fmt.Println("Counter:", counter) } func main() { stubGlobalVariable() } // output: // Counter: 200
stubs是GoStub框架的函數接口Stub返回的對象,Reset方法將全局變量的值恢復爲原值。單元測試
package main import ( "io/ioutil" "fmt" "github.com/prashantv/gostub" ) var configFile = "config.json" func GetConfig() ([]byte, error) { return ioutil.ReadFile(configFile) } func stubGlobalVariable() { stubs := gostub.Stub(&configFile, "/tmp/test.config") defer stubs.Reset() /// 返回tmp/test.config文件的內容 data, err := GetConfig() if err != nil { fmt.Println(err) } fmt.Println(data) } func main() { stubGlobalVariable() }
一般函數分爲工程自定義函數與庫函數。
假設工程中自定義函數以下:測試
func Exec(cmd string, args ...string) (string, error) { ... }
Exec函數是不能經過GoStub框架打樁的。若是想要經過GoStub框架對Exec函數進行打樁,則僅需對自定義函數進行簡單的重構,即將Exec函數定義爲匿名函數,同時將其賦值給Exec變量,重構後的代碼以下:指針
var Exec = func(cmd string, args ...string) (string, error) { ... }
當Exec函數重構成Exec變量後,並不影響既有代碼中對Exec函數的調用。因爲Exec變量是函數變量,所以通常函數變量也叫作函數。對Exec函數變量進行打樁的代碼以下:
stubs := Stub(&Exec, func(cmd string, args ...string) (string, error) { return "test", nil }) defer stubs.Reset()
GoStub框架專門提供了StubFunc函數用於函數打樁,對於函數的打樁代碼以下:
stubs := StubFunc(&Exec,"test", nil) defer stubs.Reset()
工程代碼中會調用Golang庫函數或第三方庫函數,因爲不能重構庫函數,所以須要在工程代碼中增長一層適配層,在適配層中定義庫函數的變量,而後在工程代碼中使用函數變量。
package Adapter import ( "time" "fmt" "os" "github.com/prashantv/gostub" ) var timeNow = time.Now var osHostname = os.Hostname func getDate() int { return timeNow().Day() } func getHostName() (string, error) { return osHostname() } func StubTimeNowFunction() { stubs := gostub.Stub(&timeNow, func() time.Time { return time.Date(2015, 6, 1, 0, 0, 0, 0, time.UTC) }) fmt.Println(getDate()) defer stubs.Reset() } func StubHostNameFunction() { stubs := gostub.StubFunc(&osHostname, "LocalHost", nil) defer stubs.Reset() fmt.Println(getHostName()) }
使用示例:
package main import "GoExample/GoStub/StubFunction" func main() { Adapter.StubTimeNowFunction() Adapter.StubHostNameFunction() }
沒有返回值的函數稱爲過程。一般將資源清理類函數定義爲過程。
package main import ( "fmt" "github.com/prashantv/gostub" ) var CleanUp = cleanUp func cleanUp(val string) { fmt.Println(val) } func main() { stubs := gostub.StubFunc(&CleanUp) CleanUp("Hello go") defer stubs.Reset() }
不管是調用Stub函數仍是StubFunc函數,都會生成一個Stubs對象,Stubs對象仍然有Stub方法和StubFunc方法,因此在一個測試用例中能夠同時對多個全局變量、函數或過程打樁。全局變量、函數或過程會將初始值存在一個map中,並在延遲語句中經過Reset方法統一作回滾處理。
屢次打樁代碼以下:
stubs := gostub.Stub(&v1, 1) defer stubs.Reset() // Do some testing stubs.Stub(&v1, 5) // More testing stubs.Stub(&b2, 6)
屢次打樁的級聯表達式代碼以下:defer gostub.Stub(&v1, 1).Stub(&v2, 2).Reset()
使用GoConvey測試框架和GoStub測試框架編寫的測試用例以下:
package main import ( "fmt" "testing" "GoExample/GoStub/StubFunction" "time" "github.com/prashantv/gostub" . "github.com/smartystreets/goconvey/convey" ) var counter = 100 var CleanUp = cleanUp func cleanUp(val string) { fmt.Println(val) } func TestFuncDemo(t *testing.T) { Convey("TestFuncDemo", t, func() { Convey("for succ", func() { stubs := gostub.Stub(&counter, 200) defer stubs.Reset() stubs.Stub(&Adapter.TimeNow, func() time.Time { return time.Date(2015, 6, 1, 0, 0, 0, 0, time.UTC) }) stubs.StubFunc(&CleanUp) fmt.Println(counter) fmt.Println(Adapter.TimeNow().Day()) CleanUp("Hello go") }) }) }
GoStub框架能夠解決不少場景的函數打樁問題,但下列複雜場景除外:A、被測函數中屢次調用了數據庫讀操做函數接口,而且數據庫爲key-value型。B、被測函數中有一個循環,用於一個批量操做,當某一次操做失敗,則返回失敗,並進行錯誤處理。C、被測函數中屢次調用了同一底層操做函數。