網站首頁 編程語言 正文
Go 在testing包中內置測試命令go test
,提供了最小化但完整的測試體驗。標準工具鏈還包括基準測試和基于代碼覆蓋的語句,類似于NCover(.NET)或Istanbul(Node.js)。本文詳細講解go編寫單元測試的過程,包括性能測試及測試工具的使用,另外還介紹第三方斷言庫的使用。
編寫單元測試
go中單元測試與語言中其他特性一樣具有獨特見解,如格式化、命名規范。語法有意避免使用斷言,并將檢查值和行為的責任留給開發人員。
下面通過示例進行說明。我們編寫Sum函數,實現數據求和功能:
package main func Sum(x int, y int) int { return x + y } func main() { Sum(5, 5) }
然后在單獨的文件中編寫測試代碼,測試文件可以在相同包中,或不同包中。測試代碼如下:
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測試功能特性:
- 僅需要一個參數,必須是
t *testing.T
- 以Test開頭,接著單詞或詞組,采用駱駝命名法,舉例:TestValidateClient
- 調用
t.Error
或t.Fail
表明失敗(當然也可以使用t.Errorf
提供更多細節) -
t.Log
用于提供非失敗的debug信息輸出 - 測試文件必須命名為
something_test.go
,舉例:addition_test.go
批量測試(test tables)
test tables
概念是一組(slice數組)測試輸入、輸出值:
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) } } }
如果需要觸發錯誤,我們可以修改測試數據,或修改代碼。這里修改代碼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
單元測試不僅要正向測試,更要進行負向測試。
執行測試
執行測試有兩種方法:
在相同目錄下運行命令:
go test?
這會匹配任何packagename_test.go的任何文件。
使用完整的包名
go test
現在我們可以運行單元測試了,還可以增加參數go test -v
獲得更多輸出結果。
單元測試和集成測試的區別在于單元測試通常不依賴網絡、磁盤等,僅測試一個功能,如函數。
另外還可以查看測試語句覆蓋率,增加-cover
選項。但高覆蓋率未必總是比低覆蓋率好,關鍵是功能正確。
如果執行下面命令,可以生成html文件,以可視化方式查看覆蓋率:
go test -cover -coverprofile=c.out
go tool cover -html=c.out -o coverage.html?
性能測試
benchmark 測試衡量程序性能,可以比較不同實現差異,理解影響性能原因。
go性能測試也有一定規范:
性能測試函數名必須以Benchmark
開頭,之后大寫字母或下劃線。因此BenchmarkFunctionName()
和 Benchmark_functionName()
都是合法的,但Benchmarkfunctionname()
不合法。這與單元測試以Test開頭規則一致。
雖然可以把單元測試和性能測試代碼放在相同文件,但盡量避免,文件命名仍然以_test.go結尾。如單元測試文件為simple_test.go,性能測試為benchmark_test.go。
下面通過示例進行說明,首先定義函數:
func IsPalindrome(s string) bool { for i := range s { if s[i] != s[len(s)-1-i] { return false } } return true }
先編寫單元測試,分別編寫正向測試和負向測試:
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`) } }
接著編寫基準測試(性能測試):
func BenchmarkIsPalindrome(b *testing.B) { for i := 0; i < b.N; i++ { IsPalindrome("A man, a plan, a canal: Panama") } }
執行性能測試
go test -bench . -run notest
-bench參數執行所有性能測試,也可以使用正則代替.
,默認情況單元測試也會執行,因為單元測試種有錯誤,可以通過-run 參數指定值不匹配任何測試函數名稱,從而僅執行性能測試。
我們還可以指定其他參數,下面示例指定count為2,表示對現有測試執行兩次分析。設置GOMAXPROCS為4,查看測試的內存情況,執行這些請求時間為2秒,而不是默認的1秒執行時間。命令如下:
$ 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
: 執行測試的GOMAXPROCS數量 -
1000000000
:為收集必要數據而運行的次數 -
1.349 ns/op
:測試每個循環執行速度 -
PASS
:指示基準測試運行的結束狀態。
配置計算時間
定義函數:
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 }
對應單元測試如下:
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) } }
每次執行前,隨機生成數組,造成性能測試不準確。
為了更準確計算時間,可以使用下面函數進行控制:
-StopTimer() : 停止計時器方法.
-StartTimer() : 啟動計時器方法.
-ResetTimer() : 重置計時器方法.
最終性能測試函數如下:
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++ { // 準備數據時停止計時 b.StopTimer() for j := 0; j < size; j++ { data[j] = rand.Int() } // 調用函數時啟動計時 b.StartTimer() sortAndTotal(data) } }
斷言(assertion)
go測試沒有提供斷言,對于java開發人員來說有點不習慣。這里介紹第三方庫 github.com/stretchr/testify/assert
.它提供了一組易理解的測試工具。
assert示例
assert子庫提供了便捷的斷言函數,可以大大簡化測試代碼的編寫??偟膩碚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為第一個參數,需要大量使用時比較麻煩。testify提供了一種方便的方式。先以testing.T創建一個Assertions對象,Assertions定義了前面所有的斷言方法,只是不需要再傳入TestingT參數了。
func TestEqual(t *testing.T) { assertions := assert.New(t) assertion.Equal(a, b, "") // ... }
TestingT類型定義如下,就是對*testing.T做了一個簡單的包裝:
// TestingT is an interface wrapper around *testing.T type TestingT interface { Errorf(format string, args ...interface{}) }
下面引用官網的一個示例。
首先定義功能函數Addition:
func Addition(a, b int) int { return a + b }
測試代碼:
import ( "github.com/stretchr/testify/assert" "testing" ) // 定義比較函數類型,方便后面批量準備測試數據 type ComparisonAssertionFunc func(assert.TestingT, interface{}, interface{}, ...interface{}) bool // 測試參數類型 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 { // 動態執行斷言函數 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") }
原文鏈接:https://blog.csdn.net/neweastsun/article/details/128101741
相關推薦
- 2022-08-21 導入pytorch時libmkl_intel_lp64.so找不到問題解決_python
- 2022-02-26 Echarts - 更改圖表圖例(legend)自定義顏色(并與數據段顏色對應)
- 2022-03-12 C++類和對象之多態詳解_C 語言
- 2022-12-08 C++?float轉std::string?小數位數控制問題_C 語言
- 2022-03-22 .NET?6開發TodoList開發查詢分頁_實用技巧
- 2023-03-13 React之虛擬DOM的實現原理_React
- 2023-01-28 Flutter框架解決盒約束widget和assets里加載資產技術_Android
- 2022-07-15 C#使用BitConverter與BitArray類進行預定義基礎類型轉換_C#教程
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支