testing 测试
问题
Go 的测试框架怎么用?什么是表驱动测试?如何写 Benchmark?
答案
基本结构
Go 测试文件以 _test.go 结尾,与被测文件放在同一包下:
mypackage/
├── calc.go // 源码
└── calc_test.go // 测试
package mypackage
import "testing"
func TestAdd(t *testing.T) {
got := Add(1, 2)
if got != 3 {
t.Errorf("Add(1, 2) = %d, want 3", got)
}
}
go test ./... # 运行所有测试
go test -v ./... # 详细输出
go test -run TestAdd # 运行指定测试
go test -race ./... # 开启竞争检测
go test -count=1 ./... # 禁用缓存
表驱动测试(推荐模式)
func TestAdd(t *testing.T) {
tests := []struct {
name string
a, b int
want int
}{
{"正数", 1, 2, 3},
{"负数", -1, -2, -3},
{"零", 0, 0, 0},
{"混合", -1, 2, 1},
}
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可以单独运行某个子测试- 测试报告清晰
Benchmark 性能测试
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(1, 2) // b.N 由 testing 框架自动调整
}
}
func BenchmarkConcat(b *testing.B) {
b.ReportAllocs() // 报告内存分配
for i := 0; i < b.N; i++ {
concat("hello", "world")
}
}
go test -bench=. # 运行所有 Benchmark
go test -bench=BenchmarkAdd # 运行指定 Benchmark
go test -bench=. -benchmem # 显示内存分配
go test -bench=. -benchtime=5s # 运行 5 秒
go test -bench=. -count=3 # 运行 3 次(取平均)
输出解读:
BenchmarkAdd-8 1000000000 0.29 ns/op 0 B/op 0 allocs/op
↑ ↑ ↑ ↑
运行次数 每次耗时 每次字节 每次分配次数
测试辅助
// t.Helper():标记辅助函数,错误报告定位到调用方
func assertEqual(t *testing.T, got, want int) {
t.Helper()
if got != want {
t.Errorf("got %d, want %d", got, want)
}
}
// t.Parallel():并行执行
func TestA(t *testing.T) {
t.Parallel() // 与其他 Parallel 测试并发执行
// ...
}
// t.Cleanup():测试结束后清理
func TestDB(t *testing.T) {
db := setupDB()
t.Cleanup(func() { db.Close() })
// ...
}
// t.Skip():跳过测试
func TestIntegration(t *testing.T) {
if testing.Short() { // go test -short
t.Skip("skipping in short mode")
}
}
TestMain——全局设置
func TestMain(m *testing.M) {
// 全局初始化(所有测试前执行一次)
setup()
code := m.Run() // 执行所有测试
// 全局清理
teardown()
os.Exit(code)
}
覆盖率
go test -cover ./... # 显示覆盖率
go test -coverprofile=coverage.out ./... # 生成覆盖率文件
go tool cover -html=coverage.out # 浏览器查看
go tool cover -func=coverage.out # 按函数显示
Mock 和 Stub
Go 没有内置 Mock 框架,常用方式:
// 1. 接口 + 手写 Mock
type UserRepository interface {
GetByID(id int) (*User, error)
}
type mockUserRepo struct {
user *User
err error
}
func (m *mockUserRepo) GetByID(id int) (*User, error) {
return m.user, m.err
}
// 2. 使用 testify/mock 或 gomock
常见面试问题
Q1: Go 为什么没有 assert?
答案:
Go 团队认为 assert 会导致"懒惰的错误处理"——测试应该在失败时给出清晰的错误信息,而不是简单的 assert(a == b)。t.Errorf 强制你写有意义的错误消息。
社区中 testify/assert 库被广泛使用,但标准库不提供。
Q2: Benchmark 中如何避免编译器优化消除被测代码?
答案:
var result int // 包级变量,防止优化
func BenchmarkAdd(b *testing.B) {
var r int
for i := 0; i < b.N; i++ {
r = Add(1, 2)
}
result = r // 赋值给包级变量,阻止编译器消除 Add 调用
}
Q3: 表驱动测试和 t.Parallel() 能一起用吗?
答案:
可以,但需要注意循环变量捕获(Go 1.22 前):
for _, tt := range tests {
tt := tt // Go 1.22 前需要这行
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
// 使用 tt...
})
}