1. 程式人生 > >Go語言開發(二十)、GoStub測試框架

Go語言開發(二十)、GoStub測試框架

str urn ... art 有一個 進行 single 單元測試框架 gost

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.
   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值的函數替換函數變量,返回*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、被測函數中多次調用了同一底層操作函數。

Go語言開發(二十)、GoStub測試框架