golang 压力测试与并发安全测试

2019-12-25 10:32:33   最后更新: 2019-12-25 10:32:33   访问数量:133




上一篇文章中,介绍了如何通过 go test 实现单元测试:

测试驱动开发与 golang 单元测试

 

但单元测试只是 go test 最为基础的用法,本文就来介绍 go test 更为进阶的基准测试和并发安全测试

 

 

很多时候,我们不仅需要测试程序执行的正确性,对于程序执行的性能消耗我们往往更加看重,毕竟在项目上线前,究竟需要多少资源来部署项目,项目能够承受多大的流量,不对这些了然于胸,就无法保证线上业务的安全,后果将会是灾难性的

go test 工具同样也提供了压力测试等功能的支持 -- benchmark test

 

基准测试的编写与执行

go test 的基准测试提供了将目标代码段执行 N 次统计运行时间,从而实现压测的功能

与单元测试类似,只要在项目的 xxx_test.go 文件中写入下面的方法即可实现基准测试函数的编写:

func BenchmarkXxx(*testing.B) { // 测试函数体 }

 

 

例如:

func BenchmarkFib10(b *testing.B) { for n := 0; n < b.N; n++ { Fib(10) } }

 

 

执行:

go test -bench=.

 

就会展示:

$ go test -bench=.

BenchmarkFib10-4        3000000           424 ns/op

PASS

ok      chapter09/testing    1.724s

 

表示执行了 3000000 次,耗时 1.724 秒,每次调用 424 纳秒

 

设定执行时间

我们也可以通过 -benchtime 来指定测试的执行时间:

$ go test -bench=Fib40 -benchtime=20s

BenchmarkFib40-4             30     838675800 ns/op

 

type B struct { common importPath string // import path of the package containing the benchmark context *benchContext N int previousN int // number of iterations in the previous run previousDuration time.Duration // total duration of the previous run benchFunc func(b *B) benchTime benchTimeFlag bytes int64 missingBytes bool // one of the subbenchmarks does not have bytes set. timerOn bool showAllocResult bool result BenchmarkResult parallelism int // RunParallel creates parallelism*GOMAXPROCS goroutines // The initial states of memStats.Mallocs and memStats.TotalAlloc. startAllocs uint64 startBytes uint64 // The net total of this test after being run. netAllocs uint64 netBytes uint64 // Extra metrics collected by ReportMetric. extra map[string]float64 }

 

 

可以看到,testing.B 的首个元素也是 common 结构,因此他也拥有 testing.T 中所有的报告方法,可以参考上一篇文章的讲解:

测试驱动开发与 golang 单元测试

 

既然是性能压测,串行的执行统计运行耗时常常并不是我们想要的测试手段,通过并发执行来观察资源的消耗情况是更好的测试方法

go test 提供了 b.RunParallel 方法用来实现让多个基准测试方法并行执行的功能

func BenchmarkTemplateParallel(b *testing.B) { templ := template.Must(template.New("test").Parse("Hello, {{.}}!")) b.RunParallel(func(pb *testing.PB) { // 每个 goroutine 有属于自己的 bytes.Buffer. var buf bytes.Buffer for pb.Next() { // 所有 goroutine 一起,循环一共执行 b.N 次 buf.Reset() templ.Execute(&buf, "World") } }) }

 

 

调用了 b.RunParallel 方法的测试函数将会在单独的 goroutine 中启动

需要注意的是,b.StartTimer、b.StopTime、b.ResetTimer 三个方法会影响到所有 goroutine,因此不要在并行测试中调用

 

调节并发度

并行基准测试其并发度受环境变量 GOMAXPROCS 控制,默认情况下是 CPU 核心数

可以在测试开始前,通过 b.SetParallelism 方法实现对并发度的控制,例如执行b.SetParallelism(2) 则意味着并发度为 2*GOMAXPROCS

在执行 go test 命令时,增加 -cpu 参数,可以打印 cpu 资源消耗的详情信息

 

除了观察 CPU 的调用情况,我们也常常需要去观察内存的使用情况

通过 go test 命令添加 -benchmem 参数可以打开基准测试的内存统计功能

也可以通过在测试用例执行开始前,调用 b.ReportAllocs 函数,这样做的好处是只会影响你需要的函数:

func BenchmarkTmplExucte(b *testing.B) { b.ReportAllocs() templ := template.Must(template.New("test").Parse("Hello, {{.}}!")) b.RunParallel(func(pb *testing.PB) { // Each goroutine has its own bytes.Buffer. var buf bytes.Buffer for pb.Next() { // The loop body is executed b.N times total across all goroutines. buf.Reset() templ.Execute(&buf, "World") } }) }

 

 

打印出了:

BenchmarkTmplExucte-4        2000000           898 ns/op         368 B/op           9 allocs/op

 

表示总计执行 2000000 次,平均每次耗时 898 纳秒,每次执行消耗内存 368 Bytes,共计分配内存 9 次

 

在介绍 goroutine 并发安全时,我们曾经介绍了并发安全测试相关的内容:

goroutine 并发中竞争条件的解决

 

只要在 go test 命令中加入 -race 参数,就可以在测试阶段发现可能的并发安全问题

下面是一个典型的非并发安全的例子:

func TestParallelSafe(t *testing.T) { a := 1 go func(){ a = 2 }() a = 3 t.Logf("a is ", a) time.Sleep(2 * time.Second) }

 

 

执行 go test -race . 会打印出:

runtime  go test -race .

a is  3

==================

WARNING: DATA RACE

Write by goroutine 5:

  main.func·001()

      /data/test/race1.go:11 +0x3a

Previous write by main goroutine:

  main.main()

      /data/test/race1.go:13 +0xe7

Goroutine 5 (running) created at:

  main.main()

      /data/test/race1.go:12 +0xd7

==================

Found 1 data race(s)

exit status 66

 

从而可以发现竞争条件的存在

但需要注意的是,只有测试用例覆盖到的代码才可以顺利检测出竞争,因此保证测试用例的覆盖率是一个很重要的事

 

打印测试用例覆盖率报告

go test 命令增加 -coverprofile 参数,指定输出文件,就可以输出测试的覆盖率报告

 

欢迎关注微信公众号,以技术为主,涉及历史、人文等多领域的学习与感悟,每周三到七篇推文,只有全部原创,只有干货没有鸡汤

 

 






测试      golang     


京ICP备15018585号