Go 單元測(cè)試基本介紹

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ì)原則

  1. 每個(gè)測(cè)試單元必須完全獨(dú)立、能單獨(dú)運(yùn)行。
  2. 一個(gè)測(cè)試單元應(yīng)只關(guān)注一個(gè)功能函數(shù),證明它是正確的;
  3. 測(cè)試代碼要能夠快速執(zhí)行。
  4. 不能為了單元測(cè)試而修改已完成的代碼在編寫代碼后執(zhí)行針對(duì)本次的單元測(cè)試,并執(zhí)行之前的單元測(cè)試用例。
  5. 以保證你后來編寫的代碼不會(huì)破壞任何事情;
  6. 單元測(cè)試函數(shù)使用長(zhǎng)的而且具有描述性的名字,例如都以test_開頭,然后加上具體的函數(shù)名字或者功能描述;例如:func_test.go。
  7. 測(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è)試成功,也打印所有來自 LogLogf 調(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.Errort.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.Equalassert.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è)試代碼
        })
    }
}

七、參考文檔

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容