Smithery Logo
MCPsSkillsDocsPricing
Login
Smithery Logo

Accelerating the Agent Economy

Resources

DocumentationPrivacy PolicySystem Status

Company

PricingAboutBlog

Connect

© 2026 Smithery. All rights reserved.

    affaan-m

    golang-testing

    affaan-m/golang-testing
    Coding
    42,727
    7 installs

    About

    SKILL.md

    Install

    Install via Skills CLI

    or add to your agent
    • Claude Code
      Claude Code
    • Codex
      Codex
    • OpenClaw
      OpenClaw
    • Cursor
      Cursor
    • Amp
      Amp
    • GitHub Copilot
      GitHub Copilot
    • Gemini CLI
      Gemini CLI
    • Kilo Code
      Kilo Code
    • Junie
      Junie
    • Replit
      Replit
    • Windsurf
      Windsurf
    • Cline
      Cline
    • Continue
      Continue
    • OpenCode
      OpenCode
    • OpenHands
      OpenHands
    • Roo Code
      Roo Code
    • Augment
      Augment
    • Goose
      Goose
    • Trae
      Trae
    • Zencoder
      Zencoder
    • Antigravity
      Antigravity
    ├─
    ├─
    └─

    About

    Go测试模式包括表格驱动测试、子测试、基准测试、模糊测试和测试覆盖率。遵循TDD方法论,采用地道的Go实践。

    SKILL.md

    Go 测试模式

    遵循 TDD 方法论,用于编写可靠、可维护测试的全面 Go 测试模式。

    何时激活

    • 编写新的 Go 函数或方法时
    • 为现有代码添加测试覆盖率时
    • 为性能关键代码创建基准测试时
    • 为输入验证实现模糊测试时
    • 在 Go 项目中遵循 TDD 工作流时

    Go 的 TDD 工作流

    红-绿-重构循环

    RED     → 首先编写一个失败的测试
    GREEN   → 编写最少的代码来通过测试
    REFACTOR → 改进代码,同时保持测试通过
    REPEAT  → 继续处理下一个需求
    

    Go 中的分步 TDD

    // Step 1: Define the interface/signature
    // calculator.go
    package calculator
    
    func Add(a, b int) int {
        panic("not implemented") // Placeholder
    }
    
    // Step 2: Write failing test (RED)
    // calculator_test.go
    package calculator
    
    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)
        }
    }
    
    // Step 3: Run test - verify FAIL
    // $ go test
    // --- FAIL: TestAdd (0.00s)
    // panic: not implemented
    
    // Step 4: Implement minimal code (GREEN)
    func Add(a, b int) int {
        return a + b
    }
    
    // Step 5: Run test - verify PASS
    // $ go test
    // PASS
    
    // Step 6: Refactor if needed, verify tests still pass
    

    表驱动测试

    Go 测试的标准模式。以最少的代码实现全面的覆盖。

    func TestAdd(t *testing.T) {
        tests := []struct {
            name     string
            a, b     int
            expected int
        }{
            {"positive numbers", 2, 3, 5},
            {"negative numbers", -1, -2, -3},
            {"zero values", 0, 0, 0},
            {"mixed signs", -1, 1, 0},
            {"large numbers", 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)
                }
            })
        }
    }
    

    包含错误情况的表驱动测试

    func TestParseConfig(t *testing.T) {
        tests := []struct {
            name    string
            input   string
            want    *Config
            wantErr bool
        }{
            {
                name:  "valid config",
                input: `{"host": "localhost", "port": 8080}`,
                want:  &Config{Host: "localhost", Port: 8080},
            },
            {
                name:    "invalid JSON",
                input:   `{invalid}`,
                wantErr: true,
            },
            {
                name:    "empty input",
                input:   "",
                wantErr: true,
            },
            {
                name:  "minimal config",
                input: `{}`,
                want:  &Config{}, // Zero value config
            },
        }
    
        for _, tt := range tests {
            t.Run(tt.name, func(t *testing.T) {
                got, err := ParseConfig(tt.input)
    
                if tt.wantErr {
                    if err == nil {
                        t.Error("expected error, got nil")
                    }
                    return
                }
    
                if err != nil {
                    t.Fatalf("unexpected error: %v", err)
                }
    
                if !reflect.DeepEqual(got, tt.want) {
                    t.Errorf("got %+v; want %+v", got, tt.want)
                }
            })
        }
    }
    

    子测试和子基准测试

    组织相关测试

    func TestUser(t *testing.T) {
        // Setup shared by all subtests
        db := setupTestDB(t)
    
        t.Run("Create", func(t *testing.T) {
            user := &User{Name: "Alice"}
            err := db.CreateUser(user)
            if err != nil {
                t.Fatalf("CreateUser failed: %v", err)
            }
            if user.ID == "" {
                t.Error("expected user ID to be set")
            }
        })
    
        t.Run("Get", func(t *testing.T) {
            user, err := db.GetUser("alice-id")
            if err != nil {
                t.Fatalf("GetUser failed: %v", err)
            }
            if user.Name != "Alice" {
                t.Errorf("got name %q; want %q", user.Name, "Alice")
            }
        })
    
        t.Run("Update", func(t *testing.T) {
            // ...
        })
    
        t.Run("Delete", func(t *testing.T) {
            // ...
        })
    }
    

    并行子测试

    func TestParallel(t *testing.T) {
        tests := []struct {
            name  string
            input string
        }{
            {"case1", "input1"},
            {"case2", "input2"},
            {"case3", "input3"},
        }
    
        for _, tt := range tests {
            tt := tt // Capture range variable
            t.Run(tt.name, func(t *testing.T) {
                t.Parallel() // Run subtests in parallel
                result := Process(tt.input)
                // assertions...
                _ = result
            })
        }
    }
    

    测试辅助函数

    辅助函数

    func setupTestDB(t *testing.T) *sql.DB {
        t.Helper() // Marks this as a helper function
    
        db, err := sql.Open("sqlite3", ":memory:")
        if err != nil {
            t.Fatalf("failed to open database: %v", err)
        }
    
        // Cleanup when test finishes
        t.Cleanup(func() {
            db.Close()
        })
    
        // Run migrations
        if _, err := db.Exec(schema); err != nil {
            t.Fatalf("failed to create schema: %v", err)
        }
    
        return db
    }
    
    func assertNoError(t *testing.T, err error) {
        t.Helper()
        if err != nil {
            t.Fatalf("unexpected error: %v", err)
        }
    }
    
    func assertEqual[T comparable](t *testing.T, got, want T) {
        t.Helper()
        if got != want {
            t.Errorf("got %v; want %v", got, want)
        }
    }
    

    临时文件和目录

    func TestFileProcessing(t *testing.T) {
        // Create temp directory - automatically cleaned up
        tmpDir := t.TempDir()
    
        // Create test file
        testFile := filepath.Join(tmpDir, "test.txt")
        err := os.WriteFile(testFile, []byte("test content"), 0644)
        if err != nil {
            t.Fatalf("failed to create test file: %v", err)
        }
    
        // Run test
        result, err := ProcessFile(testFile)
        if err != nil {
            t.Fatalf("ProcessFile failed: %v", err)
        }
    
        // Assert...
        _ = result
    }
    

    黄金文件

    针对存储在 testdata/ 中的预期输出文件进行测试。

    var update = flag.Bool("update", false, "update golden files")
    
    func TestRender(t *testing.T) {
        tests := []struct {
            name  string
            input Template
        }{
            {"simple", Template{Name: "test"}},
            {"complex", Template{Name: "test", Items: []string{"a", "b"}}},
        }
    
        for _, tt := range tests {
            t.Run(tt.name, func(t *testing.T) {
                got := Render(tt.input)
    
                golden := filepath.Join("testdata", tt.name+".golden")
    
                if *update {
                    // Update golden file: go test -update
                    err := os.WriteFile(golden, got, 0644)
                    if err != nil {
                        t.Fatalf("failed to update golden file: %v", err)
                    }
                }
    
                want, err := os.ReadFile(golden)
                if err != nil {
                    t.Fatalf("failed to read golden file: %v", err)
                }
    
                if !bytes.Equal(got, want) {
                    t.Errorf("output mismatch:\ngot:\n%s\nwant:\n%s", got, want)
                }
            })
        }
    }
    

    使用接口进行模拟

    基于接口的模拟

    // Define interface for dependencies
    type UserRepository interface {
        GetUser(id string) (*User, error)
        SaveUser(user *User) error
    }
    
    // Production implementation
    type PostgresUserRepository struct {
        db *sql.DB
    }
    
    func (r *PostgresUserRepository) GetUser(id string) (*User, error) {
        // Real database query
    }
    
    // Mock implementation for tests
    type MockUserRepository struct {
        GetUserFunc  func(id string) (*User, error)
        SaveUserFunc func(user *User) error
    }
    
    func (m *MockUserRepository) GetUser(id string) (*User, error) {
        return m.GetUserFunc(id)
    }
    
    func (m *MockUserRepository) SaveUser(user *User) error {
        return m.SaveUserFunc(user)
    }
    
    // Test using mock
    func TestUserService(t *testing.T) {
        mock := &MockUserRepository{
            GetUserFunc: func(id string) (*User, error) {
                if id == "123" {
                    return &User{ID: "123", Name: "Alice"}, nil
                }
                return nil, ErrNotFound
            },
        }
    
        service := NewUserService(mock)
    
        user, err := service.GetUserProfile("123")
        if err != nil {
            t.Fatalf("unexpected error: %v", err)
        }
        if user.Name != "Alice" {
            t.Errorf("got name %q; want %q", user.Name, "Alice")
        }
    }
    

    基准测试

    基本基准测试

    func BenchmarkProcess(b *testing.B) {
        data := generateTestData(1000)
        b.ResetTimer() // Don't count setup time
    
        for i := 0; i < b.N; i++ {
            Process(data)
        }
    }
    
    // Run: go test -bench=BenchmarkProcess -benchmem
    // Output: BenchmarkProcess-8   10000   105234 ns/op   4096 B/op   10 allocs/op
    

    不同大小的基准测试

    func BenchmarkSort(b *testing.B) {
        sizes := []int{100, 1000, 10000, 100000}
    
        for _, size := range sizes {
            b.Run(fmt.Sprintf("size=%d", size), func(b *testing.B) {
                data := generateRandomSlice(size)
                b.ResetTimer()
    
                for i := 0; i < b.N; i++ {
                    // Make a copy to avoid sorting already sorted data
                    tmp := make([]int, len(data))
                    copy(tmp, data)
                    sort.Ints(tmp)
                }
            })
        }
    }
    

    内存分配基准测试

    func BenchmarkStringConcat(b *testing.B) {
        parts := []string{"hello", "world", "foo", "bar", "baz"}
    
        b.Run("plus", func(b *testing.B) {
            for i := 0; i < b.N; i++ {
                var s string
                for _, p := range parts {
                    s += p
                }
                _ = s
            }
        })
    
        b.Run("builder", func(b *testing.B) {
            for i := 0; i < b.N; i++ {
                var sb strings.Builder
                for _, p := range parts {
                    sb.WriteString(p)
                }
                _ = sb.String()
            }
        })
    
        b.Run("join", func(b *testing.B) {
            for i := 0; i < b.N; i++ {
                _ = strings.Join(parts, "")
            }
        })
    }
    

    模糊测试 (Go 1.18+)

    基本模糊测试

    func FuzzParseJSON(f *testing.F) {
        // Add seed corpus
        f.Add(`{"name": "test"}`)
        f.Add(`{"count": 123}`)
        f.Add(`[]`)
        f.Add(`""`)
    
        f.Fuzz(func(t *testing.T, input string) {
            var result map[string]interface{}
            err := json.Unmarshal([]byte(input), &result)
    
            if err != nil {
                // Invalid JSON is expected for random input
                return
            }
    
            // If parsing succeeded, re-encoding should work
            _, err = json.Marshal(result)
            if err != nil {
                t.Errorf("Marshal failed after successful Unmarshal: %v", err)
            }
        })
    }
    
    // Run: go test -fuzz=FuzzParseJSON -fuzztime=30s
    

    多输入模糊测试

    func FuzzCompare(f *testing.F) {
        f.Add("hello", "world")
        f.Add("", "")
        f.Add("abc", "abc")
    
        f.Fuzz(func(t *testing.T, a, b string) {
            result := Compare(a, b)
    
            // Property: Compare(a, a) should always equal 0
            if a == b && result != 0 {
                t.Errorf("Compare(%q, %q) = %d; want 0", a, b, result)
            }
    
            // Property: Compare(a, b) and Compare(b, a) should have opposite signs
            reverse := Compare(b, a)
            if (result > 0 && reverse >= 0) || (result < 0 && reverse <= 0) {
                if result != 0 || reverse != 0 {
                    t.Errorf("Compare(%q, %q) = %d, Compare(%q, %q) = %d; inconsistent",
                        a, b, result, b, a, reverse)
                }
            }
        })
    }
    

    测试覆盖率

    运行覆盖率

    # Basic coverage
    go test -cover ./...
    
    # Generate coverage profile
    go test -coverprofile=coverage.out ./...
    
    # View coverage in browser
    go tool cover -html=coverage.out
    
    # View coverage by function
    go tool cover -func=coverage.out
    
    # Coverage with race detection
    go test -race -coverprofile=coverage.out ./...
    

    覆盖率目标

    代码类型 目标
    关键业务逻辑 100%
    公共 API 90%+
    通用代码 80%+
    生成的代码 排除

    从覆盖率中排除生成的代码

    //go:generate mockgen -source=interface.go -destination=mock_interface.go
    
    // In coverage profile, exclude with build tags:
    // go test -cover -tags=!generate ./...
    

    HTTP 处理器测试

    func TestHealthHandler(t *testing.T) {
        // Create request
        req := httptest.NewRequest(http.MethodGet, "/health", nil)
        w := httptest.NewRecorder()
    
        // Call handler
        HealthHandler(w, req)
    
        // Check response
        resp := w.Result()
        defer resp.Body.Close()
    
        if resp.StatusCode != http.StatusOK {
            t.Errorf("got status %d; want %d", resp.StatusCode, http.StatusOK)
        }
    
        body, _ := io.ReadAll(resp.Body)
        if string(body) != "OK" {
            t.Errorf("got body %q; want %q", body, "OK")
        }
    }
    
    func TestAPIHandler(t *testing.T) {
        tests := []struct {
            name       string
            method     string
            path       string
            body       string
            wantStatus int
            wantBody   string
        }{
            {
                name:       "get user",
                method:     http.MethodGet,
                path:       "/users/123",
                wantStatus: http.StatusOK,
                wantBody:   `{"id":"123","name":"Alice"}`,
            },
            {
                name:       "not found",
                method:     http.MethodGet,
                path:       "/users/999",
                wantStatus: http.StatusNotFound,
            },
            {
                name:       "create user",
                method:     http.MethodPost,
                path:       "/users",
                body:       `{"name":"Bob"}`,
                wantStatus: http.StatusCreated,
            },
        }
    
        handler := NewAPIHandler()
    
        for _, tt := range tests {
            t.Run(tt.name, func(t *testing.T) {
                var body io.Reader
                if tt.body != "" {
                    body = strings.NewReader(tt.body)
                }
    
                req := httptest.NewRequest(tt.method, tt.path, body)
                req.Header.Set("Content-Type", "application/json")
                w := httptest.NewRecorder()
    
                handler.ServeHTTP(w, req)
    
                if w.Code != tt.wantStatus {
                    t.Errorf("got status %d; want %d", w.Code, tt.wantStatus)
                }
    
                if tt.wantBody != "" && w.Body.String() != tt.wantBody {
                    t.Errorf("got body %q; want %q", w.Body.String(), tt.wantBody)
                }
            })
        }
    }
    

    命令测试

    # Run all tests
    go test ./...
    
    # Run tests with verbose output
    go test -v ./...
    
    # Run specific test
    go test -run TestAdd ./...
    
    # Run tests matching pattern
    go test -run "TestUser/Create" ./...
    
    # Run tests with race detector
    go test -race ./...
    
    # Run tests with coverage
    go test -cover -coverprofile=coverage.out ./...
    
    # Run short tests only
    go test -short ./...
    
    # Run tests with timeout
    go test -timeout 30s ./...
    
    # Run benchmarks
    go test -bench=. -benchmem ./...
    
    # Run fuzzing
    go test -fuzz=FuzzParse -fuzztime=30s ./...
    
    # Count test runs (for flaky test detection)
    go test -count=10 ./...
    

    最佳实践

    应该:

    • 先写测试 (TDD)
    • 使用表驱动测试以实现全面覆盖
    • 测试行为,而非实现
    • 在辅助函数中使用 t.Helper()
    • 对于独立的测试使用 t.Parallel()
    • 使用 t.Cleanup() 清理资源
    • 使用描述场景的有意义的测试名称

    不应该:

    • 直接测试私有函数 (通过公共 API 测试)
    • 在测试中使用 time.Sleep() (使用通道或条件)
    • 忽略不稳定的测试 (修复或移除它们)
    • 模拟所有东西 (在可能的情况下优先使用集成测试)
    • 跳过错误路径测试

    与 CI/CD 集成

    # GitHub Actions example
    test:
      runs-on: ubuntu-latest
      steps:
        - uses: actions/checkout@v4
        - uses: actions/setup-go@v5
          with:
            go-version: '1.22'
    
        - name: Run tests
          run: go test -race -coverprofile=coverage.out ./...
    
        - name: Check coverage
          run: |
            go tool cover -func=coverage.out | grep total | awk '{print $3}' | \
            awk -F'%' '{if ($1 < 80) exit 1}'
    

    记住:测试即文档。它们展示了你的代码应如何使用。清晰地编写它们并保持更新。

    Recommended Servers
    Postman
    Postman
    OpenZeppelin
    OpenZeppelin
    Svelte
    Svelte
    Repository
    affaan-m/everything-claude-code
    Files