--- name: integration-e2e-testing description: 选择并设计能在可观测边界证明已接受行为的最小集成/E2E 测试集。用于编写或评审 E2E 或集成测试时。 --- # 集成测试与 E2E 测试设计/实现规则 ## 参考资料 - **[references/e2e-design.md](references/e2e-design.md)** - 基于 Playwright 的 E2E 测试设计原则(候选属性、选择标准、候选记录) - **[references/e2e-environment-prerequisites.md](references/e2e-environment-prerequisites.md)** - service-integration-e2e 环境前提条件(种子数据、认证 fixture、环境检查清单);fixture-e2e 不需要实际运行的服务或真实数据库 ## 测试类型与选择 | 测试类型 | 目的 | 范围 | 外部依赖 | 文件格式 | 实现时机 | |-----------|---------|-------|---------------|-------------|----------------------| | Integration | 验证组件间进程内交互 | 部分系统集成(进程内模块;对于 UI 组件,React/TS 使用 RTL+MSW) | 已 Mock 或进程内 | `*.int.test.ts` | 与实现同步创建 | | fixture-e2e | 使用确定性 fixture 验证浏览器行为 | 使用 mock 后端/fixture 驱动状态的完整 UI 流程 | 仅 Mock/fixture — 无实际运行的服务 | `*.fixture-e2e.test.ts` | 与 UI 功能同步创建 | | service-integration-e2e | 验证只有运行中的技术栈才能暴露的契约 | 跨服务的完整系统 | 实际运行的本地服务或服务级桩(stub) | `*.service-e2e.test.ts` | 在所需服务就绪后执行 | **Lane 选择(仅限 E2E)**: - 面向用户的 UI 流程默认测试通道是 **fixture-e2e** —— 它在真实浏览器中针对确定性 fixture 运行,能捕捉单元/集成测试遗漏的问题(按钮无响应、状态未更新、导航失效),并且无需基础设施设置即可在 CI 中运行 - 当证明义务是真实的跨服务行为时,例如数据持久化、事务一致性或外部服务契约,选择 **service-integration-e2e** 从已接受的证明义务出发,将每项义务分配给能暴露其失败的最低成本边界,去除重复覆盖,并保留能覆盖每个剩余独立失败的最小测试集。让这些义务决定测试数量。一个功能在某个测试通道中可以合理地产生零项测试。 ## 行为优先原则 ### 候选依据 一个集成/E2E 候选项需陈述: - 该边界上由已接受行为所命名的可观测结果; - 一个跨越所选测试通道所涉及组件的实质性失败; - 一个能够复现该失败的自动化测试装置或受控环境 将可在隔离环境中观测到的行为路由至单元/组件验证。若受控环境不可用,将其记录为 service-integration-e2e 的证明前提条件。 ### 候选项路由 - 当业务逻辑准确性、数据完整性、用户可见行为和可观测的错误处理需要这些边界时,将其保留在集成/E2E 测试池中 - 将纯粹的实现细节和数据转换路由至单元测试,将性能声明路由至性能验证,将纯布局声明路由至视觉或 UI 检查 - 当外部契约本身是证明目标时,使用服务级桩或受控的本地服务来表示该契约 ## 骨架规范 ### 必需的骨架格式 一个符合项目测试匹配模式的已提交文件,对其运行器而言必须保持有效。使用所检测框架中最小的挂起(pending)套件(`describe` 加 `it.todo`,或其等价形式),仅包含测试框架的 import 语句和必需的注释。实现任务会替换掉挂起用例,并加入应用层 import、断言、fixture 和 mock 设置以配合实现。 每个测试必须包含以下注解。 ```typescript // AC: "[验收标准原文]" // Behavior: [触发] -> [处理] -> [可观测结果] // @lane: integration | fixture-e2e | service-integration-e2e // @dependency: none | [组件名称] | full-ui (mocked backend) | full-system // @real-dependency: [组件名称](可选,当“测试边界”指定了非 mock 设置时) // Primary failure mode: [必须使已实现的测试失败的具体回归问题] // Proof obligation: [已实现的测试必须断言的边界与可观测状态] // Verification items: [用以证实该义务的观察项] ``` **`@lane` 选择规则**: - `integration` — 组件间进程内交互,无浏览器(例如 React/TS 的 RTL+MSW,或任意语言中的进程内模块/处理器集成) - `fixture-e2e` — 使用 mock 后端/fixture 驱动状态的浏览器级 UI 验证。`@dependency` 通常为 `full-ui (mocked backend)` - `service-integration-e2e` — 针对实际运行的本地服务或桩的浏览器级或端到端验证。`@dependency` 为 `full-system` ### 属性注解 ```typescript // Property: `[验证表达式]` // fast-check: fc.property(fc.[arbitrary], (input) => [invariant]) ``` ## 测试集选择 1. 从约束性产物或任务中读取已接受行为以及每项已记录的证明义务。 2. 对每项义务,明确必须使测试失败的实质性失败原因,以及能暴露该失败的可观测状态。 3. 检索现有测试。仅当其针对同一边界且会因该失败而失败时,才复用该覆盖。 4. 将该义务分配给能满足要求的范围最窄的测试通道: - 单元/组件测试——当隔离执行即可暴露该行为时; - integration——用于进程内组件契约; - fixture-e2e——用于后端可为确定性的浏览器行为; - service-integration-e2e——用于持久化、事务、消息或外部契约,其失败只能通过该实际运行的边界暴露 5. 在保持断言与失败之间清晰映射关系的前提下,将由同一场景证明的多项义务合并。将不同的设置和失败模式保留在各自独立的场景中。 6. 仅输出剩余的最小覆盖集。在每个骨架中记录已接受行为、主要失败、证明义务、所选测试通道和 mock 边界。 从已接受行为和仓库证明边界出发进行选择;产品分析数据和数值估算并非必需。当已接受行为或所需契约在查阅约束来源与仓库依据后仍无法确定时,需上报处理。 ## 实现规则 ### 基于属性的测试实现 当存在 Property 注解时,必须使用 fast-check: - 以 `fc.assert(fc.property(...))` 格式编写 - 在实现中直接体现骨架中的 `// fast-check:` 注释 - 发现失败用例时,将其作为具体的单元测试添加(防止回归) ### 行为验证实现 **行为描述验证级别**: | 步骤类型 | 验证目标 | 示例 | |-----------|--------------------| --------| | Trigger | 在 Arrange 中复现 | API 失败 -> mockResolvedValue({ ok: false }) | | Process | 中间状态或调用 | 函数调用、状态变化 | | Observable Result | 最终输出值 | 返回值、错误信息、日志输出 | **通过标准**:若“可观测结果”作为测试目标的**返回值或 mock 调用参数**得到验证,则通过 ### 验证项确定规则 | 骨架状态 | 验证项确定方法 | |----------------|---------------------------------------| | 已列出 `// Verification items:` | 使用 expect 实现所有列出的项目 | | 无 `// Verification items:` | 从 “Behavior” 描述中的“可观测结果”推导 | | 两者皆有 | 优先采用验证项,将行为描述作为补充 | ### 集成测试 Mock 边界 选取第一条与被评审断言相匹配的规则: | 条件 | 应使用的边界 | |---|---| | 被测对象正是外部适配器、查询、迁移或服务契约本身 | 使用真实边界,或在 `service-integration-e2e` 测试通道中使用服务级桩 —— mock 无法证明其所替代的契约 | | 未被测试的外部 API 或网络调用 | Mock | | 被测的组件间交互 | 真实的进程内组件 | | 调用本身即是测试所验证的对象(例如日志输出) | 可验证的 mock(`vi.fn()`) | | 调用及其目标均不在测试范围内 | 使用真实对象,或忽略 | ### E2E 测试执行条件 **fixture-e2e**: - 与 UI 功能实现阶段同步执行 - 使用 mock 后端/fixture 驱动状态(`@dependency: full-ui (mocked backend)`) - 借助确定性 fixture 设置在 CI 中运行 **service-integration-e2e**: - 在待验证组件已存在、且该测试所经过的服务已启动的最早阶段执行 - 通过实际运行的本地服务或服务级桩来验证被测组件(`@dependency: full-system`) ## 评审标准 ### 骨架与实现的一致性 | 检查项 | 失败条件 | |-------|-------------------| | Property Verification | 存在 Property 注解但未使用 fast-check | | Behavior Verification | 未对“可观测结果”进行 expect | | Verification Item Coverage | 已列出的验证项未包含在 expect 中 | | Mock Boundary | 集成测试中 mock 了内部组件 | ### 实现质量 | 检查项 | 失败条件 | |-------|-------------------| | AAA Structure | Arrange/Act/Assert 划分不清晰 | | Independence | 测试间存在状态共享、执行顺序依赖 | | Reproducibility | 依赖日期/随机数,结果不稳定 | ### 共享变更操作的路由一致性 当多个路由到达同一个变更操作时——例如 CLI 路径与 HTTP 处理器、定时任务与手动触发、批量端点与单条目端点——需沿四个维度进行比较:校验、分类、资源边界,以及读取、解析、变更、上报的顺序。 只有能够决定意图的来源——需求、设计文档或 ADR——才能允许存在差异。测试处于该决策的下游——它们记录的是既有行为,因此一个覆盖了宽松路由的既有测试只是确认了该绕过行为,而非赋予其正当性。一旦某项差异被允许,测试便用于验证其行为是否符合该决策。 当某项差异没有任何允许来源时,需要一个能暴露该绕过问题的测试:通过跳过检查的路由驱动该变更操作,并断言原本被跳过的检查所要保护的状态。 | 检查项 | 失败条件 | |-------|-------------------| | Validation parity | 一个路由校验了某项输入,而另一路由未经检查即接受,且没有需求或契约允许此差异 | | Classification parity | 同一失败在不同路由中被分类不同,改变了调用方所观察到的结果 | | Resource-bound parity | 一个路由强制执行了大小、数量或超时边界,而另一路由缺失该限制 | | Operation-order parity | 各路由在读取/解析/变更/上报的顺序上存在差异,导致某一路由可能在校验之前变更,或在持久化之前上报 | | Bypass coverage | 一项未加说明的差异,没有测试通过宽松路由驱动该变更操作来加以覆盖 |