关于 go-test-table-driven
面向 Go 开发者和编码 Agent 的测试技能,说明何时适合表驱动测试,以及如何精简用例和循环逻辑,并涵盖并行测试与复杂表格的重构。
主要功能
- 01判断表驱动测试的适用场景
- 02保持测试用例聚焦
- 03为用例编写描述性名称
- 04精简测试循环逻辑
- 05并行运行安全的用例
- 06重构臃肿的测试表
适用场景
- 01用重复模式测试解析器和校验器
- 02选择测试表或显式子测试
- 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. 结构体设计规则
- 每个字段都必须至少在 2 个用例间取值不同。 如果某个字段在所有用例中都相同,那它属于设置内容——应移到表格外。
- 将
name字段设为简短的场景描述:"returns error for negative amount",不要用"case1"或"success"。 - “是否应该报错?”使用
wantErr bool——先检查它,并在循环体中提前return。 - 当调用方必须识别特定错误时,使用哨兵错误
wantErrIs error; 使用require.ErrorIs进行断言。 - 字段数不超过 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 个字段 | 移到表格外的共享设置中 |
| 添加一个用例需要了解其他所有用例 | 表格已超出适用范围 |
决策流程图
-
函数是否纯粹(输入 → 输出,无副作用)? 是 → 表驱动测试可能是理想选择。转到 2。 否 → 优先考虑显式子测试。
-
所有用例是否共享完全相同的断言模式? 是 → 表驱动测试。转到 3。 否 → 显式子测试。
-
每个用例能否用 ≤5 个结构体字段表示? 是 → 表驱动测试。 否 → 按场景拆分为单独的测试函数。
-
循环体是否 ≤10 行? 是 → 完美。 否 → 表格隐藏了复杂性。重构。
验证清单
- 表格结构体仅包含各用例之间会变化的字段
- 每个用例都有描述性的
name字段 - 循环体 ≤10 行,且没有分支
- 结构体中没有
setupFunc或mockFunc字段 wantErr是简单的布尔值或哨兵值,而不是字符串匹配- 用例覆盖:正常路径、错误路径、边缘情况(空、nil、零、最大值)
- 使用
t.Run为每个用例封装具名子测试 - 仅当函数没有副作用时才使用
t.Parallel()