Go 单元测试、压力测试、测试覆盖率

Go 语言有自带的测试框架 testing,可以用来实现单元测试和性能测试,通过 go test 命令来执行单元测试和性能测试。
go test 执行测试用例时,是以 go 包为单位进行测试的。执行时需要指定包名,比如:go test 包名,如果没有指定包名,默认会选择执行命令时所在的包。go test 在执行时会遍历以 _test.go 结尾的源码文件,执行其中以 TestBenchmarkExample 开头的测试函数。其中源码文件需要满足以下规范:
  • 文件名必须是 _test.go 结尾,跟源文件在同一个包。
  • 测试用例函数必须以 Test、Benchmark、Example 开头
  • 执行测试用例时的顺序,会按照源码中的顺序依次执行
  • 单元测试函数 TestXxx() 的参数是 testing.T,可以使用该类型来记录错误或测试状态
  • 性能测试函数 BenchmarkXxx() 的参数是 testing.B,函数内以 b.N 作为循环次数,其中 N 会动态变化
  • 示例函数 ExampleXxx() 没有参数,执行完会将输出与注释 // Output: 进行对比
  • 测试函数原型:func TestXxx(t *testing.T),Xxx 部分为任意字母数字组合,首字母大写,例如: TestgenShortId 是错误的函数名,TestGenShortId 是正确的函数名
  • 通过调用 testing.T 的 Error、Errorf、FailNow、Fatal、FatalIf 方法来说明测试不通过,通过调用 Log、Logf 方法来记录测试信息:
t.Log t.Logf # 正常信息 t.Error t.Errorf # 测试失败信息 t.Fatal t.Fatalf # 致命错误,测试程序退出的信息 t.Fail # 当前测试标记为失败 t.Failed # 查看失败标记 t.FailNow # 标记失败,并终止当前测试函数的执行,需要注意的是,我们只能在运行测试函数的 Goroutine 中调用 t.FailNow 方法,而不能在我们在测试代码创建出的 Goroutine 中调用它 t.Skip # 调用 t.Skip 方法相当于先后对 t.Log 和 t.SkipNow 方法进行调用,而调用 t.Skipf 方法则相当于先后对 t.Logf 和 t.SkipNow 方法进行调用。方法 t.Skipped 的结果值会告知我们当前的测试是否已被忽略 t.Parallel # 标记为可并行运算
执行用例
新建一个util文件夹,创建文件util.go 和 util_test.go
util.go
package util
import (
"github.com/gin-gonic/gin" //gin 框架
"github.com/teris-io/shortid"
)
func GenShortId() (string, error) {
return shortid.Generate()
}
func GetReqID(c *gin.Context) string {
v, ok := c.Get("X-Request-Id")
if !ok {
return ""
}
if requestId, ok := v.(string); ok {
return requestId
}
return ""
}
util_test.go
package util import ( "testing" ) func TestGenShortId(t *testing.T) { shortId, err := GenShortId() if shortId == "" || err != nil { t.Error("GenShortId failed!") } t.Log("GenShortId test pass") }
然后在util目录下执行 go test -v
如果要执行测试n次可以使用 go test -v -count N
性能测试用例
在util_test.go 里面新增
func BenchmarkGenShortId(b *testing.B) { for i := 0; i < b.N; i++ { GenShortId() } } func BenchmarkGenShortIdTimeConsuming(b *testing.B) { b.StopTimer() // 调用该函数停止压力测试的时间计数 shortId, err := GenShortId() if shortId == "" || err != nil { b.Error(err) } b.StartTimer() // 重新开始时间 for i := 0; i < b.N; i++ { GenShortId() } }
tips:在 b.StopTimer() 和 b.StartTimer() 之间可以做一些准备工作,这样这些时间不影响我们测试函数本身的性能。
说明:性能测试函数名必须以 Benchmark 开头,如 BenchmarkXxx 或 Benchmark_xxx
  • go test 默认不会执行压力测试函数,需要通过指定参数 -test.bench 来运行压力测试函数,-test.bench 后跟正则表达式,如 go test -test.bench=".*" 表示执行所有的压力测试函数
  • 在压力测试中,需要在循环体中指定 testing.B.N 来循环执行压力测试代码
执行方法:go test -test.bench=".*"
goos: linux
goarch: amd64
pkg: apiserver/util
BenchmarkGenShortId 500000 2180 ns/op
BenchmarkGenShortIdTimeConsuming 1000000 2200 ns/op
PASS
ok apiserver/util 3.353s
  • 上面的结果显示,我们没有执行任何 TestXXX 的单元测试函数,只执行了压力测试函数
  • 第一条显示了 BenchmarkGenShortId 执行了 500000 次,每次的执行平均时间是 2180纳秒
  • 第二条显示了 BenchmarkGenShortIdTimeConsuming 执行了 1000000,每次的平均执行时间是 2200纳秒
  • 最后一条显示总执行时间
查看性能并生成函数调用图
  1. 执行命令:
$ go test -bench=".*" -cpuprofile=cpu.profile ./util
上述命令会在当前目录下生成 cpu.profile 和 util.test 文件。
  1. 执行 go tool pprof util.test cpu.profile 查看性能(进入交互界面后执行 top 指令):
$ go tool pprof util.test cpu.profile File: util.test Type: cpu Time: Jun 5, 2018 at 7:28pm (CST) Duration: 4.93s, Total samples = 4.97s (100.78%) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top [这里输入top]
Showing nodes accounting for 2500ms, 77.64% of 3220ms total
Dropped 30 nodes (cum <= 16.10ms)
Showing top 10 nodes out of 66
flat flat% sum% cum cum%
1800ms 55.90% 55.90% 1970ms 61.18% syscall.Syscall
140ms 4.35% 60.25% 340ms 10.56% runtime.mallocgc
130ms 4.04% 64.29% 2620ms 81.37% vendor/github.com/teris-io/shortid.(*Abc).Encode
90ms 2.80% 67.08% 90ms 2.80% math.Log
70ms 2.17% 69.25% 70ms 2.17% runtime._ExternalCode
70ms 2.17% 71.43% 180ms 5.59% runtime.slicerunetostring
60ms 1.86% 73.29% 60ms 1.86% runtime.casgstatus
50ms 1.55% 74.84% 50ms 1.55% runtime.encoderune
50ms 1.55% 76.40% 3090ms 95.96% vendor/github.com/teris-io/shortid.(*Shortid).GenerateInternal
40ms 1.24% 77.64% 2070ms 64.29% io.ReadAtLeast
pprof 程序中最重要的命令就是 topN,此命令用于显示 profile 文件中的最靠前的 N 个样本(sample),它的输出格式各字段的含义依次是:
  1. 采样点落在该函数中的总时间
  2. 采样点落在该函数中的百分比
  3. 上一项的累积百分比
  4. 采样点落在该函数,以及被它调用的函数中的总时间
  5. 采样点落在该函数,以及被它调用的函数中的总次数百分比
  6. 函数名
此外,在 pprof 程序中还可以使用 svg 来生成函数调用关系图(需要安装 graphviz),例如:
为了生成该图需要安装
apt install graphviz.x86_64
执行go tool pprof 生成svg图
go tool pprof util.test cpu.profile
File: util.test
Type: cpu
Time: Jul 28, 2020 at 8:22pm (PDT)
Duration: 3.42s, Total samples = 3.22s (94.28%)
Entering interactive mode (type "help" for commands, "o" for options)
(pprof) svg 【这里输入svg】
svg 子命令会提示在 $GOPATH/src 目录下生成了一个 svg 文件 profile001.svg
使用rz profile001.svg, 下载到桌面可以在google浏览器打开
测试覆盖率
我们写单元测试的时候应该想得很全面,能够覆盖到所有的测试用例,但有时也会漏过一些 case,go 提供了 cover 工具来统计测试覆盖率。
go test -coverprofile=cover.out:在测试文件目录下运行测试并统计测试覆盖率
go tool cover -func=cover.out:分析覆盖率文件,可以看出哪些函数没有测试,哪些函数内部的分支没有测试完全,cover 工具会通过执行代码的行数与总行数的比例表示出覆盖率
测试覆盖率
$ go test -coverprofile=cover.out
PASS
coverage: 14.3% of statements
ok apiserver/util 0.005s
jack@mymaster:~/jack/mygo/src/apiserver/util$ go tool cover -func=cover.out
apiserver/util/util.go:8: GenShortId 100.0%
apiserver/util/util.go:12: GetReqID 0.0%
total: (statements) 14.3%
可以看到 GenShortId() 函数测试覆盖率为 100%,GetReqID() 测试覆盖率为 0%。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注