ToolkitX
知识库工具箱

测试与部署

单元测试, benchmark, 交叉编译

20min·进阶

01. 测试文件和基本结构

Go 有内建的测试框架,不需要接第三方库。测试文件以 _test.go 结尾,测试函数以 Test 开头,放在同包或同目录。用 go test 命令运行,-v 显示详细信息,-run 按正则过滤测试。每个测试函数接收 *testing.T 参数,用来报告失败。
go
// 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) {
    result := Add(2, 3)
    if result != 5 {
        t.Errorf("Add(2,3) = %d; want 5", result)
    }
}

// 运行: go test -v
测试文件放同包(package xxx)可以访问未导出的函数,做白盒测试。

02. 表驱动测试

表驱动测试是 Go 的惯用测试模式。把测试用例定义成一个结构体切片,每个用例有输入和期望输出。然后用循环遍历执行,失败时用 t.Run 给每个用例起个名,方便定位。这种方式添加新用例非常简单,就是在切片里加一行。
go
func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"正数相加", 2, 3, 5},
        {"零加", 0, 5, 5},
        {"负数相加", -1, -2, -3},
    }
    
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := Add(tt.a, tt.b)
            if result != tt.expected {
                t.Errorf("got %d, want %d", result, tt.expected)
            }
        })
    }
}
t.Run 让每个子用例可以独立运行:go test -run TestAdd/正数相加。

03. Mock 和 test helper

Go 的 mock 不用框架,靠接口实现。定义一个接口,生产代码用真实实现,测试用 mock 实现。辅助函数 t.Helper() 标记后,失败时错误信息指向调用方而不是辅助函数本身,方便定位。临时文件用 t.TempDir(),测试结束自动清理。
go
// 定义接口
type UserService interface {
    GetUser(id int) (*User, error)
}

// Mock 实现
type mockUserService struct {
    users map[int]*User
}

func (m *mockUserService) GetUser(id int) (*User, error) {
    u, ok := m.users[id]
    if !ok { return nil, ErrNotFound }
    return u, nil
}

// helper 示例
func assertEqual(t *testing.T, got, want interface{}) {
    t.Helper() // 关键!
    if got != want {
        t.Errorf("got %v, want %v", got, want)
    }
}
Go 社区的 mock 哲学是用接口 + 手写 mock 而不是框架生成,虽然 testify/mock 也常用。

04. 基准测试和 Fuzzing

基准测试函数以 Benchmark 开头,参数是 *testing.B。b.N 是测试框架自动调整的运行次数,运行够多次才能得到稳定的耗时测量。go test -bench . 运行基准测试。Fuzzing(Go 1.18+)用 FuzzXxx 函数,随机扔数据到被测函数看能不全崩溃。
go
// 基准测试
func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(100, 200)
    }
}

// 运行: go test -bench=.

// Fuzz 测试
func FuzzAdd(f *testing.F) {
    f.Add(1, 2) // seed corpus
    f.Fuzz(func(t *testing.T, a, b int) {
        result := Add(a, b)
        if result < a { // 简单的不变式验证
            t.Errorf("结果不合理")
        }
    })
}

// 运行: go test -fuzz=FuzzAdd -fuzztime=10s
Fuzz 测试可以找到人类想不到的边界情况,非常适合测试解析器、编码器等。

05. 测试覆盖率

go test -cover 显示覆盖率百分比,go test -coverprofile=coverage.out 生成覆盖率文件。然后用 go tool cover -html=coverage.out 在浏览器里打开,绿色是覆盖到,红色是没覆盖到。覆盖率能帮你发现哪些逻辑分支没测到,但不是 100% 覆盖率就等于没 bug。
bash
# 运行测试并生成覆盖率
go test -coverprofile=coverage.out ./...

# 浏览器查看覆盖率详情
go tool cover -html=coverage.out

# 终端查看覆盖率
go tool cover -func=coverage.out

# CI 里检查覆盖率不低于某个值
go test -cover ./... | grep coverage
不要为了 100% 覆盖率写没意义的测试。关键业务逻辑和边界条件才是重点。

知识测验

1/5正确 0

Go 测试文件命名约定是什么?