这是我参与「第五届青训营 」伴学笔记创作活动的第 4 天
Go Test 测试功能详解
Created: January 21, 2023 7:16 PM Tags: golang
完善的测试体系能够提高项目开发的效率,当项目足够复杂的时候,想要尽可能地减少 bug,有两种有效的方式分别是代码审核和测试。Go 语言自带了 test 测试包,可以进行自动化的单元测试,验证输出结果,并且可以测试性能。
Go test 测试规则
要开始一个单元测试,首先需要创建一个名称以_test.go结尾的源文件,其中包含一个至多个测试函数(也称测试用例)例如func TestXxx(*testing.T) ,每个测试函数的名称要以 Test 为前缀,Xxx 要以大写字母为开头,标识测试内容。测试用例文件会被放在与被测文件相同的目录下,但测试用例文件不会参与正常源码的编译,不会被包含到可执行文件中,只有在运行 go test 命令时才会将测试用例文件包含其中。总结一下就是:
- 测试用例文件不会参与正常源码的编译,不会被包含到可执行文件中;
- 测试用例的文件名必须以
_test.go结尾; - 测试用例文件需要使用 import 导入 testing 包;
- 测试函数的名称要以
Test或Benchmark开头,后面可以跟任意字母组成的字符串,但第一个字母必须大写,例如 TestAbc(),一个测试用例文件中可以包含多个测试函数; - 测试用例文件使用
go test命令来执行,源码中不需要 main() 函数作为入口,所有以_test.go结尾的源码文件内以Test开头的函数都会自动执行。TestMain()函数用于初始化。 - 单元测试则以
(t *testing.T)作为参数,性能测试以(t *testing.B)做为参数;因为 Go 语言的 testing 包提供了三种测试方式,分别是单元(功能)测试、性能(压力)测试和覆盖率测试。
Go test 指令
go test 默认执行当前目录下以xxx_test.go的测试文件。
go test -v 可以看到详细的输出信息。
go test -v xxx_test.go xxx.go 指定测试单个文件,需要添加测试用例文件中调用模块的文件。
go test -v -test.run TestXxx 能够指定运行某个测试函数,但需要注意的是该测试会测试包含该函数名的所有函数。
flag -v 是 verbose 输出在测试运行时记录的所有测试,即使测试成功也会打印所有来自 Log 和 Logf 调用的文本。其他常见的标志还有:
-bench regexp :运行与正则表达式匹配的 benchmarks。go test 命令默认是不运行 benchmark 用例的,如果我们想运行 benchmark 用例就需要加上 -bench 参数。要运行所有基准,则使用 -bench .或 -bench=.。正则表达式由无括号的斜杠(/)字符分割成正则表达式序列,匹配的 benchmark 用例才会得到执行。基准测试是性能分析当中十分重要的一部分,稍后也将会对基准测试做更详细的描述。
-cover:启用覆盖率分析。覆盖率测试能知道测试程序总共覆盖了多少业务代码。
-cpu n ****:****指定执行测试或基准的 GOMAXPROCS 值的列表。默认为GOMAXPROCS的当前值。
-failfast :第一次测试失败后,不要再开始新的测试。
-parallel n :允许并行执行调用 t.Parallel 的测试函数。这个标志的值是同时运行的测试的最大数量;默认情况下,它被设置为GOMAXPROCS的值。
Go test 基准测试
当我们尝试去优化代码的性能时,首先得知道当前的性能怎么样。Go 语言标准库内置的 testing 测试框架提供了基准测试(benchmark)的能力,能让我们很容易地对某一段代码进行性能测试。
性能测试受环境的影响很大,为了保证测试的可重复性,在进行性能测试时,尽可能地保持测试环境的稳定。
- 机器处于闲置状态,测试时不要执行其他任务,也不要和其他人共享硬件资源。
- 机器是否关闭了节能模式,一般笔记本会默认打开这个模式,测试时关闭。
- 避免使用虚拟机和云主机进行测试,一般情况下,为了尽可能地提高资源的利用率,虚拟机和云主机 CPU 和内存一般会超分配,超分机器的性能表现会非常地不稳定。
benchmark 用例的参数 b *testing.B,有个属性 b.N 表示这个用例需要运行的次数。b.N 对于每个用例都是不一样的。
那这个值是如何决定的呢?b.N 从 1 开始,如果该用例能够在 1s 内完成,b.N 的值便会增加,再次执行。b.N 的值大概以 1, 2, 3, 5, 10, 20, 30, 50, 100 这样的序列递增,越到后面,增加得越快。我们以一个斐波那契数列程序为例:
// fib.go
package main
func fib(n int) int {
if n == 0 || n == 1 {
return n
}
return fib(n-2) + fib(n-1)
}
并在 fib_test.go 中实现一个 benchmark 用例:
// fib_test.go
package main
import "testing"
func BenchmarkFib(b *testing.B) {
for n := 0; n < b.N; n++ {
fib(30) // run fib(30) b.N times
}
}
运行测试用例得到的结果如下:
$ go test -bench=.
goos: darwin
goarch: amd64
pkg: example
BenchmarkFib-8 200 5865240 ns/op
PASS
ok example 1.782s
仔细观察上述例子的输出,BenchmarkFib-8 中的 -8 即 GOMAXPROCS,默认等于 CPU 核数。可以通过 -cpu 参数改变 GOMAXPROCS,-cpu 支持传入一个列表作为参数,例如:
$ go test -bench='Fib$' -cpu=2,4 .
goos: darwin
goarch: amd64
pkg: example
BenchmarkFib-2 206 5774888 ns/op
BenchmarkFib-4 205 5799426 ns/op
PASS
ok example 3.563s
202 和 5865240 ns/op 表示用例执行了 202 次,每次花费约 0.006s。总耗时比 1s 略多。