做網(wǎng)站上傳電子書最新的新聞 今天
文章目錄
- 引入
- 一、單元測試基本介紹
- 1.1 什么是單元測試?
- 1.2 如何寫好單元測試
- 1.3 單元測試的優(yōu)點(diǎn)
- 1.4 單元測試的設(shè)計原則
- 二、Go語言測試
- 2.1 Go單元測試概要
- 2.2 Go單元測試基本規(guī)范
- 2.3 一個簡單例子
- 2.3.1 使用Goland 生成測試文件
- 2.3.2 運(yùn)行單元測試
- 2.3.3 完善測試用例
- 2.3.5 回歸測試
- 2.4 Goland 直接運(yùn)行單元測試
- 2.5 Go Test 命令參數(shù)
- 2.6 運(yùn)行一個文件中的單個測試
- 2.7 測試覆蓋率
- 2.8 公共的幫助函數(shù)(helpers)
- 三、`testing.T`的擁有的方法
- 四、Table Driven 模式
- 4.1 介紹
- 4.2 Go 組織測試的方式
- 4.3 舉個例子
- 4.4 運(yùn)行 Table Driven 下的單個測試
- 五、`testify/assert` 斷言工具包
- 5.1 介紹
- 5.2 安裝
- 5.3 使用
- 六、單元測試代碼模板
- 七、參考文檔
引入
正常的業(yè)務(wù)開發(fā)之后的測試流程,都是先單元測試,后集成測試。
- 單元測試:針對每一個方法進(jìn)行的測試,單獨(dú)驗(yàn)證每一個方法的正確性。
- 集成測試:多個組件合并在一起的測試,驗(yàn)證各個方法、組件之間配合無誤。
所以一般項目都是開發(fā)人員要先搞單元測試,單元測試初步驗(yàn)證之后,再集成測試。
單元測試驗(yàn)證了各個方法的基本邏輯之后,集成測試就比較少問題了。
一、單元測試基本介紹
1.1 什么是單元測試?
單元測試(Unit Tests, UT) 是一個優(yōu)秀項目不可或缺的一部分,是對軟件中的最小可測試部分進(jìn)行檢查和驗(yàn)證。在面向?qū)ο缶幊讨?#xff0c;最小測試單元通常是一個方法或函數(shù)。單元測試通常由開發(fā)者編寫,用于驗(yàn)證代碼的一個很小的、很具體的功能是否正確。單元測試是自動化測試的一部分,可以頻繁地運(yùn)行以檢測代碼的更改是否引入了新的錯誤。
特別是在一些頻繁變動和多人合作開發(fā)的項目中尤為重要。你或多或少都會有因?yàn)樽约旱奶峤?#xff0c;導(dǎo)致應(yīng)用掛掉或服務(wù)宕機(jī)的經(jīng)歷。如果這個時候你的修改導(dǎo)致測試用例失敗,你再重新審視自己的修改,發(fā)現(xiàn)之前的修改還有一些特殊場景沒有包含,恭喜你減少了一次上庫失誤。也會有這樣的情況,項目很大,啟動環(huán)境很復(fù)雜,你優(yōu)化了一個函數(shù)的性能,或是添加了某個新的特性,如果部署在正式環(huán)境上之后再進(jìn)行測試,成本太高。對于這種場景,幾個小小的測試用例或許就能夠覆蓋大部分的測試場景。而且在開發(fā)過程中,效率最高的莫過于所見即所得了,單元測試也能夠幫助你做到這一點(diǎn),試想一下,假如你一口氣寫完一千行代碼,debug 的過程也不會輕松,如果在這個過程中,對于一些邏輯較為復(fù)雜的函數(shù),同時添加一些測試用例,即時確保正確性,最后集成的時候,會是另外一番體驗(yàn)。
1.2 如何寫好單元測試
首先,學(xué)會寫測試用例。比如如何測試單個函數(shù)/方法;比如如何做基準(zhǔn)測試;比如如何寫出簡潔精煉的測試代碼;再比如遇到數(shù)據(jù)庫訪問等的方法調(diào)用時,如何 mock
。
然后,寫可測試的代碼。高內(nèi)聚,低耦合是軟件工程的原則,同樣,對測試而言,函數(shù)/方法寫法不同,測試難度也是不一樣的。職責(zé)單一,參數(shù)類型簡單,與其他函數(shù)耦合度低的函數(shù)往往更容易測試。我們經(jīng)常會說,“這種代碼沒法測試”,這種時候,就得思考函數(shù)的寫法可不可以改得更好一些。為了代碼可測試而重構(gòu)是值得的。
1.3 單元測試的優(yōu)點(diǎn)
單元測試講究的是快速測試、快速修復(fù)。
- 測試該環(huán)節(jié)中的業(yè)務(wù)問題,比如說在寫測試的時候,發(fā)現(xiàn)業(yè)務(wù)流程設(shè)計得不合理。
- 測試該環(huán)節(jié)中的技術(shù)問題,比如說
nil
之類的問題。
單元測試,從理論上來說,你不能依賴任何第三方組件。也就是說,你不能使用MySQL
或者Redis
。
如圖,要快速啟動測試,快速發(fā)現(xiàn)BUG,快速修復(fù),快速重測。
1.4 單元測試的設(shè)計原則
- 每個測試單元必須完全獨(dú)立、能單獨(dú)運(yùn)行。
- 一個測試單元應(yīng)只關(guān)注一個功能函數(shù),證明它是正確的;
- 測試代碼要能夠快速執(zhí)行。
- 不能為了單元測試而修改已完成的代碼在編寫代碼后執(zhí)行針對本次的單元測試,并執(zhí)行之前的單元測試用例。
- 以保證你后來編寫的代碼不會破壞任何事情;
- 單元測試函數(shù)使用長的而且具有描述性的名字,例如都以test_開頭,然后加上具體的函數(shù)名字或者功能描述;例如:func_test.go。
- 測試代碼必須具有可讀性。
二、Go語言測試
2.1 Go單元測試概要
Go 語言的單元測試默認(rèn)采用官方自帶的測試框架,通過引入 testing 包以及 執(zhí)行 go test
命令來實(shí)現(xiàn)單元測試功能。
在源代碼包目錄內(nèi),所有以 _test.go
為后綴名的源文件會被 go test
認(rèn)定為單元測試的文件,這些單元測試的文件不會包含在 go build
的源代碼構(gòu)建中,而是單獨(dú)通過 go test 來編譯并執(zhí)行。
2.2 Go單元測試基本規(guī)范
Go 單元測試的基本規(guī)范如下:
- 每個測試函數(shù)都必須導(dǎo)入 testing 包。測試函數(shù)的命名類似
func TestName(t *testing.T)
,入?yún)⒈仨毷?*testing.T
- 測試函數(shù)的函數(shù)名必須以大寫的 Test 開頭,后面緊跟的函數(shù)名,要么是大寫開關(guān),要么就是下劃線,比如
func TestName(t *testing.T)
或者func Test_name(t *testing.T)
都是 ok 的, 但是func Testname(t *testing.T)
不會被檢測到 - 通常情況下,需要將測試文件和源代碼放在同一個包內(nèi)。一般測試文件的命名,都是
{source_filename}_test.go
,比如我們的源代碼文件是allen.go ,那么就會在 allen.go 的相同目錄下,再建立一個 allen_test.go 的單元測試文件去測試 allen.go 文件里的相關(guān)方法。
當(dāng)運(yùn)行 go test 命令時,go test 會遍歷所有的 *_test.go
中符合上述命名規(guī)則的函數(shù),然后生成一個臨時的 main 包用于調(diào)用相應(yīng)的測試函數(shù),然后構(gòu)建并運(yùn)行、報告測試結(jié)果,最后清理測試中生成的臨時文件。
2.3 一個簡單例子
2.3.1 使用Goland 生成測試文件
我們來創(chuàng)建一個示例,創(chuàng)建名為 add.go
的文件
package mainfunc Add(a int, b int) int {return a + b
}
func Mul(a int, b int) int {return a * b
}
這里借助Goland給 ADD 函數(shù)生成并且編寫測試用例,只需要右鍵點(diǎn)擊函數(shù),轉(zhuǎn)到Generate
-> Test for file function
(生成函數(shù)測試)。
Goland 為我們生成了add_test.go
單測文件
package mainimport "testing"func TestAdd(t *testing.T) {type args struct {a intb int}tests := []struct {name stringargs argswant 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)行單元測試
運(yùn)行 go test
,該 package 下所有的測試用例都會被執(zhí)行。
go test .
ok gotest 1.060s
或 go test -v
,-v
參數(shù)會顯示每個用例的測試結(jié)果,另外 -cover
參數(shù)可以查看覆蓋率。
go test -v
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok gotest 1.208s
2.3.3 完善測試用例
接著我們來完善上面的測試用例,代碼如下:
package mainimport "testing"func TestAdd(t *testing.T) {type args struct {a intb int}tests := []struct {name stringargs argswant 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 回歸測試
我們修改了代碼之后僅僅執(zhí)行那些失敗的測試用例或新引入的測試用例是錯誤且危險的,正確的做法應(yīng)該是完整運(yùn)行所有的測試用例,保證不會因?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
測試結(jié)果表明我們的單元測試全部通過。
2.4 Goland 直接運(yùn)行單元測試
如果你的測試方法簽名沒錯的話,就能看到這個綠色圖標(biāo),點(diǎn)擊就能看到很多選項。
最主要的是:
- Run:運(yùn)行模式,直接運(yùn)行整個測試。
- Debug:Debug模式,你可以打斷點(diǎn)。
- Run xxx with Coverage:運(yùn)行并且輸出測試覆蓋率。
- 其它
Profile
都是性能分析,很少用。
除非你要看測試覆蓋率,不然都用Debug
。
2.5 Go Test 命令參數(shù)
go test 是 Go 語言的測試工具,你可以使用它來運(yùn)行 Go 程序的測試函數(shù)。
你可以在命令行中使用以下參數(shù)來調(diào)用 go test 命令:
- -run:指定要運(yùn)行的測試函數(shù)的名稱的正則表達(dá)式。例如,使用
go test -run TestAdd
可以運(yùn)行名稱為 TestSum 的測試函數(shù)。 - -bench:指定要運(yùn)行的基準(zhǔn)測試的名稱的正則表達(dá)式。例如,使用
go test -bench .
可以運(yùn)行所有基準(zhǔn)測試。 - -count:指定要運(yùn)行測試函數(shù)或基準(zhǔn)測試的次數(shù)。例如,使用
go test -count 2
可以運(yùn)行測試函數(shù)或基準(zhǔn)測試兩次。 - -v:輸出測試函數(shù)或基準(zhǔn)測試的詳細(xì)輸出。
- -timeout:設(shè)置測試函數(shù)或基準(zhǔn)測試的超時時間。例如,使用
go test -timeout 1s
可以將超時時間設(shè)置為 1 秒。
以下是一個go Test命令表格:
參數(shù) | 說明 |
---|---|
-bench regexp | 僅運(yùn)行與正則表達(dá)式匹配的基準(zhǔn)測試。默認(rèn)不運(yùn)行任何基準(zhǔn)測試。使用 -bench . 或 -bench= 來運(yùn)行所有基準(zhǔn)測試。 |
-benchtime t | 運(yùn)行每個基準(zhǔn)測試足夠多的迭代,以達(dá)到指定的時間 t (例如 -benchtime 1h30s )。默認(rèn)為1秒(1s)。特殊語法 Nx 表示運(yùn)行基準(zhǔn)測試 N 次(例如 -benchtime 100x )。 |
-count n | 運(yùn)行每個測試、基準(zhǔn)測試和模糊測試 n 次(默認(rèn)為1次)。如果設(shè)置了 -cpu ,則為每個 GOMAXPROCS 值運(yùn)行 n 次。示例總是運(yùn)行一次。-count 不適用于通過 -fuzz 匹配的模糊測試。 |
-cover | 啟用覆蓋率分析。 |
-covermode set,count,atomic | 設(shè)置覆蓋率分析的 mode。默認(rèn)為 “set”,如果啟用了 -race ,則為 “atomic”。 |
-coverpkg pattern1,pattern2,pattern3 | 對匹配模式的包應(yīng)用覆蓋率分析。默認(rèn)情況下,每個測試僅分析正在測試的包。 |
-cpu 1,2,4 | 指定一系列的 GOMAXPROCS 值,在這些值上執(zhí)行測試、基準(zhǔn)測試或模糊測試。默認(rèn)為當(dāng)前的 GOMAXPROCS 值。-cpu 不適用于通過 -fuzz 匹配的模糊測試。 |
-failfast | 在第一個測試失敗后不啟動新的測試。 |
-fullpath | 在錯誤消息中顯示完整的文件名。 |
-fuzz regexp | 運(yùn)行與正則表達(dá)式匹配的模糊測試。當(dāng)指定時,命令行參數(shù)必須精確匹配主模塊中的一個包,并且正則表達(dá)式必須精確匹配該包中的一個模糊測試。 |
-fuzztime t | 在模糊測試期間運(yùn)行足夠多的模糊目標(biāo)迭代,以達(dá)到指定的時間 t (例如 -fuzztime 1h30s )。默認(rèn)為永遠(yuǎn)運(yùn)行。特殊語法 Nx 表示運(yùn)行模糊目標(biāo) N 次(例如 -fuzztime 1000x )。 |
-fuzzminimizetime t | 在每次最小化嘗試期間運(yùn)行足夠多的模糊目標(biāo)迭代,以達(dá)到指定的時間 t (例如 -fuzzminimizetime 30s )。默認(rèn)為60秒。特殊語法 Nx 表示運(yùn)行模糊目標(biāo) N 次(例如 -fuzzminimizetime 100x )。 |
-json | 以 JSON 格式記錄詳細(xì)輸出和測試結(jié)果。這以機(jī)器可讀的格式呈現(xiàn) -v 標(biāo)志的相同信息。 |
-list regexp | 列出與正則表達(dá)式匹配的測試、基準(zhǔn)測試、模糊測試或示例。不會運(yùn)行任何測試、基準(zhǔn)測試、模糊測試或示例。 |
-parallel n | 允許并行執(zhí)行調(diào)用 t.Parallel 的測試函數(shù),以及運(yùn)行種子語料庫時的模糊目標(biāo)。此標(biāo)志的值是同時運(yùn)行的最大測試數(shù)。 |
-run regexp | 僅運(yùn)行與正則表達(dá)式匹配的測試、示例和模糊測試。 |
-short | 告訴長時間運(yùn)行的測試縮短其運(yùn)行時間。默認(rèn)情況下是關(guān)閉的,但在 all.bash 中設(shè)置,以便在安裝 Go 樹時可以運(yùn)行健全性檢查,但不花費(fèi)時間運(yùn)行詳盡的測試。 |
-shuffle off,on,N | 隨機(jī)化測試和基準(zhǔn)測試的執(zhí)行順序。默認(rèn)情況下是關(guān)閉的。如果 -shuffle 設(shè)置為 on ,則使用系統(tǒng)時鐘種子隨機(jī)化器。如果 -shuffle 設(shè)置為整數(shù) N ,則 N 將用作種子值。在這兩種情況下,種子將報告以便復(fù)現(xiàn)。 |
-skip regexp | 僅運(yùn)行與正則表達(dá)式不匹配的測試、示例、模糊測試和基準(zhǔn)測試。 |
-timeout d | 如果測試二進(jìn)制文件運(yùn)行時間超過持續(xù)時間 d ,則發(fā)生 panic。如果 d 為0,則禁用超時。默認(rèn)為10分鐘(10m)。 |
-v | 詳細(xì)輸出:記錄所有運(yùn)行的測試。即使測試成功,也打印所有來自 Log 和 Logf 調(diào)用的文本。 |
-vet list | 配置在 “go test” 期間對 “go vet” 的調(diào)用,以使用由逗號分隔的 vet 檢查列表。如果列表為空,“go test” 使用被認(rèn)為總是值得解決的精選檢查列表運(yùn)行 “go vet”。如果列表為 |
更多可以參考 Go 語言的官方文檔或使用 go help test
命令查看幫助信息
2.6 運(yùn)行一個文件中的單個測試
如果只想運(yùn)行其中的一個用例,例如 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 測試覆蓋率
測試覆蓋率是指代碼被測試套件覆蓋的百分比。通常我們使用的都是語句的覆蓋率,也就是在測試中至少被運(yùn)行一次的代碼占總代碼的比例。在公司內(nèi)部一般會要求測試覆蓋率達(dá)到80%左右。
Go提供內(nèi)置功能來檢查你的代碼覆蓋率,即使用go test -cover
來查看測試覆蓋率。
go test -cover
PASS
coverage: 100.0% of statements
ok gotest 1.381s
還可以使用 -coverprofile
標(biāo)志將覆蓋率數(shù)據(jù)輸出到一個文件中,然后使用 go tool cover
命令來查看更詳細(xì)的覆蓋率報告。
2.8 公共的幫助函數(shù)(helpers)
對一些重復(fù)的邏輯,抽取出來作為公共的幫助函數(shù)(helpers),可以增加測試代碼的可讀性和可維護(hù)性。 借助幫助函數(shù),可以讓測試用例的主邏輯看起來更清晰。
例如,我們可以將創(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)建了一個錯誤的測試用例,運(yùn)行 go test
,用例失敗,會報告錯誤發(fā)生的文件和行號信息:
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
可以看到,錯誤發(fā)生在第11行,也就是幫助函數(shù) createAddTestCase
內(nèi)部。116, 117, 118行都調(diào)用了該方法,我們第一時間并不能夠確定是哪一行發(fā)生了錯誤。有些幫助函數(shù)還可能在不同的函數(shù)中被調(diào)用,報錯信息都在同一處,不方便問題定位。因此,Go 語言在 1.9 版本中引入了 t.Helper()
,用于標(biāo)注該函數(shù)是幫助函數(shù),報錯時將輸出幫助函數(shù)調(diào)用者的信息,而不是幫助函數(shù)的內(nèi)部信息。
修改 createAddTestCaseV1
,調(diào)用 t.Helper()
type addCaseV1 struct {name stringA, B intwant 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
,報錯信息如下,可以非常清晰地知道,錯誤發(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 個建議:
- 不要返回錯誤, 幫助函數(shù)內(nèi)部直接使用
t.Error
或t.Fatal
即可,在用例主邏輯中不會因?yàn)樘嗟腻e誤處理代碼,影響可讀性。 - 調(diào)用
t.Helper()
讓報錯信息更準(zhǔn)確,有助于定位。
當(dāng)然,如果你是用Goland 編輯器的話,可以不使用t.Helper()
,自動會幫你打印出錯誤詳細(xì)信息
三、testing.T
的擁有的方法
以下是提供的 *testing.T
類型的方法及其用途的注釋:
// T 是 Go 語言測試框架中的一個結(jié)構(gòu)體類型,它提供了用于編寫測試的方法。
// 它通常通過測試函數(shù)的參數(shù)傳遞給測試函數(shù)。// Cleanup 注冊一個函數(shù),該函數(shù)將在測試結(jié)束時執(zhí)行,用于清理測試過程中創(chuàng)建的資源。
func (c *T) Cleanup(func())// Error 記錄一個錯誤信息,但不會立即停止測試的執(zhí)行。
func (c *T) Error(args ...interface{})// Errorf 根據(jù) format 和 args 記錄一個格式化的錯誤信息,但不會立即停止測試的執(zhí)行。
func (c *T) Errorf(format string, args ...interface{})// Fail 標(biāo)記測試函數(shù)為失敗,但不會停止當(dāng)前測試的執(zhí)行。
func (c *T) Fail()// FailNow 標(biāo)記測試函數(shù)為失敗,并立即停止當(dāng)前測試的執(zhí)行。
func (c *T) FailNow()// Failed 檢查測試是否失敗。
func (c *T) Failed() bool// Fatal 記錄一個錯誤信息,并立即停止測試的執(zhí)行。
func (c *T) Fatal(args ...interface{})// Fatalf 記錄一個格式化的錯誤信息,并立即停止測試的執(zhí)行。
func (c *T) Fatalf(format string, args ...interface{})// Helper 標(biāo)記當(dāng)前函數(shù)為輔助函數(shù),當(dāng)測試失敗時,輔助函數(shù)的文件名和行號將不會顯示在錯誤消息中。
func (c *T) Helper()// Log 記錄一些信息,這些信息只有在啟用詳細(xì)日志(-v標(biāo)志)時才會顯示。
func (c *T) Log(args ...interface{})// Logf 記錄一些格式化的信息,這些信息只有在啟用詳細(xì)日志(-v標(biāo)志)時才會顯示。
func (c *T) Logf(format string, args ...interface{})// Name 返回當(dāng)前測試或基準(zhǔn)測試的名稱。
func (c *T) Name() string// Skip 標(biāo)記測試為跳過,并記錄一個錯誤信息。
func (c *T) Skip(args ...interface{})// SkipNow 標(biāo)記測試為跳過,并立即停止當(dāng)前測試的執(zhí)行。
func (c *T) SkipNow()// Skipf 標(biāo)記測試為跳過,并記錄一個格式化的錯誤信息。
func (c *T) Skipf(format string, args ...interface{})// Skipped 檢查測試是否被跳過。
func (c *T) Skipped() bool// TempDir 返回一個臨時目錄的路徑,該目錄在測試結(jié)束時會被自動刪除。
func (c *T) TempDir() string
四、Table Driven 模式
4.1 介紹
Table Driven 模式是一種軟件設(shè)計模式,它通過將測試數(shù)據(jù)存儲在一個表格(通常是結(jié)構(gòu)化的數(shù)據(jù)結(jié)構(gòu),如數(shù)組、切片、映射或結(jié)構(gòu)體)中,然后在一個單獨(dú)的函數(shù)或方法中遍歷這個表格來執(zhí)行測試。這種模式使得測試代碼更加模塊化、可讀性和可維護(hù)性更高。
在 Go 語言中,Table Driven 模式通常通過定義一個結(jié)構(gòu)體來組織測試數(shù)據(jù),然后使用一個循環(huán)來遍歷這個結(jié)構(gòu)體,為每個測試用例執(zhí)行相同的測試邏輯。這種方法可以很容易地添加新的測試用例,并且可以使測試代碼更加簡潔和易于維護(hù)。
4.2 Go 組織測試的方式
Go里面,慣常的組織測試的方式,都是用Table Driven
。
Table Driven的形式如下圖。主要分成三個部分:
- 測試用例的定義:即每一個測試用例需要有什么。
- 具體的測試用例:你設(shè)計的每一個測試用例都在這里。
- 執(zhí)行測試用例:這里面還包括了對測試結(jié)果進(jìn)行斷言。
注意,你要優(yōu)先使用Table Driven
,但是不用強(qiáng)求。
你把測試用例定義看做是列名,每一個測試用例就是一行數(shù)據(jù),就能理解Table Driven這個含義了。
4.3 舉個例子
func TestMul(t *testing.T) {type args struct {a intb int}tests := []struct {name stringargs argswant 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)}})}
}
4.4 運(yùn)行 Table Driven 下的單個測試
當(dāng)你使用前面 Table Driven
的模式時,可以單個運(yùn)行測試用例。
五、testify/assert
斷言工具包
5.1 介紹
testify/assert
是一個流行的Go語言斷言庫,它提供了一組豐富的斷言函數(shù),用于簡化測試代碼的編寫。這個庫提供了一種更聲明式的方式來編寫測試,使得測試意圖更加明確,代碼更加簡潔。
使用 testify/assert
時,您不再需要編寫大量的 if
語句和 Error
方法調(diào)用來檢查條件和記錄錯誤。相反,您可以使用像 assert.Equal
、assert.Nil
、assert.True
這樣的斷言函數(shù)來驗(yàn)證測試的期望結(jié)果。
5.2 安裝
go get github.com/stretchr/testify
5.3 使用
斷言包提供了一些有用的方法,可以幫助您在Go語言中編寫更好的測試代碼。
- 打印友好、易于閱讀的失敗描述
- 允許編寫可讀性強(qiáng)的代碼
- 可以為每個斷言添加可選的注釋信息
看看它的實(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(適用于錯誤處理)assert.Nil(t, object)// 斷言不為nil(當(dāng)你期望得到某個結(jié)果時使用)if assert.NotNil(t, object) {// 現(xiàn)在我們知道object不是nil,我們可以安全地進(jìn)行// 進(jìn)一步的斷言而不會引起任何錯誤assert.Equal(t, "Something", object.Value)}
}
每個斷言函數(shù)都接受 testing.T
對象作為第一個參數(shù)。這就是它如何通過正常的Go測試能力輸出錯誤信息的方式。
每個斷言函數(shù)都返回一個布爾值,指示斷言是否成功。這對于在特定條件下繼續(xù)進(jìn)行進(jìn)一步的斷言非常有用。
當(dāng)我們有多個斷言語句時,還可以使用assert := assert.New(t)
創(chuàng)建一個assert對象,它擁有前面所有的斷言方法,只是不需要再傳入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(適用于錯誤處理)assert.Nil(object)// 斷言不為nil(當(dāng)你期望得到某個結(jié)果時使用)if assert.NotNil(object) {// 現(xiàn)在我們知道object不是nil,我們可以安全地進(jìn)行// 進(jìn)一步的斷言而不會引起任何錯誤assert.Equal("Something", object.Value)}
}
在上面的示例中,assert.New(t)
創(chuàng)建了一個新的 assert
實(shí)例,然后您可以使用這個實(shí)例的方法來進(jìn)行斷言。如果斷言失敗,testify/assert
會自動標(biāo)記測試為失敗,并記錄一個詳細(xì)的錯誤消息。
六、單元測試代碼模板
func Test_Function(t *testing.T) {testCases := []struct {name string //測試用例的名稱args any //測試用例的輸入?yún)?shù)want string //期望的返回值}{// 測試用例,測試用例表格{},{},}for _, tc := range testCases {t.Run(tc.name, func(t *testing.T) {//具體的測試代碼})}
}
七、參考文檔
- Go Test 單元測試簡明教程
- Go單元測試入門