这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记
后端开发除了正常的业务代码编写之外,考虑代码的运行成本和如何优化实际运行中的性能也是比较重要的一环,相应的在进行相关的工作的时候,如何评判性能表现、定位性能瓶颈、优化的策略都是需要考虑到的点,所以下面就让我们从这三个方面着手,总结真实场景中的测试和性能调优的方案。
性能测试
从代码产出到投入生产,中间需要经历的测试包括但不局限于回归测试、集成测试、单元测试
- 回归测试 ➡ 回归测试即通过手动终端调用来验证功能的正常使用
- 集成测试 ➡ 集成测试即通过自动化手段来进行回归测试中的操作
以上的两种方式,可以发现就是对功能性的校验,所以测试的前提先能够保证代码的正常执行,从上往下,测试的成本会逐渐降低,同时覆盖率也会越来越广,这里面就能够找到一些存在问题的模块单元。就好比leetcode刷题,测试样例的覆盖率很大程度上决定了系统能否发现程序的漏洞,所以很自然的,代码覆盖率就称为了下面单元测试的指标
- 单元测试
单元测试就是对于定位到的单元进行进一步的排错和修复,这里有两个比较重要的东西,基准测试和结果测试。
基准测试
可以理解为对执行性能的测试或者说压力测试,通过大量的循环测试,可以获得方法中每步操作的执行时间参数,其中Go语言自身就有提供 基准测试 的方法 benchmark ,要使用这一模块要求测试方法在以_test.go结尾的文件中,并且用于测试的方法要以BenchMark开头,同时参数中会自动注入 *testing.B 用于基准测试的对象,将需要测试的方法放在for循环中就能够进行基准测试了,大致的代码如下:
func BenchmarkSelect(b *testing.B) {
//初始化
b.ResetTimer() //设置开始计时点
for n:=0;n<B.N;n++ {
//执行测试内容
}
}
这个时候IDEA或者其他编译器应该可以直接进行运行测试,或者手动在命令行中输入:
go test -bench=”..“ ..即可运行测试
其中-bench是指定要测试的方法,可以用正则化的方式进行匹配要测试的方法,其余的参数也可以往后面添加,在运行完测试之后就会获得相应的测试结果,这里以简单的fibonacci循环测试数据累加为例:
func fib(n int) int {
if n < 2 {
return n
}
return fib(n-1) + fib(n-2)
}
func BenchmarkFib(b *testing.B) {
b.ResetTimer()
for n := 0; n < b.N; n++ {
fib(20)
}
}
运行结果如图:
通过Benchmark的结果可以发现,我们能够获得当前计算机的操作系统、CPU、包名以及测试数据和结果,在测试数据中 12代表的是CPU的最大核心数,或者最大同时运行的线程数,37315指的是实际测试中的循环次数,39331表示的是在执行Fib方法时,平均每一步操作的耗时为39331纳秒
结果测试
从名字就能看出,就是对模块运行结果的正确性进行一个测试,其中通过一些断言的方式来进行判断方法返回结果的正确与否,最后做一个数据判断。关于构造方式类似基准压力测试,但是要求方法以Test开头,并且注入 testing.T,然后就可以进行测试。
直接go test可以获得测试的结果,即方法返回值是否符合测试的逻辑,同时,我们也可以在命令行中给go test加上-cover来观察本次测试的代码覆盖率,所谓代码覆盖率就是测试的方法中的所有逻辑分支是否都已经覆盖,或者覆盖了多少分支,会有一个百分比的数据
关于代码的覆盖率,不同的业务对这一块的要求不同,一般为50-60上下,但是对于特定业务来说,对于数据的高要求也会相应的需要提高代码的覆盖率。
除去以上的几类测试方法,对于测试方法的构建也有基本的要求:
- 测试的分支要相互独立且全面覆盖
- 测试单元的粒度要足够小,函数的职责尽量单一
- 要满足测试方法的幂等性,对于相同的输入数据,方法的返回结果应当完全相同
除去go自带的test模块,也有一些包来进行封装,获得更健全的测试功能,比如:github.com/bouk/monkey 这是针对Go的一款强大的打桩用的包,可以为函数/接口、成员方法、变量进行打桩,实现的原理就是通过在执行的时候,将调用的内存指向转到打桩好的自己定义的方法进行一个测试,在使用上可以类比于Java中的代理实现,但是原理并不相通
在完成了测试之后,往往还需要对于需要上线的代码进行进一步的调优,关于这一块内容,我会后续整理在第二篇笔记中。
第二篇笔记已经整理完成,链接如下: