Go 常用包: 單元測試 -testing-
- 介紹
testing
包爲Go
語言提供自動化測試的支持。通過 go test
命令來執行單元測試文件,單元測試文件命名格式爲: xxx_test.go
, 在單元測試文件中, 根據測試類型不同可以分爲: 功能測試函數、基準測試函數, 區別如下:
- 功能測試
2.1 編寫規範
1. 函數格式
// 導入測試包
import "testing"
// 功能測試函數名
func TestName(t *testing.T) {
t.Log("附加的日誌信息")
// 功能邏輯代碼
if true {
// 錯誤時調用t.Error
t.Error("報告測試失敗")
}
}
整理規則如下:
-
每個測試函數必須導入
testing
包 -
函數的名字必須以
Test
開頭,可選的後綴名必須以大寫字母開頭 -
參數爲
t *testing.T
-
函數沒有返回參數
2. 運行格式
通過在go test
命令後添加-run
參數,其值對應的是個正則表達式,只有匹配上的函數纔會被go test
命令執行,如下示例:
# 執行命令
go test -run=? 文件[目錄]
-run值說明
2.2 測試示例
1. 代碼詳情
文件名:go_test.go
package test
import (
"testing"
)
// 通過測試函數
func TestPass(t *testing.T) {
t.Log("這個是通過測試函數")
}
// 不通過測試函數
func TestFail(t *testing.T) {
t.Error("運行測試失敗!")
}
2. 運行全部函數
➜ go test go_test.go -v
=== RUN TestPass
go_test.go:9: 這個是通過測試函數
--- PASS: TestPass (0.00s)
=== RUN TestFail
go_test.go:14: 運行測試失敗!
--- FAIL: TestFail (0.00s)
FAIL
FAIL command-line-arguments 1.635s
FAIL
go test
命令添加-v
參數,可以查看測試函數名稱和運行時間
3. 運行指定函數
go test
命令後添加-run
參數,其值對應的是個正則表達式,只有匹配上的函數纔會被go test
命令執行,如下示例:
# 只執行函數TestPass
➜ go test go_test.go -v -run="Pass"
=== RUN TestPass
more_func_test.go:9: 這個是通過測試函數
--- PASS: TestPass (0.00s)
PASS
ok command-line-arguments 0.889s
# 只執行TestFail函數
➜ go test go_test.go -v -run="Fail"
=== RUN TestFail
more_func_test.go:14: 運行測試失敗!
--- FAIL: TestFail (0.00s)
FAIL
FAIL command-line-arguments 0.198s
FAIL
2.3 子測試
從Go1.7+
後新增了子測試,我們可以通過使用t.Run
來執行子測試, 具體使用如下:
1. 代碼
package test
import (
"testing"
)
// 子測試使用
func TestSubtest(t *testing.T) {
// 子測試A
t.Run("subA", func(t *testing.T) {
t.Error("subA測試失敗")
})
// 子測試B
t.Run("subB", func(t *testing.T) {
t.Log("subB測試成功!")
})
}
2. 運行
➜ go test sub_test.go -v
=== RUN TestSubtest
=== RUN TestSubtest/subA
sub_test.go:11: subA測試失敗
=== RUN TestSubtest/subB
sub_test.go:15: subB測試成功!
--- FAIL: TestSubtest (0.00s)
--- FAIL: TestSubtest/subA (0.00s)
--- PASS: TestSubtest/subB (0.00s)
FAIL
FAIL command-line-arguments 0.851s
FAIL
- 基準測試
3.1 編寫規範
1. 函數格式
基準測試就是在一定的工作負載之下檢測程序性能的一種方法。基準測試函數的格式如下:
// 導入測試包
import "testing"
// 功能測試函數名
func BenchmarkName(b *testing.B){
// 被測試代碼放到循環內
for i:=0;i<b.N;i++{
// 具體業務函數
}
}
整理規則如下:
-
每個測試函數必須導入
testing
包 -
函數的名字必須以
Benchmark
開頭,可選的後綴名必須以大寫字母開頭 -
參數爲
t *testing.B
-
b.N
是基準測試框架提供的,表示循環的次數, -
函數沒有返回參數
2. 運行格式
# 執行命令
go test -bench=? 文件[目錄]
在Go
中我們還是通過go test
來執行基準測試,區別是需要加上參數-bench=?
, 而其中的?
代表匹配函數名的正則表達式,匹配規則如下:
3.2 測試示例
1. 代碼詳情
package tmp
import (
"testing"
)
// 變量賦值
func BenchmarkVar(b *testing.B) {
strSlice := make([]string,10)
for i := 0; i < b.N; i++ {
strSlice = append(strSlice,"go")
}
}
// 字符串拼接
func BenchmarkMulti(b *testing.B) {
str := ""
for i := 0; i < b.N; i++ {
str = str + strconv.Itoa(i)
}
}
@注意: 默認情況下,每個基準測試至少運行 1 秒。如果在 Benchmark 函數返回時沒有到 1 秒,則 b.N 的值會增加,並且函數會再次運行。
2. 運行全部函數
# 執行/test/bench_test.go文件中的所有Benchmark*函數
➜ go test -bench=. ./test/bench_test.go
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
BenchmarkSum-12 1000000000 0.2574 ns/op
BenchmarkMulti-12 1000000000 0.2529 ns/op
PASS
ok command-line-arguments 1.187s
3. 運行指定函數
# 只匹配BenchmarkSum*的函數
➜ go test -bench=Sum ./test/bench_test.go
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
BenchmarkSum-12 1000000000 0.2588 ns/op
PASS
ok command-line-arguments 0.457s
4. 測試結果說明
goos: darwin # 執行系統,常用的值:linux, windows, drawin (macOS)
goarch: amd64 # CPU 架構,常用的值 amd64, arm64, i386, armhf
cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz # CPU信息
#-12代表對應的 GOMAXPROCS 的值
BenchmarkVar-12 23692479(執行次數) 48.77 ns/op #(每次耗時48.77ns)
BenchmarkMulti-12 348254 110975 ns/op
PASS
ok command-line-arguments 43.349s
5. 更多維度數據
可以通過添加參數-benchmem
來獲取更多的性能數據,執行如下:
➜ go test -bench=. ./test/bench_test.go -benchmem
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
BenchmarkVar-12 18029002 59.87 ns/op 89 B/op 0 allocs/op
BenchmarkMulti-12 330717 102558 ns/op 900342 B/op 2 allocs/op
PASS
ok command-line-arguments 36.462s
3.3 提高精準度
1. 提高運行時間
默認情況下,每個基準測試至少運行 1 秒。如果在Benchmark
函數返回時沒有到 1 秒,則b.N
的值會自增加,並且函數再次運行。如果想運行更長時間,可以通過參數-benchtime
設置,如-benchtime=5s代表最少運行5秒
,下面是兩種情況的使用方法
a. 被測代碼詳情
package test
import (
"fmt"
"testing"
)
// 測試函數Sprintf性能
func BenchmarkCompute(b *testing.B) {
b.Logf("b.N=%d",b.N)
for i := 0; i < b.N; i++ {
_ = fmt.Sprintf("成績:%d",80)
}
}
b. 默認運行時間
# 默認運行
➜ go test -bench=Compute ./test/bench_test.go
goarch: amd64
cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
BenchmarkCompute-12 13177644 81.26 ns/op
--- BENCH: BenchmarkCompute-12
bench_test.go:10: b.N=1
bench_test.go:10: b.N=100
bench_test.go:10: b.N=10000
bench_test.go:10: b.N=1000000
bench_test.go:10: b.N=13177644
PASS
ok command-line-arguments 1.556s
b. 設置至少運行時間
# 通過參數-benchtime=5s,設置至少運行5秒
➜ go test -bench=Compute ./test/bench_test.go -benchtime=5s
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
BenchmarkCompute-12 69329104 82.30 ns/op
--- BENCH: BenchmarkCompute-12
bench_test.go:10: b.N=1
bench_test.go:10: b.N=100
bench_test.go:10: b.N=10000
bench_test.go:10: b.N=1000000
bench_test.go:10: b.N=69329104
PASS
ok command-line-arguments 6.208s
@注意: 通過上面代碼發現不管是運行 1.5 秒還是運行 6.2 秒,每次消耗時間沒有太大差異,從側面說明被測函數性能穩定。
2. 設置次數運行結果
默認每次都是運行一次基準測試函數活的一次運行的結果, 但是可以通過參數-count
來設置獲取運行多次的結果,具體使用如下:
執行上面示例:
# -count=5
➜ go test -bench=Compute ./test/bench_test.go -count=5
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
BenchmarkCompute-12 13069844 81.35 ns/op
BenchmarkCompute-12 14387593 81.51 ns/op
BenchmarkCompute-12 14359526 82.72 ns/op
BenchmarkCompute-12 14074830 83.99 ns/op
BenchmarkCompute-12 14114455 81.53 ns/op
PASS
ok command-line-arguments 6.523s
3.4 計時方法
1. 函數列表
進行基準測試之前可能會做一些準備,比如構建測試數據等,這些準備也需要消耗時間,所以需要把這部分時間排除在外。這時候我們可以使用
ResetTimer
方法來重置計時器,避免準備數據的耗時對測試數據造成干擾
2. 使用示例
// 重置時間使用
func BenchmarkTime(b *testing.B) {
// 準備工作
time.Sleep(time.Second * 3)
// 重置時間
b.ResetTimer()
for i := 0; i < b.N; i++ {
_ = fmt.Sprintf("hello:%v","word")
}
}
3.5 並行測試
在基準測試中可以使用RunParallel
函數, 來運行並行測試,它創建多個goroutine
,並在其中分配b.N
個迭代。goroutine
的數量默認爲GOMAXPROCS
。要想增加非CPU
基準測試的並行度,可以在調用RunParallel
之前調用SetParallelism
。也可以通過-cpu=
來設置使用。函數簽名具體如下:
func (b *B) RunParallel(body func(*PB))
body
將在每個goroutine
中運行。它應該設置任何goroutine-loca
l 狀態,然後迭代直到pb.Next
返回false
。它不應使用StartTimer
,StopTimer
或ResetTimer
函數,因爲它們具有全局作用。它也不應調用運行。
1. 函數格式
func BenchmarkXXX(b *testing.B) {
// b.SetParallelism(1) // 設置使用的CPU數
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
// 調用具體函數
}
})
}
2. 代碼示例
// 並行測試
func BenchmarkParallel(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = fmt.Sprintf("成績:%d",80)
}
})
}
4. 運行測試
# 不帶-cpu
➜ go test -bench=Parallel ./test/bench_test.go
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
BenchmarkParallel-12 50117173 22.84 ns/op
PASS
ok command-line-arguments 1.788s
# 設置CPU=4
➜ go test -bench=Parallel ./test/bench_test.go -cpu=4
goos: darwin
goarch: amd64
cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
BenchmarkParallel-4 35953536 32.66 ns/op
PASS
ok command-line-arguments 4.240s
- 報告函數
類型testing.T和testing.B
都繼承了類型testing.common
,testing.common
常用的報告函數,整理如下:
@注意: 上表中說的測試中斷,都是指中斷其所在的測試函數。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/MUvE3vomeoNsWovSBapLTg