【Go 快速入门】Go Test 工具 | 单元测试 | 基准测试

文章目录

本节项目地址:07-UnitTestBenchmarkTest

go test工具

Go语言中的测试依赖go test,该命令是一个按照一定约定和组织的测试代码的驱动程序。在包目录内,所有以_test.go为后缀名的源代码文件都是go test测试的一部分,不会被go build编译到最终的可执行文件中。

*_test.go文件中有三种类型的函数,单元测试函数、基准测试函数和示例函数。

类型 格式 作用
测试函数 函数名前缀为Test 测试程序的一些逻辑行为是否正确
基准函数 函数名前缀为Benchmark 测试函数的性能
示例函数 函数名前缀为Example 为文档提供示例文档

go test命令会遍历所有的*_test.go文件中符合上述命名规则的函数,然后生成一个临时的main包用于调用相应的测试函数,然后构建并运行、报告测试结果,最后清理测试中生成的临时文件。

单元测试

  • 固定语法

func TestFuncName(t *testing.T) {}

  1. 单元测试的测试函数都以 Test 开头,可选的后缀名以大写字母开头
  2. 参数 t 是 *testing.T 类型,用于报告测试失败和附加的日志信息

下述创建了一个测试函数:

go 复制代码
// TestStringSplit 测试 string 的 split 函数
func TestStringSplit(t *testing.T) {
	// 定义一个测试用例类型
	type test struct {
		input string
		sep   string
		want  []string
	}
	// 定义一个存储测试用例的切片
	tests := []test{
		{input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},
		{input: "a:b:c", sep: ",", want: []string{"a:b:c"}},
		{input: "abcd", sep: "bc", want: []string{"a", "d"}},
		{input: "沙河有沙又有河", sep: "沙", want: []string{"", "河有", "又有河"}},
	}
	// 遍历切片,逐一执行测试用例
	for _, tc := range tests {
		got := strings.Split(tc.input, tc.sep)
		if !reflect.DeepEqual(got, tc.want) {
			t.Errorf("expected:%#v, got:%#v", tc.want, got)
		}
	}
}
  • go test -v:执行测试,并打印出测试函数名和运行时间

    go test -v
    === RUN TestStringSplit
    --- PASS: TestStringSplit (0.00s)
    PASS
    ok 07-UnitTestBenchmarkTest/UnitTest 0.662s

子测试

如果测试用例比较多的时候,我们是没办法一眼看出来具体是哪个测试用例失败了。可以按照如下方式使用t.Run执行子测试就能够看到更清晰的输出内容了。

go 复制代码
// TestStringSplit1 测试 string 的 split 函数,子测试
func TestStringSplit1(t *testing.T) {
	// 定义一个测试用例类型
	type test struct {
		input string
		sep   string
		want  []string
	}
	// 测试用例使用map存储
	tests := map[string]test{
		"simple":      {input: "a:b:c", sep: ":", want: []string{"a", "b", "c"}},
		"wrong sep":   {input: "a:b:c", sep: ",", want: []string{"a:b:c"}},
		"more sep":    {input: "abcd", sep: "bc", want: []string{"a", "d"}},
		"leading sep": {input: "沙河有沙又有河", sep: "沙", want: []string{"河有", "又有河"}},
	}
	for name, tc := range tests {
		// 使用t.Run()执行子测试
		t.Run(name, func(t *testing.T) {
			got := strings.Split(tc.input, tc.sep)
			if !reflect.DeepEqual(got, tc.want) {
				t.Errorf("expected:%#v, got:%#v", tc.want, got)
			}
		})
	}
}
go test -v 
=== RUN   TestStringSplit
--- PASS: TestStringSplit (0.00s)
=== RUN   TestStringSplit1
=== RUN   TestStringSplit1/wrong_sep
=== RUN   TestStringSplit1/more_sep
=== RUN   TestStringSplit1/leading_sep
    string_test.go:53: expected:[]string{"河有", "又有河"}, got:[]string{"", "河有", "又有河"}
=== RUN   TestStringSplit1/simple
--- FAIL: TestStringSplit1 (0.00s)
    --- PASS: TestStringSplit1/wrong_sep (0.00s)
    --- PASS: TestStringSplit1/more_sep (0.00s)
    --- FAIL: TestStringSplit1/leading_sep (0.00s)
    --- PASS: TestStringSplit1/simple (0.00s)
FAIL
exit status 1
FAIL    07-UnitTestBenchmarkTest/UnitTest       0.629s

如果指向执行指定的测试函数,可以通过 -run 参数指定,它对应一个正则表达式,只有函数名匹配的上的测试函数才会被 go test 命令执行。

go test -v -run=Split/simple
=== RUN   TestStringSplit
--- PASS: TestStringSplit (0.00s)
=== RUN   TestStringSplit1
=== RUN   TestStringSplit1/simple
--- PASS: TestStringSplit1 (0.00s)
    --- PASS: TestStringSplit1/simple (0.00s)
PASS
ok      07-UnitTestBenchmarkTest/UnitTest       0.046s

测试覆盖率

测试覆盖率是你的代码被测试套件覆盖的百分比。通常我们使用的都是语句的覆盖率,也就是在测试中至少被运行一次的代码占总代码的比例。

Go提供内置功能来检查你的代码覆盖率。我们可以使用go test -cover来查看测试覆盖率。Go还提供了一个额外的-coverprofile参数,用来将覆盖率相关的记录信息输出到一个文件。

使用:go test -cover -coverprofile="a.out",将会生成 a.out 文件。然后可以执行go tool cover -html="a.out",使用cover工具来处理生成的记录信息,该命令会打开本地的浏览器窗口生成一个HTML报告。


基准测试

  • 固定语法

func BenchmarkFuncName(b *testing.B) {}

  1. 基准测试的测试函数都以 Benchmark 开头
  2. 参数 b 是 *testing.B 类型
  3. 基准测试必须包含for循环,循环次数是 b.N 次(b.N的值是系统根据实际情况调整,保证测试稳定性)

下述创建了一个测试函数:

go 复制代码
func split(s, sep string) (result []string) {
	result = make([]string, 0, strings.Count(s, sep)+1)
	i := strings.Index(s, sep)
	for i > -1 {
		result = append(result, s[:i])
		s = s[i+len(sep):]
		i = strings.Index(s, sep)
	}
	result = append(result, s)
	return
}

// BenchmarkSplit 测试自定义的 split 函数
func BenchmarkSplit(b *testing.B) {
	for i := 0; i < b.N; i++ {
		split("沙河有沙又有河", "沙")
	}
}

使用 go test -v -bench=Split 命令执行基准测试,基础测试默认不会执行,必须携带 -bench 参数。

go test -v -bench=Split
goos: windows
goarch: amd64
pkg: 07-UnitTestBenchmarkTest/BenchmarkTest
cpu: 13th Gen Intel(R) Core(TM) i9-13900HX
BenchmarkSplit
BenchmarkSplit-32       26486323                47.05 ns/op
PASS
ok      07-UnitTestBenchmarkTest/BenchmarkTest  1.897s

其中 BenchmarkSplit-32 表示对 Split 函数进行基准测试,数字 32 表示 GOMAXPROCS 的值,2648632347.05 ns/op 表示每次调用 Split 函数耗时 47.05ns,这个结果是 26486323 次调用的平均值。

还可以为基准测试添加 -benchmem 参数,来获得内存分配的统计数据。如下所示,48 B/op 表示每次操作内存分配了48字节,1 allocs/op 表示每次操作进行了1次内存分配。

go test -v -bench=Split -benchmem
goos: windows
goarch: amd64
pkg: 07-UnitTestBenchmarkTest/BenchmarkTest
cpu: 13th Gen Intel(R) Core(TM) i9-13900HX
BenchmarkSplit
BenchmarkSplit-32       26387544                45.29 ns/op           48 B/op          1 allocs/op
PASS
ok      07-UnitTestBenchmarkTest/BenchmarkTest  1.295s

性能比较函数

性能比较函数通常是一个带有参数的函数,被多个不同的 Benchmark 函数传入不同的值来调用。

go 复制代码
func fib(n int) int {
	if n < 2 {
		return n
	}
	return fib(n-1) + fib(n-2)
}

func benchmarkFib(b *testing.B, n int) {
	for i := 0; i < b.N; i++ {
		fib(n)
	}
}

func BenchmarkFib1(b *testing.B)  { benchmarkFib(b, 1) }
func BenchmarkFib2(b *testing.B)  { benchmarkFib(b, 2) }
func BenchmarkFib3(b *testing.B)  { benchmarkFib(b, 3) }
func BenchmarkFib10(b *testing.B) { benchmarkFib(b, 10) }
func BenchmarkFib20(b *testing.B) { benchmarkFib(b, 20) }
func BenchmarkFib40(b *testing.B) { benchmarkFib(b, 40) }

上述编写了一个斐波那契数列计算函数,并在不同大小的n值的情况下进行基准测试。

go test -v -bench=Fib          
goos: windows
goarch: amd64
pkg: 07-UnitTestBenchmarkTest/BenchmarkTest
cpu: 13th Gen Intel(R) Core(TM) i9-13900HX
BenchmarkFib1
BenchmarkFib1-32        1000000000               0.5479 ns/op
BenchmarkFib2
BenchmarkFib2-32        736555106                1.600 ns/op
BenchmarkFib3
BenchmarkFib3-32        459925009                2.590 ns/op
BenchmarkFib10
BenchmarkFib10-32        7295648               164.5 ns/op
BenchmarkFib20
BenchmarkFib20-32          59763             20160 ns/op
BenchmarkFib40
BenchmarkFib40-32              4         306910725 ns/op
PASS
ok      07-UnitTestBenchmarkTest/BenchmarkTest  8.692s

默认情况下,每个基准测试至少运行1秒,可以通过参数 -benchtime 设置基准测试时间。此外还有 -cpu 参数设置运行基准测试的并发数,-count 参数设置运行测试的总次数。

go test -v -bench="." -benchtime=2s 
goos: windows
goarch: amd64
pkg: 07-UnitTestBenchmarkTest/BenchmarkTest
cpu: 13th Gen Intel(R) Core(TM) i9-13900HX
BenchmarkSplit
BenchmarkSplit-32       48237133                44.58 ns/op
BenchmarkFib1
BenchmarkFib1-32        1000000000               0.5392 ns/op
BenchmarkFib2
BenchmarkFib2-32        1000000000               1.575 ns/op
BenchmarkFib3
BenchmarkFib3-32        931792406                2.590 ns/op
BenchmarkFib10
BenchmarkFib10-32       14704368               163.8 ns/op
BenchmarkFib20
BenchmarkFib20-32         119018             20080 ns/op
BenchmarkFib40
BenchmarkFib40-32              7         303590871 ns/op
PASS
ok      07-UnitTestBenchmarkTest/BenchmarkTest  14.857s

重置时间

b.ResetTimer 之前的处理不会放到执行时间里,也不会输出到报告中,所以可以在之前做一些不计划作为测试报告的操作。b.ReportAllocs() 记录内存分配数据。

go 复制代码
func BenchmarkResetTimer(b *testing.B) {
	time.Sleep(3 * time.Second)
	b.ResetTimer()
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		split("abc.poe.org.com", ".")
	}
}
go test -bench="Timer"
goos: windows
goarch: amd64
pkg: 07-UnitTestBenchmarkTest/BenchmarkTest
cpu: 13th Gen Intel(R) Core(TM) i9-13900HX
BenchmarkResetTimer-32          32262052                34.99 ns/op           64 B/op          1 allocs/op
PASS
ok      07-UnitTestBenchmarkTest/BenchmarkTest  16.843s

并行测试

func (b *B) RunParallel(body func(*PB)) 会以并行的方式执行给定的基准测试。

RunParallel 会创建出多个 goroutine,并将 b.N 分配给这些 goroutine 执行, 其中 goroutine 数量的默认值为GOMAXPROCS。用户如果想要增加非 CPU 受限(non-CPU-bound)基准测试的并行性, 那么可以在 RunParallel 之前调用 SetParallelismRunParallel 通常会与 -cpu 标志一同使用。

go 复制代码
func BenchmarkSplitParallel(b *testing.B) {
	// b.SetParallelism(1) // 设置使用的cpu数
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			split("cn.com.org", ".")
		}
	})
}
go test -bench="Parallel" -cpu=4
goos: windows
goarch: amd64
pkg: 07-UnitTestBenchmarkTest/BenchmarkTest
cpu: 13th Gen Intel(R) Core(TM) i9-13900HX
BenchmarkSplitParallel-4        95221719                14.72 ns/op
PASS
ok      07-UnitTestBenchmarkTest/BenchmarkTest  2.329s

参考:https://www.liwenzhou.com/posts/Go/unit-test/

相关推荐
程序猿000001号5 小时前
探索Python的pytest库:简化单元测试的艺术
python·单元测试·pytest
hkNaruto13 小时前
【P2P】【Go】采用go语言实现udp hole punching 打洞 传输速度测试 ping测试
golang·udp·p2p
入 梦皆星河13 小时前
go中常用的处理json的库
golang
海绵波波10715 小时前
Gin-vue-admin(2):项目初始化
vue.js·golang·gin
每天写点bug15 小时前
【go每日一题】:并发任务调度器
开发语言·后端·golang
一个不秃头的 程序员15 小时前
代码加入SFTP Go ---(小白篇5)
开发语言·后端·golang
基哥的奋斗历程16 小时前
初识Go语言
开发语言·后端·golang
ZVAyIVqt0UFji1 天前
go-zero负载均衡实现原理
运维·开发语言·后端·golang·负载均衡
唐墨1231 天前
golang自定义MarshalJSON、UnmarshalJSON 原理和技巧
开发语言·后端·golang
老大白菜1 天前
FastAPI vs Go 性能对比分析
开发语言·golang·fastapi