golang单元测试

纸鸢 Lv4

1、Golang测试分类

  1. 单元测试(Unit Tests)
    • Test 开头
    • 函数签名为 func TestXxx(t *testing.T)
    • 用于测试代码的最小可测试单元
  2. 基准测试(Benchmark Tests)
    • Benchmark 开头
    • 函数签名为 func BenchmarkXxx(b *testing.B)
    • 用于测试代码的性能
  3. 示例测试(Example Tests)
    • Example 开头
    • 函数签名为 func ExampleXxx()
    • 既可以作为示例代码,也可以作为测试用例
    • 可以通过注释 // Output: 来验证输出
  4. 模糊测试(Fuzzing Tests)
    • Fuzz 开头(Go 1.18+ 新增)
    • 函数签名为 func FuzzXxx(f *testing.F)
    • 通过随机数据输入来发现潜在问题

2、Golang测试模式

  1. go test(当前目录模式)
    • 只测试当前目录下的测试文件
    • 不会递归测试子目录中的测试
    • 这是最简单的测试命令
  2. go test .(包列表模式)
    • 测试当前目录及其子目录中的所有包
    • . 表示从当前目录开始
    • 会递归搜索并测试所有找到的包

3、常用参数和示例

这里介绍几个常用的参数:

  • -bench regexp 执行相应的 benchmarks,例如 -bench=.
  • -cover 开启测试覆盖率;
  • -run regexp 只运行 regexp 匹配的函数
    • 例如 -run=TestAdd(-run TestAdd) 那么就执行包含有 TestAdd 开头的函数,'TestAdd$'代表只执行Array名字的函数
    • 参数支持通配符 *,和部分正则表达式,例如 ^$
    • -run=none,代表要执行这个名字的函数,如果没有就不执行,也就代表不执行单元测试
  • -v 显示测试的详细命令。
    • go test -v-v 参数会显示每个用例的测试结果
  • -benchtime :可以自定义测试时间

go test,该 package 下所有的文件的所有测试用例都会被执行。

go test calc_test.go,运行该 package 下的calc_test.go文件的所有测试用例。

go test calc_test.go -run=TestAdd,运行该 package 下的calc_test.go文件的以TestAdd开头的所有用例。

go test calc_test.go -run='TestAdd$',运行该 package 下的calc_test.go文件的TestAdd用例。

4、测试代码规范

​ 执行 go test 命令,它会在 *_test.go 中寻找 test 测试benchmark 基准examples 示例fuzz 示例函数。测试函数必须以 TestXXX 的函数名出现(XXX 为以非小写字母开头),基准函数必须以 BenchmarkXXX 的函数名出现,示例函数必须以 ExampleXXX 的形式,模糊函数必须以 FuzzXXX的函数名出现。

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
// 单元测试
func TestAdd(t *testing.T) {
sum := Add(2, 3)
if sum != 5 {
t.Errorf("expected 5, got %d", sum)
}
}

// 基准测试
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(2, 3)
}
}

// 示例测试
func ExampleAdd() {
sum := Add(2, 3)
fmt.Println(sum)
// Output: 5
}

// 模糊测试
func FuzzAdd(f *testing.F) {
f.Add(2, 3) // 提供种子输入
f.Fuzz(func(t *testing.T, a int, b int) {
Add(a, b)
})
}

5、单元测试日志级别

​ 每个测试用例可能并发执行,使用 testing.T 提供的日志输出可以保证日志跟随这个测试上下文一起打印输出。testing.T 提供了几种日志输出方法,详见下表所示。

方 法 备 注
Log 打印日志,同时结束测试
Logf 格式化打印日志,同时结束测试
Error 打印错误日志,同时结束测试
Errorf 格式化打印错误日志,同时结束测试
Fatal 打印致命日志,同时结束测试
Fatalf 格式化打印致命日志,同时结束测试

6、单元测试

  • 测试用例名称一般命名为 Test 加上待测试的方法名。
  • 测试用的参数有且只有一个,在这里是 t *testing.T

7、基准测试

image-20250329190851000

  1. -bench=.表示运行calc_test.go文件里的所有基准测试,和单元测试中的-run类似。
  2. 第6行解释
    1. BenchmarkAdd-8 :显示基准测试名称
    2. 1000000000 :表示测试的次数,也就是 testing.B 结构中提供给程序使用的 N。
    3. “0.2605 ns/op”表示每一个操作耗费多少时间(纳秒)。
  3. -benchtime参数可以自定义测试时间

基准测试报告每一列值对应的含义如下:

1
2
3
4
5
6
7
type BenchmarkResult struct {
N int // 迭代次数
T time.Duration // 基准测试花费的时间
Bytes int64 // 一次迭代处理的字节数
MemAllocs uint64 // 总的分配内存的次数
MemBytes uint64 // 总的分配内存的字节数
}

​ 如果基准测试需要在并行设置中测试性能,则可以使用 RunParallel 辅助函数 ; 这样的基准测试一般与 go test -cpu 标志一起使用:通过 RunParallel 方法能够并行地执行给定的基准测试。RunParallel会创建出多个 goroutine,并将 b.N 分配给这些 goroutine 执行,其中 goroutine 数量的默认值为 GOMAXPROCS。用户如果想要增加非 CPU 受限(non-CPU-bound)基准测试的并行性,那么可以在 RunParallel 之前调用 SetParallelism(如 SetParallelism(2),则 goroutine 数量为 2*GOMAXPROCS)。RunParallel 通常会与 -cpu 标志一同使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
func BenchmarkTemplateParallel(b *testing.B) {
tpl := template.Must(template.New("test").Parse("Hello, {{.}}!"))
b.RunParallel(func(pb *testing.PB) {
//每个 goroutine 有属于自己的 bytes.Buffer.
var buf bytes.Buffer
for pb.Next() {
// 循环体在所有 goroutine 中总共执行 b.N 次
buf.Reset()
err := tpl.Execute(&buf, "World")
if err != nil {
return
}
}
})
}

image-20250329191553586

通过-benchtime参数可以自定义测试时间

image-20250329191630986

分析基准测试数据:

go test命令之后加上这个就可以配合pprof进行分析:

  • cpu 使用分析:-cpuprofile=cpu.pprof
  • 内存使用分析:-benchmem -memprofile=mem.pprof
  • block分析:-blockprofile=block.pprof

基准测试原理:

​ 基准测试框架对一个测试用例的默认测试时间是 1 秒。开始测试时,当以 Benchmark 开头的基准测试用例函数返回时还不到 1 秒,那么 testing.B 中的 N 值将按 1、2、5、10、20、50……递增,同时以递增后的值重新调用基准测试用例函数。

8、示例测试

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
package calc

import "fmt"

// ExampleAdd 展示了 Add 函数的基本用法
func ExampleAdd() {
sum := Add(2, 3)
fmt.Println(sum)

// 展示负数相加
sum = Add(-1, -4)
fmt.Println(sum)

// 展示与零相加
sum = Add(10, 0)
fmt.Println(sum)

// Output:
// 5
// -5
// 10
}

// ExampleAdd_multiple 展示了多个数字依次相加的场景
func ExampleAdd_multiple() {
numbers := []int{1, 2, 3, 4}
sum := 0
for _, n := range numbers {
sum = Add(sum, n)
fmt.Printf("Running sum: %d\n", sum)
}

// Output:
// Running sum: 1
// Running sum: 3
// Running sum: 6
// Running sum: 10
}

image-20250329190125343

9、模糊测试

1
2
3
4
5
6
7
8
9
10
package calc

// Reverse 返回字符串的反转形式
func Reverse(s string) string {
runes := []rune(s)
for i, j := 0, len(runes)-1; i < j; i, j = i+1, j-1 {
runes[i], runes[j] = runes[j], runes[i]
}
return string(runes)
}
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
package calc

import (
"testing"
"unicode/utf8"
)

// FuzzReverse 是一个模糊测试函数
func FuzzReverse(f *testing.F) {
// 提供种子输入
testcases := []string{"Hello, world", "!12345", "", "中文测试"}
for _, tc := range testcases {
f.Add(tc) // 添加种子语料库
}

// 模糊测试函数
f.Fuzz(func(t *testing.T, orig string) {
// 测试反转后再反转是否得到原字符串
rev := Reverse(orig)
doubleRev := Reverse(rev)
if orig != doubleRev {
t.Errorf("Before: %q, after double reverse: %q", orig, doubleRev)
}

// 测试反转后的字符串长度是否保持不变
if len(orig) != len(rev) {
t.Errorf("length changed: orig=%d, rev=%d", len(orig), len(rev))
}

// 测试UTF-8有效性
if !utf8.ValidString(rev) {
t.Errorf("Reverse produced invalid UTF-8 string %q", rev)
}
})
}

image-20250329190459715

  • 标题: golang单元测试
  • 作者: 纸鸢
  • 创建于 : 2024-10-24 16:06:03
  • 更新于 : 2025-03-29 20:00:03
  • 链接: https://www.youandgentleness.cn/2024/10/24/golang单元测试/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论