Golang單元測試對文件名和方法名,參數都有很嚴格的要求。
例如:
一、文件名必須以xx_test.go命名
二、方法必須是Test[^a-z]開頭(T必須大寫),func TestXxx (t *testing.T)
,Xxx
部分能夠爲任意的字母數字的組合,可是首字母不能是小寫字母[a-z],例如Testintdiv
是錯誤的函數名。
三、方法參數必須 t *testing.Thtml
四、測試用例會按照源代碼中寫的順序依次執行java
五、函數中經過調用testing.T
的Error
, Errorf
, FailNow
, Fatal
, FatalIf
方法,說明測試不經過,調用Log
方法用來記錄測試的信息。nginx
test的運行方式:git
其中後面的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接口。