【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/

相关推荐
明月看潮生1 小时前
青少年编程与数学 02-003 Go语言网络编程 09课题、Cookie
青少年编程·golang·网络编程·编程与数学
明月看潮生1 小时前
青少年编程与数学 02-003 Go语言网络编程 15课题、Go语言URL编程
开发语言·网络·青少年编程·golang·编程与数学
明月看潮生2 小时前
青少年编程与数学 02-003 Go语言网络编程 14课题、Go语言Udp编程
青少年编程·golang·网络编程·编程与数学
hlsd#3 小时前
go 集成go-redis 缓存操作
redis·缓存·golang
Devil枫8 小时前
Vue 3 单元测试与E2E测试
前端·vue.js·单元测试
小袁在上班14 小时前
Python 单元测试中的 Mocking 与 Stubbing:提高测试效率的关键技术
python·单元测试·log4j
测试199815 小时前
外包干了2年,快要废了。。。
自动化测试·软件测试·python·面试·职场和发展·单元测试·压力测试
qq_1728055916 小时前
GIN 反向代理功能
后端·golang·go
__AtYou__17 小时前
Golang | Leetcode Golang题解之第535题TinyURL的加密与解密
leetcode·golang·题解
安冬的码畜日常18 小时前
【The Art of Unit Testing 3_自学笔记06】3.4 + 3.5 单元测试核心技能之:函数式注入与模块化注入的解决方案简介
笔记·学习·单元测试·jest