Golang測試技術

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

本篇文章內容來源於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函數的完整測試檔案:

//strings_test.go (這裡範例代碼放入strings_test.go檔案中) package strings_testimport ("strings""testing")func TestIndex(t *testing.T) {const s, sep, want = "chicken", "ken", 4got := strings.Index(s, sep)if got != want {    t.Errorf("Index(%q,%q) = %v; want %v", s, sep, got, want)//注意原slide中的got和want寫反了}}

$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)文法讓我們可以輕鬆寫出表驅動測試。

package strings_testimport ("strings"    "testing")func TestIndex(t *testing.T) {var tests = []struct {            s   string            sep string            out int    }{            {"", "", 0},            {"", "a", -1},            {"fo", "foo", -1},            {"foo", "foo", 0},            {"oofofoofooo", "f", 2},            // etc    }    for _, test := range tests {            actual := strings.Index(test.s, test.sep)            if actual != test.out {                    t.Errorf("Index(%q,%q) = %v; want %v",                         test.s, test.sep, actual, test.out)            }    }}

$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測試。

type Server struct {URL      string // base URL of form http://ipaddr:port with no trailing slashListener net.Listener// TLS is the optional TLS configuration, populated with a new config// after TLS is started. If set on an unstarted server before StartTLS// is called, existing fields are copied into the new config.TLS *tls.Config// Config may be changed after calling NewUnstartedServer and// before Start or StartTLS.Config *http.Server}func NewServer(handler http.Handler) *Serverfunc (*Server) Close() error

4、httptest.Server實戰

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

ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {    fmt.Fprintln(w, "Hello, client")}))defer ts.Close()res, err := http.Get(ts.URL)if err != nil {    log.Fatal(err)}greeting, err := ioutil.ReadAll(res.Body)res.Body.Close()if err != nil {    log.Fatal(err)}fmt.Printf("%s", greeting)

5、httptest.ResponseRecorder

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

type ResponseRecorder struct {Code      int           // the HTTP response code from WriteHeaderHeaderMap http.Header   // the HTTP response headersBody      *bytes.Buffer // if non-nil, the bytes.Buffer to append written data toFlushed   bool }

6、httptest.ResponseRecorder實戰

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

handler := func(w http.ResponseWriter, r *http.Request) {    http.Error(w, "something failed", http.StatusInternalServerError)}req, err := http.NewRequest("GET", "http://example.com/foo", nil)if err != nil {    log.Fatal(err)}w := httptest.NewRecorder()handler(w, req)fmt.Printf("%d – %s", w.Code, w.Body.String())

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

註:一個資料競爭檢測的例子

例子代碼:

//testrace.gopackage mainimport “fmt” import “time”func main() {var i int = 0    go func() {            for {                    i++                    fmt.Println("subroutine: i = ", i)                    time.Sleep(1 * time.Second)            }    }()    for {            i++            fmt.Println("mainroutine: i = ", i)            time.Sleep(1 * time.Second)    }}

$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)}

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

func TestCrasher(t *testing.T) {if os.Getenv("BE_CRASHER") == "1" {    Crasher()    return}cmd := exec.Command(os.Args[0], "-test.run=TestCrasher")cmd.Env = append(os.Environ(), "BE_CRASHER=1")err := cmd.Run()if e, ok := err.(*exec.ExitError); ok && !e.Success() {    return}t.Fatalf("process ran with err %v, want exit status 1", err)}

2014, bigwhite. 著作權.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.