--- name: typescript-testing description: 应用具备仓库感知能力的 TypeScript 测试设计、行为依据、隔离性与 mock 边界标准。用于编写或评审单元测试时。 --- # TypeScript 测试规则 ## 前置条件检测 在选择测试框架或命令之前,先检查 `package.json`、锁文件、测试配置以及现有测试的导入方式。仅当配置了 Vitest 时才应用 Vitest 专属规则;否则使用仓库已配置的 TypeScript 测试工具,同时保留以下关于行为、隔离性与依据的规则。如果无法确认可运行的测试工具,请报告已检查的路径以及缺失的命令或配置。 ## 测试框架 - **Vitest**:当仓库配置或现有测试选用了 Vitest 时使用 - 测试导入:`import { describe, it, expect, beforeEach, vi } from 'vitest'` - Mock 创建:使用 `vi.mock()` ## 基本测试策略 ### 质量要求 - **回归保护**:将测试集中在关键路径、业务逻辑以及一旦回归就会造成实质影响的行为上。当某个未受保护的行为存在实质性回归风险时,添加测试 - **独立性**:每个测试都能独立运行,不依赖其他测试 - **可复现性**:控制时间、随机性、环境变量与外部 I/O,使相同输入产生相同的可观测结果 - **可读性**:每个测试只描述一种行为,将准备/执行/断言分离,并将测试数据限制为该行为实际用到的值 ### 测试类型与范围 1. **单元测试** - 验证单个函数或类的行为 - Mock 所有外部依赖 - 数量最多,采用细粒度实现 2. **集成测试** - 验证多个组件之间的协作 - 对属于被测行为一部分的进程内真实组件使用真实实现;关于外部 I/O 的处理见“Mock 范围决策” - 验证实现主要验收标准或跨越进程内组件边界的流程 3. **跨功能验证** - 当新功能触及共享的集成点时,若现有功能失效会破坏主要用户旅程或公开契约,或降低次要可观测行为,则现有功能的连续性成为一项证明义务。在能够暴露其失效的成本最低的边界上加以证明 - 验证模式:现有功能运行 -> 启用新功能 -> 验证现有功能的连续性 - 成功标准:保留来源验收标准所指定的响应字段与可观测行为;仅当需求或项目配置定义了处理时间阈值的取值与测量方法时,才应用该阈值 - 设计为可在 CI/CD 流水线中自动执行 ## 测试实现规范 ### 目录结构与命名 - 测试位于被测模块旁的 `__tests__/` 目录中 - 测试文件:`{target-file-name}.test.ts` - 集成测试文件:`{target-file-name}.int.test.ts` - 测试套件:描述目标功能或场景的名称 - 测试用例:描述预期行为的名称 ### 测试代码质量规则 保持每个已提交的测试处于有效状态。当测试保护的是当前行为时应修复它;只有在其对应行为已不再被要求、且源需求或实现契约确认该行为可移除时,才能删除该测试。 ## 测试质量标准 ### 边界与错误场景覆盖 在覆盖正常路径的同时,包含边界值与错误场景。 ### 字面量预期值 使用与实现计算过程无关的预期值:直接将契约的值写成字面量,或从独立的权威 fixture 或规范中获取。若预期值与被测对象使用相同的常量或公式计算得出,即便两者都错了测试依然会通过。当输入由 mock 提供时,只要实现对输入做了转换,预期值就应与 mock 的返回值不同。 ### 基于结果的验证 验证结果,而非调用顺序或调用次数。 ### 有意义的断言 每个测试都应断言其使用方所依赖的属性,以及该操作所建立的状态,而不仅仅是断言“有返回值”。 ### 能力探测的后置条件 用于检查某项功能是否可用的探测,只有在通过使用方边界进行检查,并断言使用方所需的确切属性时才算通过。 命令的退出状态、成功的导入以及对象的存在性只能说明该事物是可访问的,因此应将它们视为探测的前置条件,而将面向使用方的属性放入断言中。 | 探测意图 | 准备阶段的证据(单独不足以证明) | 应改为断言 | |---|---|---| | 模块可用 | `import` 解析成功、`expect(mod).toBeDefined()` | 通过使用方的入口点调用导出的函数,并断言其返回值或产生的效果 | | 命令可用 | 退出码为 0 | 调用方使用的输出、文件或状态变化 | | 配置已生效 | 配置文件解析成功 | 该配置本应改变的可观测行为 | | 迁移已执行 | 命令报告成功 | 通过真实引擎查询返回迁移后的数据结构 | ### Mock 范围决策 对于协作关系正被测试的每个进程内组件,使用真实实现。当测试的目标是更高层的行为时,替换直接的外部 I/O 依赖;当外部适配器、查询、迁移或服务契约本身就是测试目标时,使用真实引擎或与生产环境等效的测试实例。进行替换时,仍需断言被测对象发送的请求以及它所接受的响应结构,以确保边界契约得到验证。 ### 基于属性的测试(fast-check) 当设计文档的验收标准带有 Property 标注时,使用 `fc.assert(fc.property(...))` 形式的 fast-check。 ## Mock 类型安全强制要求 将 mock 的类型限定为被测对象实际使用的接口部分——即 `Pick`——而非完整接口,这样未被使用的方法即便发生变化也不会破坏测试,而被使用的方法发生变化则会。使用 `satisfies` 针对该 picked 类型约束 mock 对象字面量,使多余或命名错误的属性在编译期就会报错。 ## 数据层测试 ### Mock 无法验证的内容 Mock 验证的是调用模式,因此以下数据层属性在仅使用 mock 的测试中会被漏检: - Schema 不匹配(表名、列名、数据类型) - 查询正确性(join、过滤、聚合、分组) - 数据库约束(NOT NULL、UNIQUE、外键) - 迁移兼容性(导致代码与 schema 不同步的 schema 变更) **判定规则**:当这些属性中的某一项本身就是测试目标——包括仓储层或数据访问实现本身——时,应按照下方的分级方案对真实引擎进行验证。当数据访问只是一个依赖而非测试目标时,使用 mock 是正确的做法:例如接收数据的业务逻辑(mock 仓储层,测试服务层)、错误处理路径(连接失败、超时),以及数据层本身不是测试目标的单元测试。 ### 真实数据库测试(依赖环境) 针对真实数据库引擎验证数据层正确性的可选方案: - **容器化数据库**,用于 CI 环境 - **内存数据库**,用于快速反馈(注意:方言差异可能掩盖问题) - **专用测试数据库**,配合种子数据 按以下顺序选择第一个符合仓库现有依据的选项: 1. 若存在 CI 已配置的数据库测试工具,则使用它。 2. 否则,若可以执行容器,则在容器中使用相同的数据库引擎。 3. 仅当被验证的行为与方言无关时才使用内存数据库;并记录未被验证的方言相关行为。 4. 若仓库已经预置并隔离了专用测试数据库,则使用它。 当以上都不可用而数据层正确性又是测试目标时,应停止并报告缺失的环境前提条件。仅凭 mock 得出的结果不能作为查询、schema、约束或迁移正确性的依据。 ### AI 生成代码与 schema 感知 生成的数据访问代码可能在语法上正确,却引用了并不存在的 schema 元素,而基于 mock 的测试无论如何都会通过。因此设计文档应包含明确的 schema 引用,以便评审时可以将文档记录的 schema 与数据访问代码进行交叉核对。