天天看點

Golang語言--測試技術

本篇文章内容來源于Golang核心開發組成員Andrew Gerrand在Google I/O 2014的一次主題分享“Testing Techniques”,即介紹使用Golang開發 時會使用到的測試技術(主要針對單元測試),包括基本技術、進階技術(并發測試、mock/fake、競争條件測試、并發測試、内/外部測 試、vet工具等)等,感覺總結的很全面,這裡整理記錄下來,希望能給大家帶來幫助。原Slide通路需要自己搭梯子。另外這裡也要吐槽一 下:Golang官方站的slide都是以一種特有的golang artical的格式放出的(用這個工具http://go-talks.appspot.com/可以線上觀看),沒法像pdf那樣下載下傳,在國内使用和傳播極其不便。

一、基礎測試技術

1、測試Go代碼

Go語言内置測試架構。

内置的測試架構通過testing包以及go test指令來提供測試功能。

下面是一個完整的測試strings.Index函數的完整測試檔案:

Golang語言--測試技術

$go test -v strings_test.go 

=== RUN TestIndex 

— PASS: TestIndex (0.00 seconds) 

PASS 

ok command-line-arguments 0.007s

go test的-v選項是表示輸出詳細的執行資訊。

将代碼中的want常量值修改為3,我們制造一個無法通過的測試:

$go test -v strings_test.go 

=== RUN TestIndex 

— FAIL: TestIndex (0.00 seconds)

strings_test.go:12: Index("chicken","ken") = 4; want 3      

FAIL 

exit status 1 

FAIL command-line-arguments 0.008s

2、表驅動測試

Golang的struct字面值(struct literals)文法讓我們可以輕松寫出表驅動測試。

Golang語言--測試技術

$go test -v strings_test.go 

=== RUN TestIndex 

— PASS: TestIndex (0.00 seconds) 

PASS 

ok command-line-arguments 0.007s

3、T結構

*testing.T參數用于錯誤報告:

t.Errorf(“got bar = %v, want %v”, got, want) 

t.Fatalf(“Frobnicate(%v) returned error: %v”, arg, err) 

t.Logf(“iteration %v”, i)

也可以用于enable并行測試(parallet test): 

t.Parallel()

控制一個測試是否運作:

if runtime.GOARCH == “arm” {  t.Skip("this doesn't work on ARM") }      

4、運作測試

我們用go test指令來運作特定包的測試。

預設執行目前路徑下包的測試代碼。

$ go test 

PASS

$ go test -v 

=== RUN TestIndex 

— PASS: TestIndex (0.00 seconds) 

PASS

要運作工程下的所有測試,我們執行如下指令:

$ go test github.com/nf/…

标準庫的測試: 

$ go test std

注:假設strings_test.go的目前目錄為testgo,在testgo目錄下執行go test都是OK的。但如果我們切換到testgo的上一級目錄執行go test,我們會得到什麼結果呢?

$go test testgo 

can't load package: package testgo: cannot find package “testgo” in any of:

/usr/local/go/src/pkg/testgo (from $GOROOT) /Users/tony/Test/GoToolsProjects/src/testgo (from $GOPATH)      

提示找不到testgo這個包,go test後面接着的應該是一個包名,go test會在GOROOT和GOPATH下查找這個包并執行包的測試。

5、測試覆寫率

go tool指令可以報告測試覆寫率統計。

我們在testgo下執行go test -cover,結果如下:

go build /Users/tony/Test/Go/testgo: no buildable Go source files in /Users/tony/Test/Go/testgo 

FAIL /Users/tony/Test/Go/testgo [build failed]

顯然通過cover參數選項計算測試覆寫率不僅需要測試代碼,還要有被測對象(一般是函數)的源碼檔案。

我們将目錄切換到$GOROOT/src/pkg/strings下,執行go test -cover:

$go test -v -cover 

=== RUN TestReader 

— PASS: TestReader (0.00 seconds) 

… … 

=== RUN: ExampleTrimPrefix 

— PASS: ExampleTrimPrefix (1.75us) 

PASS 

coverage: 96.9% of statements 

ok strings 0.612s

go test可以生成覆寫率的profile檔案,這個檔案可以被go tool cover工具解析。

在$GOROOT/src/pkg/strings下面執行:

$ go test -coverprofile=cover.out

會再目前目錄下生成cover.out檔案。

檢視cover.out檔案,有兩種方法:

a) cover -func=cover.out

$sudo go tool cover -func=cover.out 

strings/reader.go:24: Len 66.7% 

strings/reader.go:31: Read 100.0% 

strings/reader.go:44: ReadAt 100.0% 

strings/reader.go:59: ReadByte 100.0% 

strings/reader.go:69: UnreadByte 100.0% 

… … 

strings/strings.go:638: Replace 100.0% 

strings/strings.go:674: EqualFold 100.0% 

total: (statements) 96.9%

b) 可視化檢視

執行go tool cover -html=cover.out指令,會在/tmp目錄下生成目錄coverxxxxxxx,比如/tmp/cover404256298。目錄下有一個 coverage.html檔案。用浏覽器打開coverage.html,即可以可視化的檢視代碼的測試覆寫情況。

關于go tool的cover指令,我的go version go1.3 darwin/amd64預設并不自帶,需要通過go get下載下傳。

$sudo GOPATH=/Users/tony/Test/GoToolsProjects go get code.google.com/p/go.tools/cmd/cover

下載下傳後,cover安裝在$GOROOT/pkg/tool/darwin_amd64下面。

二、進階測試技術

1、一個例子程式

outyet是一個web服務,用于宣告某個特定Go版本是否已經打标簽釋出了。其擷取方法:

go get github.com/golang/example/outyet

注: 

go get執行後,cd $GOPATH/src/github.com/golang/example/outyet下,執行go run main.go。然後用浏覽器打開http://localhost:8080即可通路該Web服務了。

2、測試Http用戶端和服務端

net/http/httptest包提供了許多幫助函數,用于測試那些發送或處理Http請求的代碼。

3、httptest.Server

httptest.Server在本地回環網口的一個系統選擇的端口上listen。它常用于端到端的HTTP測試。

Golang語言--測試技術

4、httptest.Server實戰

下面代碼建立了一個臨時Http Server,傳回簡單的Hello應答:

Golang語言--測試技術

5、httptest.ResponseRecorder

httptest.ResponseRecorder是http.ResponseWriter的一個實作,用來記錄變化,用在測試的後續檢視中。

Golang語言--測試技術

6、httptest.ResponseRecorder實戰

向一個HTTP handler中傳入一個ResponseRecorder,通過它我們可以來檢視生成的應答。

Golang語言--測試技術

7、競争檢測(race detection)

當兩個goroutine并發通路同一個變量,且至少一個goroutine對變量進行寫操作時,就會發生資料競争(data race)。

為了協助診斷這種bug,Go提供了一個内置的資料競争檢測工具。

通過傳入-race選項,go tool就可以啟動競争檢測。

$ go test -race mypkg // to test the package 

$ go run -race mysrc.go // to run the source file 

$ go build -race mycmd // to build the command 

$ go install -race mypkg // to install the package

注:一個資料競争檢測的例子

例子代碼:

Golang語言--測試技術

$go run -race testrace.go

mainroutine: i = 1

WARNING: DATA RACE 

Read by goroutine 5: 

main.func·001()

/Users/tony/Test/Go/testrace.go:10 +0×49      

Previous write by main goroutine: 

main.main()

/Users/tony/Test/Go/testrace.go:17 +0xd5      

Goroutine 5 (running) created at: 

main.main()

/Users/tony/Test/Go/testrace.go:14 +0xaf      

================== 

subroutine: i = 2 

mainroutine: i = 3 

subroutine: i = 4 

mainroutine: i = 5 

subroutine: i = 6 

mainroutine: i = 7 

subroutine: i = 8

8、測試并發(testing with concurrency)

當測試并發代碼時,總會有一種使用sleep的沖動。大多時間裡,使用sleep既簡單又有效。

但大多數時間不是”總是“。

我們可以使用Go的并發原語讓那些奇怪不靠譜的sleep驅動的測試更加值得信賴。

9、使用靜态分析工具vet查找錯誤

vet工具用于檢測代碼中程式員犯的常見錯誤:

– 錯誤的printf格式 – 錯誤的建構tag – 在閉包中使用錯誤的range循環變量 – 無用的指派操作 – 無法到達的代碼 – 錯誤使用mutex 等等。      

使用方法:

go vet [package]      

10、從内部測試

golang中大多數測試代碼都是被測試包的源碼的一部分。這意味着測試代碼可以通路包種未導出的符号以及内部邏輯。就像我們之前看到的那樣。

注:比如$GOROOT/src/pkg/path/path_test.go與path.go都在path這個包下。

11、從外部測試

有些時候,你需要從被測包的外部對被測包進行測試,比如測試代碼在package foo_test下,而不是在package foo下。

這樣可以打破依賴循環,比如:

– testing包使用fmt – fmt包的測試代碼還必須導入testing包 – 于是,fmt包的測試代碼放在fmt_test包下,這樣既可以導入testing包,也可以同時導入fmt包。      

12、Mocks和fakes

通過在代碼中使用interface,Go可以避免使用mock和fake測試機制。

例如,如果你正在編寫一個檔案格式解析器,不要這樣設計函數:

func Parser(f *os.File) error

作為替代,你可以編寫一個接受interface類型的函數:

func Parser(r io.Reader) error

和bytes.Buffer、strings.Reader一樣,*os.File也實作了io.Reader接口。

13、子程序測試

有些時候,你需要測試的是一個程序的行為,而不僅僅是一個函數。例如:

func Crasher() {  fmt.Println("Going down in flames!")  os.Exit(1) }      

為了測試上面的代碼,我們将測試程式本身作為一個子程序進行測試: