关于 testing
testing 是面向开发者和编码 Agent 的软件测试策略技能,帮助选择测试层级、提升可测试性,并为 Agent 编写的代码制定测试方法。
主要功能
- 01选择合适的测试层级
- 02优先验证可观察行为
- 03优先使用真实协作者而非模拟对象
- 04指导编码 Agent 测试
- 05涵盖生产环境反馈实践
适用场景
- 01规划新功能的测试
- 02从难写的测试发现设计问题
- 03为 Agent 编写的代码设置防护
- 04减少脆弱或过大的 E2E 测试集
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ bsene/ skills --skill testing安装后在 Claude Code 中输入/testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/acceptance-testing.mdai-agent-testing.mdasync-and-time.mdbdd-review.mdmartin-fowler-testing.mdmocks-and-fragility.mdprinciples.mdsmoke-tests.mdtcrdd.mdtdd-and-atdd.mdtest-strategy.mdtestdesiderata.mdtrustworthy-tests.mdunit-testing-principles.mdweb-api-testing.mdxp-testing-practices.mdreferences/smoke-tests/curl.mdhurl.mdreferences/tcrdd/autonomous-execution.mdbenchmarks.mdexample-and-errors.mdfurther-reading.mdgit-cycle.mdphase-guide.mdpragmatics.md详细介绍
测试——策略与理念
测试不是事后验证,而是对设计的反馈。难以编写的测试揭示了生产代码中的耦合。难以命名的测试揭示了对行为的理解不够清晰。测试套件是系统的动态规范——明确代码实际上应该做什么。无论代码由人类还是 AI Agent 编写,这一点都成立。
核心理念
| 理念 | 实践含义 | 可避免的反模式 |
|---|---|---|
| 难以测试 = 设计有问题 | 重构设计,而不是测试 | 为了强行实现可测试性而模拟一切 |
| 测试是规范,而非验证 | 将测试命名为描述行为的句子 | 根据实现细节命名测试 |
| 模拟是异味,而非策略 | 优先使用真实协作者或伪对象 | 充斥模拟对象、即使存在 Bug 也能通过的测试套件 |
| 纯函数是理想的可测试性方案 | 将 I/O 推到边界;让业务逻辑保持纯粹 | 业务逻辑与副作用纠缠在一起 |
| 通过必须意味着可以发布 | 构建失败是停止发布的信号,而非噪声 | 反复重试不稳定的测试,直到测试套件碰巧通过 |
| 测试行为,而非设计原则 | SRP 和其他架构规则指导代码应如何组织;应断言代码做了什么,而不是它是否遵循某项设计规则 | 统计方法数量、检查类的职责,或断言是否符合 SOLID,而不是验证可观察到的结果 |
测试组合
测试金字塔描述的是一种实用的默认分布,而非硬性规定。单元测试构成底层,因为它们快速且精确。更高层级的测试也很有价值,因为它们会检验连接、协作和用户旅程。测试组合原则是:选择能为场景提供所需信心的最低成本层级。
| 层级 | 数量 | 速度 | 目的 | 脆弱性 |
|---|---|---|---|---|
| 单元测试 | 多 | ms | 纯行为规范 | 聚焦行为时较低 |
| 组件测试 | 一些 | ms–秒 | 通过公共接口测试一个可部署的服务 | 低至中 |
| 集成测试 | 一些 | 秒 | 在一个进程/系统中使用真实协作者 | 中 |
| 契约测试 | 一些 | ms–秒 | 服务边界处的消费者/提供者预期 | 聚焦契约时较低 |
| E2E/UI | 少量 | 秒–分钟 | 首次出现的用户旅程 | 高——应尽量减少 |
| 生产环境 | 持续 | 近实时 | 合成监控和领域可观测性 | 运维层面——不能替代其他测试 |
如果高层级测试发现了 Bug,应先在最低的有效层级复现,再进行修复。聚焦的回归测试是永久的防护措施;高层级测试则是发现问题的机制。
决策指南
| 问题 | 答案 → 行动 |
|---|---|
| 这是纯行为问题还是连接问题? | 纯行为 → 单元测试;连接 → 组件/集成测试 |
| 场景是否跨越服务边界? | 是 → 针对消费者/提供者预期编写契约测试;E2E 保留给一个用户旅程 |
| 这是关键用户旅程吗? | 编写一个 E2E 测试,然后将各种变化推到更低层级 |
| 我需要模拟对象,还是可以使用真实协作者? | 优先使用真实协作者;仅当对外交互本身就是待测行为时才使用模拟对象 |
| 测试很难编写吗? | 将此视为设计反馈;在添加更多替身之前先重构 |
| 测试是否依赖计时或非确定性 I/O? | 修正设计;快速测试必须具有确定性 |
| 我是否见过这个测试在应该失败时失败? | 否 → 注入一个明显的 Bug,确认测试失败,然后撤销更改 |
| 生产环境中的行为会让我们意外吗? | 添加合成监控、领域可观测性,或进行探索性调查 |
| 谁在主导——人类还是 AI Agent? | 人类 → TCRDD 微步骤;Agent → 采用独立信任信号的测试优先批次 |
使用 AI 编码 Agent 进行测试
严格遵循红/绿/重构流程,是为了管理_人类_的短期记忆。Agent 的情况正好相反——工作记忆容量大,但容易产生幻觉,也容易钻测试的空子,而不是满足测试要求。因此,测试节奏和信任模型都需要调整。
| 旧有假设 | Agent 时代的调整 |
|---|---|
| 强制采用微步骤 TDD,以获得快速反馈 | 让 Agent 先编写测试,再分较大的批次实现 |
| 作者可以测试自己的代码 | 分离测试编写者和编码者的角色,避免测试将缺陷本身固化 |
| 代码审查是主要的兜底手段 | 结合覆盖率、变异测试、复杂度指标和有针对性的人工审查 |
| 验收测试是普通的代码产物 | 将其视为编码 Agent 必须遵守的不可变护栏 |
| 差异越小越好 | 优先考虑连贯的测试分组,而不是为了缩小差异而分散测试 |
| 生产环境检查是可选的 | 持续监控提示词/输出质量、偏移、延迟和故障模式 |
有关完整的流水线和信任模型,请参阅 使用 AI 编码 Agent 进行测试。对于大模型应用,还请阅读 Martin Fowler 的测试纲要。
测试反模式
| 反模式 | 问题 | 修正 |
|---|---|---|
| 过度使用模拟 | 模拟会掩盖集成错误 | 优先使用真实协作者或伪对象 |
| 测试实现细节 | 重构时测试会失效 | 测试可观察行为,而不是方法调用图 |
| 脆弱/不稳定的测试 | 结果会变成噪声 | 隔离状态;控制时间、随机性和异步完成情况 |
| 缓慢的单元测试 | 设计耦合过于紧密 | 重构设计,而不是测试 |
| 过于庞大的 E2E 测试套件 | 收益递减;每种变化都要付出全部成本 | 保留一条 E2E 路径,并将变化下沉到较低层级 |
| 仅通过 E2E 测试服务边界 | 成本高且脆弱 | 添加消费者驱动的契约测试 |
| 在测试中编写逻辑 | 测试可能带有它本应捕获的同一个错误 | 使用硬编码的预期值,不使用控制流 |
| 将覆盖率视为质量 | 高覆盖率仍可能意味着断言薄弱 | 用覆盖率查找缺口;用变异测试/契约测试检验测试强度 |
| 没有生产环境反馈循环 | 未知的故障模式仍不可见 | 添加可观测性、合成监控和探索性测试 |
| 跳过遗留代码测试 | 重构期间风险会不断增加 | 在改变行为前建立特征化测试网 |
| AI Agent 测试自己的代码 | 测试会将错误写入规范 | 分离测试编写者和编码者的角色 |
| Agent 为了通过而弱化验收测试 | 防护措施被破坏 | 让编码 Agent 无法修改验收测试 |
| 将架构规则作为行为进行测试 | 结构规则不是用户可见的行为 | 用静态分析强制执行架构;测试可观察结果 |
综合示例
之前——难以测试(问题出在设计上):
function sendOverdueReminders() {
const users = db.query("SELECT * FROM users WHERE balance < 0");
for (const user of users) {
if (Date.now() - user.lastReminded > WEEK) {
emailClient.send(user.email, "You owe us money");
}
}
}
这个函数迫使测试控制数据库、时钟和电子邮件客户端。业务规则——应该提醒谁——被埋在副作用中。
诊断: 将 I/O 推到边界,并保持规则纯粹。
之后——纯核心,I/O 位于边界:
function usersToRemind(users, now) {
return users.filter(
(user) => user.balance < 0 && now - user.lastReminded > WEEK,
);
}
function sendOverdueReminders() {
for (const user of usersToRemind(db.allUsers(), Date.now())) {
emailClient.send(user.email, "You owe us money");
}
}
usersToRemind([...], fixedNow) 是一个无需替身的一行单元测试。其余外壳只是一个轻量级集成问题。通过改变设计,而不是添加模拟,就能让难以测试变得易于测试。
按需阅读
| 阅读场景 | 文件 |
|---|---|
| 了解四项跨领域测试原则 | 测试原则 |
| 根据 12 项理想属性审查测试质量 | TestDesiderata — 质量审计 |
| 审查测试是否符合 BDD 及其结构 | BDD 测试审查 |
| 指导 AI 编码 Agent:节奏、流水线、信任模型 | 使用 AI 编码 Agent 进行测试 |
| 选择模拟什么以及断言什么 | 模拟对象与脆弱性 |
| 审查测试的可信度、可维护性和可读性 | 可信且可维护的测试 |
| 将场景分配到不同测试层;测试配方;遗留代码评估 | 测试策略与遗留代码 |
| 测试异步代码、计时器、事件或伪造模块 | 异步、时间与模块伪造 |
| 应用 Fowler 风格的测试组合、测试替身、生产环境反馈和 LLM 实践 | Martin Fowler 的测试准则 |
| 为 CI 门禁识别并编写冒烟测试(Hurl 或 curl) | 冒烟测试 |
| 绿灯时自动提交、失败时自动还原的红/绿/重构(TCR/TCRDD) | TCRDD |
| 根据 Given/When/Then 场景驱动行为;crap + mutation 质量门禁 | 验收测试工作流 |
| 测试风格、函数式核心、模拟边界、数据库测试、反模式(Khorikov) | 单元测试原则 |
| 测试优先节奏:伪造、三角验证、可测试设计、验收 TDD(Koskela) | TDD 与 ATDD |
| 测试 Web API:基于风险的检查、契约、性能、安全、SLO | Web API 测试 |
| Arrange-Act-Assert、模拟对象、来自 c2 wiki 的 TDD 笔记 | XP 测试实践 |
基准测试
场景:.benchmarks/scenarios/testing-001-agent-test-strategy.md · 运行:2026-08-31 · 日志:.benchmarks/runs/2026-08-31/testing-001-agent-test-strategy.json
| 模型 | 未使用 | 使用 | 变化 |
|---|---|---|---|
| claude-opus-4-8 | 67% | 100% | +33% |
| claude-sonnet-4-6 | 100% | 100% | +0% |
| claude-haiku-4-5 | 67% | 100% | +33% |
通过(运行于 2026-08-31)。Opus 和 haiku 提升 +33;sonnet 已达上限。门禁依据
.agents/skills/skill-optimizer/rules/release-gates.md。