- 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<T, 'usedMethod'>`——而非完整接口,这样未被使用的方法即便发生变化也不会破坏测试,而被使用的方法发生变化则会。使用 `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 与数据访问代码进行交叉核对。
عرض على GitHub