关于 go-testing
为不同代码库提供统一 Go 测试规范的技能,面向编写或维护 Go 代码的 Agent。内容涵盖测试结构、替身选择、数据库测试及验证流程。
主要功能
- 01通过导出 API 编写黑盒测试
- 02优先使用有状态 Fake
- 03使用真实 Postgres 做集成测试
- 04异步与模糊测试指南
- 05测试和 lint 命令
适用场景
- 01为 Go 功能补充测试
- 02测试 SQL 驱动的行为
- 03检查并发 Go 代码
- 04审查 Go 测试套件
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ o-marsters-1997/ my-claude-code --skill go-testing安装后在 Claude Code 中输入/go-testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
assets/golangci-depguard.ymlassets/pgtest/pgtest_test.gopgtest.gosettings.gosweep_test.goevals/evals.jsonreferences/examples.mdintegration-db.md详细介绍
Go 测试
介绍如何在每个代码库中编写 Go 测试。测试哪些内容,以代码库自带的 testing-policy 为准(如果有):它使用哪些类型的测试、每种测试的价值、功能需要哪些测试,以及随着代码库发展可复用的模式。它绝不重述如何编写测试,因此两者不会重叠。红—绿—重构循环请使用 tdd。
这里不适用的默认做法
这些习惯常常会不假思索地冒出来。写测试前先对照此表检查,完成前再检查一次,因为任务耗时较长时,它们很容易在后期重新出现。
| 你会采用的做法 | 改用这种做法 |
|---|---|
testify assert/require,或任何断言库 | 使用普通的 if 加 t.Errorf;非标量值使用 cmp.Diff |
reflect.DeepEqual | 使用 cmp.Diff(want, got),并以 (-want +got) 的形式报告 |
使用 wantErr bool 和 if tt.wantErr 的表格 | 在一个测试中使用两张表:一张测试成功情况,另一张使用 errors.Is 检查错误 |
gomock、mockery、EXPECT().Times(1) | 使用有状态的内存 fake;断言其最终状态 |
sqlmock 或模拟的 database/sql 驱动 | 使用 internal/pgtest 中的真实 Postgres |
用 TestMain 在每个包启动一个容器 | 使用 pgtest.New(t):每个代码库一个容器,每个测试一个数据库 |
在测试之间使用 TRUNCATE,共享行 | 每个测试使用一个全新的数据库,以确保 t.Parallel() 安全 |
使用 time.Sleep 等待 goroutine | 使用代码发出的信号通道、截止时间轮询,或 testing/synctest |
使用 strings.Contains(err.Error(), "...") | 针对导出的 sentinel 或类型使用 errors.Is / errors.As |
| 新增测试依赖 | 只使用 github.com/google/go-cmp;未经询问,不添加其他依赖 |
规则
- 通过导出的 API,在黑盒
package foo_test中测试行为。只有当单元按设计未导出时才使用package foo,并将export_test.go作为最后手段。 - 断言输出和状态,而不是断言调用了哪些函数。若重构未改变行为却会导致测试失败,这种测试就是变更检测器:删除它。
- 选择最忠实的替身:真实实现,其次是仿真对象,再其次是桩对象,最后才是 Mock。只对跨系统边界的状态变更调用(发送邮件、扣款)使用 Mock,并且要手写记录器,不要用 Mock 库。
- 仿真对象有状态且行为真实:使用相同的哨兵值,表现出相同的未找到行为。一套契约测试同时针对仿真对象和真实实现运行。要强制触发错误路径时,使用只实现一个方法并嵌入接口的桩对象,绝不要在仿真对象上设置故障开关。
- 使用
internal/pgtest,针对真实的 Postgres 测试 SQL。如果仓库没有internal/pgtest,请从此技能的资源中安装:加载references/integration-db.md。 - 使用
cmp.Diff(want, got)进行比较,并报告(-want +got)。如果go.mod中没有github.com/google/go-cmp,就在首次修改测试时使用go get添加它,并在同一次修改中修正规则与之矛盾的 lint 规则或文档。如果用户或仓库明确禁止添加依赖(“仅使用标准库”),则用==比较标量,用slices.Equal/maps.Equal比较切片和映射,并逐字段比较结构体。无论哪种情况,都不要使用reflect.DeepEqual。 - 失败消息格式为
Func(in) = got, want want。使用errors.Is/errors.As匹配错误;需要区分错误的调用方应获得一个导出的哨兵值或类型。 - 对每个待测函数或方法编写一个外层测试(
TestFunc、TestType_Method);每个用例都在t.Run中运行。若用例使用相同方式检查,则使用表格:由带有name字段的结构体组成的切片,并通过t.Run(tt.name, ...)运行。若检查方式不同,则在同一个外层测试中编写具名子测试。按用例字段分支时,应编写具名子测试,而不是使用一个表格。绝不要为某个场景另写一个顶层Test。 - 使用
t.Error继续执行;只有在后续步骤无法运行时才使用t.Fatal。绝不要在测试未启动的 goroutine 中调用t.Fatal;应通过 channel 传回错误。 - 辅助函数接收
t,调用t.Helper(),遇到设置错误时使测试失败,并注册t.Cleanup。辅助函数负责设置,不负责断言。不要编写通用的assertEqual。 - 不要使用
time.Sleep等待。应等待 channel、在截止时间前轮询条件,或在涉及计时器时使用synctest.Test(Go 1.25+)。 - 只有在测试不共享任何状态时才调用
t.Parallel():不得使用全局变量、t.Setenv、t.Chdir或共享行。pgtest数据库绝不共享,因此数据库测试可以并行运行。 - 比较 JSON 时,先解码 JSON,绝不要按字节比较。比较前规范化 golden 输出;每个
-update差异都必须由人工审核。 - 使用属性(不发生 panic、往返转换)对不可信输入的解析器进行模糊测试;提交
testdata/fuzz/。 - 通过真实路由器和中间件测试 HTTP 处理器,构建方式应与
main中一致。
运行
直接运行 go test ./... 即可执行所有测试,包括数据库测试:不使用构建标签、不使用 -short 跳过测试,也不通过 make 目标运行。构建标签仅用于端到端测试。如果没有 Docker,且未设置 TEST_DATABASE_URL,pgtest 会失败并给出同时提及两者的提示;不要用
t.Skip 掩盖问题。完成前运行 go test -race ./... 和 golangci-lint run。
查阅位置
| 如果你正在编写…… | 请加载 |
|---|---|
涉及 Postgres 的测试,或尚无 internal/pgtest 的代码仓库 | references/integration-db.md |
数据库测试的 CI(TEST_DATABASE_URL、服务容器) | references/integration-db.md |
| 单元、表驱动、fake 加契约套件、HTTP、异步、黄金文件、模糊测试或基准测试 | references/examples.md |
assets/pgtest/ 存放辅助工具及其自身的测试;assets/golangci-depguard.yml 存放一条 lint 规则,用于确保 testcontainers 的导入仅限于 internal/pgtest。
另请参阅
| 技能 | 适用情况 |
|---|---|
go-idiomatic | 被测代码的所有相关内容 |
tdd | 先编写测试,一次处理一个行为 |
testing-policy | 测试哪些内容:测试类型、测试价值、可复用模式(按代码仓库划分) |
评论
支持 Markdown