Go 語言中,單元測試是通過標準庫中的 testing 包來實現的,該包提供了一組功能,使得編寫、運行和管理單元測試變得簡單和高效。
一、規則
-
測試文件的命名規則
Go 中的測試文件命名規則是在被測試的源文件名后面加上_test.go
。例如,如果你有一個calculator.go
文件,相應的測試文件應該是calculator_test.go
。 -
測試函數的命名規則
測試函數必須以Test
開頭,后面可以跟任何非空字符串,例如TestAdd
、TestSubtract
等。 -
使用
testing.T
進行斷言和錯誤報告
在測試函數中,使用testing.T
類型的參數來管理測試狀態和輸出。你可以使用t.Error*
、t.Fail*
等方法來指示測試失敗,并輸出相關的錯誤信息。
二、單元測試示例
2.1 單個測試用例
在calculator包中定義了一個calculator函數,具體實現如下:
package calculatorfunc Add(a, b int) int {return a + b
}
在當前目錄下,我們創建一個calculator_test.go的測試文件,并定義一個測試函數如下:
package calculatorimport "testing"func TestAdd(t *testing.T) {result := Add(1, 2)expected := 3if result != expected {t.Errorf("Add(1,2) return %d, expected %d", result, expected)}
}
要運行這個單元測試,可以使用 go test 命令。在命令行中進入到包含 calculator.go 和 calculator_test.go 的目錄,然后執行go test
,這些結果如下
go test
PASS
ok modu 0.226s
2.2 多個測試用例
在calculator_test.go中添加如下測試函數:
func TestAdd2(t *testing.T) {result := Add(3, 2)expected := 3if result != expected {t.Errorf("Add(1,2) return %d, expected %d", result, expected)}
}
為了能更好的在輸出結果中看到每個測試用例的執行情況,我們可以為go test -v
參數,讓它輸出完整的測試結果。
go test -v
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
=== RUN TestAdd2calculator_test.go:17: Add(1,2) return 5, expected 3
--- FAIL: TestAdd2 (0.00s)
FAIL
exit status 1
FAIL modu 0.216s
2.3 指定運行測試用例
go test -run
命令可以按照指定的模式運行測試。這個命令支持通過正則表達式來選擇要運行的測試函數。
例如修正好TestAdd2用例之后,通過go tes -run=Add2
只運行TestAdd2這個測試用例,結果是
go test -run=Add2 -v
=== RUN TestAdd2
--- PASS: TestAdd2 (0.00s)
PASS
ok modu 0.198s
4. 跳過某些測試用例
新加測試函數
func TestAdd3(t *testing.T) {if testing.Short() {t.Skip("short模式下會跳過該測試用例")}result := Add(3, 2)expected := 5if result != expected {t.Errorf("Add(1,2) return %d, expected %d", result, expected)}
}
當執行go test -shor
t時,就會跳過testing.Short()
標記的測試用例,結果是
go test -short -v
=== RUN TestAdd
--- PASS: TestAdd (0.00s)
=== RUN TestAdd2
--- PASS: TestAdd2 (0.00s)
=== RUN TestAdd3calculator_test.go:23: short模式下會跳過該測試用例
--- SKIP: TestAdd3 (0.00s)
PASS
ok modu 0.635s
三、測試組和子測試
3.1 測試組和子測試
通過測試組和子測試,可以更友好來添加更多的測試用例,以及查看結果
func TestAdd(t *testing.T) {tests := []struct {name stringx, y intexpected int}{{"Add1", 1, 2, 3},{"Add2", 3, 3, 6},{"Add3", 4, 5, 8},}for _, tc := range tests {t.Run(tc.name, func(t *testing.T) {result := Add(tc.x, tc.y)if result != tc.expected {t.Errorf("Add(%d, %d) returned %d, expected %d", tc.x, tc.y, result, tc.expected)}})}
}
運行go test -v
,結果是
go test -v
=== RUN TestAdd
=== RUN TestAdd/Add2
=== RUN TestAdd/Add3calculator_test.go:51: Add(4, 5) returned 9, expected 8
--- FAIL: TestAdd (0.00s)--- PASS: TestAdd/Add1 (0.00s)--- PASS: TestAdd/Add2 (0.00s)--- FAIL: TestAdd/Add3 (0.00s)
FAIL
exit status 1
FAIL modu 0.190s
3.2 并行測試
Go語言天生支持并發,所以通過添加t.Parallel()
來實現驅動測試并行化。
func TestAdd(t *testing.T) {t.Parallel() // 將 TLog 標記為能夠與其他測試并行運行// 這里使用匿名結構體定義了若干個測試用例// 并且為每個測試用例設置了一個名稱tests := []struct {name stringx, y intexpected int}{{"Add1", 1, 2, 3},{"Add2", 3, 3, 6},{"Add3", 4, 5, 8},}for _, tc := range tests {tc := tc // 注意這里重新聲明tt變量(避免多個goroutine中使用了相同的變量)t.Run(tc.name, func(t *testing.T) {t.Parallel() // 將每個測試用例標記為能夠彼此并行運行result := Add(tc.x, tc.y)if result != tc.expected {t.Errorf("Add(%d, %d) returned %d, expected %d", tc.x, tc.y, result, tc.expected)}})}
}
3.3 測試覆蓋率
使用go test -cover
來查看測試覆蓋率
go test -cover
PASSmodu coverage: 100.0% of statements
ok modu 1.149s
四、Go單元測試工具包 – testify
在進行Go語言單元測試時,由于官方并未內置斷言功能,我們通常需要使用大量的if...else...
語句來校驗測試結果。然而,通過使用第三方庫如testify/assert
,我們可以輕松地調用多種常用的斷言函數,這些函數不僅能夠簡化測試代碼,還能生成清晰易懂的錯誤描述信息,幫助我們快速定位問題。
在上面例子當中,我們使用if...else...
語句來校驗測試結果
for _, tc := range tests {t.Run(tc.name, func(t *testing.T) {result := Add(tc.x, tc.y)if result != tc.expected {t.Errorf("Add(%d, %d) returned %d, expected %d", tc.x, tc.y, result, tc.expected)}})}
現在可使用testify/assert
之將上述判斷過程簡化如下:
for _, tc := range tests {t.Run(tc.name, func(t *testing.T) {result := Add(tc.x, tc.y)assert.Equal(t, result, tc.expected)})}
testify/require
擁有testify/assert
所有斷言函數,它們的唯一區別就是testify/require
遇到失敗的用例會立即終止本次測試。