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

2 分钟阅读

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