《Go语言学习笔记》读书笔记(1)测试
11. 测试
标准库自带单元测试框架
- 测试代码须放在当前包以”_test.go”结尾的文件中
- 测试函数以Test为名称前缀
- 测试命令(go test) 忽略以”_” 或 “.” 开头的测试文件
- 正常编译操作(go build/install)会忽略测试文件
package main
import (
"testing"
"time"
)
func add(x, y int) int {
return x + y
}
func TestAdd(t *testing.T) {
var tests = []struct {
x int
y int
expect int
} {
{1, 1, 2},
{2, 2, 4},
{3, 2, 5},
}
for _, tt := range tests {
actual := add(tt.x, tt.y)
if actual != tt.expect {
t.Errorf("add(%d, %d): expect %d, actual %d", tt.x, tt.y, tt.expect, actual)
}
}
}
func TestA(t *testing.T){
t.Parallel()
time.Sleep(time.Second * 2)
}
func TestB(t *testing.T){
t.Parallel()
time.Sleep(time.Second * 2)
}
参数 | 说明 | 示例 |
---|---|---|
-arg | 命令行参数 | |
-v | 输出详细信息 | |
-parallel | 并发执行, 默认执行GOMAXPROCS | -parallel 2 |
-run | 指定测试函数,正则表达式 | -run “Add” |
-timeout | 全部测试累计时间超时将引发panic, 默认值为10ms | -timeout 1m30s |
-count | 重复测试次数,默认次数为1 |
test main
func TestMain(m * testing.M){
// setup
code := m.Run() // 调用测试函数
// tear down
os.Exit(code) // 注意: os.Exit 不会执行defer
}
多测试用例
func TestMain(m * testing.M) {
match := func(pat, str string) (bool, error) { // pat: 命令行参数-run 提供的过滤条件
return true, nil // str: InternalTest.Name
}
tests := []testing.InternalTest {
{"b", TestB},
{"a", TestA},
}
benchmarks := []testing.InternalBenchmark{}
examples := []testing.InternalExample{}
m = testing.MainStart(match, tests, benchmarks, examples)
os.Exit(m.Run())
}
example
func ExampleAdd() {
fmt.Println(add(1, 2))
fmt.Println(add(2, 2))
// Output:
// 3
// 4
}
注意:如果没有output注释,该示例就不会被执行。另外,不能使用内置函数print/printIn, 因为他们输出到stderr
benchmark
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = add(1, 2)
}
}
go test -bench .
如果希望仅执行性能测试,那么可以用run=NONE
忽略所有测试用例。
性能测试默认以并发方式进行测试,但可用cpu参数设定多个并发限制来观察结果。
go test -bench . -cpu 1,2,4
某些耗时的目标,默认循环测试过少,取平均值不足以准确计量性能。可用benchtime
设定最小测试时间来增加循环次数,以便返回更准确的结果。
go test -bench . -benchtime 5s
timer
如果在测试函数中要执行一些额外的操作,那么应该临时i组织计时器工作。
func BenchmarkAdd(b *testing.B) {
time.Sleep(time.Second)
b.ResetTimer() // 重置
for i := 0; i < b.N; i++ {
_ = add(1, 2)
if i == 1 {
b.StopTimer() // 暂停
time.Sleep(time.Second)
b.StartTimer() // 恢复
}
}
for i := 0; i < b.N; i++ {
_ = add(1, 2)
}
}
memory
性能测试查看内存情况
func heap() []byte {
return make([]byte, 1024*10)
}
func BenchmarkHeap(b *testing.B) {
for i := 0 ; i < b.N; i++ {
_ = heap()
}
}
go test -bench . -benchmem -gcflags “-N -l” # 禁止内联和优化, 以便观察结果
也可将测试函数设置为总是输出内存分配信息,无论使用benchmem参数与否
func BenchmarkHeap(b *testing.B) {
b.ReportAllocs()
b.ReportTimer()
for i := 0 ; i < b.N; i++ {
_ = heap()
}
}
代码覆盖率
go test -cover
为获取更详细信息,可指定covermode 和coverprofile 参数
- set: 是否执行
- count: 执行次数
- atomic: 执行次数,支持并发模式
go test -cover -covermode count -coverprofile cover.out
还可以在浏览器中查看包括具体的执行次数等信息
go tool cover -html=cover.out
性能监控
引发性能问题的原因无外乎执行时间过长、内存占用过多,以及意外阻塞。通过捕获或监控相关执行状态数据,就可定位引发问题的原因,从而针对性改进算法。
go test -run NONE -bench . -memprofile mem.out -cpuprofile cpu.out net/http
参数 | 说明 | 示例 |
---|---|---|
-cpuprofile | 保存执行时间采样到指定文件 | -cpuprofile cpu.out |
-memprofile | 保存内存分配采样到指定文件 | -memprofile mem.out |
-memprofilerate | 内存分配采样起始值,默认为512KB | -memprofilerate 1 |
-blockprofile | 保存阻塞时间采样到指定文件 | -blockprofile block.out |
-blockprofilerate | 阻塞时间采样起始值,单位为:ns |
如果执行性能测试,可能需要设置benchtime
参数,以确保有足够的采样时间
可使用交互模式查看,或用命令行直接输出单向结果。
go tool pprof http.test mem.out (pprof) top5
- flat: 仅当前函数,不包括它调用的其他函数。
- sum: 列表前几行所占百分比的总和。
- cum: 当前函数调用堆栈累计。
top命令可指定排序字段,比如top5 -cum
找出需要进一步查看的目标,使用peek
命令列出调用来源
也可用list命令输出源码统计样式,以便更直观的定位
除文字模式以外,还可输出svg图形,将其保存或用浏览器查看
在线采集数据须诸如 http/pprof
包
import (
"net/http"
_ "net/http/pprof"
)
func main() {
http.ListenAndServe(":8080", http.DefaultServeMux)
}
用浏览器访问指定路径,就可看到不同的检测项。
go tool pprof http://localhost:8080/debug/pprof/heap?debug=1
必要时还可抓取数据,进行离线分析。
curl http://localhost:8080/debug/pprof/heap?debug=1 > mem.out go tool pprof test mem.out