首页 / Go 语言入门教程 / 测试与工程化

Go 语言入门教程

测试与工程化

本教程共 80 篇 · 第 79 篇 · 更新于 2026-07-27 · 约 9 分钟阅读

GoGo 入门教程testing表驱动测试Benchmark模糊测试go vetgo fix

79. 测试与工程化

本节目标:学会写单元测试和表驱动测试,跑 Benchmark 性能测试,了解模糊测试,掌握 go vet 和 go fix 工具。

第一个测试

Go 测试文件以 _test.go 结尾,和被测代码同目录。函数名以 Test 开头,参数是 *testing.T

// math.go
package math

func Add(a, b int) int {
	return a + b
}
// math_test.go
package math

import "testing"

func TestAdd(t *testing.T) {
	got := Add(1, 2)
	want := 3
	if got != want {
		t.Errorf("Add(1, 2) = %d, want %d", got, want)
	}
}

运行测试:

go test            # 当前包
go test ./...      # 所有包
go test -v         # 显示详细输出
go test -run TestAdd  # 只跑指定测试

测试失败的方式

// 报告失败,继续执行
t.Errorf("got %d, want %d", got, want)

// 报告失败,立即停止当前测试
t.Fatalf("got %d, want %d", got, want)

// 跳过测试
t.Skip("跳过:需要数据库环境")

表驱动测试

Go 最经典的测试模式—把用例放在表里循环跑:

func TestAdd(t *testing.T) {
	tests := []struct {
		name string
		a, b int
		want int
	}{
		{"正数", 1, 2, 3},
		{"负数", -1, -2, -3},
		{"零", 0, 0, 0},
		{"混合", -1, 1, 0},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got := Add(tt.a, tt.b)
			if got != tt.want {
				t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
			}
		})
	}
}

t.Run 创建子测试,每个用例有名字,失败时能看清是哪个挂了:

go test -v
# --- PASS: TestAdd/正数 (0.00s)
# --- PASS: TestAdd/负数 (0.00s)
Tip

表驱动测试是 Go 社区的最佳实践。加新用例只要往表里加一行,不用写新函数。

并行测试

测试互不依赖时可以并行跑,加速:

func TestAdd(t *testing.T) {
	t.Parallel() // 标记可并行

	tests := []struct{ ... }{...}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel() // 子测试也并行
			got := Add(tt.a, tt.b)
			if got != tt.want {
				t.Errorf("got %d, want %d", got, tt.want)
			}
		})
	}
}

Benchmark 基准测试

Benchmark 函数以 Benchmark 开头,参数是 *testing.B

func BenchmarkAdd(b *testing.B) {
	for i := 0; i < b.N; i++ {
		Add(1, 2)
	}
}

运行:

go test -bench=.              # 跑所有 benchmark
go test -bench=BenchmarkAdd   # 指定
go test -bench=. -benchmem    # 带内存分配统计

输出:

BenchmarkAdd-8    1000000000   0.3 ns/op   0 B/op   0 allocs/op
  • -8:GOMAXPROCS
  • 1000000000:跑了 10 亿次
  • 0.3 ns/op:每次操作 0.3 纳秒
  • 0 B/op:每次分配 0 字节
  • 0 allocs/op:每次 0 次分配

B.Loop(Go 1.24+)

Go 1.24 引入 b.Loop(),比手动 for i < b.N 更准确:

func BenchmarkAdd(b *testing.B) {
	for b.Loop() {
		Add(1, 2)
	}
}
Note

b.Loop 会自动处理编译器优化问题(防止循环被优化掉)。Go 1.26 进一步修复了 b.Loop 阻止内联的问题。新代码推荐用 b.Loop 代替 for i < b.N

Example 示例测试

Example 开头的函数既是文档又是测试:

func ExampleAdd() {
	fmt.Println(Add(1, 2))
	// Output: 3
}

// Output: 后面写期望输出,不匹配就测试失败。这种测试会出现在 go doc 里当文档用。

模糊测试(Go 1.18+)

模糊测试(Fuzzing)自动生成随机输入,帮你找边界 bug。函数以 Fuzz 开头:

func FuzzAdd(f *testing.F) {
	// 种子用例
	f.Add(1, 2)
	f.Add(-1, 1)

	f.Fuzz(func(t *testing.T, a, b int) {
		result := Add(a, b)
		// 逆向验证
		if result-a != b {
			t.Errorf("Add(%d, %d) = %d, 不满足 a+b-b=a", a, b, result)
		}
	})
}

运行:

go test -fuzz=FuzzAdd           # 开始模糊测试
go test -fuzz=FuzzAdd -fuzztime=30s  # 跑 30 秒

模糊测试会持续生成随机输入,发现崩溃或断言失败就把输入存到 testdata/fuzz/ 里,之后普通 go test 会自动复现。

Tip

模糊测试适合解析器、字符串处理、加解密这类「输入空间大」的代码。它能找到你没想到的边界情况。

测试辅助函数

t.Helper() 标记辅助函数,失败时报错指向调用处而非辅助函数内部:

func assertEqual(t *testing.T, got, want int) {
	t.Helper() // 标记为辅助
	if got != want {
		t.Errorf("got %d, want %d", got, want)
	}
}

func TestSomething(t *testing.T) {
	assertEqual(t, Add(1, 2), 3) // 失败时指向这行,不是 assertEqual 内部
}

go vet 静态检查

go vet 检查常见错误:

go vet ./...

能发现:锁复制、printf 格式不对、不可达代码、结构体标签错误等。

go fix 现代化器(Go 1.26 重写)

§04 介绍过 go fix 的详细用法。这里从工程化角度再强调一下:Go 1.26 全面重写了 go fix,变成「现代化器」,一键把旧代码升级为新写法:

go fix ./...

它会做几十种自动修复,比如:

  • slices.Sort 替代 sort.Slice
  • min/max 内置函数替代手写比较
  • 用新标准库 API 替代旧写法
  • 执行 //go:fix inline 指令做 API 迁移
Note

go fix 基于 go vet 同款分析框架,只做不改变行为的升级。跑完建议 review 一下再提交。旧的 fixer 已全部移除。

gofmt 格式化

gofmt -w .     # 格式化当前目录所有文件
go fmt ./...   # 等价,格式化所有包

Go 社区统一用 gofmt 格式,省去争论代码风格的时间。

常用测试命令汇总

go test ./...              # 跑所有测试
go test -v ./...           # 详细输出
go test -race ./...        # 带竞态检测
go test -cover ./...       # 覆盖率
go test -coverprofile=c.out # 生成覆盖率报告
go tool cover -html=c.out  # 看覆盖率网页
go test -bench=.           # 跑 benchmark
go test -fuzz=FuzzXxx      # 模糊测试
go vet ./...               # 静态检查
go fix ./...               # 现代化升级

小结

  • 测试文件 _test.go,函数 TestXxx(t *testing.T)
  • 表驱动测试是 Go 最佳实践,用 t.Run 做子测试
  • Benchmark 用 for b.Loop()(1.24+),-benchmem 看内存
  • 模糊测试(1.18+)自动生成输入找边界 bug
  • go vet 静态检查,go fix(1.26 重写)自动现代化代码
  • gofmt 统一代码风格
  • CI 里建议跑 go test -race -cover ./...go vet

下一节是最后一章,讲运行时性能和学习路线。