第 13 章:测试与性能优化
jerry北京市2026年5月9日Go 23 次阅读 约 14 分钟

掌握 Go 的测试框架、表驱动测试、基准测试、pprof 性能分析和常见优化技巧。
13.1 单元测试
Go 内置测试框架,测试文件以 _test.go 结尾:
// calc.go
package calc
func Add(a, b int) int {
return a + b
}
func Divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("除数不能为零")
}
return a / b, nil
}
// calc_test.go
package calc
import "testing"
func TestAdd(t *testing.T) {
result := Add(2, 3)
if result != 5 {
t.Errorf("Add(2, 3) = %d, 期望 5", result)
}
}
运行测试:
go test ./... # 运行所有测试
go test -v ./... # 详细输出
go test -run TestAdd ./... # 运行匹配的测试
go test -count=1 ./... # 禁用缓存
go test -cover ./... # 显示覆盖率
13.2 表驱动测试(推荐模式)
func TestDivide(t *testing.T) {
tests := []struct {
name string
a, b float64
want float64
wantErr bool
}{
{"正常除法", 10, 2, 5, false},
{"除以零", 10, 0, 0, true},
{"负数除法", -10, 2, -5, false},
{"小数除法", 1, 3, 0.3333333333333333, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := Divide(tt.a, tt.b)
if (err != nil) != tt.wantErr {
t.Errorf("Divide() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("Divide() = %v, want %v", got, tt.want)
}
})
}
}
13.3 测试辅助函数
// t.Helper() 标记辅助函数,错误报告时显示调用者的行号
func assertEqual(t *testing.T, got, want int) {
t.Helper()
if got != want {
t.Errorf("got %d, want %d", got, want)
}
}
// t.Cleanup() 注册清理函数
func TestWithCleanup(t *testing.T) {
tmpFile := createTempFile(t)
t.Cleanup(func() {
os.Remove(tmpFile)
})
// 测试逻辑...
}
// t.Parallel() 并行执行测试
func TestParallel(t *testing.T) {
t.Parallel()
// 这个测试会和其他 Parallel 测试并行执行
}
13.4 基准测试
func BenchmarkAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
Add(1, 2)
}
}
// 带初始化的基准测试
func BenchmarkDivide(b *testing.B) {
b.ResetTimer() // 重置计时器,排除初始化时间
for i := 0; i < b.N; i++ {
Divide(10, 3)
}
}
// 内存分配统计
func BenchmarkSliceAppend(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
s := make([]int, 0)
for j := 0; j < 1000; j++ {
s = append(s, j)
}
}
}
运行基准测试:
go test -bench=. -benchmem ./...
# 输出示例:
# BenchmarkAdd-8 1000000000 0.25 ns/op 0 B/op 0 allocs/op
# BenchmarkSliceAppend-8 50000 30000 ns/op 40960 B/op 11 allocs/op
对比优化效果
// 优化前:不预分配
func BenchmarkNoPrealloc(b *testing.B) {
for i := 0; i < b.N; i++ {
s := make([]int, 0)
for j := 0; j < 1000; j++ {
s = append(s, j)
}
}
}
// 优化后:预分配容量
func BenchmarkPrealloc(b *testing.B) {
for i := 0; i < b.N; i++ {
s := make([]int, 0, 1000)
for j := 0; j < 1000; j++ {
s = append(s, j)
}
}
}
13.5 pprof 性能分析
CPU 分析
import "runtime/pprof"
f, _ := os.Create("cpu.prof")
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
// 你的代码...
内存分析
f, _ := os.Create("mem.prof")
pprof.WriteHeapProfile(f)
f.Close()
HTTP pprof(线上服务推荐)
import _ "net/http/pprof"
go func() {
http.ListenAndServe(":6060", nil)
}()
访问 http://localhost:6060/debug/pprof/ 查看各项指标。
分析 profile
# 交互式分析
go tool pprof cpu.prof
# 常用命令
# top 查看耗时最多的函数
# list func 查看函数的逐行耗时
# web 生成调用图(需要 graphviz)
# 直接生成火焰图
go tool pprof -http=:8080 cpu.prof
13.6 常见性能优化技巧
1. 预分配切片容量
// 差
s := make([]int, 0)
for i := 0; i < n; i++ {
s = append(s, i)
}
// 好
s := make([]int, 0, n)
for i := 0; i < n; i++ {
s = append(s, i)
}
2. 字符串拼接用 strings.Builder
// 差:每次拼接都分配新内存
s := ""
for i := 0; i < 1000; i++ {
s += "hello"
}
// 好:使用 Builder
var b strings.Builder
for i := 0; i < 1000; i++ {
b.WriteString("hello")
}
s := b.String()
3. 使用 sync.Pool 复用对象
var bufPool = sync.Pool{
New: func() interface{} {
return new(bytes.Buffer)
},
}
func process() {
buf := bufPool.Get().(*bytes.Buffer)
defer func() {
buf.Reset()
bufPool.Put(buf)
}()
// 使用 buf...
}
4. 避免不必要的内存逃逸
// 逃逸到堆
func createUser() *User {
u := User{Name: "Alice"}
return &u // u 逃逸到堆
}
// 不逃逸(如果调用者不需要指针)
func createUser() User {
return User{Name: "Alice"} // 栈上分配
}
5. 减少接口转换
// 差:频繁的 interface{} 转换
func process(v interface{}) {
s := v.(string) // 类型断言有开销
}
// 好:使用具体类型或泛型
func process(s string) {
// 直接使用
}
13.7 面试要点
-
Go 测试文件的命名规则?
- 文件名以
_test.go结尾 - 测试函数以
Test开头,参数为*testing.T - 基准测试以
Benchmark开头,参数为*testing.B
- 文件名以
-
表驱动测试的优势?
- 易于添加新用例
- 代码结构清晰
- 每个用例有名字,失败时容易定位
-
如何做性能分析?
go test -bench做基准测试pprof做 CPU/内存分析-race检测数据竞争
-
常见的性能优化手段?
- 预分配切片容量
- strings.Builder 拼接字符串
- sync.Pool 复用对象
- 减少内存逃逸
- 减少接口转换
练习
- 为一个排序函数编写表驱动测试
- 编写基准测试,对比字符串拼接的不同方式
- 使用 pprof 分析一段代码的 CPU 和内存使用
- 优化一段有性能问题的代码,用基准测试验证效果
评论
登录 后发表评论
暂无评论