mcpField
G
go-testing-code-review

go-testing-code-review

github.com/existential-birds测试与质量85143 次安装11 周前更新
代码仓库

关于 go-testing-code-review

go-testing-code-review 是用于审查 Go 测试及相关代码变更的技能。它要求审查者先确认 Go 版本、阅读测试上下文并按适用清单检查,再核实问题后报告。

主要功能

  1. 01按顺序执行的审查流程
  2. 02结合 Go 版本的测试建议
  3. 03测试结构与覆盖率清单
  4. 04Mock 与测试隔离指引
  5. 05带严重级别的问题格式

适用场景

  1. 01审查 Go 测试文件的变更
  2. 02检查并行测试是否共享可变状态
  3. 03评估 HTTP、模糊、基准或黄金文件测试

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/existential-birds/beagle --skill go-testing-code-review

安装后在 Claude Code 中输入/go-testing-code-review确认已加载。

附带文件references/mocking.mdreferences/structure.md

详细介绍

Go 测试代码审查

审查工作流

按以下顺序执行。满足下面所有 通过 条件之前,不要输出任何问题。

  1. 基准 go.mod — 打开待审查模块的 go.mod,并读取 go 指令。
    通过: 可以准确写出 go X.YY 的值(在审查前言或工作笔记中)。仅当版本与此基准相符时,才应用受版本限制的建议(例如,Go 1.18+ 支持模糊测试,Go 1.22 之前的循环变量捕获)。

  2. 阅读相关测试 — 对于范围内的每个 *_test.go(或基准测试/模糊测试文件),阅读完整的测试函数及其使用的任何表格 struct{...}/辅助函数,不要只看差异代码块。
    通过: 范围内的每个文件中,至少有一个包含变更的完整 func Test.../func Benchmark.../func Fuzz...(或它调用的辅助函数)已被阅读。

  3. 确定清单范围 — 判断审查清单中的哪些条目适用(表驱动结构、并行测试、HTTP、黄金文件、模拟对象)。针对这些主题,打开 references/structure.md 和/或 references/mocking.md;对于与差异无关的条目,说明不适用的原因(一句话即可,例如“变更中没有 t.Parallel”)。
    通过: 审查(或工作笔记)列出了应用的清单主题,或说明了不适用主题及其与差异相关的原因。

  4. 报告前验证 — 加载并遵循 review-verification-protocol。
    通过: 对于要报告的每个问题,均满足该协议的报告前验证清单(已阅读实际测试代码、检查了上下文、区分了“错误”与“风格不同”等)。

强制关卡(相同顺序,简明版)

步骤客观通过条件
1在给出特定版本的测试建议前,已记录 go.mod 中的 go X.YY。
2已阅读范围内每个测试文件中的完整外围测试(或它使用的辅助函数),而非只看差异。
3已列出适用的清单主题,或说明不适用主题及其与差异相关的原因;按需打开参考文档。
4对每个报告的问题,均已完成 review-verification-protocol。

输出格式

按以下格式报告问题:

[FILE:LINE] ISSUE_TITLE
Severity: Critical | Major | Minor | Informational
Description of the issue and why it matters.

快速参考

问题类型参考文档
测试结构、命名references/structure.md
模拟对象、接口references/mocking.md

审查清单

  • 测试采用表驱动形式,且用例名称清晰
  • 子测试使用 t.Run 以便并行执行
  • 测试名称描述行为,而非实现方式
  • 错误信息包含 got/want,并有描述性说明
  • 使用 t.Cleanup 注册清理操作
  • 并行测试不共享可变状态
  • 模拟对象使用测试文件中定义的接口
  • 覆盖边界情况和错误路径
  • 性能关键函数有 Benchmark* 测试
  • 输入解析器/验证器有 Fuzz* 测试(Go 1.18+)
  • HTTP 处理器使用 httptest.NewRequest/httptest.NewRecorder 测试
  • 黄金文件测试采用 testdata/*.golden 模式,并提供 -update 标志

关键模式

表驱动测试
// BAD - repetitive
func TestAdd(t *testing.T) {
    if Add(1, 2) != 3 {
        t.Error("wrong")
    }
    if Add(0, 0) != 0 {
        t.Error("wrong")
    }
}

// GOOD
func TestAdd(t *testing.T) {
    tests := []struct {
        name     string
        a, b     int
        want     int
    }{
        {"positive numbers", 1, 2, 3},
        {"zeros", 0, 0, 0},
        {"negative", -1, 1, 0},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got := Add(tt.a, tt.b)
            if got != tt.want {
                t.Errorf("Add(%d, %d) = %d, want %d", tt.a, tt.b, got, tt.want)
            }
        })
    }
}
错误信息
// BAD
if got != want {
    t.Error("wrong result")
}

// GOOD
if got != want {
    t.Errorf("GetUser(%d) = %v, want %v", id, got, want)
}

// For complex types
if diff := cmp.Diff(want, got); diff != "" {
    t.Errorf("GetUser() mismatch (-want +got):\n%s", diff)
}
并行测试
func TestFoo(t *testing.T) {
    tests := []struct{...}

    for _, tt := range tests {
        tt := tt  // capture (not needed Go 1.22+)
        t.Run(tt.name, func(t *testing.T) {
            t.Parallel()
            // test code
        })
    }
}
清理操作
// BAD - manual cleanup, skipped on failure
func TestWithTempFile(t *testing.T) {
    f, _ := os.CreateTemp("", "test")
    defer os.Remove(f.Name())  // skipped if test panics
}

// GOOD
func TestWithTempFile(t *testing.T) {
    f, _ := os.CreateTemp("", "test")
    t.Cleanup(func() {
        os.Remove(f.Name())
    })
}

其他模式

基准测试
func BenchmarkProcess(b *testing.B) {
    data := generateTestData(1000)
    b.ResetTimer()

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

// Run: go test -bench=BenchmarkProcess -benchmem
模糊测试(Go 1.18+)
func FuzzParseInput(f *testing.F) {
    // Seed corpus
    f.Add(`{"name": "test"}`)
    f.Add(``)
    f.Add(`{invalid}`)

    f.Fuzz(func(t *testing.T, input string) {
        result, err := ParseInput(input)
        if err != nil {
            return // invalid input is expected
        }
        // If parsing succeeded, re-encoding should work
        if _, err := json.Marshal(result); err != nil {
            t.Errorf("Marshal after Parse: %v", err)
        }
    })
}

// Run: go test -fuzz=FuzzParseInput -fuzztime=30s
HTTP 处理器测试
func TestHandler(t *testing.T) {
    srv := NewServer(mockDeps)

    req := httptest.NewRequest("GET", "/api/users/123", nil)
    w := httptest.NewRecorder()

    srv.ServeHTTP(w, req)

    if w.Code != http.StatusOK {
        t.Errorf("status = %d, want %d", w.Code, http.StatusOK)
    }
}
黄金文件
var update = flag.Bool("update", false, "update golden files")

func TestRender(t *testing.T) {
    got := Render(input)
    golden := filepath.Join("testdata", t.Name()+".golden")

    if *update {
        if err := os.WriteFile(golden, got, 0644); err != nil {
            t.Fatalf("writing golden file: %v", err)
        }
    }

    want, err := os.ReadFile(golden)
    if err != nil {
        t.Fatalf("reading golden file: %v (run with -update to create)", err)
    }
    if !bytes.Equal(got, want) {
        t.Errorf("output mismatch:\ngot:\n%s\nwant:\n%s", got, want)
    }
}

反模式

1. 测试内部实现
// BAD - tests private state
func TestUser(t *testing.T) {
    u := NewUser("alice")
    if u.id != 1 {  // testing internal field
        t.Error("wrong id")
    }
}

// GOOD - tests behavior
func TestUser(t *testing.T) {
    u := NewUser("alice")
    if u.ID() != 1 {
        t.Error("wrong ID")
    }
}
2. 共享可变状态
// BAD - tests interfere with each other
var testDB = setupDB()

func TestA(t *testing.T) {
    t.Parallel()
    testDB.Insert(...)  // race!
}

// GOOD - isolated per test
func TestA(t *testing.T) {
    db := setupTestDB(t)
    t.Cleanup(func() { db.Close() })
    db.Insert(...)
}
3. 缺少上下文的断言
// BAD
assert.Equal(t, want, got)  // "expected X got Y" - which test?

// GOOD
assert.Equal(t, want, got, "user name after update")

何时加载参考文档

  • 审查测试文件结构 → structure.md
  • 审查模拟对象实现 → mocking.md

审查问题

  1. 测试是否采用表驱动形式,并为用例命名?
  2. 错误信息是否包含输入、got 和 want?
  3. 并行测试是否彼此隔离(没有共享状态)?
  4. 是否通过 t.Cleanup 执行清理?
  5. 测试验证的是行为,而非实现方式?

评论

支持 Markdown