非零基础自学Golang
文章目录
- 非零基础自学Golang
- 第15章 Go命令行工具
- 15.5 代码测试(test)
- 15.5.2 基准测试
- 15.5.3 覆盖率测试
第15章 Go命令行工具
15.5 代码测试(test)
15.5.2 基准测试
基准测试提供可自定义的计时器和一套基准测试算法,能方便快速地分析一段代码可能存在的CPU性能和内存分配问题。
类似于单元测试,使用者无须准备高精度的计时器和各种分析工具,基准测试本身即可打印出非常标准的测试报告。
基准测试的使用也有一套书写规范,使用“Benchmark”为前缀,后面加上需要测试的函数名,并使用*testing.B作为函数参数,如下所示。
func BenchmarkFunc(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
// 要测试的代码
}
b.StopTimer()
}
有些测试代码需要一定的初始化时间,如果从Benchmark()函数开始计时,很大程度上会影响测试结果的精准性。
testing.B提供了可以方便地控制计时器的一系列方法,从而让计时器只在需要的区间进行测试。ResetTimer()表示重置计时器,StopTimer()表示停止计时,需要精准测试的代码只需要放到这两个函数中间即可。
其中b.N表示必须循环b.N次,b.N这个数字会在运行中调整,以便最终达到合适的时间消耗,方便计算出合理的数据。
通过基准测试,我们能够知道使用哪种编码方式会让程序的运行效率最高。
举个例子,针对字符串拼接这个场景,有非常多的实现方式,比如使用标准库中的fmt.Sprintf、使用字符串连接符“+”直接相加、使用BytesBuf方式等。
想要知道哪种方式才是性能最优的选择,可以用基准测试来判断。
[动手写 15.5.3]
package main
import (
"bytes"
"fmt"
"strconv"
"strings"
"testing"
)
const Num = 10000
func BenchmarkSprintf(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
var str string
for i := 0; i < Num; i++ {
str = fmt.Sprintf("%s%d", str, i)
}
}
b.StopTimer()
}
func BenchmarkStringBuilder(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx ++ {
var builder strings.Builder
for i := 0; i < Num; i++ {
builder.WriteString(strconv.Itoa(i))
}
_ = builder.String()
}
b.StopTimer()
}
func BenchmarkBytesBuf(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
var buf bytes.Buffer
for i := 0; i < Num; i++ {
buf.WriteString(strconv.Itoa(i))
}
_ = buf.String()
}
b.StopTimer()
}
func BenchmarkStringAdd(b *testing.B) {
b.ResetTimer()
for idx := 0; idx < b.N; idx++ {
var str string
for i := 0; i < Num; i++ {
str += strconv.Itoa(i)
}
}
b.StopTimer()
}
动手写15.5.3分别针对几种不同的字符串连接方式进行基准测试,使用for循环连接10000个数字。
运行go test -bench=.就可以获取结果。
OK,改一下
注意 -bench=. 这几个全挨着写,别想当然的打空格,亲测
可以发现,使用StringBuilder和BytesBuf这两种字符串连接的效率最优,而使用Sprintf连接的效率最差。
15.5.3 覆盖率测试
覆盖率测试用于统计通过运行程序包的测试有多少代码得到了执行。如果执行测试代码有70%的语句得到了运行,则测试覆盖率为70%。
使用-cover参数就可以对源码文件进行覆盖率测试。
go test -cover xxx.go
例如,我们有如下Level函数需要进行测试,Level函数根据不同的分数进行不同的评级。
[ 动手写15.5.4]
package main
func Level(point int) string {
switch {
case point < 60:
return "不及格"
case point < 70:
return "及格"
case point < 80:
return "良"
case point < 100:
return "优秀"
default:
return "数据出错"
}
}
针对动手写15.5.4,我们编写如下测试代码,分别测试分数为20、65、75时,程序是否运行正确。
func TestLevel(t *testing.T) {
input := []int{20, 65, 75}
expected := []string{"不及格", "及格", "良好"}
for i := 0; i < len(input); i++ {
ret := Level(input[i])
if ret != expected[i] {
t.Errorf("input is %d , the expected is %s , the actual %s", input[i], expected[i], ret)
}
}
}
动手写15.5.5中,由于只测试了“不及格”“及格”“良”这三种情况的数据,而“优秀”和“数据出错”这两种情况未测试到,因此覆盖率约为66%。运行结果如下:
没毛病,测试覆盖率 和我们预计的 一样