mcpField
G
go-test-table-driven

go-test-table-driven

github.com/eduardo-sl测试与质量83169 次安装2 周前更新
代码仓库

关于 go-test-table-driven

面向 Go 开发者和编码 Agent 的测试技能,说明何时适合表驱动测试,以及如何精简用例和循环逻辑,并涵盖并行测试与复杂表格的重构。

主要功能

  1. 01判断表驱动测试的适用场景
  2. 02保持测试用例聚焦
  3. 03为用例编写描述性名称
  4. 04精简测试循环逻辑
  5. 05并行运行安全的用例
  6. 06重构臃肿的测试表

适用场景

  1. 01用重复模式测试解析器和校验器
  2. 02选择测试表或显式子测试
  3. 03简化带分支或复杂设置的测试表

安装

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

$npx skills add https://github.com/eduardo-sl/go-agent-skills --skill go-test-table-driven

安装后在 Claude Code 中输入/go-test-table-driven确认已加载。

附带文件references/patterns.mdreferences/refactoring.md

详细介绍

Go 表驱动测试

表驱动测试是 Go 中一种强大的惯用模式——前提是使用得当。大多数代码库要么用得不足(10 个复制粘贴的测试),要么用得过度(200 行结构体中的复杂分支逻辑)。本技能介绍如何把握适用范围。

社区默认规则。若项目技能或 Agent 配置规则明确取代了 go-test-table-driven,则以该规则为准。

按需加载的详细参考资料:

  • references/patterns.md — 完整示例:规范表格、wantErr/wantErrIs、并行表格、基于映射的表格、仅包含错误的表格,以及提升可读性的结构体对齐方式。
  • references/refactoring.md — 如何识别臃肿的表格,并将其改写为显式子测试,附带改写前后的示例。

只有在下方摘要不足以完成当前任务时,才阅读参考文件。

1. 适合使用表驱动测试的场景

只有在以下条件全部满足时才使用表格:

  • 所有用例测试同一个函数
  • 断言模式相同——输入后比较输出
  • 用例差异仅在数据,而非设置或验证逻辑
  • 至少 3 个用例——少于 3 个时,显式测试更清晰

典型用例:纯函数、解析器、验证器、格式化器。

func TestParseSize(t *testing.T) {
    tests := []struct {
        name    string
        input   string
        want    int64
        wantErr bool
    }{
        {name: "plain bytes", input: "1024", want: 1024},
        {name: "kilobytes suffix", input: "4KB", want: 4096},
        {name: "empty string", input: "", wantErr: true},
        {name: "negative size", input: "-1", wantErr: true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            got, err := ParseSize(tt.input)
            if tt.wantErr {
                require.Error(t, err)
                return
            }
            require.NoError(t, err)
            assert.Equal(t, tt.want, got)
        })
    }
}

所有用例结构相同,循环体只有几行,添加一个用例只需写一个结构体字面量。没有分支,也没有条件判断。

2. 不适合使用表驱动测试的场景

  • 每个用例都需要复杂的设置——结构体中的 setupMock/setupFunc 函数字段意味着表格掩盖了复杂性。应编写显式子测试。
  • 少于 3 个用例——结构体定义的代码量比两个普通测试函数还多。
  • 存在多个分支路径——循环体中的 if tt.shouldError / if tt.wantRedirect 表明每个分支都是伪装成共用结构的不同测试。应拆分。

关于上述各种问题的改写前后示例,请参阅 references/refactoring.md。

3. 结构体设计规则

  1. 每个字段都必须至少在 2 个用例间取值不同。 如果某个字段在所有用例中都相同,那它属于设置内容——应移到表格外。
  2. 将 name 字段设为简短的场景描述: "returns error for negative amount",不要用 "case1" 或 "success"。
  3. “是否应该报错?”使用 wantErr bool——先检查它,并在循环体中提前 return。
  4. 当调用方必须识别特定错误时,使用哨兵错误 wantErrIs error; 使用 require.ErrorIs 进行断言。
  5. 字段数不超过 5 个。 更多字段意味着场景对表格来说过于复杂——应拆分为多个测试函数。

完整的字段模式示例见 references/patterns.md。

4. 循环体必须简单

表驱动测试的关键是每个用例的执行逻辑相同。循环体应少于约 10 行:调用、错误检查、比较。 如果循环体逐渐累积条件判断或针对单个用例的设置,说明表格已超出适用范围——应改写为显式子测试。

5. 并行表驱动测试

for _, tt := range tests {
    t.Run(tt.name, func(t *testing.T) {
        t.Parallel()
        got := Transform(tt.input)
        assert.Equal(t, tt.want, got)
    })
}
  • Go 1.22+ 会为每次迭代单独作用域化循环变量——无需通过 tt := tt 捕获变量。对于 Go <1.22,仍然需要捕获。
  • 只有当被测函数没有副作用且不存在共享可变状态时,才使用 t.Parallel()。

6. 重构臃肿的表格

症状修复方式
结构体有 8 个或更多字段按场景拆分为多个测试函数
结构体中有 setupFunc 字段提取为独立子测试,并显式设置
循环体中有 if tt.shouldX每个分支都是不同的测试——拆分
每个用例中有相同的 3 个字段移到表格外的共享设置中
添加一个用例需要了解其他所有用例表格已超出适用范围

决策流程图

  1. 函数是否纯粹(输入 → 输出,无副作用)? 是 → 表驱动测试可能是理想选择。转到 2。 否 → 优先考虑显式子测试。

  2. 所有用例是否共享完全相同的断言模式? 是 → 表驱动测试。转到 3。 否 → 显式子测试。

  3. 每个用例能否用 ≤5 个结构体字段表示? 是 → 表驱动测试。 否 → 按场景拆分为单独的测试函数。

  4. 循环体是否 ≤10 行? 是 → 完美。 否 → 表格隐藏了复杂性。重构。

验证清单

  1. 表格结构体仅包含各用例之间会变化的字段
  2. 每个用例都有描述性的 name 字段
  3. 循环体 ≤10 行,且没有分支
  4. 结构体中没有 setupFunc 或 mockFunc 字段
  5. wantErr 是简单的布尔值或哨兵值,而不是字符串匹配
  6. 用例覆盖:正常路径、错误路径、边缘情况(空、nil、零、最大值)
  7. 使用 t.Run 为每个用例封装具名子测试
  8. 仅当函数没有副作用时才使用 t.Parallel()

评论

支持 Markdown