這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
前言
用了go的測試架構,再想下junit的,雖然已經Junit5,那醜陋程度還是依然。
java出來的時候,還沒有很多軟體工程的概念,語言先出來了,因此需要通過不同的外掛程式慢慢補。
go就很幸運,出來的時候很多軟體工程的概念已經基本定了下來,可以加到語言特性之中,go的測試就簡便很多,不愧是為工程而生的語言
需要測試的程式
我們需要測試的程式檔案叫做utils.go,裡面有個字串反轉的方法Reverse,代碼相對簡單,這裡就不贅述了
func Reverse(s string) string { r := []rune(s) for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 { r[i], r[j] = r[j], r[i] } return string(r)}
單元測試
go的testing包可以基於包進測試,意思是執行go test預設單位是包範圍,go test可以自動執行當前包下面的所有func TestXxx(*testing.T)格式的測試方法,Xxx可以是任意字母,但最好和你需要測試方法一一對應。
建立一個測試的檔案,要以_test.go作為檔案名稱的結尾,最好和需要測試的檔案一一對應,可以一目瞭然,這些test檔案在程式構建的時候是不會一起打包到最終執行檔案或者庫中的。
我們建立utlis_test.go檔案:
func TestReverse(t *testing.T) { if testing.Short() { t.Skip("skipping test in short mode.") } cases := []struct { in, want string }{ {"Hello, world", "dlrow ,olleH"}, {"Hello, 世界", "界世 ,olleH"}, {"", ""}, } for _, c := range cases { got := Reverse(c.in) if got != c.want { t.Errorf("Reverse(%q) == %q, want %q", c.in, got, c.want) } }}
第一個if表示如果有-short參數,將會跳過這個測試,後面的代碼相對就很容易看懂了,判斷下輸出輸入是否一致,以上測試方法通過go test命令可以直接運行
樣本驗證
還有一種可以當做測試的方法就是使用樣本驗證程式,做一個樣本在go語言也非常簡單,告別寫點樣本程式就得寫好多main方法的時代吧,格式要以Example開頭,執行go test的時候會自動執行這些樣本,要注意一點的是,Example之後需要跟已定義的變數,否則vet會報錯。
還有一點注意的是,會自動trim,而忽略前後的空格比較。
func ExampleReverse() { fmt.Println(Reverse("Hello, 世界")) // Output: 界世 ,olleH}
可以驗證輸出是否與想要的一致,還有一些情況下,比如多線程的情況下,輸出順序是隨機的,這點go也考慮到了,
func ExampleReverse() { fmt.Println(Reverse("Hello, world")) fmt.Println(Reverse("Hello, 世界")) // Unordered Output: 界世 ,olleH // dlrow ,olleH // AA}
用Unordered Output來表示無序輸出
基準測試
基準測試以Benchmark為首碼,必須要執行b.N次,這樣的測試才有對照性,b.N的值是系統根據實際情況去調整的,從而保證測試的穩定性。
b.ResetTimer()之前的處理不會放到執行時間裡,也不會輸出到報告中,所以可以在之前做一些不計劃作為測試報告的操作
基準測試並不會預設執行,他需要增加-bench參數,例如go test -bench .
func BenchmarkReverse(b *testing.B) { b.ResetTimer() for i := 0; i < b.N; i++ { Reverse("s string") }}
基準測試也可以開啟並行測試,需要執行b.RunParallel(func(pb *testing.PB)方法,預設會以邏輯CPU個數來進行並行測試。
個人意見唯寫並行測試就ok了,如果想非並行可以指定cpu數量為1,例如go test -bench . -cpu 1
func BenchmarkReverseParallel(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { Reverse("s string") } })}
子測試和子基準測試
還可以通過T和B的Run方法開啟子測試和子基準測試,主要是可以共用公用的設定和資源清除的管理
每個子測試都有一個唯一的名字,以父測試用/隔開來唯一表示,啟動並執行時候使用-run regexp指定測試和-bench regexp來指定基準測試,.表示所有。
還有一個特點就是所有子測試完成,父測試才算完成,而且所有測試都是並行的,這樣可以把一些需要同步完成的操作來進行分組測試。
func TestTeardownParallel(t *testing.T) { // This Run will not return until the parallel tests finish. t.Run("group", func(t *testing.T) { t.Run("Test1", parallelTest1) t.Run("Test2", parallelTest2) t.Run("Test3", parallelTest3) }) // <tear-down code>}
Main測試
還有一個可以做一些初始化的地方就是main測試了,代碼也相對簡單。
要注意的testmain是在主協程啟動並執行,m.Run()開始運行時,其他測試才會執行
func TestMain(m *testing.M) { fmt.Println("init") os.Exit(m.Run())}
附帶揭露一下滴滴的jsoniter
為了kpi連基準測試都要作弊嗎?
可見自己會寫基準測試很重要,不會被人忽悠瘸了
來看我的基準測試
type ColorGroup struct { ID int Name string Colors []string}var group = ColorGroup{ ID: 1, Name: "Reds", Colors: []string{"Crimson", "Red", "Ruby", "Maroon"},}func BenchmarkStdJson(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { b, err := json.Marshal(group) if err != nil { fmt.Println(b) } } })}func BenchmarkIterJson(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { b, err := jsoniter.Marshal(group) if err != nil { fmt.Println(b) } } })}
測試結果: