這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
《南皮縣誌·風土誌下·歌謠》:“兵馬不動,糧草先行”。作戰時兵馬還沒出動,軍用糧草的運輸要先行一步。在開發新功能之前,先編寫測試代碼,然後只編寫使測試通過的功能代碼,這種測試驅動開發的軟體開發模式是我非常推薦的。
對 Dit 的貢獻要求需要通過單元測試,編寫 Dit 的任意模組,都需要一併編寫測試案例。本文先簡述一下 Go 對測試的支援,後續會陸續提供 Dit 的測試方案和測試報告。
Go 對測試的支援
Go 內建的測試架構 testing 支援單元測試和效能測試。Go 規定測試檔案以 _test.go
為尾碼,使用命令 go test
命令自動運行測試案例。
單元測試
以 Test
開頭的方法為一個測試案例,並擁有一個參數 *testing.T
, 寫法如下:
func TestXxx(*testing.T)
Xxx
部分為任意的字母數字組合,首字母不能是小寫字母。*testing.T
可記錄錯誤或者標記錯誤狀態。可通過Short判斷略過一部分測試,加快測試時間。如下:
func TestTimeConsuming(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } ...}
效能測試
效能測試用例以 Benchmark
開始,參數為 *testing.B
, 寫法如下:
func BenchmarkXxx(*testing.B)
使用 go test
運行效能測試用例時,需要加上參數 -bench
。
在編寫效能測試用例時,需牢記在迴圈體內使用 testing.B.N
, 以使測試可以正常的運行:
func BenchmarkHello(b *testing.B) { for i := 0; i < b.N; i++ { fmt.Sprintf("hello") }}
對耗時的初始化操作,可在測試案例內部重設計時器 ResetTimer
,確保引入不必要的誤差:
func BenchmarkBigLen(b *testing.B) { big := NewBig() b.ResetTimer() for i := 0; i < b.N; i++ { big.Len() }}
使用 RunParallel
測試並行模組, 運行時需要加上參數 -cpu
:
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") } })}
標準輸出測試
testing 包提供對標準終端輸出的測試, 測試案例以 Example
開始,結果使用注釋的方式,寫在 Output:
之後:
func ExampleHello() { fmt.Println("hello") // Output: hello}
Example 用例的命名規則:
func Example() { ... }func ExampleF() { ... } // F - functionfunc ExampleT() { ... } // T - typefunc ExampleT_M() { ... } // T_M - method M on type T// 多個 example 可用尾碼區分,The suffix must start with a lower-case letter.func Example_suffix() { ... }func ExampleF_suffix() { ... }func ExampleT_suffix() { ... }func ExampleT_M_suffix() { ... }
Main 測試
當需要批量 setup 或 teardown 測試環境時,可使用:
func TestMain(m *testing.M)
TestMain 簡單實現如下:
func TestMain(m *testing.M) { flag.Parse() os.Exit(m.Run())}
Demo
寫一個簡單的樣本,來 Demo 一下 testing 測試架構的用法。
// div.gopackage testimport ( "errors")func div(a, b int) (int, error) { if b == 0 { return 0, errors.New("b must NOT be 0") } return a / b, nil}// div_test.gopackage testimport ( "errors" "flag" "fmt" "os" "testing" "time")func TestDivNormal(t *testing.T) { ret, err := div(6, 2) if err != nil || ret != 3 { t.Error("6/2=3") }}func TestDivZero(t *testing.T) { _, err := div(6, 0) if err.Error() != errors.New("b must NOT be 0").Error() { t.Error("zero div error") }}func BenchmarkDiv(b *testing.B) { b.Log("run times:", b.N) for i := 0; i < b.N; i++ { div(6, 2) }}func BenchmarkDiv_Sleep(b *testing.B) { b.Log("run times:", b.N) time.Sleep(3000) // 類比費時操作 b.ResetTimer() for i := 0; i < b.N; i++ { div(6, 2) }}func ExampleOutput() { ret, _ := div(6, 2) fmt.Println("6 / 2 =", ret) // Output: // 6 / 2 = 3}func TestMain(m *testing.M) { flag.Parse() fmt.Println("Setup ... Done") ret := m.Run() fmt.Println("Teardown ... Done") os.Exit(ret)}
運行 go test -v
, 結果如下:
localhost:test zdd$ go test -vSetup ... Done=== RUN TestDivNormal--- PASS: TestDivNormal (0.00s)=== RUN TestDivZero--- PASS: TestDivZero (0.00s)=== RUN: ExampleOutput--- PASS: ExampleOutput (0.00s)PASSTeardown ... Doneok github.com/zddhub/hellogo/test 0.005s
預設不執行效能測試用例,使用go test -v -bench .
執行:
localhost:test zdd$ go test -v -bench .Setup ... Done=== RUN TestDivNormal--- PASS: TestDivNormal (0.00s)=== RUN TestDivZero--- PASS: TestDivZero (0.00s)=== RUN: ExampleOutput--- PASS: ExampleOutput (0.00s)PASSBenchmarkDiv 100000000 16.2 ns/op--- BENCH: BenchmarkDiv div_test.go:27: run times: 1 div_test.go:27: run times: 100 div_test.go:27: run times: 10000 div_test.go:27: run times: 1000000 div_test.go:27: run times: 100000000BenchmarkDiv_Sleep 100000000 16.4 ns/op--- BENCH: BenchmarkDiv_Sleep div_test.go:34: run times: 1 div_test.go:34: run times: 100 div_test.go:34: run times: 10000 div_test.go:34: run times: 1000000 div_test.go:34: run times: 100000000Teardown ... Doneok github.com/zddhub/hellogo/test 3.305s
木乙言己 zddhub 出品
號: zddnotes
Just for fun!
文章唯寫給自己,如果你也喜歡,歡迎掃描以下二維碼關注哦~