Go Test
本文最后更新于:2024年3月18日 凌晨
Go Test
go test 工具
- Go 语言中的测试依赖
go test命令,编写测试代码和编写普通的 Go 代码过程是类似的,并不需要学习新的语法,规则或工具。 - go test 命令是一个按照一定约定和组织的测试代码的驱动程序,在包目录内,所有以
_test.go为后缀名的源代码文件都是go test测试的一部分,不会被go build编译到最终的可执行文件中。 - 在
*_test.go文件中有三种类型的函数,单元测试函数,基准测试函数和示例函数。
| 类型 | 格式 | 作用 |
|---|---|---|
| 测试函数 | 函数名前缀为 Test | 测试程序的一些逻辑行为是否正确 |
| 基准函数 | 函数名前缀为 Benchmark | 测试函数的性能 |
| 示例函数 | 函数名前缀为 Example | 为文档提供示例文档 |
go test命令会遍历所有的*_test.go文件中符合上述命名规则的函数,然后生成一个临时的 main 包用于调用相应的测试函数,然后构建并运行,报告测试结果,最后清理测试中生成的临时文件。
测试函数
测试函数的格式
- 每个测试函数必须导入
testing包,测试函数的基本格式(签名)如下:
1 | |
- 测试函数的名字必须以
Test开头,可选的后缀名必须以大写字母开头,举几个例子:
1 | |
- 其中参数
t用于报告测试失败和附加的日志信息,testing.T的拥有的方法如下:
1 | |
测试函数示例
-
一个软件程序也是由很多单元组件构成的,单元组件可以是函数,结构体,方法和最终用户可能依赖的任意东西,总之我们需要确保这些组件是能够正常运行的,单元测试是一些利用各种方法测试单元组件的程序,它会将结果与预期输出进行比较。
-
在当前目录下,我们创建一个
split_test.go的测试文件,并定义一个测试函数如下:
1 | |
- 在
split包路径下,执行go test命令,可以看到输出结果如下:
1 | |
-
-v:输出详细信息。 -
-run:参数,它对应一个正则表达式,只有函数名匹配上的测试函数才会被go test命令执行。 -
再次运行
go test命令,输出结果如下:
1 | |
- 这一次,我们的测试失败了,我们可以为
go test命令添加-v参数,查看测试函数名称和运行时间:
1 | |
1 | |
测试组
- 我们现在还想要测试一下
split函数对中文字符串的支持,这个时候我们可以再编写一个TestChineseSplit测试函数,但是我们也可以使用如下更友好的一种方式来添加更多的测试用例。
1 | |
- 这种情况下十分推荐使用
%#v的格式化方式。
1 | |
子测试
- Go 1.7+中新增了子测试,我们可以按照如下方式使用
t.Run执行子测试:
1 | |
- 此时我们再执行
go test命令就能够看到更清晰的输出内容了:
1 | |
- 我们都知道可以通过
-run=RegExp来指定运行的测试用例,还可以通过/来指定要运行的子测试用例,例如:go test -v -run=Split/simple只会运行simple对应的子测试用例。
测试覆盖率
- 测试覆盖率是你的代码被测试套件覆盖的百分比,通常我们使用的都是语句的覆盖率,也就是在测试中至少被运行一次的代码占总代码的比例。
- Go 提供内置功能来检查你的代码覆盖率,我们可以使用
go test -cover来查看测试覆盖率,例如:
1 | |
- 从上面的结果可以看到我们的测试用例覆盖了 100%的代码。
- Go 还提供了一个额外的
-coverprofile参数,用来将覆盖率相关的记录信息输出到一个文件,例如:
1 | |
- 上面的命令会将覆盖率相关的信息输出到当前文件夹下面的
c.out文件中,然后我们执行go tool cover -html=c.out,使用cover工具来处理生成的记录信息,该命令会打开本地的浏览器窗口生成一个 HTML报告。
- 上图中每个用绿色标记的语句块表示被覆盖了,而红色的表示没有被覆盖。
基准测试
基准测试函数格式
- 基准测试就是在一定的工作负载之下检测程序性能的一种方法,基准测试的基本格式如下:
1 | |
- 基准测试以
Benchmark为前缀,需要一个*testing. B类型的参数 b,基准测试必须要执行b.N次,这样的测试才有对照性,b.N的值是系统根据实际情况去调整的,从而保证测试的稳定性,testing. B拥有的方法如下:
1 | |
基准测试示例
- 我们为 split 包中的
Split函数编写基准测试如下:
1 | |
- 基准测试并不会默认执行,需要增加
-bench参数,所以我们通过执行go test -bench=Split命令执行基准测试,输出结果如下:
1 | |
- 其中
BenchmarkSplit-8表示对 Split 函数进行基准测试,数字8表示GOMAXPROCS的值,这个对于并发基准测试很重要,10000000和203 ns/op表示每次调用Split函数耗时203 ns,这个结果是10000000次调用的平均值。 - 我们还可以为基准测试添加
-benchmem参数,来获得内存分配的统计数据。
1 | |
- 其中,
112 B/op表示每次操作内存分配了 112 字节,3 allocs/op则表示每次操作进行了 3 次内存分配。
性能比较函数
- 上面的基准测试只能得到给定操作的绝对耗时,但是在很多性能问题是发生在两个不同操作之间的相对耗时,比如同一个函数处理 1000 个元素的耗时与处理 1 万甚至 100 万个元素的耗时的差别是多少?再或者对于同一个任务究竟使用哪种算法性能最佳?我们通常需要对两个不同算法的实现使用相同的输入来进行基准比较测试。
- 性能比较函数通常是一个带有参数的函数,被多个不同的 Benchmark 函数传入不同的值来调用,举个例子如下:
1 | |
- 例如我们编写了一个计算斐波那契数列的函数如下:
1 | |
- 我们编写的性能比较函数如下:
1 | |
- 运行基准测试:
1 | |
- 注意:默认情况下,每个基准测试至少运行 1 秒,如果在 Benchmark 函数返回时没有到 1 秒,则
b.N的值会按 1,2,5,10,20,50,…增加,并且函数再次运行。 - 最终的 BenchmarkFib 40 只运行了两次,每次运行的平均值只有不到一秒,像这种情况下我们应该可以使用
-benchtime标志增加最小基准时间,以产生更准确的结果,例如:
1 | |
- 这一次
BenchmarkFib 40函数运行了 50 次,结果就会更准确一些了。 - 使用性能比较函数做测试的时候一个容易犯的错误就是把
b.N作为输入的大小,例如以下两个例子都是错误的示范:
1 | |
重置时间
b.ResetTimer之前的处理不会放到执行时间里,也不会输出到报告中,所以可以在之前做一些不计划作为测试报告的操作,例如:
1 | |
并行测试
func (b *B) RunParallel (body func (*PB))会以并行的方式执行给定的基准测试。RunParallel会创建出多个goroutine,并将b.N分配给这些goroutine执行,其中goroutine数量的默认值为GOMAXPROCS,用户如果想要增加非 CPU 受限(non-CPU-bound)基准测试的并行性,那么可以在RunParallel之前调用SetParallelism,RunParallel通常会与-cpu标志一同使用。
1 | |
- 执行一下基准测试:
1 | |
- 还可以通过在测试命令后添加
-cpu参数如go test -bench=. -cpu 1来指定使用的 CPU 数量。
示例函数
示例函数的格式
- 被
go test特殊对待的第三种函数就是示例函数,它们的函数名以Example为前缀,它们既没有参数也没有返回值,标准格式如下:
1 | |
示例函数示例
- 下面的代码是我们为
Split函数编写的一个示例函数:
1 | |
-
为你的代码编写示例代码有如下三个用处:
-
示例函数能够作为文档直接使用,例如基于 web 的 godoc 中能把示例函数与对应的函数或包相关联。
-
示例函数只要包含了
// Output:也是可以通过go test运行的可执行测试。1
2
3$ go test -run Example
PASS
ok github. com/Test/split 0.006 s -
示例函数提供了可以直接运行的示例代码,可以直接在
golang. org的godoc文档服务器上使用Go Playground运行示例代码,下图为strings. ToUpper函数在 Playground 的示例函数效果,
-
Setup 与 TearDown
- 测试程序有时需要在测试之前进行额外的设置(setup)或在测试之后进行拆卸(teardown)
TestMain
- 通过在
*_test. go文件中定义TestMain函数来可以在测试之前进行额外的设置(setup)或在测试之后进行拆卸(teardown)操作。 - 如果测试文件包含函数:
func TestMain (m *testing. M)那么生成的测试会先调用 TestMain (m),然后再运行具体测试,TestMain运行在主goroutine中,可以在调用m.Run前后做任何设置(setup)和拆卸(teardown),退出测试的时候应该使用m.Run的返回值作为参数调用os. Exit - 一个使用
TestMain来设置 Setup 和 TearDown 的示例如下:
1 | |
- 需要注意的是:在调用
TestMain时,flag. Parse并没有被调用,所以如果TestMain依赖于 command-line 标志(包括 testing 包的标记),则应该显示的调用flag. Parse
子测试的 Setup 与 Teardown
- 有时候我们可能需要为每个测试集设置 Setup 与 Teardown,也有可能需要为每个子测试设置 Setup 与 Teardown,下面我们定义两个函数工具函数如下:
1 | |
- 使用方式如下:
1 | |
- 测试结果如下:
1 | |
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!