Go語言開發(二十)、GoStub測試框架
Go語言開發(二十)、GoStub測試框架
一、GoStub簡介
GoStub是一款輕量級的單元測試框架,介面友好,可以對全域性變數、函式或過程進行打樁。
GoStub安裝:
go get github.com/prashantv/gostub
二、GoStub常用方法
gostub用於在測試時打樁變數,一旦測試執行時,重置原來的值。
type Stubs struct { // stubs is a map from the variable pointer (being stubbed) to the original value. stubsmap[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是可賦值到變數的型別
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應用示例
1、GoStub應用場景
GoStub框架的使用場景如下:
A、為一個全域性變數打樁
B、為一個函式打樁
C、為一個過程打樁
D、由任意相同或不同的基本場景組合而成
2、為全域性變數打樁
假設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() }
3、為函式打樁
通常函式分為工程自定義函式與庫函式。
假設工程中自定義函式如下:
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() }
4、為過程打樁
沒有返回值的函式稱為過程。通常將資源清理類函式定義為過程。
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() }
5、複雜場景
不論是呼叫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") }) }) }
6、不適用場景
GoStub框架可以解決很多場景的函式打樁問題,但下列複雜場景除外:
A、被測函式中多次呼叫了資料庫讀操作函式介面,並且資料庫為key-value型。
B、被測函式中有一個迴圈,用於一個批量操作,當某一次操作失敗,則返回失敗,並進行錯誤處理。
C、被測函式中多次呼叫了同一底層操作函式。