<返回目录     Powered by claud/xia兄

第12课: 测试与基准测试

Go测试基础

Go内置了强大的测试框架,通过testing包提供单元测试、基准测试和示例测试功能。

测试文件命名规则

单元测试

基本测试

// math.go
package math

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

func Multiply(a, b int) int {
    return a * b
}

// math_test.go
package math

import "testing"

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    expected := 5

    if result != expected {
        t.Errorf("Add(2, 3) = %d; want %d", result, expected)
    }
}

func TestMultiply(t *testing.T) {
    result := Multiply(3, 4)
    expected := 12

    if result != expected {
        t.Errorf("Multiply(3, 4) = %d; want %d", result, expected)
    }
}

运行测试

# 运行当前目录的所有测试
go test

# 运行所有包的测试
go test ./...

# 运行特定测试
go test -run TestAdd

# 显示详细输出
go test -v

# 显示测试覆盖率
go test -cover

# 生成覆盖率报告
go test -coverprofile=coverage.out
go tool cover -html=coverage.out

表格驱动测试

表格驱动测试是Go中最常用的测试模式,通过测试用例表来组织测试数据。

package math

import "testing"

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"positive numbers", 2, 3, 5},
        {"negative numbers", -2, -3, -5},
        {"mixed numbers", -2, 3, 1},
        {"zero", 0, 0, 0},
    }

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

测试辅助函数

t.Helper()

func assertEqual(t *testing.T, got, want int) {
    t.Helper() // 标记为辅助函数,错误报告会指向调用者
    if got != want {
        t.Errorf("got %d, want %d", got, want)
    }
}

func TestAdd(t *testing.T) {
    result := Add(2, 3)
    assertEqual(t, result, 5)
}

Setup和Teardown

func TestMain(m *testing.M) {
    // Setup
    fmt.Println("测试前的准备工作")

    // 运行测试
    code := m.Run()

    // Teardown
    fmt.Println("测试后的清理工作")

    os.Exit(code)
}

子测试

func TestMath(t *testing.T) {
    t.Run("Add", func(t *testing.T) {
        if Add(2, 3) != 5 {
            t.Error("Add failed")
        }
    })

    t.Run("Multiply", func(t *testing.T) {
        if Multiply(2, 3) != 6 {
            t.Error("Multiply failed")
        }
    })
}

// 运行特定子测试
// go test -run TestMath/Add

基准测试

基准测试用于测量代码的性能。

基本基准测试

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

func BenchmarkMultiply(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Multiply(2, 3)
    }
}

// 运行基准测试
// go test -bench=.
// go test -bench=BenchmarkAdd

基准测试最佳实践

func BenchmarkStringConcat(b *testing.B) {
    b.ResetTimer() // 重置计时器,排除准备工作的时间

    for i := 0; i < b.N; i++ {
        _ = "hello" + "world"
    }
}

func BenchmarkStringBuilder(b *testing.B) {
    for i := 0; i < b.N; i++ {
        var sb strings.Builder
        sb.WriteString("hello")
        sb.WriteString("world")
        _ = sb.String()
    }
}

// 运行基准测试并显示内存分配
// go test -bench=. -benchmem

并行基准测试

func BenchmarkParallel(b *testing.B) {
    b.RunParallel(func(pb *testing.PB) {
        for pb.Next() {
            // 测试代码
            Add(2, 3)
        }
    })
}

测试覆盖率

生成覆盖率报告

# 生成覆盖率文件
go test -coverprofile=coverage.out

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

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

# 按包显示覆盖率
go test -cover ./...

覆盖率示例

// calculator.go
package calculator

func Divide(a, b int) (int, error) {
    if b == 0 {
        return 0, errors.New("division by zero")
    }
    return a / b, nil
}

// calculator_test.go
func TestDivide(t *testing.T) {
    tests := []struct {
        name      string
        a, b      int
        want      int
        wantError bool
    }{
        {"normal", 10, 2, 5, false},
        {"divide by zero", 10, 0, 0, true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := Divide(tt.a, tt.b)
            if tt.wantError {
                if err == nil {
                    t.Error("expected error, got nil")
                }
            } else {
                if err != nil {
                    t.Errorf("unexpected error: %v", err)
                }
                if got != tt.want {
                    t.Errorf("got %d, want %d", got, tt.want)
                }
            }
        })
    }
}

Mock和接口测试

使用接口进行测试

// user.go
type UserStore interface {
    GetUser(id int) (*User, error)
}

type UserService struct {
    store UserStore
}

func (s *UserService) GetUserName(id int) (string, error) {
    user, err := s.store.GetUser(id)
    if err != nil {
        return "", err
    }
    return user.Name, nil
}

// user_test.go
type MockUserStore struct {
    users map[int]*User
}

func (m *MockUserStore) GetUser(id int) (*User, error) {
    user, ok := m.users[id]
    if !ok {
        return nil, errors.New("user not found")
    }
    return user, nil
}

func TestGetUserName(t *testing.T) {
    mock := &MockUserStore{
        users: map[int]*User{
            1: {ID: 1, Name: "Alice"},
        },
    }

    service := &UserService{store: mock}
    name, err := service.GetUserName(1)

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

示例测试

示例测试既是测试也是文档。

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

func ExampleMultiply() {
    result := Multiply(3, 4)
    fmt.Println(result)
    // Output: 12
}

测试最佳实践

常见错误:

实践练习

  1. 为字符串处理函数编写单元测试
  2. 使用表格驱动测试测试多个场景
  3. 编写基准测试比较不同实现的性能
  4. 生成测试覆盖率报告并提高覆盖率
  5. 使用Mock测试依赖外部服务的代码
  6. 编写示例测试作为文档
  7. 实现TestMain进行全局Setup/Teardown
  8. 使用子测试组织复杂的测试场景