--- name: vetta-testing description: 为 OpenVetta 的功能变更、Bug 修复、重构、公共合同和 UI 交互设计、编写或审查测试;在决定测试范围、补充回归测试、覆盖用户常见使用流程或评估测试质量时使用。纯文档或没有运行时行为变化的编辑不使用。 --- # OpenVetta 测试设计与编写 ## 目标 编写能在真实回归发生时可靠失败、能说明哪项用户行为或稳定合同被破坏、且维护成本与风险相称的测试。不要以覆盖代码行、内部调用次数或大量快照代替行为证据。 开始前完整阅读目标目录适用的最近一级 `AGENTS.md`、相关包 README、现有测试和实现。测试命令与门禁以仓库根 `AGENTS.md` 和 `docs/dev/quality-gates.md` 为准;更近一级规则可以收紧本 Skill。 ## 先定义要证明的行为 编码前列出: 1. 本次会改变的用户可见行为或公共合同。 2. 必须保持的不变量、兼容路径、事件顺序和副作用。 3. 用户在受影响功能中最可能执行的常见使用操作路径或流程。 4. 风险最高的边界、失败、取消、重试、恢复或持久化场景。 把每个场景写成 `Given / When / Then`: - `Given`:用户已有的状态、合理默认配置和必要前置条件。 - `When`:用户从正常入口执行的连续操作,而不是直接调用本应隐藏的内部函数。 - `Then`:用户可见结果、公开输出、持久化事实、外部副作用或明确错误。 只覆盖本次改动实际影响的连续步骤,不机械穷举全产品路径。若多个常用入口经过不同的状态、协议或持久化边界,且改动同时影响这些边界,应分别覆盖代表性流程。 ## 按任务选择测试组合 - Bug 修复:先建立能在旧实现失败的最小复现或明确基线,再写回归测试;修复后还要运行受影响的常见用户流程测试。 - 新功能或行为变化:至少覆盖一条代表性常见用户流程,再为新增分支、关键边界及可恢复失败添加定向测试。 - 内部重构:明确保持的不变量,使用已有测试、差分测试或合同测试证明外部行为、事件顺序和副作用未变。 - 公共合同变更:检查生产者、消费者及兼容路径,覆盖 Schema、协议、序列化、错误和版本边界。 - UI 交互变化:覆盖真实渲染、事件接线、用户输入、提交、焦点、可访问语义、加载和错误恢复;纯函数测试不能替代组件测试。 - 权限、并发、取消、重试和生命周期:覆盖允许与拒绝、竞争顺序、清理、重复执行和恢复等会造成高后果的状态转换。 纯文档、无逻辑文案、类型转发或已由现有合同准确覆盖的机械改动可以不新增测试,但仍需运行适当验证并在交付中说明依据。 ## 识别用户常见使用流程 “常见”指普通用户通过产品正常入口、在合理默认配置下完成目标,而不是测试专用入口、调试开关或罕见内部调用。根据功能实际支持的能力和本次改动选择流程,例如: - 会话与 Agent:新建或恢复会话 → 输入任务或添加上下文 → 发送 → 查看流式回复或工具结果 → 继续追问、取消或重试 → 重新打开后看到正确历史。 - 工具与权限:触发需授权的操作 → 查看权限说明 → 批准或拒绝 → 操作继续或被阻止 → 收到明确结果和错误反馈。 - Plugin、Skill 与 MCP:安装或导入 → 校验清单与确认权限 → 启用 → 在会话中调用 → 禁用、更新或卸载后状态正确。 - 设置、Provider 与运行时:打开设置 → 修改并校验配置 → 保存 → 当前会话或重启后生效 → 配置无效或资源不可用时得到可恢复反馈。 - Agent Team:选择或创建团队 → 发起任务 → 成员执行并展示进度 → 汇总结果 → 继续协作、结束或恢复团队会话。 - 文件与项目:打开项目或选择文件 → 执行读取、编辑或生成操作 → 查看变更与状态 → 确认、重试或重新打开后结果一致。 这些是选路示例,不是要求每次修改都覆盖全部流程。优先从产品入口、用户文档、现有组件接线和生产调用链确认真实流程,不根据测试便利性发明产品行为。 ## 选择最低但充分的测试层级 - 单元测试:纯计算、解析、选择、校验和状态转换。 - 组件测试:UI 渲染、输入、提交、焦点、可访问语义和异步反馈。 - 合同测试:公共 API、IPC/RPC、Tool/Prompt Schema、事件、序列化和跨包边界。 - 集成测试:跨模块流程、真实内部装配、持久化以及多个状态转换。 - E2E:只有真实浏览器、Electron、进程、文件系统、打包布局或网络边界无法由低层测试证明时使用。 用户流程测试从用户可触达入口或最接近该入口的组件、服务公共接口进入,尽量使用真实内部装配。能够由组件、合同或集成测试证明时,不要机械升级为 E2E。只有用户在当前任务中明确要求时才运行 `verify:ui:*`。 ## 写出稳定且有诊断力的测试 - 测试名称描述用户行为或稳定合同及预期结果,例如“用户拒绝工具授权后不执行操作并看到已取消状态”。 - 使用 Arrange / Act / Assert 或 Given / When / Then,使前置状态、动作和结果清晰分离。 - 断言用户可见结果、公开返回值、持久化事实和必要副作用;不要锁定私有字段、脆弱 DOM 层级或偶然调用次数。 - 一个测试表达一个清晰场景,但允许验证同一用户流程中的连续状态,不要把流程拆成只能按顺序运行的多个测试。 - 对纯逻辑的多组边界输入优先使用表驱动测试;对用户流程保留可读的独立场景。 - 时间、随机数、并发和重试使用可控时钟、固定输入或显式同步点;不要用任意 `sleep` 等待。 - 每个测试独立建立和清理状态,释放计时器、订阅、进程、临时文件和数据库连接,不依赖执行顺序。 - 快照只用于稳定且整体形状本身就是合同的输出;不要用大面积快照掩盖关键断言。 ## Mock 与测试替身 只在真实外部边界使用 Mock,例如 Provider、网络、操作系统、外部进程或不可控时间。内部协作者优先使用真实实现、内存存储或行为明确的 Fake。 不要把被测模块内部全部 Mock 后只验证调用: ```ts expect(sendMessage).toHaveBeenCalledOnce(); ``` 应优先证明用户目标或稳定事实成立: ```ts expect(screen.getByText("分析完成")).toBeVisible(); expect(await conversationStore.load(sessionId)).toContainEqual( expect.objectContaining({ text: "分析完成" }), ); ``` 只有当“调用某个外部边界”本身就是公开合同或必要副作用时,才断言其参数和次数。 ## 审查测试质量 审查新增或现有测试时逐项判断: 1. 若实现出现目标回归,这个测试是否真的会失败? 2. 是否覆盖了本次改动影响的代表性用户常见操作流程,而不只是孤立函数或异常分支? 3. 断言是否面向可观察行为和稳定合同? 4. Mock 是否只位于真实外部边界,是否意外绕过了关键生产接线? 5. 异步、时间、并发和资源清理是否确定且无任意等待? 6. 测试层级是否足够,又没有不必要地升级为缓慢 E2E? 7. 失败信息能否直接指出被破坏的行为? 发现缺口时,优先补能捕获真实回归的最低层测试,不重复相同断言,也不为了覆盖率数字制造低价值用例。 ## 运行与交付 使用仓库统一入口,不使用裸 `bun test`、`bunx vitest`、`npx vitest` 或直接 `vitest`: ```bash bun scripts/quality/run-vitest.mjs --run bun run test:pkg bun run test:changed bun run check:quick bun run check ``` 选择与改动相称的最小充分范围:先跑定向测试,一轮编辑后针对任务文件跑 `check:quick`,涉及多个包或范围不明确时跑 `test:changed`。代码任务完成后确认影响范围内的测试、lint、守卫和必要的类型检查通过,不默认扫描全仓,也不重复已通过且未再修改的检查。全量核查使用显式的 `check:full` / `check:lint:full`;Biome 或 EditorConfig 配置变更按配置影响扩大 lint 范围。`check` 不运行测试,不能替代行为测试。 交付时说明覆盖了哪些用户流程或合同、实际运行了哪些测试与检查、哪些未运行及原因、剩余风险和兼容性影响。不得声称未执行的验证已经通过。 ## Skill 维护 `.agents/skills/vetta-testing/SKILL.md` 是事实源。`.claude/skills` 为指向 `../.agents/skills` 的 Git 符号链接(Windows 可能检出为目标路径文本);修改时核对这一指向,不创建独立副本。