GoStub是一款輕量級的單元測試框架,接口友好,可以對全局變量、函數(shù)或過程進(jìn)行打樁。
GoStub安裝:go get github.com/prashantv/gostub
成都創(chuàng)新互聯(lián)公司專注于企業(yè)網(wǎng)絡(luò)營銷推廣、網(wǎng)站重做改版、貢山網(wǎng)站定制設(shè)計、自適應(yīng)品牌網(wǎng)站建設(shè)、H5響應(yīng)式網(wǎng)站、商城網(wǎng)站制作、集團(tuán)公司官網(wǎng)建設(shè)、外貿(mào)營銷網(wǎng)站建設(shè)、高端網(wǎng)站制作、響應(yīng)式網(wǎng)頁設(shè)計等建站業(yè)務(wù),價格優(yōu)惠性價比高,為貢山等各大城市提供網(wǎng)站開發(fā)制作服務(wù)。
gostub用于在測試時打樁變量,一旦測試運行時,重置原來的值。
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是可賦值到變量的類型
func StubFunc(funcVarToStub interface{}, stubVal ...interface{}) *Stubs {
return New().StubFunc(funcVarToStub, stubVal...)
}
StubFunc用返回stubval值的函數(shù)替換函數(shù)變量,返回*Stubs
類型變量
funcVarToStub是指向函數(shù)變量的指針。如果函數(shù)返回多個值,返回的多個值被傳遞給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設(shè)置指定的環(huán)境變量到指定值func (s *Stubs) UnsetEnv(k string) *Stubs
UnsetEnv還原指定環(huán)境變量的值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值的函數(shù)替換函數(shù)變量,返回*Stubs
類型變量
funcVarToStub是指向函數(shù)變量的指針。如果函數(shù)返回多個值,返回的多個值被傳遞給StubFunc。
GoStub框架的使用場景如下:
A、為一個全局變量打樁
B、為一個函數(shù)打樁
C、為一個過程打樁
D、由任意相同或不同的基本場景組合而成
假設(shè)counter為被測函數(shù)中使用的一個全局整型變量,當(dāng)前測試用例中假定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框架的函數(shù)接口Stub返回的對象,Reset方法將全局變量的值恢復(fù)為原值。
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文件的內(nèi)容
data, err := GetConfig()
if err != nil {
fmt.Println(err)
}
fmt.Println(data)
}
func main() {
stubGlobalVariable()
}
通常函數(shù)分為工程自定義函數(shù)與庫函數(shù)。
假設(shè)工程中自定義函數(shù)如下:
func Exec(cmd string, args ...string) (string, error) {
...
}
Exec函數(shù)是不能通過GoStub框架打樁的。如果想要通過GoStub框架對Exec函數(shù)進(jìn)行打樁,則僅需對自定義函數(shù)進(jìn)行簡單的重構(gòu),即將Exec函數(shù)定義為匿名函數(shù),同時將其賦值給Exec變量,重構(gòu)后的代碼如下:
var Exec = func(cmd string, args ...string) (string, error) {
...
}
當(dāng)Exec函數(shù)重構(gòu)成Exec變量后,并不影響既有代碼中對Exec函數(shù)的調(diào)用。由于Exec變量是函數(shù)變量,因此一般函數(shù)變量也叫做函數(shù)。對Exec函數(shù)變量進(jìn)行打樁的代碼如下:
stubs := Stub(&Exec, func(cmd string, args ...string) (string, error) {
return "test", nil
})
defer stubs.Reset()
GoStub框架專門提供了StubFunc函數(shù)用于函數(shù)打樁,對于函數(shù)的打樁代碼如下:
stubs := StubFunc(&Exec,"test", nil)
defer stubs.Reset()
工程代碼中會調(diào)用Golang庫函數(shù)或第三方庫函數(shù),由于不能重構(gòu)庫函數(shù),因此需要在工程代碼中增加一層適配層,在適配層中定義庫函數(shù)的變量,然后在工程代碼中使用函數(shù)變量。
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()
}
沒有返回值的函數(shù)稱為過程。通常將資源清理類函數(shù)定義為過程。
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()
}
不論是調(diào)用Stub函數(shù)還是StubFunc函數(shù),都會生成一個Stubs對象,Stubs對象仍然有Stub方法和StubFunc方法,所以在一個測試用例中可以同時對多個全局變量、函數(shù)或過程打樁。全局變量、函數(shù)或過程會將初始值存在一個map中,并在延遲語句中通過Reset方法統(tǒng)一做回滾處理。
多次打樁代碼如下:
stubs := gostub.Stub(&v1, 1)
defer stubs.Reset()
// Do some testing
stubs.Stub(&v1, 5)
// More testing
stubs.Stub(&b2, 6)
多次打樁的級聯(lián)表達(dá)式代碼如下: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框架可以解決很多場景的函數(shù)打樁問題,但下列復(fù)雜場景除外:
A、被測函數(shù)中多次調(diào)用了數(shù)據(jù)庫讀操作函數(shù)接口,并且數(shù)據(jù)庫為key-value型。
B、被測函數(shù)中有一個循環(huán),用于一個批量操作,當(dāng)某一次操作失敗,則返回失敗,并進(jìn)行錯誤處理。
C、被測函數(shù)中多次調(diào)用了同一底層操作函數(shù)。