深入Go語言 - 12 測試、效能測試以及程式碼範例的編寫

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

目錄 [−]

  1. 測試
  2. 測試比較工具
  3. TDT (Table Driven Tests)
  4. 測試覆蓋率
  5. 效能測試
  6. Example

本章介紹Go語言的代碼的測試、效能測試以及樣本的寫法。

測試

雖然也有一些第三方的基於某種概念開發的測試庫,但是我覺得最好用還是官方的測試庫: testing。

常規地,我們會把測試代碼的檔案和正式的代碼檔案放在同一個檔案夾下,但是包含測試代碼的檔案應該以"_test.go"結尾。

測試檔案的包名可以和正式檔案的包名相同,也可以不同。比如正式的報名為abc,測試的報名就可以是abc_test,但是不能是其它的,比如xyz
這兩種風格官方庫中都有。一般來說和正式的包名相同的話,我們就可以進行白盒測試,可以直接調用包下的未匯出的方法,包名不同則進行黑箱測試。根據 "The Go programming language"一書的介紹,這種方案還可以避免循環相依性的問題。

測試的檔案名稱不能以底線或者.開始,這些檔案不會被go test包含進來。

測試的方法名有特殊的定義,以"Test"開頭,並且參數為*testing.T

1
func TestXxx(*testing.T)

Xxx可以是任意的alphanumeric字串,但是首字母X不能是[a-z]中的字元,Testxyz就不是一個測試函數,但是Test123就是。

很多語言比如Java中的Junit、testng都提供了assertion輔助函數,可以方便的判定測試結果是否和期望的結果是否一致,但是Go官方並沒有提供,而且是有意為之,說是避免讓程式員犯懶。有地方庫提供了相應的功能,比如testify/assert。

如果測試結果不是你所期望的,你可以調用FailError等方法觸發失敗訊號。

正常編譯的時候測試檔案會被排除在外,但是調用go test測試的時候會包含進來。

通過Skip方法可以掉過測試:

123456
func TestTimeConsuming(t *testing.T) {    if testing.Short() {        t.Skip("skipping test in short mode.")    }    ...}

完整的測試命令如下:
go test [build/test flags] [packages] [build/test flags & test binary flags]

不帶任何參數的時候,它會編譯和測試包下的所有的源檔案。

除了build flag,test還會額外的處理幾個編譯flag: -args-c-exec xprog-i-o file

packages可以是絕對路徑、相對路徑(.或..)或者只是一個包名(go會在GOPATH環境變數的列表中尋找DIR/src/P,假設DIR在環境變數定義的檔案清單中, P為包名)。...可以模糊比對字串,比如x/...匹配x及x的子檔案夾。 go help packages會給出詳細的介紹。

build flag包含很多的flag,一般我們都不會加這些flag,如果你想瞭解,可以看官方文檔。

官方文檔的Description of testing flags描述了全部的測試flag,我們挑幾個常用的看一下。

  • -bench regexp:效能測試,支援運算式對測試函數進行篩選。-bench .則是對所有的benchmark函數測試
  • -benchmem:效能測試的時候顯示測試函數的記憶體配置的統計資訊
  • -count n:運行測試和效能多少此,預設一次
  • -run regexp:只運行特定的測試函數, 比如-run ABC只測試函數名中包含ABC的測試函數
  • -timeout t:測試時間如果超過t, panic,預設10分鐘
  • -v:顯示測試的詳細資料,也會把LogLogf方法的日誌顯示出來

go test -v -args -x -v會編譯然後執行程式:pkg.test -test.v -x -v,這樣你就容易理解args參數的意義了。

Go 1.7中開始支援 sub-test的概念。

參考

  • https://golang.org/pkg/testing/
  • https://golang.org/doc/code.html#Testing
  • https://golang.org/cmd/go/#hdr-Test_packages
  • https://github.com/shageman/gotestit

測試比較工具

效能測試至關重要,你經常會問"A 更快還是 B更快",當然還的靠效能資料說話。當然效能測試並不是一件簡單的事情,今早我還看到陳皓寫的一篇批判Dubbo測試的一篇文章:效能測試應該怎麼做?。還好Go提供了一種容易的寫效能測試的方法,但是如何比較多個候選者之間的效能呢?

一種方式就是編寫多個測試函數,每個測試函數只測試一種候選方案,然後看測試的結果,比如我為Go序列化架構寫的效能測試:gosercomp。

本節要介紹的第一個工具就是 benchcmp,它可以比較兩個版本之間的效能的提升或者下降。比如你的程式碼程式庫在Go 1.6.2和Go 1.7編譯後的效能的改變:

123
go test -run=NONE -bench=. ./... > old.txt# make changesgo test -run=NONE -bench=. ./... > new.txt

然後用這個工具進行比較:

123456789
$ benchcmp old.txt new.txtbenchmark           old ns/op     new ns/op     deltaBenchmarkConcat     523           68.6          -86.88%benchmark           old allocs     new allocs     deltaBenchmarkConcat     3              1              -66.67%benchmark           old bytes     new bytes     deltaBenchmarkConcat     80            48            -40.00%

第二個工具是prettybench,它可以將Go自己的效能的測試報告美化,更好讀:

PASSbenchmark                                    iter    time/iter---------                                    ----    ---------BenchmarkCSSEscaper                       1000000   2843 ns/opBenchmarkCSSEscaperNoSpecials             5000000    671 ns/opBenchmarkDecodeCSS                        1000000   1183 ns/opBenchmarkDecodeCSSNoSpecials             50000000     32 ns/opBenchmarkCSSValueFilter                   5000000    501 ns/opBenchmarkCSSValueFilterOk                 5000000    707 ns/opBenchmarkEscapedExecute                    500000   6191 ns/opBenchmarkHTMLNospaceEscaper               1000000   2523 ns/opBenchmarkHTMLNospaceEscaperNoSpecials     5000000    596 ns/opBenchmarkStripTags                        1000000   2351 ns/opBenchmarkStripTagsNoSpecials             10000000    260 ns/opBenchmarkJSValEscaperWithNum              1000000   1123 ns/opBenchmarkJSValEscaperWithStr               500000   4882 ns/opBenchmarkJSValEscaperWithStrNoSpecials    1000000   1461 ns/opBenchmarkJSValEscaperWithObj               500000   5052 ns/opBenchmarkJSValEscaperWithObjNoSpecials    1000000   1897 ns/opBenchmarkJSStrEscaperNoSpecials           5000000    608 ns/opBenchmarkJSStrEscaper                     1000000   2633 ns/opBenchmarkJSRegexpEscaperNoSpecials        5000000    661 ns/opBenchmarkJSRegexpEscaper                  1000000   2510 ns/opBenchmarkURLEscaper                        500000   4424 ns/opBenchmarkURLEscaperNoSpecials             5000000    422 ns/opBenchmarkURLNormalizer                     500000   3068 ns/opBenchmarkURLNormalizerNoSpecials          5000000    431 ns/opok      html/template    62.874s

第三個要介紹的工具是benchviz,它使用benchcmp的結果,但是可以圖形化顯示效能的提升:

benchstat這個工具可以將多次測試的結果匯總,產生概要資訊。

參考:

  • http://dominik.honnef.co/posts/2014/12/an_incomplete_list_of_go_tools/

TDT (Table Driven Tests)

TDT也叫表格驅動方法,有時也被歸為關鍵字驅動測試(keyword-driven testing,是針對自動化測試的軟體測試方法,它將建立測試程式的步驟分為規劃及實現二個階段。

Go官方庫中有些測試就使用了這種測試方法。

TDT中每個表格項就是一個完整的test case,包含輸入和期望的輸出,有時候還會加一些額外的資訊比如測試名稱。如果你發現你的測試中經常copy/paste操作,你就可以考慮把它們改造成TDT。

測試代碼就一塊,但是可以測試表格中的每一項。

下面是一個例子:

123456789101112131415161718192021222324252627
var flagtests = []struct {    in  string    out string}{    {"%a", "[%a]"},    {"%-a", "[%-a]"},    {"%+a", "[%+a]"},    {"%#a", "[%#a]"},    {"% a", "[% a]"},    {"%0a", "[%0a]"},    {"%1.2a", "[%1.2a]"},    {"%-1.2a", "[%-1.2a]"},    {"%+1.2a", "[%+1.2a]"},    {"%-+1.2a", "[%+-1.2a]"},    {"%-+1.2abc", "[%+-1.2a]bc"},    {"%-1.2abc", "[%-1.2a]bc"},}func TestFlagParser(t *testing.T) {    var flagprinter flagPrinter    for _, tt := range flagtests {        s := Sprintf(tt.in, &flagprinter)        if s != tt.out {            t.Errorf("Sprintf(%q, &flagprinter) => %q, want %q", tt.in, s, tt.out)        }    }}

參考

  • https://github.com/golang/go/wiki/TableDrivenTests
  • http://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go

測試覆蓋率

從 Go 1.2開始, Go就提供了一個產生程式碼涵蓋範圍的工具cover

程式碼涵蓋範圍描述了包中代碼有多少語句被測試所覆蓋。

比如代碼:

1234567891011121314151617
package sizefunc Size(a int) string {    switch {    case a < 0:        return "negative"    case a == 0:        return "zero"    case a < 10:        return "small"    case a < 100:        return "big"    case a < 1000:        return "huge"    }    return "enormous"}

測試代碼:

12345678910111213141516171819202122
package sizeimport "testing"type Test struct {    in  int    out string}var tests = []Test{    {-1, "negative"},    {5, "small"},}func TestSize(t *testing.T) {    for i, test := range tests {        size := Size(test.in)        if size != test.out {            t.Errorf("#%d: Size(%d)=%s; want %s", i, test.in, size, test.out)        }    }}

查看程式碼涵蓋範圍:

12345
% go test -coverPASScoverage: 42.9% of statementsok      size    0.026s%

想查看詳細的覆蓋率,可以產生coverage profile檔案:

12345
% go test -coverprofile=coverage.outPASScoverage: 42.9% of statementsok      size    0.030s%

產生html測試報告:

1
$ go tool cover -html=coverage.out

一些網站可以協助你測試產生程式碼涵蓋範圍,你還可以把你的項目的badge寫在README檔案中, 比如gocover、coveralls

參考

  • https://blog.golang.org/cover

效能測試

效能測試的寫法和單元測試的寫法類似,但是用"Benchmark"代替"Test"作為函數的開頭,而且函數的參數改為*testing.B:

1
func BenchmarkXxx(*testing.B)

測試的時候,加上 -bench就可以執行效能的測試,如go test -bench .

一個簡單的效能測試代碼如下:

12345
func BenchmarkHello(b *testing.B) {    for i := 0; i < b.N; i++ {        fmt.Sprintf("hello")    }}

測試代碼會執行b.N次,但是N會根據你的代碼的效能進行調整,代碼執行的快,N會大一些,代碼慢,N就小一些。
測試結果如下,執行了10000000次測試,每次測試花費282納秒:

1
BenchmarkHello    10000000    282 ns/op

如果測試之前你需要準備一些花費時間較長的工作,你可以調用ResetTimer指定測試開始的時機:

1234567
func BenchmarkBigLen(b *testing.B) {    big := NewBig()    b.ResetTimer()    for i := 0; i < b.N; i++ {        big.Len()    }}

如果需要並行地執行測試,可以在測試的時候加上-cpu參數,可以執行RunParallel輔助方法:

12345678910
func BenchmarkTemplateParallel(b *testing.B) {    templ := template.Must(template.New("test").Parse("Hello, { {.} }!"))    b.RunParallel(func(pb *testing.PB) {        var buf bytes.Buffer        for pb.Next() {            buf.Reset()            templ.Execute(&buf, "World")        }    })}

Example

一個程式碼範例函數就像一個測試函數一樣,但是它並不使用*testing.T作為參數報告錯誤或失敗,而是將輸出結果輸出到 os.Stdout 和 os.Stderr。
輸出結果會和函數內的Output:注釋中的結果比較, 這個注釋在函數體的最底部。如果沒有`Output:注釋,或者它的後面沒有文本,則代碼只會編譯,不會執行。

Godoc 可以顯示 ExampleXXX 的實現代碼, 用來示範函數XXX或者常量XXX或者變數XXX的使用。如果receiver為T或者*T的方法M,它的範例程式碼的命名方式應該是ExampleT_M。如果一個函數、常量或者變數有多可以在範例程式碼的方法名後加尾碼_xxx, xxx的第一個字元不能大寫。

舉個例子:

1234567891011
package ch11import "fmt"func ExampleAdd() {k := Add(1, 2)fmt.Println(k)// Output:// 3}

相信你已經在godoc中看到了很多這樣的例子,你也應該為你的庫提供相應的例子,這樣別人很容易熟悉你的代碼。

你可以使用go help testfunc查看詳細說明。

範例程式碼的檔案名稱一般用example_test.go, 因為它們也是測試函數,所以檔案名稱要以"_test.go"結尾。

運行測試代碼:go test -v可以看到樣本函數也被測試了。
如果我們將上例注釋中的// 3改為// 2,運行go test -v可以看到出錯,因為執行結果和我們的輸出不一致。

12345678910111213
smallnestMBP:ch11 smallnest$ go test -v=== RUN   Test123--- PASS: Test123 (0.00s)=== RUN   ExampleAdd--- FAIL: ExampleAdd (0.00s)got:3want:2FAILexit status 1FAILgithub.com/smallnest/dive-into-go/ch110.005ssmallnestMBP:ch11 smallnest$

有時候,我們可能要為一組函數寫一個樣本,這個時候我們就需要一個whole file example,一個whole file example以"_test.go"結尾,只包含一個樣本函數,沒有測試函數或者效能測試函數,至少包含一個其它包層級的聲明,如下例就是一個完整的檔案:

123456789101112131415161718
bool { return a[i].Age < a[j].Age }func Example() {    people := []Person{        {"Bob", 31},        {"John", 42},        {"Michael", 17},        {"Jenny", 26},    }    fmt.Println(people)    sort.Sort(ByAge(people))    fmt.Println(people)    // Output:    // [Bob: 31 John: 42 Michael: 17 Jenny: 26]    // [Michael: 17 Jenny: 26 Bob: 31 John: 42]}

參考

  • https://blog.golang.org/examples

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.