Go语言测试与持续集成
Go语言测试与持续集成一、测试基础Go语言内置了强大的测试框架通过testing包提供测试支持。基本测试结构package main import ( testing ) func Add(a, b int) int { return a b } func TestAdd(t *testing.T) { tests : []struct { name string a, b int expected int }{ {positive numbers, 2, 3, 5}, {negative numbers, -2, -3, -5}, {zero, 0, 0, 0}, {mixed, -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 test # 运行指定测试 go test -run TestAdd # 显示详细输出 go test -v # 生成测试覆盖率报告 go test -cover # 生成覆盖率profile go test -coverprofilecoverage.out go tool cover -htmlcoverage.out二、测试类型单元测试package main import ( testing ) type Counter struct { value int } func (c *Counter) Increment() { c.value } func (c *Counter) Value() int { return c.value } func TestCounter(t *testing.T) { c : Counter{} // 初始值应为0 if c.Value() ! 0 { t.Errorf(Initial value should be 0, got %d, c.Value()) } // 递增测试 c.Increment() if c.Value() ! 1 { t.Errorf(After increment, value should be 1, got %d, c.Value()) } // 多次递增 c.Increment() c.Increment() if c.Value() ! 3 { t.Errorf(After 3 increments, value should be 3, got %d, c.Value()) } }基准测试package main import ( testing ) func Fibonacci(n int) int { if n 1 { return n } return Fibonacci(n-1) Fibonacci(n-2) } func BenchmarkFibonacci(b *testing.B) { // 重置计时器 b.ResetTimer() for i : 0; i b.N; i { Fibonacci(20) } } func BenchmarkFibonacciParallel(b *testing.B) { b.RunParallel(func(pb *testing.PB) { for pb.Next() { Fibonacci(20) } }) }运行基准测试go test -bench. go test -benchFibonacci -benchmem表驱动测试package main import ( testing ) func ValidateEmail(email string) bool { // 简化的邮箱验证 if len(email) 0 { return false } if email[0] { return false } if len(email) 3 { return false } return true } func TestValidateEmail(t *testing.T) { testCases : []struct { name string email string expected bool }{ {valid email, userexample.com, true}, {empty email, , false}, {email starting with , example.com, false}, {short email, ab, false}, {missing , userexample.com, false}, } for _, tc : range testCases { t.Run(tc.name, func(t *testing.T) { result : ValidateEmail(tc.email) if result ! tc.expected { t.Errorf(ValidateEmail(%q) %v, want %v, tc.email, result, tc.expected) } }) } }三、Mock测试使用interface进行Mockpackage main import ( testing ) type Database interface { GetUser(id int) (string, error) } type RealDatabase struct{} func (r *RealDatabase) GetUser(id int) (string, error) { // 真实数据库查询 return John, nil } type MockDatabase struct { users map[int]string } func (m *MockDatabase) GetUser(id int) (string, error) { if user, ok : m.users[id]; ok { return user, nil } return , fmt.Errorf(user not found) } type Service struct { db Database } func (s *Service) GetUserName(id int) (string, error) { return s.db.GetUser(id) } func TestGetUserName(t *testing.T) { mockDB : MockDatabase{ users: map[int]string{ 1: Alice, 2: Bob, }, } service : Service{db: mockDB} name, err : service.GetUserName(1) if err ! nil { t.Fatalf(Unexpected error: %v, err) } if name ! Alice { t.Errorf(Expected Alice, got %s, name) } _, err service.GetUserName(99) if err nil { t.Error(Expected error for non-existent user) } }使用gomockgo install github.com/golang/mock/mockgenlatest生成mockmockgen -sourcedatabase.go -destinationmock_database.go -packagemain使用mockpackage main import ( testing github.com/golang/mock/gomock ) func TestServiceWithMock(t *testing.T) { ctrl : gomock.NewController(t) defer ctrl.Finish() mockDB : NewMockDatabase(ctrl) // 设置期望 mockDB.EXPECT().GetUser(1).Return(Alice, nil) mockDB.EXPECT().GetUser(2).Return(, fmt.Errorf(not found)) service : Service{db: mockDB} name, err : service.GetUserName(1) if err ! nil { t.Fatalf(Unexpected error: %v, err) } if name ! Alice { t.Errorf(Expected Alice, got %s, name) } }四、测试工具testifygo get github.com/stretchr/testifypackage main import ( testing github.com/stretchr/testify/assert github.com/stretchr/testify/require ) func TestWithTestify(t *testing.T) { // assert示例 assert.Equal(t, 4, 22, 22 should equal 4) assert.NotNil(t, hello) assert.True(t, true) // require示例失败时立即终止 require.NoError(t, someFunction()) require.NotEmpty(t, test) // 断言错误类型 _, err : someFunction() assert.Error(t, err) assert.EqualError(t, err, expected error message) }httptestpackage main import ( net/http net/http/httptest testing ) func handler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) w.Write([]byte(Hello, World!)) } func TestHandler(t *testing.T) { req : httptest.NewRequest(http.MethodGet, /, nil) rr : httptest.NewRecorder() handler(rr, req) // 检查状态码 assert.Equal(t, http.StatusOK, rr.Code) // 检查响应体 expected : Hello, World! assert.Equal(t, expected, rr.Body.String()) }五、持续集成GitHub Actions配置name: CI on: push: branches: [ main ] pull_request: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - name: Set up Go uses: actions/setup-gov3 with: go-version: 1.21 - name: Build run: go build ./... - name: Test run: go test -v -race ./... - name: Coverage run: go test -coverprofilecoverage.out ./... - name: Upload coverage uses: codecov/codecov-actionv3 with: files: ./coverage.outGitLab CI配置stages: - build - test - deploy build: stage: build image: golang:1.21 script: - go build ./... test: stage: test image: golang:1.21 script: - go test -v -race ./... - go test -coverprofilecoverage.out ./... deploy: stage: deploy image: alpine:latest script: - echo Deploying... only: - mainJenkins Pipeline配置pipeline { agent any stages { stage(Build) { steps { sh go build ./... } } stage(Test) { steps { sh go test -v -race ./... sh go test -coverprofilecoverage.out ./... } } stage(Deploy) { when { branch main } steps { sh kubectl apply -f deployment.yaml } } } }六、测试最佳实践测试命名规范// 好的命名 func TestAdd_PositiveNumbers(t *testing.T) func TestAdd_NegativeNumbers(t *testing.T) func TestUserService_GetUser_Success(t *testing.T) func TestUserService_GetUser_NotFound(t *testing.T) // 不好的命名 func TestAdd1(t *testing.T) func TestSomething(t *testing.T)测试隔离func TestDatabase(t *testing.T) { // 每个测试用例使用独立的数据库连接 db : setupTestDatabase(t) defer teardownTestDatabase(t, db) // 测试逻辑... } func setupTestDatabase(t *testing.T) *sql.DB { db, err : sql.Open(sqlite, :memory:) if err ! nil { t.Fatalf(Failed to open database: %v, err) } // 创建测试表 _, err db.Exec(CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)) if err ! nil { t.Fatalf(Failed to create table: %v, err) } return db }测试文档// TestAdd tests the Add function with various inputs. // It covers: // - Positive numbers // - Negative numbers // - Zero values // - Mixed positive and negative numbers func TestAdd(t *testing.T) { // ... }跳过测试func TestIntegration(t *testing.T) { // 只在特定条件下运行 if os.Getenv(INTEGRATION_TEST) ! true { t.Skip(Skipping integration test) } // 集成测试逻辑... }七、代码质量检查go vetgo vet ./...golangci-lintgo install github.com/golangci/golangci-lint/cmd/golangci-lintlatest golangci-lint run配置文件.golangci.ymlrun: timeout: 5m linters: enable: - errcheck - gofmt - goimports - gosec - unused - varcheck linters-settings: gofmt: simplify: true八、总结Go语言的测试框架提供了强大的工具来保证代码质量。通过以下实践可以构建可靠的测试体系测试类型单元测试、基准测试、表驱动测试Mock技术使用interface隔离依赖使用gomock生成mock测试工具testify、httptest等辅助库持续集成GitHub Actions、GitLab CI、Jenkins代码质量go vet、golangci-lint良好的测试覆盖率和持续集成流程是保证代码质量的关键能够在开发过程中及时发现问题减少生产环境的bug。