如何用好 Go 的测试黑科技

测试是每一个开发人员都需要掌握的技能,尽管你不需要像测试人员那么专业,但你也应该尽可能的做到那么专业,据我了解到我身边的一些Go开发人员,他们对Go的测试仅仅局限于写一个_test.go 测试文件,对执行方法进行测试,然后在goland的Ide中右键run方法运行,观测结果是否为绿色,仅此而已,我想说的是这只是一些皮毛,所以今天分享一些Go的测试技能,希望大家有收获。

Go测试用例

Go的测试文件命名规则为xxx_test.go,其中xxx是需要测试的源代码文件的名称。在test文件中,可以编写测试函数,Go的测试函数整理分为4种,如下,其中XXX是需要测试的方法名称

  1. 单元测试:TestXXX(t *testing.T)
  2. 基准测试:BenchmarkXXX(b *testing.B)
  3. Main函数测试:TestMain(m *testing.M)
  4. 控制台测试 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 打印一个概要行,显示测试状态、包名和运行时间

  1. go test 当软件包属于$GOPATH时,不需要从 Go Module 内部执行此命令,就进行测试

包列表模式,在使用显示包参数调用 go test 时发生(例如 go test math , go test ./... 甚至是 go test .)。在此模式下,Go测试编译并测试在命令上列出的每个包。如果一个包测试通过, go test 只打印最终的 ok 总结行。如果一个包测试失败, go test 将输出完整的测试输出。如果使用 -bench 或 -v 标志,则 go test 会输出完整的输出,甚至是通过包测试,以显示所请求的基准测试结果或详细日志记录

  1. go test math ,指的是测试Go的SDK的math包中的test文件
  2. go test ./... , 指的是测试递归测试当前目录下的所有test文件,因为当前目录下还有还有子文件夹,子文件夹下面还有子文件夹。
  3. go test . 测试当前目录中的软件包
  4. 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的测试黑科技

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的测试黑科技

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包的方法

如何用好Go的测试黑科技

如何用好Go的测试黑科技

本作品采用《CC 协议》,转载必须注明作者和本文链接
那小子阿伟
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!