Go Test 总结

golang test 要求我们以 *_test.go 来创建测试文件,注意 以 _ 包括(_test.go) 或者 . 开头的文件都会被忽略
测试文件中可以包含 测试函数,基准测试函数, 示例函数

  • 测试函数必须以 TestXxx 命名,其中Xxx不能小写字母开头
  • 基准测试函数必须以 BenchmarkXxx 命名
  • 示例函数必须以 ExampleXxx 命名

go build 时,并不会将 *_test.go 文件一起打包编译,所以 *_test.go 中的一些 init 或者全局变量,并不能在执行中访问

go test 会编译当前目录中的包和测试文件,然后将生成的二进制文件存放到临时目录,然后运行测试二进制文件,执行完成后便删除文件

生成测试二进制可执行文件


如果只想编译测试二进制
执行go test -c 将测试二进制编译为 .test 的二进制文件,并且不会运行测试

可以直接执行测试二进制文件来测试 ./<pkg-name>.test

如果在运行完测试后,保留测试的二进制文件
执行 go test -o <filename> -o 指定二进制文件的名字

测试二进制文件可以通过 -test.<flag> 来使用一些 go test 的 flag
比如 打印测试日志 ./<pkg-name>.test -test.v

测试其他包或者目录


go test 在不指定想要的测试的包的时候会查找当前目录的包和测试文件,并且不会使用缓存

go test <pkg> 可以指定包名称,他会在 GOROOT 中查找包
go test .会在当前目录执行测试
go test ./...可以遍历子目录来执行测试

测试缓存

在指定了包或者目录的情况下,Go 会测试成功的结果,以避免重复运行相同的测试

  • 显式禁用测试缓存,使用 -count=1 flag
  • 禁用全局缓存,可以设置 GOCACHE 环境变量 go env -w GOCACHE=off

也可以手动清理缓存 go clean -testcache

测试覆盖率


-cover 开启覆盖分析

-coverprofile cover.out:在所有测试通过后,将测试覆盖报告写到文件中。
配合 -covermode 可以显示代码被覆盖的次数,也就是代码的测试热度

-covermode set 为默认 flag,只显示代码是否被覆盖到
-covermode count 显示代码被测试覆盖的次数

go tool cover -html=c.out 直接通过浏览器查看报告
go tool cover -html=cover.out -o cover.html

子测试


t.Run(name, t func(t *testing.T))

子测试中可以使用 t.Parallel() 来并行执行测试,不过父级测试会等到所有子测试完成,t.Run() 才会返回

func TestTeardownParallel(t *testing.T) {
    // This Run will not return until the parallel tests finish.
    t.Run("group", func(t *testing.T) {
        t.Run("Test1", parallelTest1)
        t.Run("Test2", parallelTest2)
        t.Run("Test3", parallelTest3)
    })
    // tear-down code
}

-run 执行指定的测试


默认,go test 会执行包中所有的测试函数,并打印测试结果,无论每个测试是否成功或者失败

go test <filename>... 可以测试指定的测试文件

可以通过 -run regexp 来执行指定的测试函数或者子测试函数

执行所有测试 go test -run '',相当于 go test / go test .

执行包含 Foo 的测试 go test -run Foo

执行包含 Foo 测试的 Sub2 子测试 go test -run Foo/Sub2

只执行子测试名字为 Sub1 的子测试 go test -run /Sub1

提供对 -short 的支持


go test 如果设置了 -short 标志,那么测试中 testing.Short() 会返回 true

如果在测试中增加了 short 判断,那么就可以跳过一些运行时间很长的测试函数

func TestPICalculation(t *testing.T){
    if testing.Short() {
        t.Skip("skipping PI Calculation in short model")
    }
}

-short 标志只是一个标示,具体还依赖于开发者,是否需要避免一些运行比较慢的函数被执行。

测试中常用的函数


func TestA (t *testing.T){...}
打印日志
t.Log 类似 fmt.Println()
t.Logf 类似 fmt.Prinltn(fmt.Sprintf())

标记测试失败
t.Fail 标记函数测试失败,但是会继续执行,不会中断测试函数
t.FailNow 标记失败,并调用 runtime.Goexit 终止当前测试

如果在测试函数中创建的 goroutine 中执行 t.FailNow 的话,会退出 goroutine,并不会中断测试函数

跳过测试
t.SkipNow 将测试标记为跳过,并调用 runtime.Goexit 中断测试函数

衍生函数
t.Fatalf = t.Logf + t.FailNow
t.Fatal = t.Log + t.FailNow

t.Error = t.Log + t.Fail
t.Errorf = t.Logf + t.Fail

t.Skip = t.Log + t.SkipNow
t.Skipf = t.Logf + t.SkipNow

如果测试函数先被标记为失败,再被标记为跳过,那么依然为失败

t.CleanUp 注册一个在测试及其所有子测试完成时调用的函数。清理函数将按“最后添加,先调用”的顺序调用。
t.TempDir 返回测试要使用的临时目录,当测试完成后,会调用 t.CleanUp 自动删除该目录

黑盒测试与循环引用问题


通常情况下,测试和要被测试的代码在同一个包中,这样才能访问内部实现细节的代码。
但是当包出现循环引用时,测试文件就无法被正确编译了

为了解决这个问题,go test 支持使用测试文件的包名以 <pkg>_test 后缀命名,并被编译成独立的包
这种机制也被称为提供了黑盒测试

package foo_test

import (
    "foo/mock" // also imports foo
    "foo"
)

这时只能调用 foo 包中的公开变量和函数

黑盒测试文件虽然和被测试的包处于同一个目录中,但是依然需要通过 foo 包名来调用变量或函数
可以使用 import . 来假装测试文件是 foo 包的一部分,注意 foo 包中的私有变量 和 函数,依然不会导入进来

package foo_test

import (
    "foo/mock" // also imports foo
    . "foo"
)

在其他情况下,不要使用 import . ,因为可能会引起变量混乱的问题

测试前的初始化准备工作


为了设置环境或者为了避免测试数据污染,有时候需要一些初始化和测试结束的清理操作,比如在所有的测试开始的前后清空某个测试数据库中的内容等。
这样的任务如果在每个测试用例中都重复执行,那不仅是的代码冗余,也是资源的浪费。

可以通过 TestMain 来帮我们执行初始化和清理操作

func TestMain(m *testing.M){
    doSomeSetup()
    r := m.Run()
    doSomeClear()
    
    os.Exit(r)
}

TestMain 函数是 GO 测试框架的入口点,一个包下只能有一个 TestMain 函数,通过调用 m.Run 来执行其他测试。
如果没有在所有测试的前后执行一些任务,那么就不需要实现 TestMain

对于单个测试,或者多个子测试进行清理操作可以使用 t.CleanUp 函数,会在测试及其所有子测试完成时调用的函数

并发测试


默认情况下,指定包的测试是按照顺序执行的,但也可以通过在测试的函数内部使用 t.Parallel() 来标志某些测试也可以被安全的并发执行。

func TestParallel(t *testing.T) {
    t.Parallel()
    // ...
}

在并行执行的情况下,只有那些被标记为并行的测试才会被并行执行,所以只有一个测试函数时是没意义的。
它应该在测试函数体中第一个被调用(在任何需要跳过的条件之后),因为它会重置测试时间

并行测试的数量默认取决于 GOMAXPROCS,可以通过 -parallel n 来指定测试的最大并发数

包级并发测试

调用 go test ./... 测试所有子目录中的包,或者 go test <pkg1> <pkg2> 指定测试多个包时,包会被先编译,并同时被执行(并发执行)。

这对于总的时间来说是有好处的,但它也可能会导致错误变得具有不可预测性,比如一些资源被多个包同时使用时(例如,一些测试需要访问数据库,并删除一些行,而这些行又刚好被其他的测试包使用的话)。

为了保持多包测试的可控性,-p 标志可以用来指定编译和测试的并发数。

  • 没有带 -p 标志执行时,总的测试时间应该接近于运行时间最长的包的时间。
  • -p 1 使编译和测试工具只能在一个包中执行时,总的测试时间应该接近于所有独立的包测试的时间之和

helper 函数


对于一些重复的逻辑,可以抽取出来作为公共的帮助函数(helpers),增加测试代码的可读性和可维护性
借助帮助函数,可以让测试用例的主逻辑看起来更清晰

正常的函数会报告错误发生的文件和行号信息
如果在函数中调用了 t.Helper(), 那么就会将函数标注为帮助函数,报错是将输出帮助函数调用者的信息,而不是帮助函数中的内部信息

其他 test flag

  • -args 可以传递命令行参数给测试文件
  • -i 安装与测试相关的包,不运行测试
  • -v 是用于输出所有Log的信息, 可以打印一些额外的调试日志以及每个测试函数的执行情况
  • -json 将输出转换成 json 格式
  • -failfast 指定如果测试失败,会立即停止其他测试
  • -list regexp 只列出匹配成功的测试函数,不会真正执行,并且不会列出子函数
  • -timeout d[s|m|h] 默认测试执行超过10分钟会超时退出

性能测试


go test 默认不执行性能测试,只有使用 -bench 才运行性能测试,并且只运行性能测试

-bench regexp 可以接正则,来匹配需要执行的性能测试
-bench . -bench=. 可以执行所有基准测试

性能测试同样可以包含子测试

func BenchmarkSub(b *testing.B){
    b.Run("A", benchSub1)
    b.Run("b", benchSub2)
}

-bench Sub/A 指定子测试
-benchtime <t>s 指定每个性能测试的执行时间,默认为 1s

-cpu n1, n2 提供 CPU 个数的列表,测试会按照列表指定的 CPU 数设置 GOMAXPROCS 并测试
-cpu 1,2,3 每个测试会执行3次,分别使用 1,2,3 个cpu

-count n 每个测试执行的次数,默认执行一次
如果同时指定了 -count,-cpu 那么测试会在每种 cpu 数量下执行 count 指定的次数

  • -benchmem 可以打印每个操作分配的字节数,每个操作分配的对象数
  • -cpuprofile cpu.out:在退出之前,将一个 CPU 概要文件写入指定的文件。
  • -memprofile mem.out:在所有测试通过后,将内存概要文件写到文件中。
  • -memprofilerate n:开启更精确的内存配置。如果为 1,将会记录所有内存分配到 profile。
  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值