1. 介绍
testing
包为Go
语言提供自动化测试的支持。通过 go test
命令来执行单元测试文件,单元测试文件命名格式为: xxx_test.go
,在单元测试文件中,根据测试类型不同可以分为:功能测试函数、基准测试函数,区别如下:
类型 |
函数格式 |
作用 |
功能测试函数 |
TestXXX(t *testing.T) |
测试函数功能是否正常 |
基准测试函数 |
BenchmarkXXX(b *testing.B) |
测试函数的性能 |
2. 功能测试
2.1 编写规范
1. 函数格式
import "testing"
func TestName(t *testing.T) { t.Log("附加的日志信息") if true { t.Error("报告测试失败") } }
|
整理规则如下:
- 每个测试函数必须导入
testing
包
- 函数的名字必须以
Test
开头,可选的后缀名必须以大写字母开头
- 参数为
t *testing.T
- 函数没有返回参数
2. 运行格式
通过在go test
命令后添加-run
参数,其值对应的是个正则表达式,只有匹配上的函数才会被go test
命令执行,如下示例:
-run值说明
值 |
说明 |
go test -run=. |
执行当前目录下所有测试文件中的TestXX 函数 |
go test -run=Pass |
执行当前目录下所有测试文件中的TestPass* 函数 |
go test -run=. a_test.go |
执行文件a_test.go 中的TestXX 函数 |
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
命令执行,如下示例:
➜ 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
➜ 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) { t.Run("subA", func(t *testing.T) { t.Error("subA测试失败") }) 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. 基准测试
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
中我们还是通过go test
来执行基准测试,区别是需要加上参数-bench=?
,而其中的?
代表匹配函数名的正则表达式,匹配规则如下:
?= |
说明 |
-bench=. |
代表执行所有函数 |
-bench=Sub |
代表执行所有BenchmarkSub* 的函数 |
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. 运行全部函数
➜ 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. 运行指定函数
➜ 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 goarch: amd64 cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
BenchmarkVar-12 23692479(执行次数) 48.77 ns/op 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
|
指标 |
说明 |
x ns/op |
每次执行耗时x ns |
x B/op |
每次操作内存分配了x 字节 |
x allocs/op |
每次操作进行了x 次内存分配 |
3.3 提高精准度
1. 提高运行时间
默认情况下,每个基准测试至少运行1秒。如果在Benchmark
函数返回时没有到1秒,则b.N
的值会自增加,并且函数再次运行。如果想运行更长时间,可以通过参数-benchtime
设置,如-benchtime=5s代表最少运行5秒
,下面是两种情况的使用方法
a. 被测代码详情
package test
import ( "fmt" "testing" )
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. 设置至少运行时间
➜ 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
来设置获取运行多次的结果,具体使用如下:
执行上面示例:
➜ 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 |
重置计时器 |
StartTimer |
控制开始计时 |
StopTimer |
控制停止计时 |
进行基准测试之前可能会做一些准备,比如构建测试数据等,这些准备也需要消耗时间,所以需要把这部分时间排除在外。这时候我们可以使用 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.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. 运行测试
➜ 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
➜ 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
|
4. 报告函数
类型testing.T和testing.B
都继承了类型testing.common
,testing.common
常用的报告函数,整理如下:
函数名 |
作用 |
Fail |
测试失败,但是后续代码依然会执行 |
FailNow |
测试失败,并中断代码执行 |
SkipNow |
跳过测试,中断代码执行,并且不会标识测试失败 |
Log |
输出信息 |
Logf |
输出格式化的信息 |
Skip |
相当于Log + SkipNow |
Skipf |
相当于 Logf + SkipNow |
Error |
相当于 Log + Fail |
Errorf |
相当于 Logf + Fail |
Fatal |
相当于 Log + FailNow |
Fatalf |
相当于 Logf + FailNow |
@注意: 上表中说的测试中断,都是指中断其所在的测试函数。