目录
单元测试
概述
go test 参数解读
单元测试日志
基准测试
基础测试基本使用
基准测试原理
自定义测试时间
测试内存
控制计时器
Go 语言拥有一套单元测试和性能测试系统, 仅需要添加很少的代码就可以快速测试一段需求代码.
性能测试系统可以给出代码的性能数据, 帮助测试者分析性能问题.
单元测试
概述
单元测试(unit testing), 是指对软件中的最小可测试单元进行检查和验证. 对于单元测试中单元的含义, 一般要根据实际情况去判定其具体含义, 如 C 语言中单元指一个函数, Java 里单元指一个类, 图形化的软件中可以指一个窗口或一个菜单等. 总的来说, 单元就是人为规定的最小的被测功能模块.
单元测试是在软件开发过程中要进行的最低级别的测试活动, 软件的独立单元将在与程序的其他部分相隔离的情况下进行测试.
testing 提供对 Go 包的自动化测试的支持. 通过 go test 命令, 能够自动执行如下形式的任何函数:
func TestXxx(*testing.T)
测试用例文件不会参与正常源码编译, 不会被包含到可执行文件中.
测试用例文件使用 go test 指令来执行, 没有也不需要 main() 作为函数入口. 所有在以_test 结尾的源码内以 Test 开头的函数会自动被执行.
测试用例可以不传入 *testing.T 参数.
在这些函数中, 使用 Error, Fail 或相关方法来发出失败信号.
要编写一个新的测试套件, 需要创建一个名称以 _test.go 结尾的文件, 该文件包含 TestXxx 函数, 如上所述. 将该文件放在与被测试的包相同的包中. 该文件将被排除在正常的程序包之外, 但在运行 "go test" 命令时将被包含. 有关详细信息, 请运行 "go help test" 和 "go help testflag" 了解.
如果有需要, 可以调用 T 和 B 的 Skip 方法, 跳过该测试或基准测试:
- func TestTimeConsuming(t *testing.T) {
- if testing.Short() {
- t.Skip("skipping test in short mode.")
- }
- ...
- }
Go 语言的单元测试对文件名和方法名, 参数都有很严格的要求.
文件名必须以 xxx_test.go 命名
方法必须是 Test[^a-z]开头
** 方法参加必须 t *testing.T**
使用 go test 执行单元测试
go test 参数解读
go test 是 go 语言自带的测试工具, 其中包含的是两类, 单元测试和性能测试
通过 go help test 可以看到 go test 的使用说明:
格式形如:
go test [-c] [-i] [build/test flags] [packages] [build/test flags & test binary flags]
参数解读:
-c : 编译 go test 成为可执行的二进制文件, 但是不运行测试.
-i : 安装测试包依赖的 package, 但是不运行测试.
关于 build flags, 调用 go help build, 这些是编译运行过程中需要使用到的参数, 一般设置为空
关于 packages, 调用 go help packages, 这些是关于包的管理, 一般设置为空
关于 flags for test binary, 调用 go help testflag, 这些是 go test 过程中经常使用到的参数
-test.v : 是否输出全部的单元测试用例(不管成功或者失败), 默认没有加上, 所以只输出失败的单元测试用例.
-test.run pattern: 只跑哪些单元测试用例
-test.bench patten: 只跑那些性能测试用例
-test.benchmem : 是否在性能测试的时候输出内存情况
-test.benchtime t : 性能测试运行的时间, 默认是 1s
-test.cpuprofile CPU.out : 是否输出 CPU 性能分析文件
-test.memprofile mem.out : 是否输出内存性能分析文件
-test.blockprofile block.out : 是否输出内部 goroutine 阻塞的性能分析文件
-test.memprofilerate n : 内存性能分析的时候有一个分配了多少的时候才打点记录的问题. 这个参数就是设置打点的内存分配间隔, 也就是 profile 中一个 sample 代表的内存大小. 默认是设置为 512 * 1024 的. 如果你将它设置为 1, 则每分配一个内存块就会在 profile 中有个打点, 那么生成的 profile 的 sample 就会非常多. 如果你设置为 0, 那就是不做打点了.
你可以通过设置 memprofilerate=1 和 GOGC=off 来关闭内存回收, 并且对每个内存块的分配进行观察.
-test.blockprofilerate n: 基本同上, 控制的是 goroutine 阻塞时候打点的纳秒数. 默认不设置就相当于 - test.blockprofilerate=1, 每一纳秒都打点记录一下
-test.parallel n : 性能测试的程序并行 CPU 数, 默认等于 GOMAXPROCS.
-test.timeout t : 如果测试用例运行时间超过 t, 则抛出 panic
-test.CPU 1,2,4 : 程序运行在哪些 CPU 上面, 使用二进制的 1 所在位代表, 和 nginx 的 nginx_worker_cpu_affinity 是一个道理
-test.short : 将那些运行时间较长的测试用例运行时间缩短
示例:
定义一个 test 包, 包内有一个加法和减法的函数
- package test
- func Sum(a int, b int) int {
- return a + b
- }
- func Sub(a int, b int) int {
- return a - b
- }
测试文件的名称不需要和包文件名称一样, 也可以叫 abc_test.go 等
测试文件 test_test.go 的测试代码如下
- package test
- import (
- "testing"
- )
- // 编写一个测试用例, 去测试 Sum 函数是否正确
- func TestSum(t *testing.T) {
- res := Sum(10, 20)
- if res != 30 {
- t.Fatalf("Sum(10, 20)执行错误")
- }
- // 如果正确, 输出日志
- t.Logf("Sum(10, 20)执行正确")
- }
- func TestOk(t *testing.T) {
- t.Logf("这个方法也进来啦")
- }
- func TestSub(t *testing.T) {
- res := Sub(10, 5)
- if res != 5 {
- t.Fatalf("Sub(10, 5)执行错误")
- }
- // 如果正确, 输出日志
- t.Logf("Sub(10, 5)执行正确")
- }
在终端进入该包的目录内, 使用 go test 命令进行测试
加上 - v 参数可以让测试时显示详细的流程.
我们再新建一个 abc_test.go 文件, 把 test_test.go 文件里的 TestSub()方法挪到 abc_test.go 文件中
然后重新执行 go test -v 命令
可以看到 test_test.go 内的方法都被执行了, 由此可知, 使用 go test -v 命令会把该包目录内的所有测试文件的所有方法都执行一遍.
如果想测试包里面的单个文件, 一定要带上被测试的原文件, 如
go test -v abc_test.go test.go
如果想测试单个方法, 需要加上 -run 参数, 并且方法名末尾要加上 $, 原因是 - run 跟随的测试用例的名称支持正则表达式, 使用 - run TestSub$ 即可只执行 TestSub 测试用例. 否则会执行所有以 TestSub 开头的所有函数如, 修改 abc_test.go
- package test
- import (
- "testing"
- )
- // 编写一个测试用例, 去测试 Sum 函数是否正确
- func TestSum(t *testing.T) {
- res := Sum(10, 20)
- if res != 30 {
- t.Fatalf("Sum(10, 20)执行错误")
- }
- // 如果正确, 输出日志
- t.Logf("Sum(10, 20)执行正确")
- }
- func TestOk(t *testing.T) {
- t.Logf("这个方法也进来啦")
- }
- func TestSub2(t *testing.T) {
- t.Logf("进入到 TestSub2 方法里来了")
- }
- go test -v -test.run TestSub$
或
go test -v -run TestSub$
如果不加 $, 所有以 TestSub 开头的所有测试函数都会被执行
单元测试日志
每个测试用例可能并发执行, 使用 testing.T 提供的日志输出可以保证日志跟随这个测试上下文一起打印输出. testing.T 提供了几种日志输出方法, 详见下表所示.
单元测试框架提供的日志方法
方 法 | 备 注 |
---|---|
Log | 打印日志,同时结束测试 |
Logf | 格式化打印日志,同时结束测试 |
Error | 打印错误日志,同时结束测试 |
Errorf | 格式化打印错误日志,同时结束测试 |
Fatal | 打印致命日志,同时结束测试 |
Fatalf | 格式化打印致命日志,同时结束测试 |
基准测试
基准测试可以测试一段程序的运行性能及耗费 CPU 的程度. Go 语言中提供了基准测试框架, 使用方法类似于单元测试, 使用者无须准备高精度的计时器和各种分析工具, 基准测试本身即可以打印出非常标准的测试报告.
压力测试用来检测函数 (方法) 的性能, 和编写单元功能测试的方法类似, 但需要注意以下几点:
压力测试用例必须遵循如下格式, 其中 XXX 可以是任意字母数字的组合, 但是首字母不能是小写字母
func BenchmarkXXX(b *testing.B) { ... }
go test 不会默认执行压力测试的函数, 如果要执行压力测试需要带上参数 - test.bench, 语法:-test.bench="test_name_regex", 例如 go test -test.bench=".*" 表示测试全部的压力测试函数
在压力测试用例中, 请记得在循环体内使用 testing.B.N, 以使测试可以正常的运行
文件名也必须以_test.go 结尾
基础测试基本使用
新建一个压力测试文件 bench_test.go
目录结构
bench_test.go 代码
- package test
- import "testing"
- func BenchmarkSub(b *testing.B) {
- for i := 0; i < b.N; i++ { //use b.N for looping
- Sub(10, 5)
- }
- }
- func BenchmarkTimeConsumingFunction(b *testing.B) {
- b.StopTimer() // 调用该函数停止压力测试的时间计数
- // 做一些初始化的工作, 例如读取文件数据, 数据库连接之类的,
- // 这样这些时间不影响我们测试函数本身的性能
- b.StartTimer() // 重新开始时间
- for i := 0; i < b.N; i++ {
- Sub(10, 5)
- }
- }
这段代码使用基准测试框架测试减法性能. 第 7 行中的 b.N 由基准测试框架提供. 测试代码需要保证函数可重入性及无状态, 也就是说, 测试代码不使用全局变量等带有记忆性质的数据结构. 避免多次运行同一段代码时的环境不一致, 不能假设 N 值范围.
执行命令
go test -test.bench=".*"
输出结果:
- goos: darwin
- goarch: amd64
- pkg: test
- BenchmarkSub-8 2000000000 0.32 ns/op
- //BenchmarkSub 执行了 2000000000 次, 每次的执行平均时间是 0.32 纳秒
- BenchmarkTimeConsumingFunction-8 2000000000 0.31 ns/op
- //BenchmarkTimeConsumingFunction, 执行了 2000000000 次, 每次的执行平均时间是 0.31 纳秒
- PASS
- ok test 1.328s
我们还可以使用 - count 执行次数
go test -test.bench=".*" -count=5
输出结果:
- goos: darwin
- goarch: amd64
- pkg: test
- BenchmarkSub-8 2000000000 0.31 ns/op
- BenchmarkSub-8 2000000000 0.32 ns/op
- BenchmarkSub-8 2000000000 0.32 ns/op
- BenchmarkSub-8 2000000000 0.31 ns/op
- BenchmarkSub-8 2000000000 0.31 ns/op
- BenchmarkTimeConsumingFunction-8 2000000000 0.31 ns/op
- BenchmarkTimeConsumingFunction-8 2000000000 0.31 ns/op
- BenchmarkTimeConsumingFunction-8 2000000000 0.31 ns/op
- BenchmarkTimeConsumingFunction-8 2000000000 0.31 ns/op
- BenchmarkTimeConsumingFunction-8 2000000000 0.31 ns/op
- PASS
- ok test 6.573s
基准测试原理
基准测试框架对一个测试用例的默认测试时间是 1 秒. 开始测试时, 当以 Benchmark 开头的基准测试用例函数返回时还不到 1 秒, 那么 testing.B 中的 N 值将按 1,2,5,10,20,50...... 递增, 同时以递增后的值重新调用基准测试用例函数.
自定义测试时间
通过 - benchtime 参数可以自定义测试时间, 例如:
go test -test.bench=".*" -count=5 -benchtime=5s
测试内存
基准测试可以对一段代码可能存在的内存分配进行统计, 下面是一段使用字符串格式化的函数, 内部会进行一些分配操作.
- func Benchmark_Alloc(b *testing.B) {
- for i := 0; i < b.N; i++ {
- fmt.Sprintf("%d", i)
- }
- }
在命令行中添加 - benchmem 参数以显示内存分配情况, 参见下面的指令:
- bogon:test itbsl$ go test -test.bench=Alloc -benchmem
- goos: darwin
- goarch: amd64
- pkg: test
- BenchmarkAlloc-8 20000000 111 ns/op 16 B/op 2 allocs/op
- PASS
- ok test 2.354s
代码说明如下:
第 1 行的代码中 - bench 后添加了 Alloc, 指定只测试 Benchmark_Alloc() 函数.
第 4 行代码的 "16 B/op" 表示每一次调用需要分配 16 个字节,"2 allocs/op" 表示每一次调用有两次分配.
开发者根据这些信息可以迅速找到可能的分配点, 进行优化和调整.
控制计时器
有些测试需要一定的启动和初始化时间, 如果从 Benchmark() 函数开始计时会很大程度上影响测试结果的精准性. testing.B 提供了一系列的方法可以方便地控制计时器, 从而让计时器只在需要的区间进行测试. 我们通过下面的代码来了解计时器的控制.
基准测试中的计时器控制
- func BenchmarkAddTimerControl(b *testing.B) {
- // 重置计时器
- b.ResetTimer()
- // 停止计时器
- b.StopTimer()
- // 开始计时器
- b.StartTimer()
- var n int
- for i := 0; i < b.N; i++ {
- n++
- }
- }
从 Benchmark() 函数开始, Timer 就开始计数. StopTimer() 可以停止这个计数过程, 做一些耗时的操作, 通过 StartTimer() 重新开始计时. ResetTimer() 可以重置计数器的数据.
计数器内部不仅包含耗时数据, 还包括内存分配的数据.
来源: https://www.cnblogs.com/itbsl/p/10572487.html