1. 程式人生 > 其它 >GO/testing包

GO/testing包

前言

之前在寫GO單元測試的時候, 使用了這個結構testing.T. 進來無事翻了翻, 發現testing包中還有一些其他的結構體, 想來是不同用處. 沒想到GOtesting包竟然默默做了這麼多支援, 之前竟然不知道.

testing包中包含一下結構體:

  • testing.T: 這就是我們平常使用的單元測試
  • testing.F: 模糊測試, 可以自動生成測試用例
  • testing.B: 基準測試. 對函式的執行時間進行統計.
  • testing.M: 測試的鉤子函式, 可預置測試前後的操作.
  • testing.PB: 測試時並行執行.

依次對GO的各個測試型別進行介紹.

以下各項測試中出現的方法Reverse

如下:

// 此方法源自 Go 官方文件
func Reverse(s string) string {
	bs := []byte(s)
	length := len(bs)
	for i := 0; i < length/2; i++ {
		bs[i], bs[length-i-1] = bs[length-i-1], bs[i]
	}
	return string(bs)
}

testing.T

用於進行單元測試. 官方文件

Go對單元測試函式要求如下:

  1. 檔名形如: xxx_test.go
  2. 函式簽名形如: func TestXxx(t *testing.T)

我們建立檔案lib_test.go

, 並在其中定義如下方法:

func TestReverse(t *testing.T) {
	str := "abc"
	revStr1 := Reverse(str)
	revStr2 := Reverse(revStr1)
	if str != revStr2 {
		// error 方法報錯後, 會繼續向下執行
		t.Error("error")
		// fatal 方法報錯後, 會退出測試
		// t.Fatal("fatal")
		// 輸出除錯資訊
		// t.Log("log")
		// 測試中斷, 但是測試結果不會十遍
		// t.Skip("skip")
	}
  // 可啟動多個子測試, 子測試之間並行執行
	for _, str = range []string{"abcd", "aceb"} {
    // 第一個引數為子測試的標識
		t.Run(str, func(t *testing.T) {
			revStr1 := Reverse(str)
			revStr2 := Reverse(revStr1)
			if str != revStr2 {
				t.Error("error")
			}
		})
	}
}

使用如下命令執行測試用例(test.run 指定執行某一個函式):

go test -test.run TestReverse

這就是單元測試的簡單應用了, 是不是so easy啦.

testing.F

用於模糊測試, 會自動生成測試用例. 官方文件

其內部會自動生成各種測試用例, 並自動呼叫執行. Go對模糊測試的函式要求如下:

  1. 檔名形如: xxx_test.go
  2. 函式簽名形如: func FuzzXxx(f *testing.F)

其測試函式定義如下:

func FuzzReverse(f *testing.F) {
	// 設定測試用例需要隨機生成的變數型別
	f.Add("Hello, world!")
	// 生成測試用例並進行測試. 回電函式接收的引數, 與 f.Add 設定的引數型別一致
	f.Fuzz(func(t *testing.T, str string) {
		revStr1 := Reverse(str)
		revStr2 := Reverse(revStr1)
		if revStr2 != str {
			t.Error("error")
		}
		// 判斷是否是合法的 utf8 編碼
		if utf8.ValidString(str) && !utf8.ValidString(revStr1) {
			t.Error("utf8 error")
		}
	})
}

執行命令開始測試: go test -test.fuzz FuzzReverse -test.run ^$ (其中test.run指定不執行test函式)

當測試失敗的時候, 失敗的用力會寫入指定的檔案, 檔案在控制檯輸出.

testing.B

用於基準測試. 對函式的執行時間進行統計. , 對函式要求如下:

  1. 檔名形如: xxx_test.go
  2. 函式簽名形如: func BenchmarkXxx(b *testing.B)

函式定義如下:

func BenchmarkReverse(b *testing.B) {
  // 開啟記憶體統計
  b.ReportAllocs()
	// 按照要求執行 n 遍
	for i := 0; i < b.N; i++ {
		Reverse("hello")
	}
}

執行命令: go test -test.bench BenchmarkReverse -test.run ^$

結果中指出了執行次數及平均時間. 其中各項值得含義如下:

  • 100000000: 迭代次數

  • ns/op: 平均每次迭代消耗的時間

  • B/op: 平均每次迭代消耗的記憶體

  • allocs/op: 平均每次迭代記憶體的分配次數

testing.M

定義在執行測試的前後執行的操作. 對函式的要求如下:

  1. 檔名形如: xxx_test.go
  2. 函式簽名為: func TestMain(m *testing.M)

函式定義如下:

func TestMain(m *testing.M) {
	// 測試之前執行的操作
	fmt.Println("starting test main")
	// 執行測試
	code := m.Run()
	// 測試之後執行的操作
	fmt.Println("ending test main")
	os.Exit(code)
}

此函式會在執行所有測試時自動呼叫.

testing.PB

用於在測試時進行併發測試. 上面的 單元測試/模糊測試/基準測試 都可以使用. 以基準測試為例, 使用如下:

// 充分利用 CPU 資源, 並行執行 n 次
func BenchmarkReverse2(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			// 此迴圈體總共執行 b.N 次
			Reverse("hello")
		}
	})
}

如此便可並行執行啦.


好, 有關Go的單元測試, 到這裡就差不多了. 以上這些已經基本能夠滿足日常使用了

原文連結: https://hujingnb.com/archives/798