文章目录
本节项目地址:07-UnitTestBenchmarkTest
go test工具
Go语言中的测试依赖
go test
,该命令是一个按照一定约定和组织的测试代码的驱动程序。在包目录内,所有以
_test.go
为后缀名的源代码文件都是
go test
测试的一部分,不会被
go build
编译到最终的可执行文件中。
在
*_test.go
文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数。
类型格式作用测试函数函数名前缀为
Test
测试程序的一些逻辑行为是否正确基准函数函数名前缀为
Benchmark
测试函数的性能示例函数函数名前缀为
Example
为文档提供示例文档
go test
命令会遍历所有的
*_test.go
文件中符合上述命名规则的函数,然后生成一个临时的
main
包用于调用相应的测试函数,然后构建并运行、报告测试结果,最后清理测试中生成的临时文件。
单元测试
- 固定语法
func TestFuncName(t *testing.T) {}
- 单元测试的测试函数都以
Test
开头,可选的后缀名以大写字母开头 - 参数 t 是
*testing.T
类型,用于报告测试失败和附加的日志信息
下述创建了一个测试函数:
// TestStringSplit 测试 string 的 split 函数funcTestStringSplit(t *testing.T){// 定义一个测试用例类型type test struct{
input string
sep string
want []string}// 定义一个存储测试用例的切片
tests :=[]test{{input:"a:b:c", sep:":", want:[]string{"a","b","c"}},{input:"a:b:c", sep:",", want:[]string{"a:b:c"}},{input:"abcd", sep:"bc", want:[]string{"a","d"}},{input:"沙河有沙又有河", sep:"沙", want:[]string{"","河有","又有河"}},}// 遍历切片,逐一执行测试用例for_, tc :=range tests {
got := strings.Split(tc.input, tc.sep)if!reflect.DeepEqual(got, tc.want){
t.Errorf("expected:%#v, got:%#v", tc.want, got)}}}
go test -v
:执行测试,并打印出测试函数名和运行时间
go test -v
=== RUN TestStringSplit
--- PASS: TestStringSplit (0.00s)
PASS
ok 07-UnitTestBenchmarkTest/UnitTest 0.662s
子测试
如果测试用例比较多的时候,我们是没办法一眼看出来具体是哪个测试用例失败了。可以按照如下方式使用
t.Run
执行子测试就能够看到更清晰的输出内容了。
// TestStringSplit1 测试 string 的 split 函数,子测试funcTestStringSplit1(t *testing.T){// 定义一个测试用例类型type test struct{
input string
sep string
want []string}// 测试用例使用map存储
tests :=map[string]test{"simple":{input:"a:b:c", sep:":", want:[]string{"a","b","c"}},"wrong sep":{input:"a:b:c", sep:",", want:[]string{"a:b:c"}},"more sep":{input:"abcd", sep:"bc", want:[]string{"a","d"}},"leading sep":{input:"沙河有沙又有河", sep:"沙", want:[]string{"河有","又有河"}},}for name, tc :=range tests {// 使用t.Run()执行子测试
t.Run(name,func(t *testing.T){
got := strings.Split(tc.input, tc.sep)if!reflect.DeepEqual(got, tc.want){
t.Errorf("expected:%#v, got:%#v", tc.want, got)}})}}
go test -v
=== RUN TestStringSplit
--- PASS: TestStringSplit (0.00s)
=== RUN TestStringSplit1
=== RUN TestStringSplit1/wrong_sep
=== RUN TestStringSplit1/more_sep
=== RUN TestStringSplit1/leading_sep
string_test.go:53: expected:[]string{"河有", "又有河"}, got:[]string{"", "河有", "又有河"}
=== RUN TestStringSplit1/simple
--- FAIL: TestStringSplit1 (0.00s)
--- PASS: TestStringSplit1/wrong_sep (0.00s)
--- PASS: TestStringSplit1/more_sep (0.00s)
--- FAIL: TestStringSplit1/leading_sep (0.00s)
--- PASS: TestStringSplit1/simple (0.00s)
FAIL
exit status 1
FAIL 07-UnitTestBenchmarkTest/UnitTest 0.629s
如果指向执行指定的测试函数,可以通过
-run
参数指定,它对应一个正则表达式,只有函数名匹配的上的测试函数才会被
go test
命令执行。
go test -v -run=Split/simple
=== RUN TestStringSplit
--- PASS: TestStringSplit (0.00s)
=== RUN TestStringSplit1
=== RUN TestStringSplit1/simple
--- PASS: TestStringSplit1 (0.00s)
--- PASS: TestStringSplit1/simple (0.00s)
PASS
ok 07-UnitTestBenchmarkTest/UnitTest 0.046s
测试覆盖率
测试覆盖率是你的代码被测试套件覆盖的百分比。通常我们使用的都是语句的覆盖率,也就是在测试中至少被运行一次的代码占总代码的比例。
Go提供内置功能来检查你的代码覆盖率。我们可以使用
go test -cover
来查看测试覆盖率。Go还提供了一个额外的
-coverprofile
参数,用来将覆盖率相关的记录信息输出到一个文件。
使用:
go test -cover -coverprofile="a.out"
,将会生成
a.out
文件。然后可以执行
go tool cover -html="a.out"
,使用
cover
工具来处理生成的记录信息,该命令会打开本地的浏览器窗口生成一个
HTML
报告。
基准测试
- 固定语法
func BenchmarkFuncName(b *testing.B) {}
- 基准测试的测试函数都以
Benchmark
开头 - 参数 b 是
*testing.B
类型 - 基准测试必须包含
for
循环,循环次数是b.N
次(b.N
的值是系统根据实际情况调整,保证测试稳定性)
下述创建了一个测试函数:
funcsplit(s, sep string)(result []string){
result =make([]string,0, strings.Count(s, sep)+1)
i := strings.Index(s, sep)for i >-1{
result =append(result, s[:i])
s = s[i+len(sep):]
i = strings.Index(s, sep)}
result =append(result, s)return}// BenchmarkSplit 测试自定义的 split 函数funcBenchmarkSplit(b *testing.B){for i :=0; i < b.N; i++{split("沙河有沙又有河","沙")}}
使用
go test -v -bench=Split
命令执行基准测试,基础测试默认不会执行,必须携带
-bench
参数。
go test -v -bench=Split
goos: windows
goarch: amd64
pkg: 07-UnitTestBenchmarkTest/BenchmarkTest
cpu: 13th Gen Intel(R) Core(TM) i9-13900HX
BenchmarkSplit
BenchmarkSplit-32 26486323 47.05 ns/op
PASS
ok 07-UnitTestBenchmarkTest/BenchmarkTest 1.897s
其中
BenchmarkSplit-32
表示对 Split 函数进行基准测试,数字
32
表示
GOMAXPROCS
的值,
26486323
和
47.05 ns/op
表示每次调用 Split 函数耗时
47.05ns
,这个结果是
26486323
次调用的平均值。
还可以为基准测试添加
-benchmem
参数,来获得内存分配的统计数据。如下所示,
48 B/op
表示每次操作内存分配了48字节,
1 allocs/op
表示每次操作进行了1次内存分配。
go test -v -bench=Split -benchmem
goos: windows
goarch: amd64
pkg: 07-UnitTestBenchmarkTest/BenchmarkTest
cpu: 13th Gen Intel(R) Core(TM) i9-13900HX
BenchmarkSplit
BenchmarkSplit-32 26387544 45.29 ns/op 48 B/op 1 allocs/op
PASS
ok 07-UnitTestBenchmarkTest/BenchmarkTest 1.295s
性能比较函数
性能比较函数通常是一个带有参数的函数,被多个不同的
Benchmark
函数传入不同的值来调用。
funcfib(n int)int{if n <2{return n
}returnfib(n-1)+fib(n-2)}funcbenchmarkFib(b *testing.B, n int){for i :=0; i < b.N; i++{fib(n)}}funcBenchmarkFib1(b *testing.B){benchmarkFib(b,1)}funcBenchmarkFib2(b *testing.B){benchmarkFib(b,2)}funcBenchmarkFib3(b *testing.B){benchmarkFib(b,3)}funcBenchmarkFib10(b *testing.B){benchmarkFib(b,10)}funcBenchmarkFib20(b *testing.B){benchmarkFib(b,20)}funcBenchmarkFib40(b *testing.B){benchmarkFib(b,40)}
上述编写了一个斐波那契数列计算函数,并在不同大小的n值的情况下进行基准测试。
go test -v -bench=Fib
goos: windows
goarch: amd64
pkg: 07-UnitTestBenchmarkTest/BenchmarkTest
cpu: 13th Gen Intel(R) Core(TM) i9-13900HX
BenchmarkFib1
BenchmarkFib1-32 1000000000 0.5479 ns/op
BenchmarkFib2
BenchmarkFib2-32 736555106 1.600 ns/op
BenchmarkFib3
BenchmarkFib3-32 459925009 2.590 ns/op
BenchmarkFib10
BenchmarkFib10-32 7295648 164.5 ns/op
BenchmarkFib20
BenchmarkFib20-32 59763 20160 ns/op
BenchmarkFib40
BenchmarkFib40-32 4 306910725 ns/op
PASS
ok 07-UnitTestBenchmarkTest/BenchmarkTest 8.692s
默认情况下,每个基准测试至少运行1秒,可以通过参数
-benchtime
设置基准测试时间。此外还有
-cpu
参数设置运行基准测试的并发数,
-count
参数设置运行测试的总次数。
go test -v -bench="." -benchtime=2s
goos: windows
goarch: amd64
pkg: 07-UnitTestBenchmarkTest/BenchmarkTest
cpu: 13th Gen Intel(R) Core(TM) i9-13900HX
BenchmarkSplit
BenchmarkSplit-32 48237133 44.58 ns/op
BenchmarkFib1
BenchmarkFib1-32 1000000000 0.5392 ns/op
BenchmarkFib2
BenchmarkFib2-32 1000000000 1.575 ns/op
BenchmarkFib3
BenchmarkFib3-32 931792406 2.590 ns/op
BenchmarkFib10
BenchmarkFib10-32 14704368 163.8 ns/op
BenchmarkFib20
BenchmarkFib20-32 119018 20080 ns/op
BenchmarkFib40
BenchmarkFib40-32 7 303590871 ns/op
PASS
ok 07-UnitTestBenchmarkTest/BenchmarkTest 14.857s
重置时间
b.ResetTimer
之前的处理不会放到执行时间里,也不会输出到报告中,所以可以在之前做一些不计划作为测试报告的操作。
b.ReportAllocs()
记录内存分配数据。
funcBenchmarkResetTimer(b *testing.B){
time.Sleep(3* time.Second)
b.ResetTimer()
b.ReportAllocs()for i :=0; i < b.N; i++{split("abc.poe.org.com",".")}}
go test -bench="Timer"
goos: windows
goarch: amd64
pkg: 07-UnitTestBenchmarkTest/BenchmarkTest
cpu: 13th Gen Intel(R) Core(TM) i9-13900HX
BenchmarkResetTimer-32 32262052 34.99 ns/op 64 B/op 1 allocs/op
PASS
ok 07-UnitTestBenchmarkTest/BenchmarkTest 16.843s
并行测试
func (b *B) RunParallel(body func(*PB))
会以并行的方式执行给定的基准测试。
RunParallel
会创建出多个
goroutine
,并将
b.N
分配给这些
goroutine
执行, 其中
goroutine
数量的默认值为
GOMAXPROCS
。用户如果想要增加非
CPU
受限
(non-CPU-bound)
基准测试的并行性, 那么可以在
RunParallel
之前调用
SetParallelism
。
RunParallel
通常会与
-cpu
标志一同使用。
funcBenchmarkSplitParallel(b *testing.B){// b.SetParallelism(1) // 设置使用的cpu数
b.RunParallel(func(pb *testing.PB){for pb.Next(){split("cn.com.org",".")}})}
go test -bench="Parallel" -cpu=4
goos: windows
goarch: amd64
pkg: 07-UnitTestBenchmarkTest/BenchmarkTest
cpu: 13th Gen Intel(R) Core(TM) i9-13900HX
BenchmarkSplitParallel-4 95221719 14.72 ns/op
PASS
ok 07-UnitTestBenchmarkTest/BenchmarkTest 2.329s
版权归原作者 ღCauchyོꦿ࿐ 所有, 如有侵权,请联系我们删除。