search.png
关于我
menu.png
go 压力测试

基础

Go语言中的测试依赖go test命令。
go test命令是一个按照一定约定和组织的测试代码的驱动程序。在包目录内,所有以_test.go为后缀名的源代码文件都是go test测试的一部分,不会被go build编译到最终的可执行文件中。

在*_test.go文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数

类型 格式 作用
基础测试 前缀为Test 测试程序的逻辑是否正确
性能测试 前缀为Benchmark 测试程序的性能
提供示例 前缀为Example 为程序提供示例

go test中可用的参数有:
go test是go语言自带的测试工具,其中包含的是两类,单元测试和性能测试

通过go help test可以看到go test的使用说明:

格式形如: go test [-c] [-i] [build flags] [packages] [flags for test binary]

参数解读:

-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 : go test -cpu=1,2,4 将会执行 3 次,其中 GOMAXPROCS 值分别为 1,2,和 4。

-test.short : 将那些运行时间较长的测试用例运行时间缩短

性能测试示例

性能测试函数需要以Benchmark开头,并且参数是*testing.B:

func Division(a, b float64) (float64, error) {
    if b == 0 {
        return 0, errors.New("除数不能为0")
    }

    return a / b, nil
}

// go test -bench '.*' .
func Benchmark_Division(b *testing.B) {
    for i := 0; i < b.N; i++ { //use b.N for looping
        Division(4, 5)
    }
}

func Benchmark_TimeConsumingFunction(b *testing.B) {
    b.StopTimer() //调用该函数停止压力测试的时间计数

    //做一些初始化的工作,例如读取文件数据,数据库连接之类的,
    //这样这些时间不影响我们测试函数本身的性能

    b.StartTimer() //重新开始时间
    for i := 0; i < b.N; i++ {
        Division(4, 5)
    }
}

使用go test -bench '.*' .运行测试:

goos: windows
goarch: amd64
pkg: mytest/testperformancetest
cpu: AMD Ryzen 9 3900X 12-Core Processor
Benchmark_Division-24                   1000000000               0.2467 ns/op
Benchmark_TimeConsumingFunction-24      1000000000               0.2433 ns/op
PASS
ok      mytest/testperformancetest      0.615s

其中Benchmark_Division-24表示对Division函数进行基准测试,数字24表示GOMAXPROCS的值,这个对于并发基准测试很重要。1000000000和0.2467 ns/op表示每次调用Benchmark_Division函数耗时0.2467 ns,这个结果是1000000000次调用的平均值。
可用使用-test.cup n来设置cpu数量:
例如 go test -bench '.*' -cpu 4 .

goos: windows
goarch: amd64
pkg: mytest/testperformancetest
cpu: AMD Ryzen 9 3900X 12-Core Processor
Benchmark_Division-4                    1000000000               0.2448 ns/op
Benchmark_TimeConsumingFunction-4       1000000000               0.2459 ns/op
PASS
ok      mytest/testperformancetest      0.616s

增加-test.benchmem来显示内存占用:
例如 go test -bench '.*' -benchmem .:

goos: windows
goarch: amd64
pkg: mytest/testperformancetest
cpu: AMD Ryzen 9 3900X 12-Core Processor
Benchmark_Division-24                   1000000000               0.2452 ns/op          0 B/op          0 allocs/op
Benchmark_TimeConsumingFunction-24      1000000000               0.2461 ns/op          0 B/op          0 allocs/op
PASS
ok      mytest/testperformancetest      0.623s

测试斐波那契递归和非递归的性能对比

// 斐波那契数列:后一个数等于前面两个数的和
func fb_recursion(num int) int {
    if num <= 2 {
        return 1
    }
    return fb_recursion(num - 1) + fb_recursion(num -2)
}

func fb_norecursion(num int) int {
    l := make([]int, num+1)
    for i := 1; i <= num; i++ {
        if i <= 2 {
            l[i] = 1
        } else {
            l[i] = l[i-1] + l[i-2]
        }
    }
    return l[num]
}

// 性能测试

func Benchmark_fb_recursion(b *testing.B) {
    for i := 40; i <= 43; i++ {
        b.Run(strconv.Itoa(i), func(b *testing.B) {
            fb_recursion(i)
        })
    }
}

func Benchmark_fb_norecursion(b *testing.B) {
    for i := 40; i <= 43; i++ {
        b.Run(strconv.Itoa(i), func(b *testing.B) {
            fb_norecursion(i)
        })
    }
}


该测试分别对比了递归和非递归版本运行计算40-43的斐波那契数列的性能,可以看到,非递归版本的性能优势是很明显的:
go test -bench '.*fb.*' -benchmem -benchtime 2s -cpu 1 .

goos: windows
goarch: amd64
pkg: mytest/testperformancetest
cpu: AMD Ryzen 9 3900X 12-Core Processor
Benchmark_fb_recursion/40               1000000000               0.3809 ns/op          0 B/op          0 allocs/op
Benchmark_fb_recursion/41               1000000000               0.5769 ns/op          0 B/op          0 allocs/op
Benchmark_fb_recursion/42               1000000000               1.047 ns/op           0 B/op          0 allocs/op
Benchmark_fb_recursion/43               1000000000               1.781 ns/op           0 B/op          0 allocs/op
Benchmark_fb_norecursion/40             1000000000             0 B/op          0 allocs/op
Benchmark_fb_norecursion/41             1000000000             0 B/op          0 allocs/op
Benchmark_fb_norecursion/42             1000000000             0 B/op          0 allocs/op
Benchmark_fb_norecursion/43             1000000000             0 B/op          0 allocs/op
PASS
ok      mytest/testperformancetest      124.822s

性能测试中跳过某些步骤


func Benchmark_TimeConsumingFunction(b *testing.B) {
    b.StopTimer() //调用该函数停止压力测试的时间计数

    //做一些初始化的工作,例如读取文件数据,数据库连接之类的,
    //这样这些时间不影响我们测试函数本身的性能

    b.StartTimer() //重新开始时间
    for i := 0; i < b.N; i++ {
        Division(4, 5)
    }
}

版权声明

知识共享许可协议 本文章由作者“衡于墨”创作,转载请注明出处,未经允许禁止用于商业用途

本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。
发布时间:2021年03月11日 17:00:57

评论区#

还没有评论哦,期待您的评论!

关闭特效