Skip to content
On this page

测试

Go 内置了强大的测试框架,本章将详细介绍 Go 的测试方法。

基本测试

测试函数

go
package math

import "testing"

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

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

运行测试

bash
# 运行所有测试
go test

# 运行指定包的测试
go test ./math

# 详细输出
go test -v

# 运行指定测试函数
go test -run TestAdd

表格驱动测试

go
func TestAddTable(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"正数", 2, 3, 5},
        {"负数", -2, -3, -5},
        {"", 0, 0, 0},
        {"混合", -2, 3, 1},
    }
    
    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)
            }
        })
    }
}

子测试

go
func TestSubtests(t *testing.T) {
    t.Run("Group1", func(t *testing.T) {
        t.Run("Test1", func(t *testing.T) {
            if Add(1, 2) != 3 {
                t.Error("Test1 failed")
            }
        })
        t.Run("Test2", func(t *testing.T) {
            if Add(2, 3) != 5 {
                t.Error("Test2 failed")
            }
        })
    })
}

基准测试

基本基准测试

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

运行基准测试

bash
# 运行基准测试
go test -bench=.

# 运行指定基准测试
go test -bench=BenchmarkAdd

# 指定运行时间
go test -bench=. -benchtime=5s

# 内存分析
go test -bench=. -benchmem

基准测试示例

go
func BenchmarkStringConcat(b *testing.B) {
    b.Run("Plus", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            s := "hello" + "world"
            _ = s
        }
    })
    
    b.Run("Sprintf", func(b *testing.B) {
        for i := 0; i < b.N; i++ {
            s := fmt.Sprintf("%s%s", "hello", "world")
            _ = s
        }
    })
}

示例测试

基本示例

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

运行示例测试

bash
go test -run ExampleAdd

测试辅助函数

Skip

go
func TestSkip(t *testing.T) {
    if testing.Short() {
        t.Skip("跳过长时间测试")
    }
    
    // 长时间测试...
}

Parallel

go
func TestParallel(t *testing.T) {
    t.Run("Group", func(t *testing.T) {
        tests := []struct {
            name string
            fn   func(t *testing.T)
        }{
            {"Test1", func(t *testing.T) { t.Parallel() }},
            {"Test2", func(t *testing.T) { t.Parallel() }},
            {"Test3", func(t *testing.T) { t.Parallel() }},
        }
        
        for _, tt := range tests {
            t.Run(tt.name, tt.fn)
        }
    })
}

Cleanup

go
func TestCleanup(t *testing.T) {
    file, err := os.Create("test.txt")
    if err != nil {
        t.Fatal(err)
    }
    
    t.Cleanup(func() {
        file.Close()
        os.Remove("test.txt")
    })
    
    // 测试代码...
}

Mock 和 Stub

接口 Mock

go
type Database interface {
    Get(id int) (string, error)
    Save(id int, data string) error
}

type MockDatabase struct {
    data map[int]string
}

func (m *MockDatabase) Get(id int) (string, error) {
    return m.data[id], nil
}

func (m *MockDatabase) Save(id int, data string) error {
    m.data[id] = data
    return nil
}

func TestWithMock(t *testing.T) {
    mock := &MockDatabase{data: make(map[int]string)}
    mock.Save(1, "test")
    
    result, err := mock.Get(1)
    if err != nil {
        t.Fatal(err)
    }
    
    if result != "test" {
        t.Errorf("expected 'test', got '%s'", result)
    }
}

测试覆盖率

生成覆盖率报告

bash
# 生成覆盖率
go test -cover

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

# 查看覆盖率报告
go tool cover -html=coverage.out

# 输出到文件
go tool cover -html=coverage.out -o coverage.html

设置覆盖率阈值

bash
# 覆盖率低于 80% 失败
go test -coverprofile=coverage.out -covermode=atomic
go tool cover -func=coverage.out | grep total

测试最佳实践

1. 保持测试独立

go
// 推荐:每个测试独立
func TestA(t *testing.T) {
    // 测试 A
}

func TestB(t *testing.T) {
    // 测试 B
}

// 不推荐:测试依赖
var sharedVar int

func TestA(t *testing.T) {
    sharedVar = 1
}

func TestB(t *testing.T) {
    // 依赖 TestA 的结果
}

2. 使用表驱动测试

go
// 推荐
func TestAdd(t *testing.T) {
    tests := []struct {
        a, b, expected int
    }{
        {1, 2, 3},
        {2, 3, 5},
    }
    
    for _, tt := range tests {
        if got := Add(tt.a, tt.b); got != tt.expected {
            t.Errorf("Add(%d, %d) = %d; want %d", tt.a, tt.b, got, tt.expected)
        }
    }
}

3. 测试边界条件

go
func TestAdd(t *testing.T) {
    tests := []struct {
        a, b, expected int
    }{
        {0, 0, 0},        // 零
        {1, -1, 0},       // 负数
        {1<<30, 1<<30, 1<<31}, // 大数
    }
    // ...
}

测试工具

testify

go
import "github.com/stretchr/testify/assert"

func TestWithTestify(t *testing.T) {
    assert.Equal(t, 5, Add(2, 3))
    assert.NotNil(t, "hello")
    assert.True(t, true)
}

gomock

go
import "github.com/golang/mock/gomock"

func TestWithGomock(t *testing.T) {
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()
    
    mock := NewMockDatabase(ctrl)
    mock.EXPECT().Get(1).Return("test", nil)
    
    result, err := mock.Get(1)
    assert.NoError(t, err)
    assert.Equal(t, "test", result)
}

练习

练习 1:编写测试

为以下函数编写测试:

go
func Max(a, b int) int {
    if a > b {
        return a
    }
    return b
}
答案
go
func TestMax(t *testing.T) {
    tests := []struct {
        a, b, expected int
    }{
        {1, 2, 2},
        {2, 1, 2},
        {1, 1, 1},
        {-1, 1, 1},
    }
    
    for _, tt := range tests {
        result := Max(tt.a, tt.b)
        if result != tt.expected {
            t.Errorf("Max(%d, %d) = %d; want %d", tt.a, tt.b, result, tt.expected)
        }
    }
}

总结

  • 使用 testing 包编写测试
  • 表格驱动测试提高可读性
  • 基准测试测量性能
  • 使用 Mock 隔离依赖
  • 测试覆盖率保证质量
  • 遵循测试最佳实践

下一章:性能优化

基于 MIT 许可发布