关于 shift-left-testing
面向开发与 QA 团队,提供将质量验证前移至需求、开发和 PR 阶段的实践指南。内容包括 QA 协作、TDD 决策、PR 检查清单和成熟度模型。
主要功能
- 01将 QA 纳入迭代规划
- 02使用 Three Amigos 梳理需求
- 03协作设计测试用例
- 04指导 TDD 与测试后写的选择
- 05提供 PR 测试质量检查清单
- 06评估团队成熟度与下一步
适用场景
- 01在开发开始前发现验收标准缺口
- 02为缺陷修复先编写失败测试
- 03审查 PR 或 AI 生成代码的测试质量
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ petrkindlmann/ qa-skills --skill shift-left-testing安装后在 Claude Code 中输入/shift-left-testing确认已加载。
附带文件references/tdd-examples.mdreferences/templates.md
详细介绍
快速导航
| 情况 | 前往 |
|---|---|
| QA 只有在开发“完成”后才看到功能 | 开发/QA 协作 → 在冲刺规划中引入 QA |
| 需要在开发前讨论需求 | 开发/QA 协作 → 三方讨论会 |
| 正在决定这项工作是否采用 TDD | TDD 推进 → 何时采用 TDD,何时采用先开发后测试 |
| 审查 PR 的测试质量 | PR 审查清单 |
| 审查 AI 生成或使用 AI 的 PR | PR 审查清单 → 更改由 AI 生成时 |
| “我的团队目前处于什么阶段,下一步该做什么?” | 左移成熟度模型 |
了解现状
先检查 .agents/qa-project-context.md——如果文件存在,就使用其中的信息(团队组成、开发/QA 工作流、冲刺结构、质量目标),并跳过其中已解答的问题。
当前开发/QA 工作流
-
QA 何时首次看到某项功能? PR 提交后?合并到预发布环境后?还是只有出现 Bug 时?答案能反映出当前质量保障流程有多靠后。
-
谁在何时编写测试? 只有开发人员编写?开发“完成”后才由 QA 编写?双方都会编写,但时间安排不同?在改变现状前,必须先了解当前的职责分工。
-
如何沟通需求? 书面规格说明?口头交接?只有 Figma 链接,没有验收标准?需求不明确是左移实践要预防的头号缺陷来源。
-
团队是否有兴趣采用 TDD? 以前尝试过吗?后来坚持下来了,还是半途而废?了解过去的尝试可以避免重蹈失败的覆辙。
-
PR 审查流程是怎样的? 谁负责审查?可测试性是否属于审查标准?合并前是否必须有测试?PR 审查是引入质量检查阻力最小的环节。
-
团队的完成定义是什么? 有明确文档,还是不成文的约定?是否包含测试?是强制执行,还是仅为理想目标?完成定义是“进行中”和“完成”之间的约定边界。
-
QA 如何参与冲刺规划? 完全不参与?参与估算咨询?主动细化用户故事?参与冲刺规划的程度决定了 QA 的思维能多早进入开发周期。
核心原则
1. 质量是每个人的责任
质量不是由 QA 团队在开发结束后执行的一个阶段,而是整个工作流的一项属性:产品经理编写可测试的需求,开发人员随代码一同编写测试,代码审查人员检查可测试性,QA 工程师设计测试策略并发现自动化测试遗漏的问题。质量由每个人共同负责时,谁最先遇到缺陷,谁就能及时发现它。
2. 越早发现,修复成本越低(总体而言)
在用户故事细化时发现缺少验证规则,只需花 5 分钟沟通。同一个缺陷若在生产环境中才被发现,就会演变成事故、紧急修复、事后复盘,并侵蚀用户信任。缺陷发现得越晚,修复成本就会急剧上升——需求细化 < 设计 < 开发 < QA < 预发布环境 < 生产环境。这种趋势确实存在,且有充分佐证;但具体倍数并无依据。 广为引用的“1 倍 → 100 倍”表格源自 IBM Systems Science Institute 一张没有日期、没有来源,也没有公开方法论的培训图表,因此应将任何精确数字视为示例,而非实测结果。应优先讲述上文具体的成本案例,而不是编造数字。左移实践旨在尽早在成本较低的左侧阶段——需求细化到开发——发现缺陷,避免它们进入 QA、预发布环境或生产环境。
3. QA 是嵌入式协作环节,而非关卡
传统 QA 在开发结束时充当关卡:代码被“扔过墙”交由测试。左移则将 QA 融入整个流程。QA 参与需求细化,与开发人员结对设计测试,审查 PR 的可测试性,并通过持续测试及早验证。关卡模式会造成瓶颈和对立关系;嵌入式模式则促进协作和共同负责。
4. 可测试性是设计考量
难以测试的代码通常也难以维护、难以调试,而且很可能包含缺陷。可测试性应与性能、安全性和易用性一样,作为首要设计约束。在设计阶段——甚至还没写下一行代码之前——开发人员就开始思考“我们要如何测试这个功能?”,最终形成的架构会更简洁、模块化程度更高,也更可靠。
5. 从小处着手,证明价值,再逐步扩展
同时引入所有左移实践会让团队不堪重负。选择一项实践(通常是 PR 审查清单或 Three Amigos),用数据证明其价值(逃逸的缺陷更少、PR 周期更短),再以此为依据推动下一项实践。文化变革每次都靠一个切实证明的成果逐步实现。
开发/QA 结对模式
QA 参与 Sprint 规划
**具体做法:**QA 工程师参加 Sprint 规划,并积极参与需求细化。他们会针对边缘情况提出澄清问题,找出缺失的验收标准,并在开发开始前标出风险区域。
规划期间的具体行动:
- 审查每个需求的验收标准是否可测试。 每项验收标准都应可验证——“用户可以对表格排序”是可测试的;“表格对用户友好”则不是。
- 找出边缘情况和负面场景。 数据为空时会怎样?输入达到最大长度时呢?有多个并发用户时呢?操作进行到一半网络故障时呢?
- 标出集成风险。 这个需求会涉及第三方 API 吗?会更改数据库架构吗?会影响现有测试数据吗?
- 估算 QA 工作量。 自动化所需时间、探索性测试所需时间、环境设置所需时间。将这些纳入 Sprint 容量规划。
- 为每项需求确定测试方法。 业务逻辑采用单元测试,API 变更采用集成测试,面向用户的流程采用 E2E 测试。
模板:每项需求的 QA 问题
Story: [PROJ-1234] Add coupon code to checkout
───────────────────────────────────────────────
QA questions before development starts:
1. What happens if the coupon is expired?
2. What happens if the coupon is already used (single-use)?
3. Can multiple coupons be stacked?
4. What error message does the user see for invalid codes?
5. Does the discount update the total in real-time or on submit?
6. Is there a rate limit on coupon validation attempts?
Test approach:
- Unit: coupon validation logic, discount calculation, expiry check
- Integration: coupon API endpoint, database state after redemption
- E2E: apply coupon in checkout flow, verify discount on confirmation
- Exploratory: edge cases with currency rounding, max discount limits
Three Amigos 会议
开发开始前,三种视角进行一次结构化的 15-30 分钟讨论。
三种视角:
- 产品/业务: 用户需要什么?这为什么重要?
- 开发: 我们要如何实现?有哪些技术限制?
- QA/测试: 我们要如何验证?可能会出什么问题?
可选的第四位伙伴(AI 参与者): 编码 Agent 可以根据验收标准,在会议期间生成边缘情况和反向场景。将 AI 的输出视为待验证的检查清单,而非决策依据——验收标准仍由人类负责。
会议形式(最多 30 分钟):
- 产品介绍需求(5 分钟)——用户需求、验收标准
- 开发提出澄清问题(5 分钟)——可行性、依赖关系
- QA 提出测试问题(5 分钟)——边缘情况、错误状态、可测试性
- 小组找出遗漏项(10 分钟)——补充缺失的标准,明确列出假设
- 达成一致并确定后续步骤(5 分钟)——更新需求、记录风险、商定测试方法
**适用情况:**风险评分为 Medium 或更高的需求、涉及支付/身份验证/数据完整性的事项、需求含糊不清的情况、跨团队需求。
**可跳过的情况:**复现步骤明确的简单缺陷修复、仅涉及文案/文本的变更、不会造成行为变化的依赖项更新。
关于测试优先设计的 QA 结对协作
QA 和开发人员在实现之前共同协作编写测试用例。这不是完整的 TDD,而是通过协作来践行测试思维。
工作方式:
- 开发人员和 QA 一起讨论(或共享屏幕),用时 20-30 分钟
- QA 描述计划测试的场景
- 开发人员编写测试签名(函数名称、输入、预期输出)
- 双方共同确定哪些测试属于单元测试、集成测试和端到端测试
- 开发人员以这些测试为目标实现功能
**结对协作的示例产出:**一组事先商定的测试签名,涵盖单元测试、集成测试和端到端测试。完整的优惠券功能结对协作产出见 references/tdd-examples.md。
QA 审查 PR
QA 工程师审查拉取请求时,会重点关注可测试性和测试质量,与其他开发人员进行的代码审查相辅相成。
刚开始进行 QA PR 审查的团队可以这样做:
- 先只安排一位 QA 审查高风险 PR。 刚开始不要尝试审查每个 PR。
- 将审查时间限制在 15 分钟内。 QA 检查的是测试质量,而不是重新审查业务逻辑。
- 使用下方的 PR 审查清单。 清单提供了具体、客观的标准,无需主观判断。
- 以建议而非要求的方式发表评论。 表述为“可以考虑为无内容状态添加测试”,而不是“缺少测试”。
- 跟踪价值。 记录 QA 审查发现问题的情况。经过 2-4 周后,与团队分享次数,以展示投资回报。
TDD 引导
Red-Green-Refactor
TDD 遵循严格的三步循环。每一步都有明确的目标和明确的退出条件。
┌──────────────────────────────────────────────────────┐
│ RED: Write a failing test │
│ - Test describes the desired behavior │
│ - Test MUST fail (if it passes, it tests nothing) │
│ - Write the minimum test to specify one behavior │
│ │
│ GREEN: Make the test pass │
│ - Write the minimum code to pass the test │
│ - No extra features, no premature optimization │
│ - It is OK if the code is ugly │
│ │
│ REFACTOR: Clean up │
│ - Improve code structure without changing behavior │
│ - All tests still pass after refactoring │
│ - Remove duplication, improve naming, simplify │
└──────────────────────────────────────────────────────┘
示例:使用 TDD 编写密码强度验证器——先编写失败的测试,再编写能通过测试的最少代码,然后在不改变行为的前提下重构为规则数组。完整的 Red-Green-Refactor 演练见 references/tdd-examples.md。
TDD 与测试后编写:决策指南
TDD 并非总是合适的选择。使用本指南来决定。
| 场景 | 方法 | 原因 |
|---|---|---|
| 纯业务逻辑(验证器、计算器、转换器) | TDD | 输入和输出明确,反馈快速,测试可以记录行为 |
| 有明确复现步骤的缺陷修复 | TDD | 先编写失败的测试 = 证明修复有效 |
| 契约明确的 API 端点 | TDD | 请求和响应是天然的测试边界 |
| 探索性 UI 原型开发 | 测试后编写 | 设计尚不稳定,测试会不断重写 |
| 第三方集成 | 测试后编写 | 需要先了解 API 的行为 |
| 复杂的数据迁移 | 使用固定数据进行测试后编写 | 先编写样例数据,再测试转换过程 |
| 性能优化 | 使用基准测试进行测试后编写 | 需要先建立基线,再测试改进效果 |
| AI 生成的实现 | TDD(先编写测试) | 大模型很容易生成看似能通过测试的代码;失败的测试就是 Agent 必须满足的规范,也是检查 AI 输出最有效的手段。 |
用 TDD 修复缺陷(试金石)
每个缺陷修复都应从一个能复现该缺陷的失败测试开始。这种做法可以提供三项保证:
- 你理解这个缺陷。 如果你无法编写一个会失败的测试,就说明你还不理解这个缺陷。
- 修复确实有效。 应用修复后,测试会通过。
- 缺陷不会再出现。 测试会保留在测试套件中,防止回归。
references/tdd-examples.md 中提供了一个先编写失败测试的完整示例(JPY 零小数位舍入缺陷)。
面向团队的 TDD 练习
用于培养 TDD 肌肉记忆的短时练习(30-60 分钟):
| 练习 | 难度 | 关键经验 |
|---|---|---|
| FizzBuzz | 入门 | 基本的 Red-Green-Refactor 循环 |
| String Calculator | 入门 | 渐进增加复杂度,处理边界情况 |
| Roman Numerals | 中级 | 识别模式,重构 |
| Bowling Game | 中级 | 状态管理,处理复杂规则 |
| Gilded Rose | 高级 | 在测试框架下重构遗留代码 |
**形式:**结对编程,45 分钟,每 5 分钟轮换一次驾驶员。用 15 分钟进行复盘:哪些地方比较难?哪些地方感觉自然?你会做哪些不同的尝试?
PR 审查清单:QA 视角
审查 PR 的测试质量和可测试性时,请使用此清单。并非每项都适用于每个 PR——请根据变更范围自行判断。
测试存在且有意义
- 测试与代码变更一同提交。 新功能?添加新测试。修复 Bug?添加回归测试。重构?现有测试仍能通过(最好还能有所改进)。涉及行为变更的 PR 若没有测试,必须给出明确理由。
- 涵盖正常流程和边界情况。 至少包括:有效输入、无效输入、空值/null 输入、边界值。对于面向用户的功能,还应包括错误状态、加载状态和空状态。
- 测试描述行为,而非实现。 测试名称应像规格说明:
rejects expired coupon with clear error message,而不是test coupon validator function line 42。
代码可测试
- 函数具有明确的输入和输出。 纯函数很容易测试。带副作用的函数应隔离副作用(依赖注入、包装函数)。
- 依赖项可注入。 数据库客户端、HTTP 客户端、时钟和随机数生成器应作为参数传入或注入,而不是直接在业务逻辑中导入。
- 不使用硬编码的魔法值。 常量应有名称且可配置。测试无需修改生产代码即可覆盖这些值。
测试质量
- 选择器使用稳定的策略。 E2E 测试使用
data-testid、getByRole或getByLabel,而不是 CSS 类或 XPath。请参阅test-reliability中的选择器稳定性评分。 - 断言具体明确。 使用
expect(result).toEqual({ status: 'expired', code: 'COUPON_EXPIRED' }),而不是expect(result).toBeTruthy()。 - 测试数据具有确定性。 不依赖当前日期、随机值或未经显式控制的自增 ID。使用工厂或固件。
- 测试会清理自身产生的影响。 删除创建的记录。恢复已修改的状态。避免测试之间相互污染。
- 测试名称描述测试场景。 即使读者不熟悉代码,仅凭测试名称也应能理解测试内容。
- 不编写只为覆盖率服务的测试。 执行代码却没有有意义断言的测试会虚增覆盖率,却无法提供保障。
当变更由 AI 生成或使用 AI 时
当 PR 包含由 AI Agent 编写的代码,或引入 AI 驱动的功能时,请执行以下额外检查。
- 披露 AI 来源。 PR 描述应注明所用 Agent、模型以及生成的内容(以便审阅者适当调整审查力度)。
- 测试先写或由人工编写。 AI 生成的实现配上 AI 生成的测试会形成闭环——测试与实现至少有一方应由人工编写或经过人工严格审查(参见上文的 TDD 决策指南表格行)。
- 固定提示词和模型版本。 对于使用 AI 的功能(大模型调用、提示词模板),提示词版本和模型 ID 应存储在基于功能标志的配置存储中,例如 LaunchDarkly AI Configs(GA 2025)或同等方案,而不是嵌入容易变动的临时字符串。PR 应引用配置键,而不是直接粘贴提示词。
- 接入运行时紧急开关。 所有 AI 功能都应通过功能标志发布,以便无需重新部署即可禁用。将左移预防与右移遏制结合起来。
- 包含提示词评估测试。 至少有一个回归测试,用代表性输入验证提示词的行为(参见
ai-system-testing)。 - 不含虚构的 API 或导入项。 审阅者应核实每个导入的符号确实存在——大模型会编造听起来似乎合理的 API。
Definition of Done 模板
Definition of Done(DoD)是团队对“完成”含义的共同约定。每个用户故事在看板上移至“Done”之前都必须符合该约定。
推荐的质量门禁 DoD
完整的 DoD 会将检查项归入代码完成、测试、质量门禁通过、文档和部署就绪几个类别。测试类别要求:为业务逻辑编写单元测试;为 API/服务变更编写集成测试;为面向用户的关键路径编写端到端测试;覆盖边界情况和错误状态;对中/高风险变更完成手动探索性测试。质量门禁类别要求:CI 流水线通过;没有新增 lint/类型错误;代码覆盖率不低于基线(基线应是一个具体数值,而不是凭空设定的绝对阈值)。完整的可直接复制粘贴的检查清单请参见 references/templates.md。
执行 DoD
只有得到执行,DoD 才有效。以下是三种执行机制:
- CI 中的自动化门禁。 测试必须通过,覆盖率不得下降,lint 检查必须通过。未经团队负责人批准,不得绕过这些要求。
- PR 模板检查清单。 在 PR 模板中加入 DoD 检查清单。审阅者核实各项均已勾选。
- Sprint 评审验证。 在 Sprint 评审期间,只有符合 DoD 的用户故事才会被验收。“功能能用,但测试还没写”意味着尚未完成。
左移成熟度模型
评估团队当前所处的阶段,并确定切实可行的下一步改进措施。
Level 1:被动响应
表现:
- 只有开发完成后 QA 才进行测试
- 在预发布或生产环境中发现 Bug
- 没有自动化测试,或测试覆盖率极低
- 需求含糊不清;QA 在测试期间才发现缺漏
- “QA 阶段”是 Sprint 末尾一个独立的环节
下一步: 将 QA 纳入 Sprint 规划。首先让 QA 在开发开始前,针对每个用户故事提出澄清问题。衡量指标:规划阶段发现的需求缺漏数量与测试阶段发现的数量之比。
Level 2:关卡
表现:
- QA 会审查 PR,但不参与设计
- 存在自动化测试,但要等功能完成后才编写
- 已制定 Definition of Done,但测试项经常被跳过
- QA 是一个检查点,而非协作者
- 开发后测试导致 Bug 发现较晚,返工很常见
下一步: 为高风险用户故事引入 Three Amigos。QA、开发人员和产品人员在开发开始前讨论需求、边界情况和测试方案。衡量指标:QA 测试期间发现的 Bug 数量是否减少(随着上游质量提升,该数量应逐步下降)。
Level 3:嵌入式
表现: QA 参与冲刺规划和用户故事细化。开发人员在开发过程中编写单元测试和集成测试。PR 审查包含可测试性检查。QA 和开发人员结对设计测试用例。通过自动化质量门禁强制执行 DoD。
下一步: 对缺陷修复引入测试优先实践(每次缺陷修复都从一个失败的测试开始)。逐步将其扩展到纯业务逻辑的 TDD。衡量指标:回归率(应接近零)。
Level 4:协作式
表现: 对中高风险用户故事,Three Amigos 已成为标准做法。开发人员对业务逻辑和缺陷修复采用 TDD。QA 专注于探索性测试、策略和风险分析。定期跟踪并审查质量指标。质量由跨职能团队共同负责。
下一步: 将左移扩展到架构和设计审查。QA 在实施开始前审查系统设计文档的可测试性。衡量指标:缺陷逃逸率(持续低于 5%)。
Level 5:预防式
表现: 质量融入每个阶段。缺陷逃逸率持续低于 3%。QA 工程师专注于策略、指导和系统性改进。生产问题极少发生,且出现时会触发根因分析。团队已无法想象没有早期质量实践的工作方式。
维持此级别: 每季度进行成熟度评估。新团队成员从第一天起就接受质量实践培训。回顾会议包含质量指标。
自我评估工作表
对八项实践(QA 参与规划、Three Amigos、PR 审查、开发期间编写测试、缺陷修复先写失败测试、业务逻辑采用 TDD、强制执行 DoD、指标审查)按“从不/有时/通常/始终”的等级评分,以确定团队所处的 Level 1–5。可查看 references/templates.md 中可打印的工作表和评分区间。
反模式
将“左移”作为裁撤 QA 的借口
把“开发人员编写所有测试”重新包装成左移,以此为理由取消 QA 岗位。左移改变的是质量发生的时间,而不是由谁负责。QA 工程师具备测试思维、风险分析技能和探索性测试能力,而开发人员通常不会培养这些能力。取消 QA 并要求开发人员“多测试就行”,带来的只会是盲点,而非节省。
只有仪式,没有实质
把 Three Amigos 会议当作走过场,没人提出尖锐问题。如果会议没有至少促成一项验收标准的修改或发现一个新的边界情况,那就不是真正的讨论。将“Three Amigos 发现的缺口”作为一项指标跟踪。
时时刻刻都用 TDD
在 UI 原型设计、探索性技术验证或设计仍不稳定的实验性功能中强制采用 TDD。TDD 最适用于预期行为明确的情况。对于不确定的领域,应先进行技术验证,再围绕最终形成的设计编写测试。请使用上文的决策指南。
团队不认同的质量门禁
在未说明原因或未让团队参与设定阈值的情况下,强制实施严格的质量门禁(覆盖率阈值、必须经过 QA 审查)。团队若认为门禁是强加的,就会觉得它拖慢进度并设法绕过。协作制定的门禁则会得到团队维护。
在错误的层级测试所有内容
为应由单元测试验证的业务逻辑编写 E2E 测试。为需要通过 E2E 验证的用户流程编写单元测试。左移不仅仅是“更早测试”,而是“尽早在正确的层级测试”。计算错误需要单元测试,而不是浏览器测试。
衡量活动,而非结果
跟踪“三 Amigos 会议的召开次数”,而不是“规划阶段发现的缺陷与生产环境中发现的缺陷”。活动是投入,结果是产出。应衡量左移实践是否确实减少了逃逸缺陷和返工。
验证
证明这些门禁确实有效——从未触发的门禁毫无证明力:
- 没有测试的行为变更 PR 会被拦截。 创建一个临时 PR,提交行为变更但不添加测试(或使覆盖率低于基线),确认 CI 变红且合并按钮已禁用。如果 PR 仍能合并,说明这个门禁只是摆设。
- DoD 检查清单会显示在 PR 模板中。 确认
.github/pull_request_template.md(或你所用平台的等效文件)中包含 DoD 检查清单,让审阅者在每个 PR 中都能看到它——test -f .github/pull_request_template.md && grep -qi "unit test" .github/pull_request_template.md。 - AI 紧急关闭开关可以关闭功能。 对于任何由 AI 驱动的路径,在功能标志平台中将其功能标志切换为禁用,并确认该路径随之关闭,无需重新部署。
完成标准
- 已更新 Definition of Done,加入测试标准(单元、集成和 E2E 门禁),并已提交到仓库(例如,已写入
.github/pull_request_template.md) - 已将包含测试覆盖率检查的 PR 审查清单签入 PR 模板,并由分支保护规则强制要求
- 已针对即将推出的功能至少开展一次 Three Amigos 会议,记录发现的差距,并更新工单中的验收标准
- 已开展首次开发/QA 配对会议,并将商定的测试签名提交到仓库
- CI 中已启用合并前质量门禁(测试通过、覆盖率未下降、lint 检查),且已观察到无测试 PR 被拦截(见验证步骤 1)
- 每条有风险或由 AI 驱动的代码路径都有对应的功能标志,且已在功能标志平台中验证其禁用开关(见验证步骤 3),确保预防(左移)和遏制(右移)措施一并落地
参考文件(位于 references/ 中)
- tdd-examples.md — 可运行代码,展示开发/QA 配对的测试优先设计、密码验证器的 Red-Green-Refactor 演练,以及先编写失败测试的 bug 示例。
- templates.md — 可直接复制粘贴的 Definition of Done,其中包含质量门禁和左移成熟度自评工作表。
相关技能
- unit-testing -- 编写有效单元测试的详细模式,这是左移开发实践的主要产物。
- ai-qa-review -- 自动审查 PR 的测试质量和可测试性,扩展此处介绍的 QA 审查模式。
- test-strategy -- 左移实践在日常层面所落实的整体测试方法。
- qa-project-context -- 决定应优先引入哪些左移实践的项目特定背景信息。
- quality-postmortem -- 左移实践失效、缺陷逃逸时,复盘可识别出原本能够发现问题的实践。
- qa-project-bootstrap -- 新团队成员入职时,包括向他们介绍团队的左移实践。