Golang 語言的單元測試和性能測試(也叫 壓力測試)

Golang單元測試對文件名和方法名,參數都有很嚴格的要求。
例如:
  一、文件名必須以xx_test.go命名
  二、方法必須是Test[^a-z]開頭(T必須大寫),func TestXxx (t *testing.T),Xxx部分能夠爲任意的字母數字的組合,可是首字母不能是小寫字母[a-z],例如Testintdiv是錯誤的函數名。
  三、方法參數必須 t *testing.Thtml

       四、測試用例會按照源代碼中寫的順序依次執行java

       五、函數中經過調用testing.TErrorErrorfFailNowFatalFatalIf方法,說明測試不經過,調用Log方法用來記錄測試的信息。nginx

test的運行方式:git

go test -v github.com/welhzh/dago/test

其中後面的test爲test目錄程序員

Go 提供了 TestMain(*testing.M) 的函數,它在須要的時候代替運行全部的測試。使用 TestMain() 函數時,您有機會在測試運行以前或以後插入所需的任何自定義代碼,但惟一須要注意的是必須處理 flag 解析並使用測試結果調用 os.Exit()。這聽起來可能很複雜,但實際上只有兩行代碼。github

 

測試的覆蓋率golang

go tool命令能夠報告測試覆蓋率統計。使用 go test -cover 測試覆蓋率。瀏覽器

可視化查看覆蓋率:閉包

執行go tool cover -html=cover.out命令,會在/tmp目錄下生成目錄coverxxxxxxx,好比/tmp/cover404256298。目錄下有一個 coverage.html文件。用瀏覽器打開coverage.html,便可以可視化的查看代碼的測試覆蓋狀況。併發

 

從內部測試

golang中大多數測試代碼都是被測試包的源碼的一部分。這意味着測試代碼能夠訪問包種未導出的符號以及內部邏輯。就像咱們以前看到的那樣。

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

從外部測試

有些時候,你須要從被測包的外部對被測包進行測試,好比測試代碼在package foo_test下,而不是在package foo下。這樣能夠打破依賴循環。

 

一、功能測試

上一個完整的例子:

// how to run: go test -v github.com/welhzh/dago/test
package client

import (
    "flag"
    "fmt"
    "os"
    "testing"

    "github.com/welhzh/dago/client"
)

func initTest() {
}

func destroyTest() {
}
// test example func TestSum(t *testing.T) { // 任選一種測試方式進行測試 // ================= test 方式一 ==================== var testInputs = []struct { slice []int // other inputs expect int }{ {[]int{1, 2, 3, 4, 5}, 15}, {[]int{1, 2, 3, 4, -5}, 5}, } for _, oneInput := range testInputs { actual := oneInput.slice[2] if actual != oneInput.expect { t.Errorf("Sum(%q, %q) = %v; want %v", oneInput, oneInput, actual, oneInput.expect) } } // ================================================= // ================= test 方式二 ==================== t.Run("[1,2,3,4,5]", testSumFunc(testInputs[0].slice, testInputs[0].expect)) t.Run("[1,2,3,4,-5]", testSumFunc(testInputs[1].slice, testInputs[1].expect)) // ================================================= } func testSumFunc(numbers []int, expected int) func(*testing.T) { return func(t *testing.T) { actual := numbers[4] if actual != expected { t.Error(fmt.Sprintf("Expected the sum of %v to be %d but instead got %d!", numbers, expected, actual)) } } } func TestMain(m *testing.M) { fmt.Println("test begin ...") initTest() flag.Parse() exitCode := m.Run() destroyTest() fmt.Println("test end ...") // Exit os.Exit(exitCode) }

 

格式形如:

 

go test [-c] [-i] [build flags] [packages] [flags for test binary]

參數解讀:

-c : 編譯go test成爲可執行的二進制文件,可是不運行測試。

-i : 安裝測試包依賴的package,可是不運行測試。

關於build flags,調用go help build,這些是編譯運行過程當中須要使用到的參數,通常設置爲空

關於packages,調用go help packages,這些是關於包的管理,通常設置爲空

關於flags for test binary,調用go help testflag,這些是go test過程當中常用到的參數

-test.v : 是否輸出所有的單元測試用例(無論成功或者失敗),默認沒有加上,因此只輸出失敗的單元測試用例。

-test.run pattern: 只跑哪些單元測試用例

-test.bench patten: 只跑那些性能測試用例

-test.benchmem : 是否在性能測試的時候輸出內存狀況

-test.benchtime t : 性能測試運行的時間,默認是1s

-test.cpuprofile cpu.out : 是否輸出cpu性能分析文件

-test.memprofile mem.out : 是否輸出內存性能分析文件

-test.blockprofile block.out : 是否輸出內部goroutine阻塞的性能分析文件

-test.memprofilerate n : 內存性能分析的時候有一個分配了多少的時候纔打點記錄的問題。這個參數就是設置打點的內存分配間隔,也就是profile中一個sample表明的內存大小。默認是設置爲512 * 1024的。若是你將它設置爲1,則每分配一個內存塊就會在profile中有個打點,那麼生成的profile的sample就會很是多。若是你設置爲0,那就是不作打點了。

你能夠經過設置memprofilerate=1和GOGC=off來關閉內存回收,而且對每一個內存塊的分配進行觀察。

-test.blockprofilerate n: 基本同上,控制的是goroutine阻塞時候打點的納秒數。默認不設置就至關於-test.blockprofilerate=1,每一納秒都打點記錄一下

-test.parallel n : 性能測試的程序並行cpu數,默認等於GOMAXPROCS。

-test.timeout t : 若是測試用例運行時間超過t,則拋出panic

-test.cpu 1,2,4 : 程序運行在哪些CPU上面,使用二進制的1所在位表明,和nginx的nginx_worker_cpu_affinity是一個道理

-test.short : 將那些運行時間較長的測試用例運行時間縮短

 

二、性能測試

若是須要進行性能測試,則函數開頭使用Benchmark就能夠了。

//性能測試
func BenchmarkFibonacci(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Fibonacci(10)
    }
}

接下來執行這個性能測試:

$ go test -bench=. lib
 PASS
 BenchmarkFibonacci 5000000 436 ns/op
 ok lib 2.608s

其中第二行輸出表示這個函數運行了5000000次,平均運行一次的時間是436ns。

這個性能測試只測試參數爲10的狀況。若是有須要能夠測試多個參數:

//測試參數爲5的性能
func BenchmarkFibonacci5(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Fibonacci(5)
    }
}
 
//測試參數爲20的性能
func BenchmarkFibonacci20(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Fibonacci(20)
    }
}

運行一下:

$ go test -bench=. lib
 PASS
 BenchmarkFibonacci 5000000 357 ns/op
 BenchmarkFibonacci5 100000000 29.5 ns/op
 BenchmarkFibonacci20 50000 44688 ns/op
 ok lib 7.824s

若是性能測試的方法很是多,那須要的時間就會比較久。能夠經過-bench=參數設置須要運行的性能測試的函數:

$ go test -bench=Fibonacci20 lib
 PASS
 BenchmarkFibonacci20 50000 44367 ns/op
 ok lib 2.677s

 

 

三、IO相關測試 (高級測試技術)

testing/iotest包中實現了經常使用的出錯的Reader和Writer,可供咱們在io相關的測試中使用。主要有:

觸發數據錯誤dataErrReader,經過DataErrReader()函數建立

讀取一半內容的halfReader,經過HalfReader()函數建立

讀取一個byte的oneByteReader,經過OneByteReader()函數建立

觸發超時錯誤的timeoutReader,經過TimeoutReader()函數建立

寫入指定位數內容後中止的truncateWriter,經過TruncateWriter()函數建立

讀取時記錄日誌的readLogger,經過NewReadLogger()函數建立

寫入時記錄日誌的writeLogger,經過NewWriteLogger()函數建立

 

四、黑盒測試 (高級測試技術)

testing/quick包實現了幫助黑盒測試的實用函數 Check和CheckEqual。

Check函數的第1個參數是要測試的只返回bool值的黑盒函數f,Check會爲f的每一個參數設置任意值並屢次調用,若是f返回false,Check函數會返回錯誤值 *CheckError。Check函數的第2個參數 能夠指定一個quick.Config類型的config,傳nil則會默認使用quick.defaultConfig。quick.Config結構體包含了測試運行的選項。

# /usr/local/go/src/math/big/int_test.go
func checkMul(a, b []byte) bool {
 var x, y, z1 Int
 x.SetBytes(a)
 y.SetBytes(b)
 z1.Mul(&x, &y)
 
 var z2 Int
 z2.SetBytes(mulBytes(a, b))
 
 return z1.Cmp(&z2) == 0
}
 
func TestMul(t *testing.T) {
 if err := quick.Check(checkMul, nil); err != nil {
  t.Error(err)
 }
}

CheckEqual函數是比較給定的兩個黑盒函數是否相等,函數原型以下:

func CheckEqual(f, g interface{}, config *Config) (err error)

 

五、http測試 (高級測試技術)

net/http/httptest包提供了HTTP相關代碼的工具,咱們的測試代碼中能夠建立一個臨時的httptest.Server來測試發送HTTP請求的代碼:

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)

還能夠建立一個應答的記錄器httptest.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())

 

六、在進程裏測試 (高級測試技術)

當咱們被測函數有操做進程的行爲,能夠將被測程序做爲一個子進程執行測試。下面是一個例子:

//被測試的進程退出函數
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(

 

七、競爭檢測 (高級測試技術)

當兩個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.go

package main

import 「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

 

八、併發測試 (高級測試技術)testing with concurrency

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

但大多數時間不是」老是「。

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

 

九、靜態分析工具vet查找錯誤 (高級測試技術)

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

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

使用方法:

go vet [package]

 

十、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接口。

相關文章
相關標籤/搜索