Go 單元測(cè)試基本介紹
一、單元測(cè)試基本介紹
1.1 什么是單元測(cè)試?
單元測(cè)試(Unit Tests, UT) 是一個(gè)優(yōu)秀項(xiàng)目不可或缺的一部分,是對(duì)軟件中的最小可測(cè)試部分進(jìn)行檢查和驗(yàn)證。在面向?qū)ο缶幊讨校钚y(cè)試單元通常是一個(gè)方法或函數(shù)。單元測(cè)試通常由開發(fā)者編寫,用于驗(yàn)證代碼的一個(gè)很小的、很具體的功能是否正確。單元測(cè)試是自動(dòng)化測(cè)試的一部分,可以頻繁地運(yùn)行以檢測(cè)代碼的更改是否引入了新的錯(cuò)誤。
特別是在一些頻繁變動(dòng)和多人合作開發(fā)的項(xiàng)目中尤為重要。你或多或少都會(huì)有因?yàn)樽约旱奶峤?,?dǎo)致應(yīng)用掛掉或服務(wù)宕機(jī)的經(jīng)歷。如果這個(gè)時(shí)候你的修改導(dǎo)致測(cè)試用例失敗,你再重新審視自己的修改,發(fā)現(xiàn)之前的修改還有一些特殊場(chǎng)景沒有包含,恭喜你減少了一次上庫失誤。也會(huì)有這樣的情況,項(xiàng)目很大,啟動(dòng)環(huán)境很復(fù)雜,你優(yōu)化了一個(gè)函數(shù)的性能,或是添加了某個(gè)新的特性,如果部署在正式環(huán)境上之后再進(jìn)行測(cè)試,成本太高。對(duì)于這種場(chǎng)景,幾個(gè)小小的測(cè)試用例或許就能夠覆蓋大部分的測(cè)試場(chǎng)景。而且在開發(fā)過程中,效率最高的莫過于所見即所得了,單元測(cè)試也能夠幫助你做到這一點(diǎn),試想一下,假如你一口氣寫完一千行代碼,debug 的過程也不會(huì)輕松,如果在這個(gè)過程中,對(duì)于一些邏輯較為復(fù)雜的函數(shù),同時(shí)添加一些測(cè)試用例,即時(shí)確保正確性,最后集成的時(shí)候,會(huì)是另外一番體驗(yàn)。
1.2 如何寫好單元測(cè)試
首先,學(xué)會(huì)寫測(cè)試用例。比如如何測(cè)試單個(gè)函數(shù)/方法;比如如何做基準(zhǔn)測(cè)試;比如如何寫出簡(jiǎn)潔精煉的測(cè)試代碼;再比如遇到數(shù)據(jù)庫訪問等的方法調(diào)用時(shí),如何 mock。
然后,寫可測(cè)試的代碼。高內(nèi)聚,低耦合是軟件工程的原則,同樣,對(duì)測(cè)試而言,函數(shù)/方法寫法不同,測(cè)試難度也是不一樣的。職責(zé)單一,參數(shù)類型簡(jiǎn)單,與其他函數(shù)耦合度低的函數(shù)往往更容易測(cè)試。我們經(jīng)常會(huì)說,“這種代碼沒法測(cè)試”,這種時(shí)候,就得思考函數(shù)的寫法可不可以改得更好一些。為了代碼可測(cè)試而重構(gòu)是值得的。
1.3 單元測(cè)試的優(yōu)點(diǎn)
單元測(cè)試講究的是快速測(cè)試、快速修復(fù)。
- 測(cè)試該環(huán)節(jié)中的業(yè)務(wù)問題,比如說在寫測(cè)試的時(shí)候,發(fā)現(xiàn)業(yè)務(wù)流程設(shè)計(jì)得不合理。
- 測(cè)試該環(huán)節(jié)中的技術(shù)問題,比如說
nil之類的問題。
單元測(cè)試,從理論上來說,你不能依賴任何第三方組件。也就是說,你不能使用MySQL或者Redis。
如圖,要快速啟動(dòng)測(cè)試,快速發(fā)現(xiàn)BUG,快速修復(fù),快速重測(cè)。

1.4 單元測(cè)試的設(shè)計(jì)原則
- 每個(gè)測(cè)試單元必須完全獨(dú)立、能單獨(dú)運(yùn)行。
- 一個(gè)測(cè)試單元應(yīng)只關(guān)注一個(gè)功能函數(shù),證明它是正確的;
- 測(cè)試代碼要能夠快速執(zhí)行。
- 不能為了單元測(cè)試而修改已完成的代碼在編寫代碼后執(zhí)行針對(duì)本次的單元測(cè)試,并執(zhí)行之前的單元測(cè)試用例。
- 以保證你后來編寫的代碼不會(huì)破壞任何事情;
- 單元測(cè)試函數(shù)使用長(zhǎng)的而且具有描述性的名字,例如都以test_開頭,然后加上具體的函數(shù)名字或者功能描述;例如:func_test.go。
- 測(cè)試代碼必須具有可讀性。
二、Go語言測(cè)試
2.1 Go單元測(cè)試概要
Go 語言的單元測(cè)試默認(rèn)采用官方自帶的測(cè)試框架,通過引入 testing 包以及 執(zhí)行 go test 命令來實(shí)現(xiàn)單元測(cè)試功能。
在源代碼包目錄內(nèi),所有以 _test.go 為后綴名的源文件會(huì)被 go test 認(rèn)定為單元測(cè)試的文件,這些單元測(cè)試的文件不會(huì)包含在 go build 的源代碼構(gòu)建中,而是單獨(dú)通過 go test 來編譯并執(zhí)行。
2.2 Go單元測(cè)試基本規(guī)范
Go 單元測(cè)試的基本規(guī)范如下:
- 每個(gè)測(cè)試函數(shù)都必須導(dǎo)入 testing 包。測(cè)試函數(shù)的命名類似
func TestName(t *testing.T),入?yún)⒈仨毷?*testing.T - 測(cè)試函數(shù)的函數(shù)名必須以大寫的 Test 開頭,后面緊跟的函數(shù)名,要么是大寫開關(guān),要么就是下劃線,比如
func TestName(t *testing.T)或者func Test_name(t *testing.T)都是 ok 的, 但是func Testname(t *testing.T)不會(huì)被檢測(cè)到 - 通常情況下,需要將測(cè)試文件和源代碼放在同一個(gè)包內(nèi)。一般測(cè)試文件的命名,都是
{source_filename}_test.go,比如我們的源代碼文件是allen.go ,那么就會(huì)在 allen.go 的相同目錄下,再建立一個(gè) allen_test.go 的單元測(cè)試文件去測(cè)試 allen.go 文件里的相關(guān)方法。
當(dāng)運(yùn)行 go test 命令時(shí),go test 會(huì)遍歷所有的 *_test.go 中符合上述命名規(guī)則的函數(shù),然后生成一個(gè)臨時(shí)的 main 包用于調(diào)用相應(yīng)的測(cè)試函數(shù),然后構(gòu)建并運(yùn)行、報(bào)告測(cè)試結(jié)果,最后清理測(cè)試中生成的臨時(shí)文件。
2.3 一個(gè)簡(jiǎn)單例子
2.3.1 使用Goland 生成測(cè)試文件
我們來創(chuàng)建一個(gè)示例,創(chuàng)建名為 add.go的文件
package main
func Add(a int, b int) int {
return a + b
}
func Mul(a int, b int) int {
return a * b
}
這里借助Goland給 ADD 函數(shù)生成并且編寫測(cè)試用例,只需要右鍵點(diǎn)擊函數(shù),轉(zhuǎn)到Generate -> Test for file function(生成函數(shù)測(cè)試)。


Goland 為我們生成了add_test.go單測(cè)文件
package main
import "testing"
func TestAdd(t *testing.T) {
type args struct {
a int
b int
}
tests := []struct {
name string
args args
want int
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Add(tt.args.a, tt.args.b); got != tt.want {
t.Errorf("Add() = %v, want %v", got, tt.want)
}
})
}
}
2.3.2 運(yùn)行單元測(cè)試
運(yùn)行 go test,該 package 下所有的測(cè)試用例都會(huì)被執(zhí)行。
go test .
ok gotest 1.060s
或 go test -v,-v 參數(shù)會(huì)顯示每個(gè)用例的測(cè)試結(jié)果,另外 -cover 參數(shù)可以查看覆蓋率。
go test -v
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok gotest 1.208s
2.3.3 完善測(cè)試用例
接著我們來完善上面的測(cè)試用例,代碼如下:
package main
import "testing"
func TestAdd(t *testing.T) {
type args struct {
a int
b int
}
tests := []struct {
name string
args args
want int
}{
{
name: "Adding positive numbers",
args: args{a: 2, b: 3},
want: 5,
},
{
name: "Adding negative numbers",
args: args{a: -2, b: -3},
want: -5,
},
{
name: "Adding positive and negative numbers",
args: args{a: 2, b: -3},
want: -1,
},
{
name: "Adding zero",
args: args{a: 2, b: 0},
want: 2,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Add(tt.args.a, tt.args.b); got != tt.want {
t.Errorf("Add() = %v, want %v", got, tt.want)
}
})
}
}
2.3.5 回歸測(cè)試
我們修改了代碼之后僅僅執(zhí)行那些失敗的測(cè)試用例或新引入的測(cè)試用例是錯(cuò)誤且危險(xiǎn)的,正確的做法應(yīng)該是完整運(yùn)行所有的測(cè)試用例,保證不會(huì)因?yàn)樾薷拇a而引入新的問題。
go test -v
=== RUN TestAdd
=== RUN TestAdd/Adding_positive_numbers
=== RUN TestAdd/Adding_negative_numbers
=== RUN TestAdd/Adding_positive_and_negative_numbers
=== RUN TestAdd/Adding_zero
--- PASS: TestAdd (0.00s)
--- PASS: TestAdd/Adding_positive_numbers (0.00s)
--- PASS: TestAdd/Adding_negative_numbers (0.00s)
--- PASS: TestAdd/Adding_positive_and_negative_numbers (0.00s)
--- PASS: TestAdd/Adding_zero (0.00s)
=== RUN TestMul
=== RUN TestMul/結(jié)果為0
=== RUN TestMul/結(jié)果為-1
=== RUN TestMul/結(jié)果為1
--- PASS: TestMul (0.00s)
--- PASS: TestMul/結(jié)果為0 (0.00s)
--- PASS: TestMul/結(jié)果為-1 (0.00s)
--- PASS: TestMul/結(jié)果為1 (0.00s)
PASS
ok gotest 0.912s
測(cè)試結(jié)果表明我們的單元測(cè)試全部通過。
2.4 Goland 直接運(yùn)行單元測(cè)試
如果你的測(cè)試方法簽名沒錯(cuò)的話,就能看到這個(gè)綠色圖標(biāo),點(diǎn)擊就能看到很多選項(xiàng)。
最主要的是:
- Run:運(yùn)行模式,直接運(yùn)行整個(gè)測(cè)試。
- Debug:Debug模式,你可以打斷點(diǎn)。
- Run xxx with Coverage:運(yùn)行并且輸出測(cè)試覆蓋率。
- 其它Profile都是性能分析,很少用。
除非你要看測(cè)試覆蓋率,不然都用Debug。

2.5 Go Test 命令參數(shù)
go test 是 Go 語言的測(cè)試工具,你可以使用它來運(yùn)行 Go 程序的測(cè)試函數(shù)。
你可以在命令行中使用以下參數(shù)來調(diào)用 go test 命令:
- -run:指定要運(yùn)行的測(cè)試函數(shù)的名稱的正則表達(dá)式。例如,使用
go test -run TestAdd可以運(yùn)行名稱為 TestSum 的測(cè)試函數(shù)。 - -bench:指定要運(yùn)行的基準(zhǔn)測(cè)試的名稱的正則表達(dá)式。例如,使用
go test -bench .可以運(yùn)行所有基準(zhǔn)測(cè)試。 - -count:指定要運(yùn)行測(cè)試函數(shù)或基準(zhǔn)測(cè)試的次數(shù)。例如,使用
go test -count 2可以運(yùn)行測(cè)試函數(shù)或基準(zhǔn)測(cè)試兩次。 - -v:輸出測(cè)試函數(shù)或基準(zhǔn)測(cè)試的詳細(xì)輸出。
- -timeout:設(shè)置測(cè)試函數(shù)或基準(zhǔn)測(cè)試的超時(shí)時(shí)間。例如,使用
go test -timeout 1s可以將超時(shí)時(shí)間設(shè)置為 1 秒。
以下是一個(gè)go Test命令表格:
| 參數(shù) | 說明 |
|---|---|
-bench regexp |
僅運(yùn)行與正則表達(dá)式匹配的基準(zhǔn)測(cè)試。默認(rèn)不運(yùn)行任何基準(zhǔn)測(cè)試。使用 -bench . 或 -bench= 來運(yùn)行所有基準(zhǔn)測(cè)試。 |
-benchtime t |
運(yùn)行每個(gè)基準(zhǔn)測(cè)試足夠多的迭代,以達(dá)到指定的時(shí)間 t(例如 -benchtime 1h30s)。默認(rèn)為1秒(1s)。特殊語法 Nx 表示運(yùn)行基準(zhǔn)測(cè)試 N 次(例如 -benchtime 100x)。 |
-count n |
運(yùn)行每個(gè)測(cè)試、基準(zhǔn)測(cè)試和模糊測(cè)試 n 次(默認(rèn)為1次)。如果設(shè)置了 -cpu,則為每個(gè) GOMAXPROCS 值運(yùn)行 n 次。示例總是運(yùn)行一次。-count 不適用于通過 -fuzz 匹配的模糊測(cè)試。 |
-cover |
啟用覆蓋率分析。 |
-covermode set,count,atomic |
設(shè)置覆蓋率分析的 mode。默認(rèn)為 "set",如果啟用了 -race,則為 "atomic"。 |
-coverpkg pattern1,pattern2,pattern3 |
對(duì)匹配模式的包應(yīng)用覆蓋率分析。默認(rèn)情況下,每個(gè)測(cè)試僅分析正在測(cè)試的包。 |
-cpu 1,2,4 |
指定一系列的 GOMAXPROCS 值,在這些值上執(zhí)行測(cè)試、基準(zhǔn)測(cè)試或模糊測(cè)試。默認(rèn)為當(dāng)前的 GOMAXPROCS 值。-cpu 不適用于通過 -fuzz 匹配的模糊測(cè)試。 |
-failfast |
在第一個(gè)測(cè)試失敗后不啟動(dòng)新的測(cè)試。 |
-fullpath |
在錯(cuò)誤消息中顯示完整的文件名。 |
-fuzz regexp |
運(yùn)行與正則表達(dá)式匹配的模糊測(cè)試。當(dāng)指定時(shí),命令行參數(shù)必須精確匹配主模塊中的一個(gè)包,并且正則表達(dá)式必須精確匹配該包中的一個(gè)模糊測(cè)試。 |
-fuzztime t |
在模糊測(cè)試期間運(yùn)行足夠多的模糊目標(biāo)迭代,以達(dá)到指定的時(shí)間 t(例如 -fuzztime 1h30s)。默認(rèn)為永遠(yuǎn)運(yùn)行。特殊語法 Nx 表示運(yùn)行模糊目標(biāo) N 次(例如 -fuzztime 1000x)。 |
-fuzzminimizetime t |
在每次最小化嘗試期間運(yùn)行足夠多的模糊目標(biāo)迭代,以達(dá)到指定的時(shí)間 t(例如 -fuzzminimizetime 30s)。默認(rèn)為60秒。特殊語法 Nx 表示運(yùn)行模糊目標(biāo) N 次(例如 -fuzzminimizetime 100x)。 |
-json |
以 JSON 格式記錄詳細(xì)輸出和測(cè)試結(jié)果。這以機(jī)器可讀的格式呈現(xiàn) -v 標(biāo)志的相同信息。 |
-list regexp |
列出與正則表達(dá)式匹配的測(cè)試、基準(zhǔn)測(cè)試、模糊測(cè)試或示例。不會(huì)運(yùn)行任何測(cè)試、基準(zhǔn)測(cè)試、模糊測(cè)試或示例。 |
-parallel n |
允許并行執(zhí)行調(diào)用 t.Parallel 的測(cè)試函數(shù),以及運(yùn)行種子語料庫時(shí)的模糊目標(biāo)。此標(biāo)志的值是同時(shí)運(yùn)行的最大測(cè)試數(shù)。 |
-run regexp |
僅運(yùn)行與正則表達(dá)式匹配的測(cè)試、示例和模糊測(cè)試。 |
-short |
告訴長(zhǎng)時(shí)間運(yùn)行的測(cè)試縮短其運(yùn)行時(shí)間。默認(rèn)情況下是關(guān)閉的,但在 all.bash 中設(shè)置,以便在安裝 Go 樹時(shí)可以運(yùn)行健全性檢查,但不花費(fèi)時(shí)間運(yùn)行詳盡的測(cè)試。 |
-shuffle off,on,N |
隨機(jī)化測(cè)試和基準(zhǔn)測(cè)試的執(zhí)行順序。默認(rèn)情況下是關(guān)閉的。如果 -shuffle 設(shè)置為 on,則使用系統(tǒng)時(shí)鐘種子隨機(jī)化器。如果 -shuffle 設(shè)置為整數(shù) N,則 N 將用作種子值。在這兩種情況下,種子將報(bào)告以便復(fù)現(xiàn)。 |
-skip regexp |
僅運(yùn)行與正則表達(dá)式不匹配的測(cè)試、示例、模糊測(cè)試和基準(zhǔn)測(cè)試。 |
-timeout d |
如果測(cè)試二進(jìn)制文件運(yùn)行時(shí)間超過持續(xù)時(shí)間 d,則發(fā)生 panic。如果 d 為0,則禁用超時(shí)。默認(rèn)為10分鐘(10m)。 |
-v |
詳細(xì)輸出:記錄所有運(yùn)行的測(cè)試。即使測(cè)試成功,也打印所有來自 Log 和 Logf 調(diào)用的文本。 |
-vet list |
配置在 "go test" 期間對(duì) "go vet" 的調(diào)用,以使用由逗號(hào)分隔的 vet 檢查列表。如果列表為空,"go test" 使用被認(rèn)為總是值得解決的精選檢查列表運(yùn)行 "go vet"。如果列表為 |
更多可以參考 Go 語言的官方文檔或使用 go help test 命令查看幫助信息
2.6 運(yùn)行一個(gè)文件中的單個(gè)測(cè)試
如果只想運(yùn)行其中的一個(gè)用例,例如 TestAdd,可以用 -run 參數(shù)指定,該參數(shù)支持通配符 *,和部分正則表達(dá)式,例如 ^、$。
go test -run TestAdd -v
=== RUN TestAdd
=== RUN TestAdd/Adding_positive_numbers
=== RUN TestAdd/Adding_negative_numbers
=== RUN TestAdd/Adding_positive_and_negative_numbers
=== RUN TestAdd/Adding_zero
--- PASS: TestAdd (0.00s)
--- PASS: TestAdd/Adding_positive_numbers (0.00s)
--- PASS: TestAdd/Adding_negative_numbers (0.00s)
--- PASS: TestAdd/Adding_positive_and_negative_numbers (0.00s)
--- PASS: TestAdd/Adding_zero (0.00s)
PASS
ok gotest 1.008s
2.7 測(cè)試覆蓋率
測(cè)試覆蓋率是指代碼被測(cè)試套件覆蓋的百分比。通常我們使用的都是語句的覆蓋率,也就是在測(cè)試中至少被運(yùn)行一次的代碼占總代碼的比例。在公司內(nèi)部一般會(huì)要求測(cè)試覆蓋率達(dá)到80%左右。
Go提供內(nèi)置功能來檢查你的代碼覆蓋率,即使用go test -cover來查看測(cè)試覆蓋率。
go test -cover
PASS
coverage: 100.0% of statements
ok gotest 1.381s
還可以使用 -coverprofile 標(biāo)志將覆蓋率數(shù)據(jù)輸出到一個(gè)文件中,然后使用 go tool cover 命令來查看更詳細(xì)的覆蓋率報(bào)告。
2.8 公共的幫助函數(shù)(helpers)
對(duì)一些重復(fù)的邏輯,抽取出來作為公共的幫助函數(shù)(helpers),可以增加測(cè)試代碼的可讀性和可維護(hù)性。 借助幫助函數(shù),可以讓測(cè)試用例的主邏輯看起來更清晰。
例如,我們可以將創(chuàng)建多次使用的邏輯抽取出來:
type addCase struct{ A, B, want int }
func createAddTestCase(t *testing.T, c *addCase) {
// t.Helper()
if ans := Add(c.A, c.B); ans != c.want {
t.Fatalf("%d * %d expected %d, but %d got",
c.A, c.B, c.want, ans)
}
}
func TestAdd2(t *testing.T) {
createAddTestCase(t, &addCase{1, 1, 2})
createAddTestCase(t, &addCase{2, -3, -1})
createAddTestCase(t, &addCase{0, -1, 0}) // wrong case
}
在這里,我們故意創(chuàng)建了一個(gè)錯(cuò)誤的測(cè)試用例,運(yùn)行 go test,用例失敗,會(huì)報(bào)告錯(cuò)誤發(fā)生的文件和行號(hào)信息:
go test
--- FAIL: TestAdd2 (0.00s)
add_test.go:109: 0 * -1 expected 0, but -1 got
FAIL
exit status 1
FAIL gotest 1.090s
可以看到,錯(cuò)誤發(fā)生在第11行,也就是幫助函數(shù) createAddTestCase 內(nèi)部。116, 117, 118行都調(diào)用了該方法,我們第一時(shí)間并不能夠確定是哪一行發(fā)生了錯(cuò)誤。有些幫助函數(shù)還可能在不同的函數(shù)中被調(diào)用,報(bào)錯(cuò)信息都在同一處,不方便問題定位。因此,Go 語言在 1.9 版本中引入了 t.Helper(),用于標(biāo)注該函數(shù)是幫助函數(shù),報(bào)錯(cuò)時(shí)將輸出幫助函數(shù)調(diào)用者的信息,而不是幫助函數(shù)的內(nèi)部信息。
修改 createAddTestCaseV1,調(diào)用 t.Helper()
type addCaseV1 struct {
name string
A, B int
want int
}
func createAddTestCaseV1(c *addCaseV1, t *testing.T) {
t.Helper()
t.Run(c.name, func(t *testing.T) {
if ans := Add(c.A, c.B); ans != c.want {
t.Fatalf("%s: %d + %d expected %d, but %d got",
c.name, c.A, c.B, c.want, ans)
}
})
}
func TestAddV1(t *testing.T) {
createAddTestCaseV1(&addCaseV1{"case 1", 1, 1, 2}, t)
createAddTestCaseV1(&addCaseV1{"case 2", 2, -3, -1}, t)
createAddTestCaseV1(&addCaseV1{"case 3", 0, -1, 0}, t)
}
運(yùn)行 go test,報(bào)錯(cuò)信息如下,可以非常清晰地知道,錯(cuò)誤發(fā)生在第 131 行。
go test
--- FAIL: TestAddV1 (0.00s)
--- FAIL: TestAddV1/case_3 (0.00s)
add_test.go:131: case 3: 0 + -1 expected 0, but -1 got
FAIL
exit status 1
FAIL gotest 0.434s
關(guān)于 helper 函數(shù)的 2 個(gè)建議:
- 不要返回錯(cuò)誤, 幫助函數(shù)內(nèi)部直接使用
t.Error或t.Fatal即可,在用例主邏輯中不會(huì)因?yàn)樘嗟腻e(cuò)誤處理代碼,影響可讀性。 - 調(diào)用
t.Helper()讓報(bào)錯(cuò)信息更準(zhǔn)確,有助于定位。
當(dāng)然,如果你是用Goland 編輯器的話,可以不使用t.Helper(),自動(dòng)會(huì)幫你打印出錯(cuò)誤詳細(xì)信息
三、testing.T的擁有的方法
以下是提供的 *testing.T 類型的方法及其用途的注釋:
// T 是 Go 語言測(cè)試框架中的一個(gè)結(jié)構(gòu)體類型,它提供了用于編寫測(cè)試的方法。
// 它通常通過測(cè)試函數(shù)的參數(shù)傳遞給測(cè)試函數(shù)。
// Cleanup 注冊(cè)一個(gè)函數(shù),該函數(shù)將在測(cè)試結(jié)束時(shí)執(zhí)行,用于清理測(cè)試過程中創(chuàng)建的資源。
func (c *T) Cleanup(func())
// Error 記錄一個(gè)錯(cuò)誤信息,但不會(huì)立即停止測(cè)試的執(zhí)行。
func (c *T) Error(args ...interface{})
// Errorf 根據(jù) format 和 args 記錄一個(gè)格式化的錯(cuò)誤信息,但不會(huì)立即停止測(cè)試的執(zhí)行。
func (c *T) Errorf(format string, args ...interface{})
// Fail 標(biāo)記測(cè)試函數(shù)為失敗,但不會(huì)停止當(dāng)前測(cè)試的執(zhí)行。
func (c *T) Fail()
// FailNow 標(biāo)記測(cè)試函數(shù)為失敗,并立即停止當(dāng)前測(cè)試的執(zhí)行。
func (c *T) FailNow()
// Failed 檢查測(cè)試是否失敗。
func (c *T) Failed() bool
// Fatal 記錄一個(gè)錯(cuò)誤信息,并立即停止測(cè)試的執(zhí)行。
func (c *T) Fatal(args ...interface{})
// Fatalf 記錄一個(gè)格式化的錯(cuò)誤信息,并立即停止測(cè)試的執(zhí)行。
func (c *T) Fatalf(format string, args ...interface{})
// Helper 標(biāo)記當(dāng)前函數(shù)為輔助函數(shù),當(dāng)測(cè)試失敗時(shí),輔助函數(shù)的文件名和行號(hào)將不會(huì)顯示在錯(cuò)誤消息中。
func (c *T) Helper()
// Log 記錄一些信息,這些信息只有在啟用詳細(xì)日志(-v標(biāo)志)時(shí)才會(huì)顯示。
func (c *T) Log(args ...interface{})
// Logf 記錄一些格式化的信息,這些信息只有在啟用詳細(xì)日志(-v標(biāo)志)時(shí)才會(huì)顯示。
func (c *T) Logf(format string, args ...interface{})
// Name 返回當(dāng)前測(cè)試或基準(zhǔn)測(cè)試的名稱。
func (c *T) Name() string
// Skip 標(biāo)記測(cè)試為跳過,并記錄一個(gè)錯(cuò)誤信息。
func (c *T) Skip(args ...interface{})
// SkipNow 標(biāo)記測(cè)試為跳過,并立即停止當(dāng)前測(cè)試的執(zhí)行。
func (c *T) SkipNow()
// Skipf 標(biāo)記測(cè)試為跳過,并記錄一個(gè)格式化的錯(cuò)誤信息。
func (c *T) Skipf(format string, args ...interface{})
// Skipped 檢查測(cè)試是否被跳過。
func (c *T) Skipped() bool
// TempDir 返回一個(gè)臨時(shí)目錄的路徑,該目錄在測(cè)試結(jié)束時(shí)會(huì)被自動(dòng)刪除。
func (c *T) TempDir() string
四、表格驅(qū)動(dòng)測(cè)試
4.1 介紹
表格驅(qū)動(dòng)測(cè)試不是工具、包或其他任何東西,它只是編寫更清晰測(cè)試的一種方式和視角。
編寫好的測(cè)試并非易事,但在許多情況下,表格驅(qū)動(dòng)測(cè)試可以涵蓋很多方面:表格里的每一個(gè)條目都是一個(gè)完整的測(cè)試用例,包含輸入和預(yù)期結(jié)果,有時(shí)還包含測(cè)試名稱等附加信息,以使測(cè)試輸出易于閱讀。
使用表格驅(qū)動(dòng)測(cè)試能夠很方便的維護(hù)多個(gè)測(cè)試用例,避免在編寫單元測(cè)試時(shí)頻繁的復(fù)制粘貼。
表格驅(qū)動(dòng)測(cè)試的步驟通常是定義一個(gè)測(cè)試用例表格,然后遍歷表格,并使用t.Run對(duì)每個(gè)條目執(zhí)行必要的測(cè)試。
4.2 舉個(gè)例子
func TestMul(t *testing.T) {
type args struct {
a int
b int
}
tests := []struct {
name string
args args
want int
}{
{
name: "結(jié)果為0",
args: args{a: 2, b: 0},
want: 0,
},
{
name: "結(jié)果為-1",
args: args{a: -1, b: 1},
want: -1,
},
{
name: "結(jié)果為1",
args: args{a: -1, b: -1},
want: 1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := Mul(tt.args.a, tt.args.b); got != tt.want {
t.Errorf("Mul() = %v, want %v", got, tt.want)
}
})
}
}
五、testify/assert 斷言工具包
5.1 介紹
testify/assert 是一個(gè)流行的Go語言斷言庫,它提供了一組豐富的斷言函數(shù),用于簡(jiǎn)化測(cè)試代碼的編寫。這個(gè)庫提供了一種更聲明式的方式來編寫測(cè)試,使得測(cè)試意圖更加明確,代碼更加簡(jiǎn)潔。
使用 testify/assert 時(shí),您不再需要編寫大量的 if 語句和 Error 方法調(diào)用來檢查條件和記錄錯(cuò)誤。相反,您可以使用像 assert.Equal、assert.Nil、assert.True 這樣的斷言函數(shù)來驗(yàn)證測(cè)試的期望結(jié)果。
5.2 安裝
go get github.com/stretchr/testify
5.3 使用
斷言包提供了一些有用的方法,可以幫助您在Go語言中編寫更好的測(cè)試代碼。
- 打印友好、易于閱讀的失敗描述
- 允許編寫可讀性強(qiáng)的代碼
- 可以為每個(gè)斷言添加可選的注釋信息
看看它的實(shí)際應(yīng)用:
package yours
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSomething(t *testing.T) {
// 斷言相等
assert.Equal(t, 123, 123, "它們應(yīng)該相等")
// 斷言不等
assert.NotEqual(t, 123, 456, "它們不應(yīng)該相等")
// 斷言為nil(適用于錯(cuò)誤處理)
assert.Nil(t, object)
// 斷言不為nil(當(dāng)你期望得到某個(gè)結(jié)果時(shí)使用)
if assert.NotNil(t, object) {
// 現(xiàn)在我們知道object不是nil,我們可以安全地進(jìn)行
// 進(jìn)一步的斷言而不會(huì)引起任何錯(cuò)誤
assert.Equal(t, "Something", object.Value)
}
}
每個(gè)斷言函數(shù)都接受 testing.T 對(duì)象作為第一個(gè)參數(shù)。這就是它如何通過正常的Go測(cè)試能力輸出錯(cuò)誤信息的方式。
每個(gè)斷言函數(shù)都返回一個(gè)布爾值,指示斷言是否成功。這對(duì)于在特定條件下繼續(xù)進(jìn)行進(jìn)一步的斷言非常有用。
當(dāng)我們有多個(gè)斷言語句時(shí),還可以使用assert := assert.New(t)創(chuàng)建一個(gè)assert對(duì)象,它擁有前面所有的斷言方法,只是不需要再傳入Testing.T參數(shù)了。
package yours
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestSomething(t *testing.T) {
assert := assert.New(t)
// 斷言相等
assert.Equal(123, 123, "它們應(yīng)該相等")
// 斷言不等
assert.NotEqual(123, 456, "它們不應(yīng)該相等")
// 斷言為nil(適用于錯(cuò)誤處理)
assert.Nil(object)
// 斷言不為nil(當(dāng)你期望得到某個(gè)結(jié)果時(shí)使用)
if assert.NotNil(object) {
// 現(xiàn)在我們知道object不是nil,我們可以安全地進(jìn)行
// 進(jìn)一步的斷言而不會(huì)引起任何錯(cuò)誤
assert.Equal("Something", object.Value)
}
}
在上面的示例中,assert.New(t) 創(chuàng)建了一個(gè)新的 assert 實(shí)例,然后您可以使用這個(gè)實(shí)例的方法來進(jìn)行斷言。如果斷言失敗,testify/assert 會(huì)自動(dòng)標(biāo)記測(cè)試為失敗,并記錄一個(gè)詳細(xì)的錯(cuò)誤消息。
六、單元測(cè)試代碼模板
func Test_Function(t *testing.T) {
testCases := []struct {
name string //測(cè)試用例的名稱
args any //測(cè)試用例的輸入?yún)?shù)
want string //期望的返回值
}{
// 測(cè)試用例,測(cè)試用例表格
{},
{},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
//具體的測(cè)試代碼
})
}
}