32

如何用好 Go 的测试黑科技 | Go 技术论坛

 4 years ago
source link: https://learnku.com/articles/39971?
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

如何用好 Go 的测试黑科技

/ 76 / 0 / 发布于 10个月前

测试是每一个开发人员都需要掌握的技能,尽管你不需要像测试人员那么专业,但你也应该尽可能的做到那么专业,据我了解到我身边的一些 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 协议》,转载必须注明作者和本文链接
那小子阿伟

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK