前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >golang 压力测试与并发安全测试

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

作者头像
用户3147702
发布2022-06-27 14:16:11
2.5K0
发布2022-06-27 14:16:11
举报
文章被收录于专栏:小脑斧科技博客

1. 引言

上一篇文章中,介绍了如何通过 go test 实现单元测试: 测试驱动开发与 golang 单元测试

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

2. 基准测试 — benchmark test

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

2.1. 基准测试的编写与执行

go test 的基准测试提供了将目标代码段执行 N 次统计运行时间,从而实现压测的功能。 与单元测试类似,只要在项目的 xxx_test.go 文件中写入下面的方法即可实现基准测试函数的编写:

代码语言:javascript
复制
func BenchmarkXxx(*testing.B) {
// 测试函数体
}

例如:

代码语言:javascript
复制
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 纳秒。

2.2. 设定执行时间

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

$ go test -bench=Fib40 -benchtime=20s BenchmarkFib40-4 30 838675800 ns/op

3. testing.B

代码语言:javascript
复制
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 单元测试

4. 并行测试

既然是性能压测,串行的执行统计运行耗时常常并不是我们想要的测试手段,通过并发执行来观察资源的消耗情况是更好的测试方法。 go test 提供了 b.RunParallel 方法用来实现让多个基准测试方法并行执行的功能。

代码语言:javascript
复制
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,因此不要在并行测试中调用。

4.1. 调节并发度

并行基准测试其并发度受环境变量 GOMAXPROCS 控制,默认情况下是 CPU 核心数。 可以在测试开始前,通过 b.SetParallelism 方法实现对并发度的控制,例如执行b.SetParallelism(2) 则意味着并发度为 2*GOMAXPROCS。 在执行 go test 命令时,增加 -cpu 参数,可以打印 cpu 资源消耗的详情信息。

5. 内存统计

除了观察 CPU 的调用情况,我们也常常需要去观察内存的使用情况。 通过 go test 命令添加 -benchmem 参数可以打开基准测试的内存统计功能。 也可以通过在测试用例执行开始前,调用 b.ReportAllocs 函数,这样做的好处是只会影响你需要的函数:

代码语言:javascript
复制
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 次。

6. 并发安全测试 — -race

在介绍 goroutine 并发安全时,我们曾经介绍了并发安全测试相关的内容: goroutine 并发中竞争条件的解决

只要在 go test 命令中加入 -race 参数,就可以在测试阶段发现可能的并发安全问题。 下面是一个典型的非并发安全的例子:

代码语言:javascript
复制
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

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

6.1. 打印测试用例覆盖率报告

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

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2019-12-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 小脑斧科技博客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 引言
  • 2. 基准测试 — benchmark test
    • 2.1. 基准测试的编写与执行
      • 2.2. 设定执行时间
      • 3. testing.B
      • 4. 并行测试
        • 4.1. 调节并发度
        • 5. 内存统计
        • 6. 并发安全测试 — -race
          • 6.1. 打印测试用例覆盖率报告
          相关产品与服务
          手游安全测试
          手游安全测试(Security Radar,SR)为企业提供私密的安全测试服务,通过主动挖掘游戏业务安全漏洞(如钻石盗刷、服务器宕机、无敌秒杀等40多种漏洞),提前暴露游戏潜在安全风险,提供解决方案及时修复,最大程度降低事后外挂危害与外挂打击成本。该服务为腾讯游戏开放的手游安全漏洞挖掘技术,杜绝游戏外挂损失。
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档