Communitygithub.com

golang-testing

Go 测试模式,包括表格驱动测试、子测试、基准测试、模糊测试和测试覆盖率。遵循 TDD 方法论和惯用 Go 实践。在编写 Go 测试时使用——表格驱动用例、子测试、基准测试、模糊测试或覆盖率。

golang-testing 是什么?

该技能提供了关于 Go 测试模式的全面指南,专注于可靠且可维护的测试。它详细介绍了表格驱动测试,这是 Go 中的标准测试模式,能够用最少的代码实现全面的测试覆盖,并包括处理成功和错误用例的示例。技能还涵盖了子测试和子基准测试,用于组织相关测试和并行执行,以及用于简化测试设置和清理的测试辅助函数。此外,它包括使用 golden 文件、基于接口的模拟、针对不同数据集大小和内存分配的基准测试,以及 Go 1.18+ 中用于输入验证的模糊测试。关于测试覆盖率的运行和目标的指导也包括在内,确保开发团队遵循 TDD 工作流。该技能高度兼容 Claude Code、Cursor 和 Codex 等 AI 代理工具,提供增强 Go 代码库中测试实践的直接示例。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/golang-testing

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

Go 测试模式

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

何时激活

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

Go 的 TDD 工作流

RED-GREEN-REFACTOR 循环

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

Go 中的逐步 TDD

// 步骤 1: 定义接口/签名
// calculator.go
package calculator

func Add(a, b int) int {
    panic("未实现") // 占位符
}

// 步骤 2: 编写失败的测试 (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)
    }
}

// 步骤 3: 运行测试 - 验证 FAIL
// $ go test
// --- FAIL: TestAdd (0.00s)
// panic: 未实现

// 步骤 4: 实现最少的代码 (GREEN)
func Add(a, b int) int {
    return a + b
}

// 步骤 5: 运行测试 - 验证 PASS
// $ go test
// PASS

// 步骤 6: 如果需要,重构,并验证测试仍然通过

表格驱动测试

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

func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        expected int
    }{
        {"正数", 2, 3, 5},
        {"负数", -1, -2, -3},
        {"零值", 0, 0, 0},
        {"混合符号", -1, 1, 0},
        {"大数", 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:  "有效的配置",
            input: `{"host": "localhost", "port": 8080}`,
            want:  &Config{Host: "localhost", Port: 8080},
        },
        {
            name:    "无效的 JSON",
            input:   `{invalid}`,
            wantErr: true,
        },
        {
            name:    "空输入",
            input:   "",
            wantErr: true,
        },
        {
            name:  "最小配置",
            input: `{}`,
            want:  &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("期望错误,但得到 nil")
                }
                return
            }

            if err != nil {
                t.Fatalf("意外的错误: %v", err)
            }

            if !reflect.DeepEqual(got, tt.want) {
                t.Errorf("got %+v; want %+v", got, tt.want)
            }
        })
    }
}

子测试和子基准测试

组织相关测试

func TestUser(t *testing.T) {
    // 所有子测试共享的设置
    db := setupTestDB(t)

    t.Run("创建", func(t *testing.T) {
        user := &User{Name: "Alice"}
        err := db.CreateUser(user)
        if err != nil {
            t.Fatalf("CreateUser 失败: %v", err)
        }
        if user.ID == "" {
            t.Error("期望用户 ID 被设置")
        }
    })

    t.Run("获取", func(t *testing.T) {
        user, err := db.GetUser("alice-id")
        if err != nil {
            t.Fatalf("GetUser 失败: %v", err)
        }
        if user.Name != "Alice" {
            t.Errorf("got name %q; want %q", user.Name, "Alice")
        }
    })

    t.Run("更新", func(t *testing.T) {
        // ...
    })

    t.Run("删除", 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 // 捕获范围变量
        t.Run(tt.name, func(t *testing.T) {
            t.Parallel() // 并行运行子测试
            result := Process(tt.input)
            // 断言...
            _ = result
        })
    }
}

测试辅助函数

辅助函数

func setupTestDB(t *testing.T) *sql.DB {
    t.Helper() // 将此标记为辅助函数

    db, err := sql.Open("sqlite3", ":memory:")
    if err != nil {
        t.Fatalf("打开数据库失败: %v", err)
    }

    // 测试完成时清理
    t.Cleanup(func() {
        db.Close()
    })

    // 运行迁移
    if _, err := db.Exec(schema); err != nil {
        t.Fatalf("创建模式失败: %v", err)
    }

    return db
}

func assertNoError(t *testing.T, err error) {
    t.Helper()
    if err != nil {
        t.Fatalf("意外的错误: %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) {
    // 创建临时目录 - 自动清理
    tmpDir := t.TempDir()

    // 创建测试文件
    testFile := filepath.Join(tmpDir, "test.txt")
    err := os.WriteFile(testFile, []byte("测试内容"), 0644)
    if err != nil {
        t.Fatalf("创建测试文件失败: %v", err)
    }

    // 运行测试
    result, err := ProcessFile(testFile)
    if err != nil {
        t.Fatalf("ProcessFile 失败: %v", err)
    }

    // 断言...
    _ = result
}

Golden 文件

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

var update = flag.Bool("update", false, "更新 golden 文件")

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 {
                // 更新 golden 文件: go test -update
                err := os.WriteFile(golden, got, 0644)
                if err != nil {
                    t.Fatalf("更新 golden 文件失败: %v", err)
                }
            }

            want, err := os.ReadFile(golden)
            if err != nil {
                t.Fatalf("读取 golden 文件失败: %v", err)
            }

            if !bytes.Equal(got, want) {
                t.Errorf("输出不匹配:\ngot:\n%s\nwant:\n%s", got, want)
            }
        })
    }
}

使用接口进行模拟

基于接口的模拟

// 定义依赖项的接口
type UserRepository interface {
    GetUser(id string) (*User, error)
    SaveUser(user *User) error
}

// 生产实现
type PostgresUserRepository struct {
    db *sql.DB
}

func (r *PostgresUserRepository) GetUser(id string) (*User, error) {
    // 真实数据库查询
}

// 测试用的模拟实现
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)
}

// 使用模拟的测试
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("意外的错误: %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() // 不计入设置时间

    for i := 0; i < b.N; i++ {
        Process(data)
    }
}

// 运行: go test -bench=BenchmarkProcess -benchmem
// 输出: 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++ {
                // 复制以避免对已排序的数据排序
                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) {
    // 添加种子语料库
    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 {
            // 随机输入预期会有无效 JSON
            return
        }

        // 如果解析成功,重新编码应该工作
        _, err = json.Marshal(result)
        if err != nil {
            t.Errorf("成功 Unmarshal 后 Marshal 失败: %v", err)
        }
    })
}

// 运行: 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)

        // 属性: Compare(a, a) 应始终等于 0
        if a == b && result != 0 {
            t.Errorf("Compare(%q, %q) = %d; want 0", a, b, result)
        }

        // 属性: Compare(a, b) 和 Compare(b, a) 应有相反的符号
        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; 不一致",
                    a, b, result, b, a, reverse)
            }
        }
    })
}

测试覆盖率

运行覆盖率

# 基本覆盖率
go test -cover ./...

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

# 在浏览器中查看覆盖率
go tool cover -html=coverage.out

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

# 带竞争检测的覆盖率
go test -race -coverprofile=coverage.out ./...

覆盖率目标

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

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

//go:generate mockgen -source=interface.go -destination=mock_interface.go

// 在覆盖率概要文件中,使用构建标签排除:
// go test -cover -tags=!generate ./...

HTTP 处理器测试

func TestHealthHandler(t *testing.T) {
    // 创建请求
    req := httptest.NewRequest(http.MethodGet, "/health", nil)
    w := httptest.NewRecorder()

    // 调用处理器
    HealthHandler(w, req)

    // 检查响应
    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:       "获取用户",
            method:     http.MethodGet,
            path:       "/users/123",
            wantStatus: http.StatusOK,
            wantBody:   `{"id":"123","name":"Alice"}`,
        },
        {
            name:       "未找到",
            method:     http.MethodGet,
            path:       "/users/999",
            wantStatus: http.StatusNotFound,
        },
        {
            name:       "创建用户",
            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) {
            // ...
        })
    }
}

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

相关技能