Golang單元測試與斷言編寫流程詳解
Go 在testing包中內(nèi)置測試命令go test
,提供了最小化但完整的測試體驗(yàn)。標(biāo)準(zhǔn)工具鏈還包括基準(zhǔn)測試和基于代碼覆蓋的語句,類似于NCover(.NET)或Istanbul(Node.js)。本文詳細(xì)講解go編寫單元測試的過程,包括性能測試及測試工具的使用,另外還介紹第三方斷言庫的使用。
編寫單元測試
go中單元測試與語言中其他特性一樣具有獨(dú)特見解,如格式化、命名規(guī)范。語法有意避免使用斷言,并將檢查值和行為的責(zé)任留給開發(fā)人員。
下面通過示例進(jìn)行說明。我們編寫Sum函數(shù),實(shí)現(xiàn)數(shù)據(jù)求和功能:
package main func Sum(x int, y int) int { return x + y } func main() { Sum(5, 5) }
然后在單獨(dú)的文件中編寫測試代碼,測試文件可以在相同包中,或不同包中。測試代碼如下:
package main import "testing" func TestSum(t *testing.T) { total := Sum(5, 5) if total != 10 { t.Errorf("Sum was incorrect, got: %d, want: %d.", total, 10) } }
Golang測試功能特性:
- 僅需要一個(gè)參數(shù),必須是
t *testing.T
- 以Test開頭,接著單詞或詞組,采用駱駝命名法,舉例:TestValidateClient
- 調(diào)用
t.Error
或t.Fail
表明失敗(當(dāng)然也可以使用t.Errorf
提供更多細(xì)節(jié)) t.Log
用于提供非失敗的debug信息輸出- 測試文件必須命名為
something_test.go
,舉例:addition_test.go
批量測試(test tables)
test tables
概念是一組(slice數(shù)組)測試輸入、輸出值:
func TestSum(t *testing.T) { tables := []struct { x int y int n int }{ {1, 1, 2}, {1, 2, 3}, {2, 2, 4}, {5, 2, 7}, } for _, table := range tables { total := Sum(table.x, table.y) if total != table.n { t.Errorf("Sum of (%d+%d) was incorrect, got: %d, want: %d.", table.x, table.y, total, table.n) } } }
如果需要觸發(fā)錯(cuò)誤,我們可以修改測試數(shù)據(jù),或修改代碼。這里修改代碼return x*y
, 輸出如下:
=== RUN TestSum
math_test.go:61: Sum of (1+1) was incorrect, got: 1, want: 2.
math_test.go:61: Sum of (1+2) was incorrect, got: 2, want: 3.
math_test.go:61: Sum of (5+2) was incorrect, got: 10, want: 7.
--- FAIL: TestSum (0.00s)FAIL
單元測試不僅要正向測試,更要進(jìn)行負(fù)向測試。
執(zhí)行測試
執(zhí)行測試有兩種方法:
在相同目錄下運(yùn)行命令:
go test
這會匹配任何packagename_test.go的任何文件。
使用完整的包名
go test
現(xiàn)在我們可以運(yùn)行單元測試了,還可以增加參數(shù)go test -v
獲得更多輸出結(jié)果。
單元測試和集成測試的區(qū)別在于單元測試通常不依賴網(wǎng)絡(luò)、磁盤等,僅測試一個(gè)功能,如函數(shù)。
另外還可以查看測試語句覆蓋率,增加-cover
選項(xiàng)。但高覆蓋率未必總是比低覆蓋率好,關(guān)鍵是功能正確。
如果執(zhí)行下面命令,可以生成html文件,以可視化方式查看覆蓋率:
go test -cover -coverprofile=c.out
go tool cover -html=c.out -o coverage.html
性能測試
benchmark 測試衡量程序性能,可以比較不同實(shí)現(xiàn)差異,理解影響性能原因。
go性能測試也有一定規(guī)范:
性能測試函數(shù)名必須以Benchmark
開頭,之后大寫字母或下劃線。因此BenchmarkFunctionName()
和 Benchmark_functionName()
都是合法的,但Benchmarkfunctionname()
不合法。這與單元測試以Test開頭規(guī)則一致。
雖然可以把單元測試和性能測試代碼放在相同文件,但盡量避免,文件命名仍然以_test.go結(jié)尾。如單元測試文件為simple_test.go,性能測試為benchmark_test.go。
下面通過示例進(jìn)行說明,首先定義函數(shù):
func IsPalindrome(s string) bool { for i := range s { if s[i] != s[len(s)-1-i] { return false } } return true }
先編寫單元測試,分別編寫正向測試和負(fù)向測試:
func TestPalindrome(t *testing.T) { if !IsPalindrome("detartrated") { t.Error(`IsPalindrome("detartrated") = false`) } if !IsPalindrome("kayak") { t.Error(`IsPalindrome("kayak") = false`) } } func TestNonPalindrome(t *testing.T) { if IsPalindrome("palindrome") { t.Error(`IsPalindrome("palindrome") = true`) } }
接著編寫基準(zhǔn)測試(性能測試):
func BenchmarkIsPalindrome(b *testing.B) { for i := 0; i < b.N; i++ { IsPalindrome("A man, a plan, a canal: Panama") } }
執(zhí)行性能測試
go test -bench . -run notest
-bench參數(shù)執(zhí)行所有性能測試,也可以使用正則代替.
,默認(rèn)情況單元測試也會執(zhí)行,因?yàn)閱卧獪y試種有錯(cuò)誤,可以通過-run 參數(shù)指定值不匹配任何測試函數(shù)名稱,從而僅執(zhí)行性能測試。
我們還可以指定其他參數(shù),下面示例指定count為2,表示對現(xiàn)有測試執(zhí)行兩次分析。設(shè)置GOMAXPROCS為4,查看測試的內(nèi)存情況,執(zhí)行這些請求時(shí)間為2秒,而不是默認(rèn)的1秒執(zhí)行時(shí)間。命令如下:
$ go test -bench=. -benchtime 2s -count 2 -benchmem -cpu 4 -run notest
goos: windows
goarch: amd64
pkg: gin01/math
cpu: Intel(R) Core(TM) i7-10510U CPU @ 1.80GHz
BenchmarkIsPalindrome
BenchmarkIsPalindrome-4 1000000000 1.349 ns/op 0 B/op 0 allocs/op
BenchmarkIsPalindrome-4 1000000000 1.356 ns/op 0 B/op 0 allocs/op
PASS
ok gin01/math 3.234s
-4
: 執(zhí)行測試的GOMAXPROCS數(shù)量1000000000
:為收集必要數(shù)據(jù)而運(yùn)行的次數(shù)1.349 ns/op
:測試每個(gè)循環(huán)執(zhí)行速度PASS
:指示基準(zhǔn)測試運(yùn)行的結(jié)束狀態(tài)。
配置計(jì)算時(shí)間
定義函數(shù):
func sortAndTotal(vals []int) (sorted []int, total int) { sorted = make([]int, len(vals)) copy(sorted, vals) sort.Ints(sorted) for _, val := range sorted { total += val total++ } return }
對應(yīng)單元測試如下:
func BenchmarkSort(b *testing.B) { rand.Seed(time.Now().UnixNano()) size := 250 data := make([]int, size) for i := 0; i < b.N; i++ { for j := 0; j < size; j++ { data[j] = rand.Int() } sortAndTotal(data) } }
每次執(zhí)行前,隨機(jī)生成數(shù)組,造成性能測試不準(zhǔn)確。
為了更準(zhǔn)確計(jì)算時(shí)間,可以使用下面函數(shù)進(jìn)行控制:
-StopTimer() : 停止計(jì)時(shí)器方法.
-StartTimer() : 啟動(dòng)計(jì)時(shí)器方法.
-ResetTimer() : 重置計(jì)時(shí)器方法.
最終性能測試函數(shù)如下:
func BenchmarkSort(b *testing.B) { rand.Seed(time.Now().UnixNano()) size := 250 data := make([]int, size) // 開始前先重置 b.ResetTimer() for i := 0; i < b.N; i++ { // 準(zhǔn)備數(shù)據(jù)時(shí)停止計(jì)時(shí) b.StopTimer() for j := 0; j < size; j++ { data[j] = rand.Int() } // 調(diào)用函數(shù)時(shí)啟動(dòng)計(jì)時(shí) b.StartTimer() sortAndTotal(data) } }
斷言(assertion)
go測試沒有提供斷言,對于java開發(fā)人員來說有點(diǎn)不習(xí)慣。這里介紹第三方庫 github.com/stretchr/testify/assert
.它提供了一組易理解的測試工具。
assert示例
assert子庫提供了便捷的斷言函數(shù),可以大大簡化測試代碼的編寫??偟膩碚f,它將之前需要判斷 + 信息輸出的模式:
import ( "testing" "github.com/stretchr/testify/assert" ) func TestSomething(t *testing.T) { var a string = "Hello" var b string = "Hello" assert.Equal(t, a, b, "The two words should be the same.") }
觀察到上面的斷言都是以TestingT為第一個(gè)參數(shù),需要大量使用時(shí)比較麻煩。testify提供了一種方便的方式。先以testing.T創(chuàng)建一個(gè)Assertions對象,Assertions定義了前面所有的斷言方法,只是不需要再傳入TestingT參數(shù)了。
func TestEqual(t *testing.T) { assertions := assert.New(t) assertion.Equal(a, b, "") // ... }
TestingT類型定義如下,就是對*testing.T做了一個(gè)簡單的包裝:
// TestingT is an interface wrapper around *testing.T type TestingT interface { Errorf(format string, args ...interface{}) }
下面引用官網(wǎng)的一個(gè)示例。
首先定義功能函數(shù)Addition:
func Addition(a, b int) int { return a + b }
測試代碼:
import ( "github.com/stretchr/testify/assert" "testing" ) // 定義比較函數(shù)類型,方便后面批量準(zhǔn)備測試數(shù)據(jù) type ComparisonAssertionFunc func(assert.TestingT, interface{}, interface{}, ...interface{}) bool // 測試參數(shù)類型 type args struct { x int y int } func TestAddition(t *testing.T) { tests := []struct { name string args args expect int assertion ComparisonAssertionFunc }{ {"2+2=4", args{2, 2}, 4, assert.Equal}, {"2+2!=5", args{2, 2}, 5, assert.NotEqual}, {"2+3==5", args{2, 3}, 5, assert.Exactly}, } for _, tt := range tests { // 動(dòng)態(tài)執(zhí)行斷言函數(shù) t.Run(tt.name, func(t *testing.T) { tt.assertion(t, tt.expect, Addition(tt.args.x, tt.args.y)) }) } assert.Equal(t, 2, Addition(1, 1), "sum result is equal") }
到此這篇關(guān)于Golang單元測試與斷言編寫流程詳解的文章就介紹到這了,更多相關(guān)Go單元測試內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang Gorm與數(shù)據(jù)庫完整性約束詳解
這篇文章主要介紹了golang Gorm與數(shù)據(jù)庫完整性約束詳解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12深入分析Go?實(shí)現(xiàn)?MySQL?數(shù)據(jù)庫事務(wù)
本文深入分析了Go語言實(shí)現(xiàn)MySQL數(shù)據(jù)庫事務(wù)的原理和實(shí)現(xiàn)方式,包括事務(wù)的ACID特性、事務(wù)的隔離級別、事務(wù)的實(shí)現(xiàn)方式等。同時(shí),本文還介紹了Go語言中的事務(wù)處理機(jī)制和相關(guān)的API函數(shù),以及如何使用Go語言實(shí)現(xiàn)MySQL數(shù)據(jù)庫事務(wù)。2023-06-06Go語言如何使用golang-jwt/jwt/v4進(jìn)行JWT鑒權(quán)詳解
最近項(xiàng)目中需要用到鑒權(quán)機(jī)制,golang中jwt可以用,這篇文章主要給大家介紹了關(guān)于Go語言如何使用golang-jwt/jwt/v4進(jìn)行JWT鑒權(quán)的相關(guān)資料,需要的朋友可以參考下2022-09-09詳解golang中bufio包的實(shí)現(xiàn)原理
這篇文章主要介紹了詳解golang中bufio包的實(shí)現(xiàn)原理,通過分析golang中bufio包的源碼,來了解為什么bufio能夠提高文件讀寫的效率和速度2018-01-01golang生成RSA公鑰和密鑰的實(shí)現(xiàn)方法
本文主要介紹了golang生成RSA公鑰和密鑰的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-08-08