Go语言测试与质量保障
Go语言测试与质量保障
测试是保障代码质量的关键环节。本文将深入探讨Go语言的测试框架和质量保障策略。
一、测试基础
1.1 单元测试
package calculator import "testing" func TestAdd(t *testing.T) { result := Add(2, 3) expected := 5 if result != expected { t.Errorf("Add(2, 3) = %d, expected %d", result, expected) } } func TestSubtract(t *testing.T) { result := Subtract(5, 3) expected := 2 if result != expected { t.Errorf("Subtract(5, 3) = %d, expected %d", result, expected) } }1.2 表格驱动测试
func TestAddTable(t *testing.T) { tests := []struct { name string a int b int expected int }{ {"positive numbers", 2, 3, 5}, {"negative numbers", -2, -3, -5}, {"zero and number", 0, 5, 5}, {"large numbers", 1000, 2000, 3000}, } 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, expected %d", tt.a, tt.b, result, tt.expected) } }) } }1.3 子测试
func TestCalculator(t *testing.T) { t.Run("Addition", func(t *testing.T) { result := Add(2, 3) if result != 5 { t.Errorf("Add failed: got %d, want %d", result, 5) } }) t.Run("Subtraction", func(t *testing.T) { result := Subtract(5, 3) if result != 2 { t.Errorf("Subtract failed: got %d, want %d", result, 2) } }) t.Run("Multiplication", func(t *testing.T) { result := Multiply(4, 5) if result != 20 { t.Errorf("Multiply failed: got %d, want %d", result, 20) } }) }二、测试进阶
2.1 Mock测试
type Database interface { GetUser(id string) (User, error) SaveUser(user User) error } type MockDB struct { users map[string]User } func (m *MockDB) GetUser(id string) (User, error) { user, ok := m.users[id] if !ok { return User{}, fmt.Errorf("user not found") } return user, nil } func (m *MockDB) SaveUser(user User) error { m.users[user.ID] = user return nil } func TestUserService_GetUser(t *testing.T) { mockDB := &MockDB{ users: map[string]User{ "1": {ID: "1", Name: "Test User"}, }, } service := NewUserService(mockDB) user, err := service.GetUser("1") if err != nil { t.Fatalf("Unexpected error: %v", err) } if user.Name != "Test User" { t.Errorf("Expected name 'Test User', got '%s'", user.Name) } }2.2 基准测试
func BenchmarkAdd(b *testing.B) { for i := 0; i < b.N; i++ { Add(2, 3) } } func BenchmarkSort(b *testing.B) { data := make([]int, 1000) for i := 0; i < 1000; i++ { data[i] = rand.Intn(10000) } b.ResetTimer() for i := 0; i < b.N; i++ { sort.Ints(data) } }2.3 Fuzz测试
func FuzzAdd(f *testing.F) { f.Add(1, 2) f.Add(-1, 1) f.Add(0, 0) f.Fuzz(func(t *testing.T, a, b int) { result := Add(a, b) if result != a+b { t.Errorf("Add(%d, %d) = %d, expected %d", a, b, result, a+b) } if Add(a, b) != Add(b, a) { t.Errorf("Add is not commutative") } }) }三、测试最佳实践
3.1 测试覆盖率
// go test -cover -coverprofile=coverage.out // go tool cover -html=coverage.out func TestCoverageExample(t *testing.T) { // 测试应该覆盖主要代码路径 tests := []struct { name string input int want bool }{ {"positive", 5, true}, {"zero", 0, false}, {"negative", -1, false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { got := IsPositive(tt.input) if got != tt.want { t.Errorf("IsPositive(%d) = %v, want %v", tt.input, got, tt.want) } }) } }3.2 测试隔离
func TestDatabaseOperations(t *testing.T) { // 使用临时数据库 db := setupTestDB() defer teardownTestDB(db) t.Run("CreateUser", func(t *testing.T) { user := User{ID: "1", Name: "Test"} err := db.SaveUser(user) if err != nil { t.Fatalf("Failed to save user: %v", err) } }) t.Run("GetUser", func(t *testing.T) { user, err := db.GetUser("1") if err != nil { t.Fatalf("Failed to get user: %v", err) } if user.Name != "Test" { t.Errorf("Expected name 'Test', got '%s'", user.Name) } }) }3.3 测试辅助函数
func mustCreateUser(t *testing.T, db Database, user User) { t.Helper() err := db.SaveUser(user) if err != nil { t.Fatalf("Failed to create user: %v", err) } } func assertEqual(t *testing.T, expected, actual interface{}) { t.Helper() if expected != actual { t.Errorf("Expected %v, got %v", expected, actual) } } func TestWithHelpers(t *testing.T) { db := setupTestDB() defer teardownTestDB(db) mustCreateUser(t, db, User{ID: "1", Name: "Test"}) user, err := db.GetUser("1") if err != nil { t.Fatalf("Failed to get user: %v", err) } assertEqual(t, "Test", user.Name) }四、集成测试
4.1 HTTP集成测试
func TestAPIRoutes(t *testing.T) { // 创建测试服务器 router := setupRouter() server := httptest.NewServer(router) defer server.Close() t.Run("GET /users", func(t *testing.T) { resp, err := http.Get(server.URL + "/users") if err != nil { t.Fatalf("Failed to make request: %v", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { t.Errorf("Expected status 200, got %d", resp.StatusCode) } var users []User if err := json.NewDecoder(resp.Body).Decode(&users); err != nil { t.Fatalf("Failed to decode response: %v", err) } if len(users) != 0 { t.Errorf("Expected 0 users, got %d", len(users)) } }) }4.2 数据库集成测试
func TestDatabaseIntegration(t *testing.T) { // 使用真实数据库进行测试 db, err := sql.Open("postgres", "postgres://user:pass@localhost/testdb") if err != nil { t.Skip("Skipping database test: connection failed") } defer db.Close() // 创建测试表 _, err = db.Exec(`CREATE TABLE IF NOT EXISTS users ( id TEXT PRIMARY KEY, name TEXT )`) if err != nil { t.Fatalf("Failed to create table: %v", err) } defer db.Exec("DROP TABLE users") // 测试插入 _, err = db.Exec("INSERT INTO users (id, name) VALUES ($1, $2)", "1", "Test") if err != nil { t.Fatalf("Failed to insert user: %v", err) } // 测试查询 var name string err = db.QueryRow("SELECT name FROM users WHERE id = $1", "1").Scan(&name) if err != nil { t.Fatalf("Failed to query user: %v", err) } if name != "Test" { t.Errorf("Expected name 'Test', got '%s'", name) } }五、测试工具
5.1 testing包高级用法
func TestWithTimeout(t *testing.T) { // 设置测试超时 t.Timeout(5 * time.Second) // 标记测试为预期失败 t.Skip("This test is not implemented yet") // 标记测试为需要关注 t.Log("This is a debug log message") }5.2 第三方测试库
// 使用 testify/assert import ( "testing" "github.com/stretchr/testify/assert" ) func TestWithTestify(t *testing.T) { result := Add(2, 3) assert.Equal(t, 5, result, "Add should return 5") assert.NotNil(t, result) assert.Greater(t, result, 0) } // 使用 gomock import ( "testing" "github.com/golang/mock/gomock" ) func TestWithMock(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockDB := NewMockDatabase(ctrl) mockDB.EXPECT().GetUser("1").Return(User{ID: "1", Name: "Test"}, nil) service := NewUserService(mockDB) user, err := service.GetUser("1") assert.NoError(t, err) assert.Equal(t, "Test", user.Name) }六、质量保障策略
6.1 CI/CD集成
# .github/workflows/test.yml name: Test on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: '1.22' - name: Run tests run: go test -v -cover ./... - name: Run linter uses: golangci/golangci-lint-action@v46.2 静态分析
# 使用 golangci-lint golangci-lint run ./... # 使用 go vet go vet ./... # 使用 errcheck errcheck ./...6.3 代码审查
// 代码审查检查项 // 1. 错误处理是否正确 func bad() { file, _ := os.Open("file.txt") // ❌ 忽略错误 defer file.Close() } func good() error { file, err := os.Open("file.txt") // ✅ 正确处理错误 if err != nil { return err } defer file.Close() return nil } // 2. 资源是否正确释放 func bad() { conn := connect() // 缺少 defer conn.Close() } func good() { conn := connect() defer conn.Close() // ✅ 正确释放资源 } // 3. 并发安全性 func bad() { var wg sync.WaitGroup wg.Add(1) go func() { // 缺少 wg.Done() }() wg.Wait() } func good() { var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() // ✅ 正确使用 WaitGroup }() wg.Wait() }七、总结
Go语言的测试框架提供了完整的测试能力:
- 单元测试:testing包提供基础测试能力
- 表格驱动测试:结构化测试多种输入输出
- Mock测试:隔离外部依赖
- 基准测试:性能测试
- Fuzz测试:模糊测试发现边界问题
- 集成测试:验证组件协作
- 质量保障:CI/CD、静态分析、代码审查
掌握这些测试技术可以编写出高质量、可靠的代码。
