测试是每一个开发人员都需要掌握的技能,尽管你不需要像测试人员那么专业,但你也应该尽可能的做到那么专业,据我了解到我身边的一些Go开发人员,他们对Go的测试仅仅局限于写一个_test.go 测试文件,对执行方法进行测试,然后在goland的Ide中右键run方法运行,观测结果是否为绿色,仅此而已,我想说的是这只是一些皮毛,所以今天分享一些Go的测试技能,希望大家有收获。
Go测试用例
Go的测试文件命名规则为xxx_test.go
,其中xxx是需要测试的源代码文件的名称。在test文件中,可以编写测试函数,Go的测试函数整理分为4种,如下,其中XXX是需要测试的方法名称
- 单元测试:TestXXX(t *testing.T)
- 基准测试:BenchmarkXXX(b *testing.B)
- Main函数测试:TestMain(m *testing.M)
- 控制台测试 ExampleXXX()
假设我们有一个array_utils.go
的源代码文件,包名为array_utils
,我们在该包创建一个测试文件,名称为:array_utils_test.go
文件,源代码文件中有一个求最大子序列和的方法,我们针对该方法测试,如下代码,_test.go文件中可以有任意多个测试方法,这些测试方法的合集被称作测试套件。
我们的源码文件如下:
package array_utils
//求最大子序列和 (就是说子序列加起来和最大)
func FindMaxSeqSum(array []int) int {
SeqSum := make([]int, 0) // 存储子序列和
// 初始子序列和为 数组下标为0的值
SeqSum = append(SeqSum, array[0])
for i := 1; i < len(array); i++ {
if array[i] > SeqSum[i-1]+array[i] {
SeqSum = append(SeqSum, array[i])
} else {
SeqSum = append(SeqSum, SeqSum[i-1]+array[i])
}
}
max := SeqSum[0]
for j := 1; j < len(SeqSum); j++ {
if SeqSum[j] > SeqSum[j-1] {
max = SeqSum[j]
}
}
fmt.Println(max) //打印结果
return max
}
我们的测试文件如下
package array_utils
import (
"fmt"
"os"
"testing"
)
//TestMain会在下面所有测试方法执行开始前先执行,一般用于初始化资源和执行完后释放资源
func TestMain(m *testing.M) {
fmt.Println("初始化资源")
result := m.Run() //运行go的测试,相当于调用main方法
fmt.Println("释放资源")
os.Exit(result) //退出程序
}
//单元测试
func TestFindMaxSeqSum(t *testing.T) {
sum := FindMaxSeqSum([]int{1, 3, -9, 6, 8, -19})
if sum == 14 {
t.Log("successful")
} else {
t.Error("failed")
}
}
//基准测试
func BenchmarkFindMaxSeqSum(b *testing.B) {
for i := 0; i < b.N; i++ {
FindMaxSeqSum([]int{1, 3, -9, 6, 8, -19})
}
}
//这个验证的是FindMaxSeqSum方法控制台输出的max和OutPut后面的14是否一致,如果相同,则表示验证通过,否则测试用例失败
func ExampleFindMaxSeqSum() {
FindMaxSeqSum([]int{1, 3, -9, 6, 8, -19})
// OutPut: 14
}
我们通过命令go test来运行这段测试代码,进入到array_utils包下面,go test会遍历当前包下所有的xxx_test.go
中符合上述命名规则的函数,然后生成一个临时的main包用于调用相应的测试函数,然后构建并运行、报告测试结果,最后清理测试中生成的临时文件,结果如下:
初始化资源
=== RUN TestFindMaxSeqSum
14
--- PASS: TestFindMaxSeqSum (0.00s)
array_utils_test.go:16: successful
PASS
释放资源
Go test 有两种运行模式
本地目录模式,在没有包参数(例如 go test 或 go test -v )调用时发生。在此模式下, go test 编译当前目录中找到的包和测试,然后运行测试二进制文件。在这种模式下,caching 是禁用的。在包测试完成后,go test 打印一个概要行,显示测试状态、包名和运行时间
- go test 当软件包属于$GOPATH时,不需要从 Go Module 内部执行此命令,就进行测试
包列表模式,在使用显示包参数调用 go test 时发生(例如 go test math , go test ./... 甚至是 go test .)。在此模式下,Go测试编译并测试在命令上列出的每个包。如果一个包测试通过, go test 只打印最终的 ok 总结行。如果一个包测试失败, go test 将输出完整的测试输出。如果使用 -bench 或 -v 标志,则 go test 会输出完整的输出,甚至是通过包测试,以显示所请求的基准测试结果或详细日志记录
- go test math ,指的是测试Go的SDK的math包中的test文件
- go test ./... , 指的是测试递归测试当前目录下的所有test文件,因为当前目录下还有还有子文件夹,子文件夹下面还有子文件夹。
- go test . 测试当前目录中的软件包
- go test ./tranform 来测试 ./tranform 目录中的包
在包列表模式下,Go缓存成功的测试结果,以避免重复运行相同的测试。每当 GO 在包上运行测试时,Go都会创建一个测试二进制文件并运行它,如果要全局禁用缓存,可以将GOCACHE环境变量设置为off,
set GOCACHE=off //关闭,go1.12版本后必须打开,否则编译器报错
set GOCACHE=on //开启
go clean -testcache //手动清除缓存
Go的test常用参数实践
上面我们只是执行了go test的命令,关于go test可能的flag还有很多,不同的flag其对应的功能不同,接下来我们来实践一下。
基础功能参数
go test -c 生成用于运行测试的可执行文件,但不执行,在window平台下生成的是.exe文件,截图如下
go test -i 安装/重新安装运行测试所需的依赖包,但不编译和运行测试代码。
go test -o array_utils.test.exe 运行指定的的可执行的测试文件
go test -v 输出打印有关测试函数的其它信息
go test -v array_utils_test.go array_utils.go 测试指定的的文件
go test -v array_utils_test.go array_utils.go -test.run TestFindMaxSeqSum 测试指定文件的指定方法
go test -v -run=TestFindMaxSeqSum 测试指定文件的指定方法,-run后面可以匹配正则表达式,这个指的是测试名字等于TestFindMaxSeqSum的方法,如果是多个方法的话,可以使用|来隔开方法名。
代码覆盖率
由单元测试的代码,触发运行到的被测试代码的代码行数占所有代码行数的比例,被称为测试覆盖率,代码覆盖率不一定完全精准,但是可以作为参考,可以帮我们测量和我们预计的覆盖率之间的差距
go test -cover 生成代码测试覆盖率 ,coverage: 8.1% of statements
go test -v -coverprofile=c.out 将生成的代码测试覆盖率放入一个文件,然后运行下面的命令可以将c.out文件转换成一个html文件用浏览器阅读,截图如下,no coverage 代表没有覆盖的代码,high coverage代表高覆盖率的代表,一个红色,一个绿色,这里红色的截图上没体现出来,大家可本地试验一下。
go tool cover -html=c.out -o=tag.html
go test -covermode=set 覆盖测试模式,有三种值set,count,atomic,其中set代表的是这个语句运行吗?count代表的是这个语句执行多少次,atomic代表的是多线程正确使用的,耗资源的。
基准测试
go test默认情况下只会运行单元测试,那么基准测试如何执行呢?接下来一起看看。
go test -bench=. 执行当前测试包下的基准测试方法,在执行过程中会根据实际case的执行时间是否稳定会增加b.N的次数,要注意如果是要测试一个非稳态的函数,那么它可能永远也执行不完,记住-bench后面跟的是正则表达式
这是执行结果,-8指的是运行时对应的 GOMAXPROCS 的值,5000000指的是for循环的次数,249 ns/op 指的是每一次循环耗时239纳秒
BenchmarkFindMaxSeqSum-8 5000000 249 ns/op
go test -run=none -bench=. 通过指定方法名称为none来过滤掉单元测试,只执行基准测试的方法,当然也可以根据-bench后面的正则表达式来匹配。
go test -benchtime=3s -bench=. 在持续时间3s内运行每个基准测试
go test -benchmem -bench=. 打印基准测试时的内存分配
120 B/op代表每次操作消耗120B内存(1kb=1024b), 4 allocs/op 代表每次操作分配内存的次数
BenchmarkFindMaxSeqSum-8 5000000 300 ns/op 120 B/op 4 allocs/op
go test -count=2 -bench=. 执行指定次数的基准测试,在-count=1时相当于禁用缓存
go test -cpu=1 -bench=. 设置指定的cpu数量来进行基准测试,可以指定多个不同的cpu个数列别,比如:-cpu=1,2,4
其它一些参数控制
go test -timeout=3s默认情况下,测试执行超过10分钟就会超时而退出,我们可以通过这个时间指定超时时间
go test -parallel=2 当测试使用t.Parallel()方法将测试转为并发时,将受到最大并发数的限制,默认情况下最多有GOMAXPROCS个测试并发,其他的测试只能阻塞等待,这个可以用来并发安全的测试。
go test -short 缩短长时间运行的测试的测试时间。默认关闭
go test -v -cpuprofile=cpuprof.out 生成cpuprof的文件,通过运行下面的命令可以查看cpuprof的文件,默认是在控制台查看,当然也可以web界面查看,这不是本篇文章的重点,后面会单说。
go tool pprof prof.out
go test -trace trace.out 在退出之前,将执行跟踪写入指定文件。
go test -race 检测并发情况下数据竞争的问题,这个的使用比较复杂,后面也会单写文章来介绍。
testing 的变量
test.short : 一个快速测试的标记,在测试用例中可以使用 testing.Short() 来绕开一些测试
test.outputdir : 输出目录
test.coverprofile : 测试覆盖率参数,指定输出文件
test.run : 指定正则来运行某个 / 某些测试用例
test.memprofile : 内存分析参数,指定输出文件
test.memprofilerate : 内存分析参数,内存分析的抽样率
test.cpuprofile : cpu 分析输出参数,为空则不做 cpu 分析
test.blockprofile : 阻塞事件的分析参数,指定输出文件
test.blockprofilerate : 阻塞事件的分析参数,指定抽样频率
test.timeout : 超时时间
test.cpu : 指定 cpu 数量
test.parallel : 指定运行测试用例的并行数
还有很多,需要读者们自行研究,总结,分享,可以留言区讨论
testing.T 和 testing.B
testing 包内的结构
B : 压力测试
BenchmarkResult : 压力测试结果
Cover : 代码覆盖率相关结构体
CoverBlock : 代码覆盖率相关结构体
InternalBenchmark : 内部使用的结构
InternalExample : 内部使用的结构
InternalTest : 内部使用的结构
M : main 测试使用的结构
PB : Parallel benchmarks 并行测试使用结果
T : 普通测试用例
TB : 测试用例的接口
testing包的方法
如果有帮助,关注下公众号,阅读更多精彩文章