欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

在Go中編寫(xiě)測(cè)試代碼的方法總結(jié)

 更新時(shí)間:2023年07月17日 10:14:31   作者:江湖十年  
在程序開(kāi)發(fā)過(guò)程中,測(cè)試是非常重要的一環(huán),甚至有一種開(kāi)發(fā)模式叫?TDD,先編寫(xiě)測(cè)試,再編寫(xiě)功能代碼,通過(guò)測(cè)試來(lái)推動(dòng)整個(gè)開(kāi)發(fā)的進(jìn)行,可見(jiàn)測(cè)試在開(kāi)發(fā)中的重要程度,為此,Go提供了testing框架來(lái)方便我們編寫(xiě)測(cè)試,本文將向大家介紹在Go中如何編寫(xiě)測(cè)試代碼

測(cè)試分類(lèi)

在 Go 中,編寫(xiě)的測(cè)試用例可以分為四類(lèi):

  • 單元測(cè)試:測(cè)試函數(shù)名稱(chēng)以 Test 開(kāi)頭,如 TestXxx、Test_Xxx,用來(lái)對(duì)程序的最小單元進(jìn)行測(cè)試,如函數(shù)、方法等。

  • 基準(zhǔn)測(cè)試:也叫性能測(cè)試,測(cè)試函數(shù)名稱(chēng)以 Benchmark 開(kāi)頭,用來(lái)測(cè)量程序的性能指標(biāo)。

  • 示例測(cè)試:測(cè)試函數(shù)名稱(chēng)以 Example 開(kāi)頭,可以用來(lái)測(cè)試程序的標(biāo)準(zhǔn)輸出內(nèi)容。

  • 模糊測(cè)試:也叫隨機(jī)測(cè)試,測(cè)試函數(shù)名稱(chēng)以 Fuzz 開(kāi)頭,是一種基于隨機(jī)輸入的自動(dòng)化測(cè)試技術(shù),適合用來(lái)測(cè)試處理用戶(hù)輸入的代碼,在 Go 1.18 中被引入。

這四類(lèi)測(cè)試用例都有各自的適用場(chǎng)景,其中單元測(cè)試最為常用,你一定要掌握。

以上這些測(cè)試用例都可以使用 go test 命令來(lái)執(zhí)行。

測(cè)試規(guī)范

編寫(xiě)測(cè)試代碼并不需要我們學(xué)習(xí)新的 Go 語(yǔ)法,但有些測(cè)試規(guī)范還是需要遵守的。

Go 在提供 testing 測(cè)試框架時(shí),就規(guī)定了很多測(cè)試規(guī)范,用來(lái)約束我們編寫(xiě)測(cè)試的方式,這有助于項(xiàng)目的工程化。

測(cè)試文件命名規(guī)范

首先,測(cè)試文件命名必須以 _test.go 結(jié)尾,否則將被測(cè)試框架忽略。比如我們的 Go 代碼文件名為 hello.go,則測(cè)試文件可以命名為 hello_test.go。

只有以 _test.go 結(jié)尾的測(cè)試文件,才能使用 go test 命令執(zhí)行。

在構(gòu)建 Go 程序時(shí),go build 命令會(huì)忽略以 _test.go 結(jié)尾的測(cè)試文件。

測(cè)試包命名規(guī)范

測(cè)試用例除了根據(jù)使用場(chǎng)景可以分為四類(lèi),還可以根據(jù)代碼和測(cè)試用例是否在一個(gè)包中,分為白盒測(cè)試和黑盒測(cè)試。

  • 白盒測(cè)試:將測(cè)試代碼和被測(cè)代碼放在同一個(gè)包中,也就是二者包名相同,這些測(cè)試用例屬于白盒測(cè)試。比如 Go 代碼文件名為 hello.go,包名為 hello,測(cè)試文件可以命名為 hello_test.go,并且必須與 hello.go 放在同一個(gè)目錄下,包名也必須為 hello。白盒測(cè)試的測(cè)試用例可以使用和測(cè)試當(dāng)前包中所有標(biāo)識(shí)符(變量、函數(shù)等),包括未導(dǎo)出的標(biāo)識(shí)符。

  • 黑盒測(cè)試:將測(cè)試代碼和被測(cè)代碼放在不同的包中,即包名不同,這些測(cè)試用例屬于黑盒測(cè)試。比如 Go 代碼文件名為 hello.go,包名為 hello,測(cè)試文件同樣可以命名為 hello_test.go,與 hello.go 放在同一個(gè)目錄下,但包名不能再叫 hello,應(yīng)該命名為 hello_test,hello_test.go 文件也可以放在專(zhuān)門(mén)的 test 目錄下,此時(shí)可以隨意命名包名。黑盒測(cè)試的測(cè)試用例僅能夠使用和測(cè)試被測(cè)代碼包中可導(dǎo)出的標(biāo)識(shí)符,因?yàn)槎咭呀?jīng)不再屬于同一個(gè)包,這遵循 Go 語(yǔ)法規(guī)范。

根據(jù)二者各自特點(diǎn),在開(kāi)發(fā)時(shí)我們應(yīng)該多編寫(xiě)白盒測(cè)試,這樣才能提升代碼測(cè)試覆蓋率。

測(cè)試用例命名規(guī)范

在 Go 中我們使用測(cè)試函數(shù)來(lái)編寫(xiě)測(cè)試用例,根據(jù)單元測(cè)試、基準(zhǔn)測(cè)試、示例測(cè)試、模糊測(cè)試四種不同類(lèi)型的測(cè)試分類(lèi),測(cè)試函數(shù)必須以 Test、Benchmark、ExampleFuzz 其中一種開(kāi)頭。

測(cè)試函數(shù)簽名示例如下:

func TestXxx(*testing.T)
func BenchmarkXxx(*testing.B)
func ExampleXxx()
func FuzzXxx(*testing.F)

測(cè)試函數(shù)不能有返回值,其中單元測(cè)試、基準(zhǔn)測(cè)試和模糊測(cè)試都接收一個(gè)參數(shù),由 testing 框架提供,示例測(cè)試則不需要傳遞參數(shù)。

其中 Xxx 一般是被測(cè)試的函數(shù)名稱(chēng),首字母必須大寫(xiě)。如果是以 Test_Xxx 方式命名測(cè)試函數(shù),則 Xxx 首字母大小寫(xiě)均可。

測(cè)試變量命名規(guī)范

對(duì)于測(cè)試變量的命名,testing 框架沒(méi)有有強(qiáng)制約束,但社區(qū)中也形成了一些規(guī)范。

比如,函數(shù)簽名中的參數(shù)變量定義如下:

func TestXxx(t *testing.T)
func BenchmarkXxx(b *testing.B)
func FuzzXxx(f *testing.F)

單元測(cè)試、基準(zhǔn)測(cè)試和模糊測(cè)試參數(shù)變量即為參數(shù)類(lèi)型 *testing.<T> 的小寫(xiě)形式。

在編寫(xiě)測(cè)試代碼時(shí),有一個(gè)最常見(jiàn)的場(chǎng)景,就是比較被測(cè)函數(shù)的實(shí)際輸出和測(cè)試函數(shù)中的預(yù)期輸出是否相等,通??梢允褂?got/wantactual/expected 來(lái)命名變量:

if got != want {
	t.Errorf("Xxx(x) = %s; want %s", got, want)
}

或:

if actual != expected {
	t.Errorf("Xxx(x) = %s; expected %s", actual, expected)
}

讀者可以根據(jù)喜好和團(tuán)隊(duì)中的開(kāi)發(fā)規(guī)范選擇其中一種變量命名。

此外,在單元測(cè)試中我們還會(huì)經(jīng)常編寫(xiě)一種叫表格測(cè)試的測(cè)試用例,寫(xiě)法如下:

func TestXxx(t *testing.T) {
	tests := []struct {
		name string
		arg  float64
		want float64
	}{
	...
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Xxx(tt.arg); got != tt.want {
				t.Errorf("Xxx(%f) = %v, want %v", tt.arg, got, tt.want)
			}
		})
	}
}

其中 tests 代表多個(gè)測(cè)試用例,循環(huán)時(shí)以 tt 作為循環(huán)變量(tt 可以避免與單元測(cè)試函數(shù)的參數(shù)變量 t 命名沖突)。

表格測(cè)試還有另一個(gè)版本:

func TestXxx(t *testing.T) {
	cases := []struct {
		name string
		arg  float64
		want float64
	}{
	...
	}
	for _, cc := range cases {
		t.Run(cc.name, func(t *testing.T) {
			if got := Xxx(cc.arg); got != cc.want {
				t.Errorf("Xxx(%f) = %v, want %v", cc.arg, got, cc.want)
			}
		})
	}
}

現(xiàn)在 cases 代表多個(gè)測(cè)試用例,循環(huán)時(shí)以 cc 作為循環(huán)變量(cc 可以避免與常見(jiàn)的 context 縮寫(xiě) c 命名沖突)。

編寫(xiě)測(cè)試代碼的常見(jiàn)規(guī)范我們就先講解到這里,更多規(guī)范將在下文講解對(duì)應(yīng)示例時(shí)再進(jìn)行詳細(xì)說(shuō)明。

單元測(cè)試

單元測(cè)試是我們最常編寫(xiě)的測(cè)試用例,所以先來(lái)學(xué)習(xí)下如何編寫(xiě)單元測(cè)試。

首先,我們準(zhǔn)備一個(gè) Abs 函數(shù)作為被測(cè)試的代碼,存放于 abs.go 文件中,其包名為 abs,代碼如下:

package abs
import "math"
func Abs(x float64) float64 {
	return math.Abs(x)
}

白盒測(cè)試

現(xiàn)在為 Abs 編寫(xiě)一個(gè)白盒測(cè)試函數(shù),在存放 abs.go 文件的同一目錄下,新建 abs_test.go 文件,包名同樣定義為 abs,編寫(xiě)測(cè)試代碼如下:

package abs
import "testing"
func TestAbs(t *testing.T) {
	got := Abs(-1)
	if got != 1 {
		t.Errorf("Abs(-1) = %f; want 1", got)
	}
}

單元測(cè)試函數(shù) TestAbs 代碼非常簡(jiǎn)單,先調(diào)用了 Abs(-1) 函數(shù),并將得到的返回結(jié)果 got1 做相等性比較,如果不相等,則說(shuō)明測(cè)試沒(méi)有通過(guò),使用 t.Errorf 打印錯(cuò)誤信息。

參數(shù) *testing.T 是一個(gè)結(jié)構(gòu)體指針,提供了如下幾個(gè)方法用于錯(cuò)誤報(bào)告:

  • t.Log/t.Logf:打印正常日志信息,類(lèi)似 fmt.Print

  • t.Error/t.Errorf:打印測(cè)試失敗時(shí)的錯(cuò)誤信息,不影響當(dāng)前測(cè)試函數(shù)內(nèi)后續(xù)代碼的繼續(xù)執(zhí)行。

  • t.Fatal/t.Fatalf:打印測(cè)試失敗時(shí)的錯(cuò)誤信息,并終止當(dāng)前測(cè)試函數(shù)執(zhí)行。

在測(cè)試函數(shù)所在目錄下使用 go test 命令執(zhí)行測(cè)試代碼:

$ go test
PASS
ok      github.com/jianghushinian/blog-go-example/test/getting-started/abs      0.139s

go test 會(huì)自動(dòng)查找當(dāng)前目錄下所有以 _test.go 結(jié)尾來(lái)命名的測(cè)試文件,并執(zhí)行其內(nèi)部編寫(xiě)的全部測(cè)試函數(shù)。

輸出 PASS 表示測(cè)試通過(guò),github.com/jianghushinian/blog-go-example/test/getting-started/abs 是程序的 module 名稱(chēng)。

go test 命令還支持使用 -v 標(biāo)志輸出更多信息:

$ go test -v
=== RUN   TestAbs
--- PASS: TestAbs (0.00s)
PASS
ok      github.com/jianghushinian/blog-go-example/test/getting-started/abs      0.437s

如果我們不小心將單元測(cè)試的函數(shù)名錯(cuò)誤的寫(xiě)成 Testabs,即 abs 沒(méi)有大寫(xiě)開(kāi)頭:

func Testabs(t *testing.T) {
	got := Abs(-1)
	if got != 1 {
		t.Errorf("Abs(-1) = %f; want 1", got)
	}
}

則測(cè)試函數(shù)不會(huì)被 go test 命令執(zhí)行。

通常情況下,我們不會(huì)對(duì)一個(gè)函數(shù)只做一種輸入?yún)?shù)的測(cè)試,為了提高測(cè)試覆蓋率,我們可能還需要多測(cè)試幾種參數(shù)的用例,比如測(cè)試下 Abs(2)、Abs(3) 等是否正確。

這時(shí),可以像如下這樣編寫(xiě)測(cè)試函數(shù):

func TestAbs(t *testing.T) {
	got := Abs(-1)
	if got != 1 {
		t.Errorf("Abs(-1) = %f; want 1", got)
	}
	got = Abs(2)
	if got != 2 {
		t.Errorf("Abs(2) = %f; want 2", got)
	}
}

但這樣的代碼顯然過(guò)于“平鋪直敘”,不夠優(yōu)雅。

在這種更加復(fù)雜的情況下,我們可以使用「表格測(cè)試」,代碼如下:

func TestAbs_TableDriven(t *testing.T) {
	tests := []struct {
		name string
		x    float64
		want float64
	}{
		{
			name: "positive",
			x:    2,
			want: 2,
		},
		{
			name: "negative",
			x:    -3,
			want: 3,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if got := Abs(tt.x); got != tt.want {
				t.Errorf("Abs(%f) = %v, want %v", tt.x, got, tt.want)
			}
		})
	}
}

為了便于與之前編寫(xiě)的測(cè)試函數(shù) TestAbs 區(qū)分,我為當(dāng)前測(cè)試函數(shù)命名為 TestAbs_TableDriven,代表這是一個(gè)表格驅(qū)動(dòng)的測(cè)試。

在測(cè)試函數(shù)內(nèi)部,首先定義了一個(gè)匿名結(jié)構(gòu)體切片,用來(lái)保存多個(gè)測(cè)試用例。

name 是一個(gè)字符串,可以是任何句子,用來(lái)標(biāo)記當(dāng)前測(cè)試用例所測(cè)試的場(chǎng)景,這樣代碼維護(hù)者通過(guò) name 字段就能夠知道當(dāng)前用例所測(cè)試的場(chǎng)景,作用相當(dāng)于代碼注釋。

x 作為 Abs 函數(shù)的入?yún)ⅲ漕?lèi)型等同于 Abs 函數(shù)的參數(shù),如果被測(cè)試函數(shù)有多個(gè)參數(shù),這里也可以使用一個(gè)結(jié)構(gòu)體來(lái)保存。

want 記錄當(dāng)前測(cè)試用例的期望值。

for 循環(huán)中,我們可以使用 *testing.T 提供的 t.Run 方法執(zhí)行測(cè)試用例,這和直接編寫(xiě)的 TestXxx 測(cè)試函數(shù)沒(méi)什么本質(zhì)區(qū)別。

現(xiàn)在使用 go test 命令執(zhí)行測(cè)試代碼:

go test -v
=== RUN   TestAbs
--- PASS: TestAbs (0.00s)
=== RUN   TestAbs_TableDriven
=== RUN   TestAbs_TableDriven/positive
=== RUN   TestAbs_TableDriven/negative
--- PASS: TestAbs_TableDriven (0.00s)
    --- PASS: TestAbs_TableDriven/positive (0.00s)
    --- PASS: TestAbs_TableDriven/negative (0.00s)
PASS
ok      github.com/jianghushinian/blog-go-example/test/getting-started/abs      0.145s

可以發(fā)現(xiàn),表格測(cè)試的輸出信息更加豐富,能夠分別打印出表格中的每一個(gè)測(cè)試用例,并且使用縮進(jìn)來(lái)展示層級(jí)關(guān)系。

現(xiàn)在我們故意將其中的一個(gè)測(cè)試用例改錯(cuò):

{
	name: "negative",
	x:    -3,
	want: 33,
}

再次使用 go test 命令執(zhí)行測(cè)試代碼看下如何輸出:

go test -v
=== RUN   TestAbs
--- PASS: TestAbs (0.00s)
=== RUN   TestAbs_TableDriven
=== RUN   TestAbs_TableDriven/positive
=== RUN   TestAbs_TableDriven/negative
    abs_test.go:36: Abs(-3.000000) = 3, want 33
--- FAIL: TestAbs_TableDriven (0.00s)
    --- PASS: TestAbs_TableDriven/positive (0.00s)
    --- FAIL: TestAbs_TableDriven/negative (0.00s)
FAIL
exit status 1
FAIL    github.com/jianghushinian/blog-go-example/test/getting-started/abs      0.515s

根據(jù)打印結(jié)果,我們很容易能夠發(fā)現(xiàn)是 TestAbs_TableDriven 測(cè)試函數(shù)中 negative 這個(gè)測(cè)試用例執(zhí)行失敗了。

有些場(chǎng)景下,我們可能想要跳過(guò)某些測(cè)試用例,可以使用 (*testing.T).Skip 方法來(lái)實(shí)現(xiàn):

func TestAbs_Skip(t *testing.T) {
	// CI 環(huán)境跳過(guò)當(dāng)前測(cè)試
	if os.Getenv("CI") != "" {
		t.Skip("it's too slow, skip when running in CI")
	}
	t.Log(t.Skipped())
	got := Abs(-2)
	if got != 2 {
		t.Errorf("Abs(-2) = %f; want 2", got)
	}
}

假如 TestAbs_Skip 是一個(gè)非常耗時(shí)的測(cè)試用例,我們就可以使用 t.Skip 在 CI 環(huán)境下跳過(guò)此測(cè)試。

t.Skipped() 返回當(dāng)前測(cè)試用例是否被跳過(guò)。

使用 go test 命令執(zhí)行測(cè)試:

$ CI=1 go test -v -run="TestAbs_Skip"
=== RUN   TestAbs_Skip
    abs_test.go:46: it's too slow, skip when running in CI
--- SKIP: TestAbs_Skip (0.00s)
PASS
ok      github.com/jianghushinian/blog-go-example/test/getting-started/abs      0.103s

這次我們使用 -run 參數(shù)來(lái)指定想要執(zhí)行的測(cè)試用例,-run 參數(shù)的值支持正則。

并且指定了環(huán)境變量 CI=1。

從打印結(jié)果來(lái)看,TestAbs_Skip 測(cè)試用例的確被跳過(guò)了,所以 t.Log(t.Skipped()) 沒(méi)有被執(zhí)行到。

默認(rèn)情況下,測(cè)試用例是從上到下按照順序執(zhí)行的,不過(guò),我們可以使用 (*testint.T).Parallel 來(lái)標(biāo)記一個(gè)測(cè)試函數(shù)支持并發(fā)執(zhí)行:

func TestAbs_Parallel(t *testing.T) {
	t.Log("Parallel before")
	// 標(biāo)記當(dāng)前測(cè)試支持并行
	t.Parallel()
	t.Log("Parallel after")
	got := Abs(2)
	if got != 2 {
		t.Errorf("Abs(2) = %f; want 2", got)
	}
}

只有一個(gè)測(cè)試函數(shù)支持并發(fā)執(zhí)行意義不大,我們可以將 TestAbs 測(cè)試函數(shù)也修改為支持并發(fā)執(zhí)行:

func TestAbs(t *testing.T) {
	t.Parallel()
	got := Abs(-1)
	if got != 1 {
		t.Errorf("Abs(-1) = %f; want 1", got)
	}
}

現(xiàn)在,使用 go test 命令來(lái)測(cè)試下并發(fā)執(zhí)行測(cè)試用例:

$ go test -v -run=".*Parallel.*|^TestAbs$"
=== RUN   TestAbs
=== PAUSE TestAbs
=== RUN   TestAbs_Parallel
    abs_test.go:59: Parallel before
=== PAUSE TestAbs_Parallel
=== CONT  TestAbs
--- PASS: TestAbs (0.00s)
=== CONT  TestAbs_Parallel
    abs_test.go:62: Parallel after
--- PASS: TestAbs_Parallel (0.00s)
PASS
ok      github.com/jianghushinian/blog-go-example/test/getting-started/abs      0.200s

這里我們只執(zhí)行了 TestAbs_Parallel、TestAbs 這兩個(gè)測(cè)試函數(shù)。

可以發(fā)現(xiàn),兩個(gè)函數(shù)都不是一次性執(zhí)行完成的,日志中 PAUSE 表示暫停當(dāng)前函數(shù)的執(zhí)行,CONT 表示恢復(fù)當(dāng)前函數(shù)執(zhí)行。

有時(shí)候,我們測(cè)試的并不是一個(gè)函數(shù),而是一個(gè)方法,比如我們想要測(cè)試 Animal 結(jié)構(gòu)體的 shout 方法:

package animal
type Animal struct {
	Name string
}
func (a Animal) shout() string {
	if a.Name == "dog" {
		return "旺!"
	}
	if a.Name == "cat" {
		return "喵~"
	}
	return "吼~"
}

那么,測(cè)試函數(shù)可以命名為 TestAnimal_shout,如下是我們針對(duì) Dog 和 Cat 兩種不同的 Animal 對(duì)象編寫(xiě)的測(cè)試代碼:

package animal
import (
	"testing"
)
func TestAnimalDog_shout(t *testing.T) {
	dog := Animal{Name: "dog"}
	got := dog.shout()
	want := "旺!"
	if got != want {
		t.Errorf("got %s; want %s", got, want)
	}
}
func TestAnimalCat_shout(t *testing.T) {
	cat := Animal{Name: "cat"}
	got := cat.shout()
	want := "喵~"
	if got != want {
		t.Errorf("got %s; want %s", got, want)
	}
}

黑盒測(cè)試

講完了白盒測(cè)試,我們?cè)賮?lái)演示下如何編寫(xiě)黑盒測(cè)試。

要為 Abs 編寫(xiě)黑盒測(cè)試非常簡(jiǎn)單,我們只需要將 TestAbs 移動(dòng)到新的包中即可。

package abs_test
import (
	"testing"
	"github.com/jianghushinian/blog-go-example/test/getting-started/abs"
)
func TestAbs(t *testing.T) {
	got := abs.Abs(-1)
	if got != 1 {
		t.Errorf("Abs(-1) = %f; want 1", got)
	}
}

因?yàn)楹诤袦y(cè)試的函數(shù) TestAbsAbs 不在同一個(gè)包中,所以需要先使用 import 導(dǎo)入 abs 包,之后才能使用 abs.Abs 函數(shù)。

至此,常見(jiàn)的單元測(cè)試場(chǎng)景我們就介紹完了。

接下來(lái),我們一起來(lái)看如何編寫(xiě)基準(zhǔn)測(cè)試。

基準(zhǔn)測(cè)試

基準(zhǔn)測(cè)試也叫性能測(cè)試,顧名思義,是為了度量程序的性能。

Abs 編寫(xiě)的基準(zhǔn)測(cè)試代碼如下:

func BenchmarkAbs(b *testing.B) {
	for i := 0; i < b.N; i++ {
		Abs(-1)
	}
}

基準(zhǔn)測(cè)試同樣放在 abs_test.go 文件中,以 Benchmark 開(kāi)頭,參數(shù)不再是 *testing.T,而是 *testing.B,在測(cè)試函數(shù)中,我們循環(huán)了 b.N 次調(diào)用 Abs(-1),b.N 的值是一個(gè)動(dòng)態(tài)值,我們無(wú)需操心,testing 框架會(huì)為其分配合理的值,以使測(cè)試函數(shù)運(yùn)行足夠多的次數(shù),可以準(zhǔn)確的計(jì)時(shí)。

默認(rèn)情況下,go test 命令并不會(huì)運(yùn)行基準(zhǔn)測(cè)試,需要指定 -bench 參數(shù):

$ go test -bench="." 
goos: darwin
goarch: arm64
pkg: github.com/jianghushinian/blog-go-example/test/getting-started/abs
BenchmarkAbs-8          1000000000               0.5096 ns/op
PASS
ok      github.com/jianghushinian/blog-go-example/test/getting-started/abs      0.674s

-bench 參數(shù)同樣接收一個(gè)正則,. 匹配所有基準(zhǔn)測(cè)試。

我們需要重點(diǎn)關(guān)注的是這行結(jié)果:

BenchmarkAbs-8          1000000000               0.5096 ns/op

BenchmarkAbs-8 中,BenchmarkAbs 是測(cè)試函數(shù)名,8GOMAXPROCS 的值,即參與執(zhí)行的 CPU 核心數(shù)。

1000000000 表示測(cè)試執(zhí)行了這么多次。

0.5096 ns/op 表示每次循環(huán)平均消耗的納秒數(shù)。

如果還想查看基準(zhǔn)測(cè)試的內(nèi)存統(tǒng)計(jì)情況,則可以指定 -benchmem 參數(shù):

$ go test -bench="BenchmarkAbs$" -benchmem           
goos: darwin
goarch: arm64
pkg: github.com/jianghushinian/blog-go-example/test/getting-started/abs
BenchmarkAbs-8          1000000000               0.5097 ns/op          0 B/op          0 allocs/op
PASS
ok      github.com/jianghushinian/blog-go-example/test/getting-started/abs      0.681s

現(xiàn)在,BenchmarkAbs-8 這行得到了更多輸出:

BenchmarkAbs-8          1000000000               0.5097 ns/op          0 B/op          0 allocs/op

0 B/op 表示每次執(zhí)行測(cè)試代碼分配了多少字節(jié)內(nèi)存。

0 allocs/op 表示每次執(zhí)行測(cè)試代碼分配了多少次內(nèi)存。

此外,在執(zhí)行 go test 命令時(shí),我們可以使用 -benchtime=Ns 參數(shù)指定基準(zhǔn)測(cè)試函數(shù)執(zhí)行時(shí)間為 N 秒:

$ go test -bench="BenchmarkAbs$" -benchtime=0.1s 
goos: darwin
goarch: arm64
pkg: github.com/jianghushinian/blog-go-example/test/getting-started/abs
BenchmarkAbs-8          210435709                0.5096 ns/op
PASS
ok      github.com/jianghushinian/blog-go-example/test/getting-started/abs      0.600s

-benchtime 參數(shù)值為 time.Duration 類(lèi)型支持的時(shí)間格式。

-benchtime 參數(shù)還有一個(gè)特殊語(yǔ)法 -benchtime=Nx 參數(shù),可以指定基準(zhǔn)測(cè)試函數(shù)執(zhí)行次數(shù)為 N 次:

$ go test -bench="BenchmarkAbs$" -benchtime=10x
goos: darwin
goarch: arm64
pkg: github.com/jianghushinian/blog-go-example/test/getting-started/abs
BenchmarkAbs-8                10                20.90 ns/op
PASS
ok      github.com/jianghushinian/blog-go-example/test/getting-started/abs      0.391s

有時(shí)候,我們?cè)诰帉?xiě)基準(zhǔn)測(cè)試時(shí),被測(cè)函數(shù)可能需要一些準(zhǔn)備數(shù)據(jù),而這些準(zhǔn)備數(shù)據(jù)的時(shí)間不應(yīng)該算做被測(cè)試函數(shù)的耗時(shí)。

此時(shí),可以使用 (*testing.B).ResetTimer 重置計(jì)時(shí):

func BenchmarkAbsResetTimer(b *testing.B) {
	time.Sleep(100 * time.Millisecond) // 模擬耗時(shí)的準(zhǔn)備工作
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		Abs(-1)
	}
}

這樣,在調(diào)用 b.ResetTimer() 之前的耗時(shí)操作將不被記入測(cè)試結(jié)果的耗時(shí)中。

還有一種方法,也可以跳過(guò)準(zhǔn)備工作的計(jì)時(shí),即先使用 (*testing.B).StopTimer 停止計(jì)時(shí),耗時(shí)的準(zhǔn)備工作完成后再使用 (*testing.B).StartTimer 恢復(fù)計(jì)時(shí):

func BenchmarkAbsStopTimerStartTimer(b *testing.B) {
	b.StopTimer()
	time.Sleep(100 * time.Millisecond) // 模擬耗時(shí)的準(zhǔn)備工作
	b.StartTimer()
	for i := 0; i < b.N; i++ {
		Abs(-1)
	}
}

默認(rèn)情況下,基準(zhǔn)測(cè)試 for 循環(huán)中的代碼是串行執(zhí)行的,如果想要并行執(zhí)行,可以將被測(cè)試代碼的調(diào)用放在 (*testing.B).RunParallel 中:

func BenchmarkAbsParallel(b *testing.B) {
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			Abs(-1)
		}
	})
}

我們還可以使用 (*testing.B).SetParallelism 控制并發(fā)協(xié)程數(shù):

func BenchmarkAbsParallel(b *testing.B) {
	b.SetParallelism(2) // 設(shè)置并發(fā) Goroutines 數(shù)量為 2 * GOMAXPROCS
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			Abs(-1)
		}
	})
}

在使用 go test 命令執(zhí)行基準(zhǔn)測(cè)試時(shí),可以指定 -cpu 參數(shù)來(lái)設(shè)置 GOMAXPROCS。

要想了解 go test 支持的更多參數(shù),可以使用 go help testflag 命令進(jìn)行查看。

示例測(cè)試

示例測(cè)試以 Example 開(kāi)頭,無(wú)參數(shù)和返回值,通常存放在 example_test.go 文件中。

約定一個(gè)包、函數(shù) F、類(lèi)型 T、方法 M 的示例測(cè)試命名如下:

func Example() { ... } // 整個(gè)包的示例測(cè)試
func ExampleF() { ... } // 函數(shù) F 的示例測(cè)試
func ExampleT() { ... } // 類(lèi)型 T 的示例測(cè)試
func ExampleT_M() { ... } // 類(lèi)型 T 的 M 方法的示例測(cè)試

一個(gè)包、函數(shù)、類(lèi)型、方法如果存在多個(gè)示例測(cè)試,可以通過(guò)在名稱(chēng)后面附加一個(gè)不同的后綴來(lái)命名示例測(cè)試函數(shù),后綴必須以小寫(xiě)字母開(kāi)頭,如下:

func Example_suffix() { ... }
func ExampleF_suffix() { ... }
func ExampleT_suffix() { ... }
func ExampleT_M_suffix() { ... }

以下是一個(gè)為 Abs 函數(shù)編寫(xiě)的示例測(cè)試:

func ExampleAbs() {
	fmt.Println(Abs(-1))
	fmt.Println(Abs(2))
	// Output:
	// 1
	// 2
}

示例測(cè)試函數(shù)末尾需要使用 // Output: 注釋?zhuān)瑏?lái)標(biāo)記被測(cè)試函數(shù)的標(biāo)準(zhǔn)輸出內(nèi)容。

這里分別使用 fmt.Println(Abs(-1))、fmt.Println(Abs(2)) 調(diào)用了兩次 Abs 函數(shù),所以會(huì)得到兩個(gè)輸出。

示例測(cè)試會(huì)攔截測(cè)試過(guò)程中的標(biāo)準(zhǔn)輸出,并與 // Output: 注釋之后的內(nèi)容做對(duì)比,如果相等,則測(cè)試通過(guò)。

go test 默認(rèn)情況下,不會(huì)執(zhí)行示例測(cè)試,可以通過(guò) -run 指定示例測(cè)試函數(shù):

$ go test -v -run "ExampleAbs$"           
=== RUN   ExampleAbs
--- PASS: ExampleAbs (0.00s)
PASS
ok      github.com/jianghushinian/blog-go-example/test/getting-started/abs      2.050s

我們還可以使用 // Unordered Output: 注釋?zhuān)瑏?lái)標(biāo)記被測(cè)試函數(shù)的標(biāo)準(zhǔn)輸出內(nèi)容。

這將忽略被測(cè)試函數(shù)的輸出順序:

func ExampleAbs_unordered() {
	fmt.Println(Abs(2))
	fmt.Println(Abs(-1))
	// Unordered Output:
	// 1
	// 2
}

以上這個(gè)示例測(cè)試函數(shù)中,無(wú)論是先調(diào)用 Abs(2) 還是先調(diào)用 Abs(-1),測(cè)試函數(shù)都能通過(guò)。

沒(méi)有輸出注釋 // Output:// Unordered Output:的示例測(cè)試函數(shù)只會(huì)被編譯,但不會(huì)執(zhí)行。

此外,示例測(cè)試還有一個(gè)非常有用功能,它能被 godocpkgsite 工具所識(shí)別,將示例函數(shù)的代碼提取后作為被測(cè)試函數(shù)文檔的一部分。

注意:舊版本的 Go 自帶了 godoc 工具,能夠在本地啟動(dòng)一個(gè) Web 服務(wù)器,對(duì)本地安裝的 Go 包提供文檔服務(wù),不過(guò)現(xiàn)在官方已經(jīng)不維護(hù) godoc 了,所以不再推薦使用。Go 1.15 以后雖然集成了 go doc 工具,但是無(wú)法啟動(dòng) Web 服務(wù),比較適合命令行中查看 Go 包的文檔?,F(xiàn)在,Go 官方比較推薦使用的工具是 pkgsite,能夠啟動(dòng) Web 服務(wù),并且它與 Go 在線(xiàn)文檔站點(diǎn)長(zhǎng)得一樣。

這里以 pkgsite 工具為例展示下示例測(cè)試函數(shù)生成的文檔效果。

首先,安裝 pkgsite

$ go install golang.org/x/pkgsite/cmd/pkgsite@latest

然后,在 abs.go 目錄下執(zhí)行 pkgsite 即可啟動(dòng)文檔服務(wù):

$ pkgsite

現(xiàn)在訪(fǎng)問(wèn) http://localhost:8080 即可進(jìn)入文檔服務(wù)首頁(yè):

點(diǎn)擊模塊名 github.com/jianghushinian/blog-go-example/test/getting-started 即可找到 Abs 函數(shù)位置,在 Abs 函數(shù)下方,標(biāo)題 Example、Example (Unordered) 下就是通過(guò)示例測(cè)試生成的示例文檔:

注意:這里需要額外提及的一點(diǎn)是,我們查看文檔的本地包模塊名稱(chēng)(module)應(yīng)該帶 .,也就是一般使用域名作為包名的一部分,否則啟動(dòng) pkgsite 后將會(huì)報(bào)錯(cuò),無(wú)法查看本地包的文檔。

模糊測(cè)試

模糊測(cè)試在 Go 1.18 中被引入,模糊測(cè)試(fuzz testing)又叫隨機(jī)測(cè)試,是一種基于隨機(jī)輸入的自動(dòng)化測(cè)試技術(shù)。

模糊測(cè)試比較適合用于發(fā)現(xiàn)處理用戶(hù)輸入的代碼中存在的問(wèn)題。

關(guān)于模糊測(cè)試的編寫(xiě)方式,有一張圖廣泛流傳:

模糊測(cè)試同樣需要放在 _test.go 文件中,并且以 Fuzz 開(kāi)頭,參數(shù)為 *testing.F。

上圖中,f.Add(5, "hello") 是在為模糊測(cè)試提供初始的種子語(yǔ)料,其實(shí)就是被測(cè)試函數(shù)接收的合法參數(shù),后續(xù)的模糊測(cè)試過(guò)程中,會(huì)根據(jù)這個(gè)種子語(yǔ)料,生成更多的模糊測(cè)試參數(shù)。這有點(diǎn)類(lèi)似我們生成隨機(jī)數(shù)時(shí)需要傳遞一個(gè)隨機(jī)種子。雖然調(diào)用 f.Add 方法不是必須的,但提供合法的種子語(yǔ)料有利于更早發(fā)現(xiàn)被測(cè)試函數(shù)的問(wèn)題。

f.Fuzz 是模糊測(cè)試的主體邏輯,它接收一個(gè)函數(shù),函數(shù)的第一個(gè)參數(shù)為 *testing.T,之后是被測(cè)函數(shù)接收的參數(shù),稱(chēng)為 Fuzzing arguments。

Fuzzing arguments 參數(shù)是 testing 框架隨機(jī)生成的,所以叫隨機(jī)測(cè)試,這些隨機(jī)生成的參數(shù)將依次傳遞給 Foo 函數(shù)。

調(diào)用 Foo 函數(shù)和判斷測(cè)試結(jié)果是否正確的代碼,就跟我們編寫(xiě)的普通單元測(cè)試一樣了。

可以發(fā)現(xiàn),模糊測(cè)試相較于單元測(cè)試,多了一個(gè)自動(dòng)生成測(cè)試參數(shù)的過(guò)程。

不過(guò),Fuzzing arguments 支持的參數(shù)類(lèi)型有限,僅支持如下幾種類(lèi)型:

  • string, []byte
  • int, int8, int16, int32/rune, int64
  • uint, uint8/byte, uint16, uint32, uint64
  • float32, float64
  • bool

此外,編寫(xiě)模糊測(cè)試時(shí),Fuzz target 不要依賴(lài)全局狀態(tài),因?yàn)槟:郎y(cè)試會(huì)并行執(zhí)行。

為了演示如何編寫(xiě)模糊測(cè)試,我編寫(xiě)了一個(gè) Hello 函數(shù):

package hello
import "errors"
var (
	ErrEmptyName   = errors.New("empty name")
	ErrTooLongName = errors.New("too long name")
)
func Hello(name string) (string, error) {
	if name == "" {
		return "", ErrEmptyName
	}
	if len(name) > 10 {
		return "", ErrTooLongName
	}
	return "Hello " + name, nil
}

將 Hello 函數(shù)放在 hello.go 文件中。

在 Hello 函數(shù)內(nèi)部,對(duì) name 參數(shù)進(jìn)行了校驗(yàn),不能為空,且長(zhǎng)度不能超過(guò) 10。

為 Hello 函數(shù)編寫(xiě)模糊測(cè)試代碼如下:

func FuzzHello(f *testing.F) {
	f.Add("Foo")
	f.Fuzz(func(t *testing.T, name string) {
		_, err := Hello(name)
		if err != nil {
			if errors.Is(err, ErrEmptyName) || errors.Is(err, ErrTooLongName) {
				return
			}
			t.Errorf("unexpected error: %s, name: %s", err, name)
		}
	})
}

模糊測(cè)試代碼放在 hello_fuzz_test.go 中,包名同樣為 hello。

f.Fuzz 中調(diào)用了 Hello 函數(shù),并判斷返回的 err 是否符合預(yù)期,如果不符合預(yù)期,則表示測(cè)試失敗。

go test 命令默認(rèn)情況下同樣不會(huì)執(zhí)行模糊測(cè)試,我們需要指定 -fuzz 參數(shù):

$ go test -fuzz="FuzzHello"
fuzz: elapsed: 0s, gathering baseline coverage: 0/1 completed
fuzz: elapsed: 0s, gathering baseline coverage: 1/1 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 521335 (173703/sec), new interesting: 2 (total: 3)
fuzz: elapsed: 6s, execs: 947014 (141945/sec), new interesting: 2 (total: 3)
fuzz: elapsed: 9s, execs: 1391822 (148228/sec), new interesting: 2 (total: 3)
fuzz: elapsed: 12s, execs: 1838008 (148764/sec), new interesting: 2 (total: 3)
fuzz: elapsed: 15s, execs: 2266978 (143002/sec), new interesting: 2 (total: 3)
^Cfuzz: elapsed: 15s, execs: 2308214 (139431/sec), new interesting: 2 (total: 3)
PASS
ok      github.com/jianghushinian/blog-go-example/test/getting-started/hello    17.131s

以上測(cè)試執(zhí)行過(guò)程中,我使用 ^C 終止了測(cè)試。模糊測(cè)試默認(rèn)情況下會(huì)一直執(zhí)行下去,直至遇到 crash 終止。

通過(guò)以上示例,我們可以發(fā)現(xiàn),模糊測(cè)試之所以強(qiáng)大,就是因?yàn)槠鋾?huì)一直執(zhí)行,不斷生成測(cè)試參數(shù),以覆蓋更多的情況和邊界條件。

也正因?yàn)槿绱?,模糊測(cè)試通常不建議在 CI 中執(zhí)行。

不過(guò),我們可以使用 -fuzztime 限制模糊測(cè)試執(zhí)行的時(shí)間:

go test -fuzz="FuzzHello" -fuzztime 10s
fuzz: elapsed: 0s, gathering baseline coverage: 0/3 completed
fuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 8 workers
fuzz: elapsed: 3s, execs: 470505 (156828/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 6s, execs: 948821 (159392/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 9s, execs: 1423720 (158326/sec), new interesting: 0 (total: 3)
fuzz: elapsed: 10s, execs: 1573524 (139348/sec), new interesting: 0 (total: 3)
PASS
ok      github.com/jianghushinian/blog-go-example/test/getting-started/hello    11.820s

這次,我沒(méi)有按 ^C 鍵終止測(cè)試,而是 10 秒過(guò)后,模糊測(cè)試自動(dòng)終止。

現(xiàn)在,我們修改下 Hello 函數(shù),使其返回一個(gè)未知的錯(cuò)誤:

func Hello(name string) (string, error) {
	if name == "" {
		return "", ErrEmptyName
	}
	if len(name) > 10 {
		return "", ErrTooLongName
	}
	if name == "Bob" {
		return "", errors.New("not allowed")
	}
	return "Hello " + name, nil
}

當(dāng) name 值為 Bob 時(shí),Hello 函數(shù)將返回一個(gè)未知錯(cuò)誤,模擬 BUG 場(chǎng)景。

再次使用 go test 命令執(zhí)行模糊測(cè)試:

$ go test -fuzz="FuzzHello"              
fuzz: elapsed: 0s, gathering baseline coverage: 0/3 completed
fuzz: elapsed: 0s, gathering baseline coverage: 3/3 completed, now fuzzing with 8 workers
fuzz: minimizing 30-byte failing input file
fuzz: elapsed: 1s, minimizing
--- FAIL: FuzzHello (1.17s)
    --- FAIL: FuzzHello (0.00s)
        hello_fuzz_test.go:18: unexpected error: not allowed, name: Bob
    Failing input written to testdata/fuzz/FuzzHello/19f92ff5a07664a0
    To re-run:
    go test -run=FuzzHello/19f92ff5a07664a0
FAIL
exit status 1
FAIL    github.com/jianghushinian/blog-go-example/test/getting-started/hello    1.626s

可以發(fā)現(xiàn),這次模糊測(cè)試失敗了。

根據(jù)測(cè)試結(jié)果,是在執(zhí)行 FuzzHello/19f92ff5a07664a0 時(shí)失敗的,19f92ff5a07664a0 是模糊測(cè)試生成的文件,位于 testdata/fuzz/FuzzHello/19f92ff5a07664a0

使用 tree 命令查看 19f92ff5a07664a0 位置:

 tree hello
hello
├── hello.go
├── hello_fuzz_test.go
└── testdata
    └── fuzz
        └── FuzzHello
            └── 19f92ff5a07664a0

testdata 目錄及目錄下所有內(nèi)容都是模糊測(cè)試自動(dòng)生成的。

19f92ff5a07664a0 文件內(nèi)容如下:

go test fuzz v1
string("Bob")

文件第一行 go test fuzz v1 是模糊測(cè)試要求的文件頭,用于標(biāo)識(shí)這是一個(gè)種子語(yǔ)料文件,并且使用的編解碼器的版本為 v1。

第二行就是種子語(yǔ)料,是一個(gè) Go 代碼片段,即 string 類(lèi)型的 Bob 參數(shù)。正是這個(gè)參數(shù),引發(fā)了錯(cuò)誤。

至此,我們使用模糊測(cè)試發(fā)現(xiàn)了 Hello 函數(shù)中隱藏的 BUG,這在黑盒測(cè)試中尤其有效,我們無(wú)需查看 Hello 函數(shù)內(nèi)部代碼,為每個(gè)邊界條件編寫(xiě)測(cè)試用例,模糊測(cè)試會(huì)自動(dòng)生成大量的隨機(jī)參數(shù),檢測(cè)程序的異常。

測(cè)試覆蓋率

go test 命令支持使用 -cover 標(biāo)志查看測(cè)試覆蓋率:

$ go test -cover ./... 
?       github.com/jianghushinian/blog-go-example/test/getting-started  [no test files]
ok      github.com/jianghushinian/blog-go-example/test/getting-started/abs      2.334s  coverage: 100.0% of statements
ok      github.com/jianghushinian/blog-go-example/test/getting-started/animal   1.924s  coverage: 100.0% of statements
ok      github.com/jianghushinian/blog-go-example/test/getting-started/hello    2.738s  coverage: 60.0% of statements
ok      github.com/jianghushinian/blog-go-example/test/getting-started/test/abs 3.136s  coverage: [no statements]

注意:根據(jù)我的實(shí)際測(cè)試結(jié)果來(lái)看,測(cè)試覆蓋率默認(rèn)僅包含單元測(cè)試、示例測(cè)試和模糊測(cè)試(模糊測(cè)試僅執(zhí)行 f.Add 添加的種子參數(shù)測(cè)試),基準(zhǔn)測(cè)試并不會(huì)被統(tǒng)計(jì)。要想將基準(zhǔn)測(cè)試納入覆蓋率統(tǒng)計(jì),需要增加 -bench 參數(shù)。你可以增加 -v 函數(shù)查看更詳細(xì)信息。

此外,go test 命令還支持使用 -coverprofile 參數(shù)生成覆蓋率 profile 文件:

$ go test -coverprofile=coverage.out ./...
?       github.com/jianghushinian/blog-go-example/test/getting-started  [no test files]
ok      github.com/jianghushinian/blog-go-example/test/getting-started/abs      3.101s  coverage: 100.0% of statements
ok      github.com/jianghushinian/blog-go-example/test/getting-started/animal   3.495s  coverage: 100.0% of statements
ok      github.com/jianghushinian/blog-go-example/test/getting-started/hello    3.910s  coverage: 60.0% of statements
ok      github.com/jianghushinian/blog-go-example/test/getting-started/test/abs 4.312s  coverage: [no statements]

命令執(zhí)行后,將在當(dāng)前目錄生成一個(gè) coverage.out 文件,內(nèi)容如下:

cat coverage.out   
mode: set
github.com/jianghushinian/blog-go-example/test/getting-started/abs/abs.go:5.29,7.2 1 1
github.com/jianghushinian/blog-go-example/test/getting-started/animal/animal.go:7.32,8.21 1 1
github.com/jianghushinian/blog-go-example/test/getting-started/animal/animal.go:8.21,10.3 1 1
github.com/jianghushinian/blog-go-example/test/getting-started/animal/animal.go:11.2,11.21 1 1
github.com/jianghushinian/blog-go-example/test/getting-started/animal/animal.go:11.21,13.3 1 1
github.com/jianghushinian/blog-go-example/test/getting-started/animal/animal.go:14.2,14.17 1 1
github.com/jianghushinian/blog-go-example/test/getting-started/hello/hello.go:10.41,11.16 1 1
github.com/jianghushinian/blog-go-example/test/getting-started/hello/hello.go:11.16,13.3 1 0
github.com/jianghushinian/blog-go-example/test/getting-started/hello/hello.go:14.2,14.20 1 1
github.com/jianghushinian/blog-go-example/test/getting-started/hello/hello.go:14.20,16.3 1 0
github.com/jianghushinian/blog-go-example/test/getting-started/hello/hello.go:17.2,17.29 1 1

有了 coverage.out 文件,我們可以直接使用 go tool cover 命令查看測(cè)試覆蓋率:

$ go tool cover -func=coverage.out                       
github.com/jianghushinian/blog-go-example/test/getting-started/abs/abs.go:5:            Abs             100.0%
github.com/jianghushinian/blog-go-example/test/getting-started/animal/animal.go:7:      shout           100.0%
github.com/jianghushinian/blog-go-example/test/getting-started/hello/hello.go:10:       Hello           60.0%
total:                                                                                  (statements)    81.8%

以上方式,實(shí)現(xiàn)了在命令行查看程序測(cè)試覆蓋率。

我們還可以通過(guò) go tool cover 命令以可視化的方式查看測(cè)試覆蓋率:

$ go tool cover -html=coverage.out -o=coverage.html

執(zhí)行命令后,會(huì)在當(dāng)前目錄下生成 coverage.html 文件,使用瀏覽器打開(kāi)內(nèi)容如下:

在頁(yè)面頂部左側(cè),可以切換查看不同的測(cè)試文件和對(duì)應(yīng)測(cè)試覆蓋率。

灰色代碼表示未被跟蹤 not tracked。

紅色部分表示未被測(cè)試的代碼 not covered。

綠色部分表示已經(jīng)被測(cè)試覆蓋的代碼 covered。

這樣,我們就可以更加直觀的查看和分析代碼測(cè)試覆蓋率了。

總結(jié)

本文向大家介紹了 Go 中編寫(xiě)各種測(cè)試代碼的方式。

Go 支持單元測(cè)試、基準(zhǔn)測(cè)試、示例測(cè)試以及模糊測(cè)試四種測(cè)試方法。

單元測(cè)試是我們最常使用的測(cè)試方法,如果被測(cè)代碼需要編寫(xiě)多個(gè)測(cè)試用例,可以使用表格測(cè)試。

基準(zhǔn)測(cè)試能夠測(cè)量程序的性能指標(biāo),默認(rèn)情況下 go test 不會(huì)執(zhí)行基準(zhǔn)測(cè)試,需要指定 -bench regexp 參數(shù)才可以執(zhí)行。

示例測(cè)試可以測(cè)試程序的標(biāo)準(zhǔn)輸出內(nèi)容,并且能夠配合 pkgsite 工具,在查看本地包文檔時(shí)作為被測(cè)函數(shù)文檔的一部分。

模糊測(cè)試是 Go 1.18 版本引入的,是一種基于隨機(jī)輸入的自動(dòng)化測(cè)試技術(shù),非常強(qiáng)大,適合用于發(fā)現(xiàn)處理用戶(hù)輸入的代碼中存在的問(wèn)題。

根據(jù)測(cè)試代碼與被測(cè)代碼是否在同一個(gè)包中,測(cè)試又可以分為白盒測(cè)試和黑盒測(cè)試,我們應(yīng)該盡量編寫(xiě)白盒測(cè)試。

可以使用 go test -cover 查看測(cè)試覆蓋率,我們可以將測(cè)試覆蓋率基線(xiàn)集成到 CI 中,來(lái)保證單元測(cè)試覆蓋率。

go test 命令支持的更多參數(shù)可以通過(guò) go help testflag 命令查看。

由于篇幅所限,本文僅算做是 Go 單元測(cè)試的基礎(chǔ)入門(mén),更多單元測(cè)試在實(shí)戰(zhàn)場(chǎng)景中的應(yīng)用,我會(huì)在后續(xù)文章中進(jìn)行講解,敬請(qǐng)期待。

本文完整代碼示例:blog-go-example/test/getting-started at main · jianghushinian/blog-go-example · GitHub

以上就是在Go中編寫(xiě)測(cè)試代碼的方法總結(jié)的詳細(xì)內(nèi)容,更多關(guān)于Go編寫(xiě)測(cè)試代碼的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • go語(yǔ)言操作redis連接池的方法

    go語(yǔ)言操作redis連接池的方法

    這篇文章主要介紹了go語(yǔ)言操作redis連接池的方法,涉及Go語(yǔ)言操作radis的技巧,需要的朋友可以參考下
    2015-03-03
  • 詳解Go hash算法的支持

    詳解Go hash算法的支持

    這篇文章主要介紹了詳解Go hash算法的支持,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-09-09
  • Go操作各大消息隊(duì)列教程(RabbitMQ、Kafka)

    Go操作各大消息隊(duì)列教程(RabbitMQ、Kafka)

    消息隊(duì)列是一種異步的服務(wù)間通信方式,適用于無(wú)服務(wù)器和微服務(wù)架構(gòu),本文主要介紹了Go操作各大消息隊(duì)列教程(RabbitMQ、Kafka),需要的朋友可以了解一下
    2024-02-02
  • Go讀取yaml文件到struct類(lèi)的實(shí)現(xiàn)方法

    Go讀取yaml文件到struct類(lèi)的實(shí)現(xiàn)方法

    本文主要介紹了Go讀取yaml文件到struct類(lèi),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • go責(zé)任鏈行為型設(shè)計(jì)模式Chain?Of?Responsibility

    go責(zé)任鏈行為型設(shè)計(jì)模式Chain?Of?Responsibility

    這篇文章主要為大家介紹了go行為型設(shè)計(jì)模式之責(zé)任鏈Chain?Of?Responsibility使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-12-12
  • Golang通脈之map詳情

    Golang通脈之map詳情

    這篇文章主要介紹了Golang通脈之map,Go語(yǔ)言中提供的映射關(guān)系容器為map,其內(nèi)部使用散列表(hash)實(shí)現(xiàn),map 是一種無(wú)序的鍵值對(duì)的集合。map 最重要的一點(diǎn)是通過(guò) key 來(lái)快速檢索數(shù)據(jù),key 類(lèi)似于索引,指向數(shù)據(jù)的值 map 是一種集合,所以可以像迭代數(shù)組和切片那樣迭代它
    2021-10-10
  • 基于Go語(yǔ)言開(kāi)發(fā)一個(gè)編解碼工具

    基于Go語(yǔ)言開(kāi)發(fā)一個(gè)編解碼工具

    這篇文章主要為大家詳細(xì)介紹了如何基于Go語(yǔ)言開(kāi)發(fā)一個(gè)編解碼工具,文中的示例代碼講解詳細(xì),具有一定的借鑒價(jià)值,感興趣的小伙伴可以跟隨小編一起了解一下
    2025-03-03
  • go Antlr重構(gòu)腳本解釋器實(shí)現(xiàn)示例

    go Antlr重構(gòu)腳本解釋器實(shí)現(xiàn)示例

    這篇文章主要為大家介紹了go Antlr重構(gòu)腳本解釋器實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • golang 并發(fā)編程之生產(chǎn)者消費(fèi)者詳解

    golang 并發(fā)編程之生產(chǎn)者消費(fèi)者詳解

    這篇文章主要介紹了golang 并發(fā)編程之生產(chǎn)者消費(fèi)者詳解,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-05-05
  • Go調(diào)度器學(xué)習(xí)之goroutine調(diào)度詳解

    Go調(diào)度器學(xué)習(xí)之goroutine調(diào)度詳解

    這篇文章主要為大家詳細(xì)介紹了Go調(diào)度器中g(shù)oroutine調(diào)度的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-03-03

最新評(píng)論