0%

《Go语言学习笔记》读书笔记(1)测试

11. 测试

标准库自带单元测试框架

  • 测试代码须放在当前包以”_test.go”结尾的文件中
  • 测试函数以Test为名称前缀
  • 测试命令(go test) 忽略以”_” 或 “.” 开头的测试文件
  • 正常编译操作(go build/install)会忽略测试文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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

1
2
3
4
5
6
func TestMain(m * testing.M){
// setup
code := m.Run() // 调用测试函数
// tear down
os.Exit(code) // 注意: os.Exit 不会执行defer
}

多测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

1
2
3
4
5
6
7
8
func ExampleAdd() {
fmt.Println(add(1, 2))
fmt.Println(add(2, 2))

// Output:
// 3
// 4
}

注意:如果没有output注释,该示例就不会被执行。另外,不能使用内置函数print/printIn, 因为他们输出到stderr

benchmark

1
2
3
4
5
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组织计时器工作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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

性能测试查看内存情况

1
2
3
4
5
6
7
8
9
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参数与否

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
8
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