强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Go 语言完全指南 / 21 - 测试:testing 包、表驱动测试、Mock、TestMain

21 - 测试

21.1 基本测试

// math.go
package math

func Add(a, b int) int {
    return a + b
}

func Divide(a, b float64) (float64, error) {
    if b == 0 {
        return 0, fmt.Errorf("division by zero")
    }
    return a / b, nil
}
// math_test.go
package math

import "testing"

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

func TestDivide(t *testing.T) {
    result, err := Divide(10, 2)
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    if result != 5.0 {
        t.Errorf("Divide(10, 2) = %f, want 5.0", result)
    }
}

func TestDivideByZero(t *testing.T) {
    _, err := Divide(10, 0)
    if err == nil {
        t.Error("expected error for division by zero")
    }
}
函数作用
t.Error()报告错误,继续执行
t.Errorf()格式化报告错误
t.Fatal()报告错误,立即停止当前测试
t.Fatalf()格式化报告错误并停止
t.Skip()跳过当前测试
t.Log()记录日志(仅 -v 时显示)
t.Run()运行子测试

21.2 表驱动测试(Table-Driven Tests)

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"正数相加", 2, 3, 5},
        {"负数相加", -2, -3, -5},
        {"零值", 0, 0, 0},
        {"正负相加", 5, -3, 2},
        {"大数", 1000000, 2000000, 3000000},
    }

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

21.3 测试辅助函数

func TestWithHelper(t *testing.T) {
    t.Helper() // 标记为辅助函数,错误位置报告到调用者

    assertEqual := func(t *testing.T, got, want int) {
        t.Helper()
        if got != want {
            t.Errorf("got %d, want %d", got, want)
        }
    }

    assertEqual(t, Add(2, 3), 5)
    assertEqual(t, Add(0, 0), 0)
}

func TestWithError(t *testing.T) {
    assertError := func(t *testing.T, err error) {
        t.Helper()
        if err == nil {
            t.Error("expected error, got nil")
        }
    }

    assertNoError := func(t *testing.T, err error) {
        t.Helper()
        if err != nil {
            t.Fatalf("unexpected error: %v", err)
        }
    }

    _, err := Divide(10, 0)
    assertError(t, err)

    _, err = Divide(10, 2)
    assertNoError(t, err)
}

21.4 TestMain

func TestMain(m *testing.M) {
    // 测试前设置
    setup()
    
    // 运行所有测试
    code := m.Run()
    
    // 测试后清理
    teardown()
    
    os.Exit(code)
}

func setup() {
    // 初始化测试数据库、启动服务等
}

func teardown() {
    // 清理资源
}

21.5 Mock 和接口测试

// 定义接口
type UserRepository interface {
    FindByID(id int) (*User, error)
    Save(user *User) error
}

// 真实实现
type PostgresUserRepo struct {
    db *sql.DB
}

func (r *PostgresUserRepo) FindByID(id int) (*User, error) {
    // SQL 查询...
}

// Mock 实现
type MockUserRepo struct {
    users map[int]*User
    err   error
}

func (m *MockUserRepo) FindByID(id int) (*User, error) {
    if m.err != nil {
        return nil, m.err
    }
    return m.users[id], nil
}

func (m *MockUserRepo) Save(user *User) error {
    if m.err != nil {
        return m.err
    }
    m.users[user.ID] = user
    return nil
}

// 测试服务层
func TestUserService_GetUser(t *testing.T) {
    mock := &MockUserRepo{
        users: map[int]*User{
            1: {ID: 1, Name: "Alice"},
        },
    }
    service := NewUserService(mock)

    user, err := service.GetUser(1)
    if err != nil {
        t.Fatalf("unexpected error: %v", err)
    }
    if user.Name != "Alice" {
        t.Errorf("got name %s, want Alice", user.Name)
    }
}

21.6 并发测试

func TestConcurrent(t *testing.T) {
    counter := 0
    var mu sync.Mutex
    var wg sync.WaitGroup

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go func() {
            defer wg.Done()
            mu.Lock()
            counter++
            mu.Unlock()
        }()
    }
    wg.Wait()

    if counter != 1000 {
        t.Errorf("counter = %d, want 1000", counter)
    }
}

// 使用 -race 检测竞态
// go test -race ./...

21.7 测试覆盖率

# 生成覆盖率报告
go test -cover ./...

# 生成详细报告
go test -coverprofile=coverage.out ./...

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

# 生成 HTML 报告
go tool cover -html=coverage.out -o coverage.html

🏢 业务场景

  1. 单元测试:测试函数、方法的正确性
  2. 集成测试:TestMain 初始化数据库,测试完整流程
  3. 接口测试:Mock 外部依赖,测试业务逻辑
  4. 回归测试:表驱动测试覆盖边界条件

📖 扩展阅读