SuperAPI AI 工具集

tests

测试与质量

查看项目

更新于 10-16 22:15

技能介绍

用途

本文档是一份测试规范(Testing Guidelines),用于指导项目中的测试编写方式,统一测试理念、命令与测试文件布局约定。它面向需要新增功能、修复缺陷或维护测试的开发者,帮助其判断该写哪类测试、放在哪里、如何运行与验证。

测试理念

文档主张优先采用两类测试:

  1. 真正的集成测试——使用真实运行时、真实文件系统、真实网络调用,不使用 mock、stub 或 fake,用以证明系统端到端可用。
  2. 针对纯逻辑或隔离逻辑的单元测试——测试输入输出清晰的纯函数或高度隔离的模块,因代码无外部依赖,无需 mock。

单元测试以 .test.ts[x] 文件与所测源码同目录存放。集成测试位于 tests/ 目录,主要测试装置包括:

  • tests/ipc——依赖 IPC、聚焦后端行为的测试。
  • tests/ui——使用真实 IPC 与 happy-dom 全应用渲染的前端集成测试。
  • tests/e2e——使用 Playwright 的端到端测试,用于验证 happy-dom 难以覆盖的浏览器行为。

此外,src/browser/stories 中的 stories 主要用于人工视觉验证 UI 变更。

Mock 使用原则

避免大量使用 mock 的测试,因为这类测试验证的是实现细节而非行为。如果需要 mock 才能测试,应考虑重构代码使其更易测试。文档指出一个例外:存在 mockAiRouter 可用于模拟 LLM 响应。测试中 LLM 的使用遵循以下规则:

  • 验证与 LLM 提供商集成的测试使用真实 LLM(例如断言正确识别上下文超限错误)。
  • 验证 LLM 逻辑周边行为的测试使用 mockAiRouter(例如断言 LLM 响应后消息正确排队)。
  • 完全不涉及智能体行为的逻辑,不使用真实 LLM 或 mockAiRouter(例如测试打开终端窗口是否工作)。

同时避免同义反复的测试(简单映射、实现代码的完全复制),聚焦不变量与边界失败。

何时测试

理想情况下所有新功能和缺陷都应被良好测试。没有稳健的测试策略就不要实现功能或修复。修复缺陷时始终从测试开始(TDD 实践):先在测试中复现缺陷,再修复生产代码,最后验证测试通过。

测试运行方式

tests/ 中的所有测试在设置 TEST_INTEGRATION=1 的情况下通过 bun x jest 运行。位于 src/ 中的测试(通常为单元测试)通过 bun test 运行。

运行时与检查

  • 切勿终止正在运行的 Xum 进程;本地验证依赖以下命令:
    • make typecheck
    • make static-check(快速本地 lint/typecheck/fmt 路径;仅 oxlint,无类型感知 lint 规则)
    • make static-check-full(增加对所有文件的类型感知 ESLint、文档链接与 CI 中使用的 bench-agent 验证)
    • 定向测试调用(例如 bun x jest tests/ipc/sendMessage.test.ts -t "pattern")
  • 仅在本地定向检查通过后才等待 CI 通过。
  • 优先使用外科手术式的定向测试调用,而非运行完整测试套件。
  • 保持工具函数纯净,或将外部副作用参数化,以便测试。

覆盖率期望

  • 优先采用简化现有代码的修复;此类简化通常不需要新测试。
  • 增加复杂度时,应新增或扩展测试。若覆盖率需要新基础设施,先提出测试装置方案,再在其中添加测试。
  • 被要求 TDD 时,编写真实的仓库测试(不使用 /tmp 脚本)并提交。
  • 将复杂逻辑抽取到易于测试的工具函数中。以最少用例实现广泛覆盖,证明功能确实重要。

Storybook

  • 确保所有 UI 变更至少被一个 story 覆盖(许多变更已被现有 story 覆盖)。
  • 仅使用全应用 story(App.*.stories.tsx)。不要添加孤立组件 story,即使是很小的 UI 变更(本仓库不使用也不接受)。
  • 使用 storybook/test 工具(within、userEvent、waitFor)的 play 函数与 UI 交互并设置所需视觉状态。不要仅为 storybook 便利而给生产组件添加 props。
  • 保持 story 数据确定性:避免在 story 设置中使用 Math.random()、Date.now() 等非确定性值。当顺序或时序对视觉稳定性重要时,传入显式值。
  • 滚动稳定:在改变元素尺寸的异步操作(Shiki 高亮、Mermaid 渲染、工具展开)之后,等待 useAutoScroll 的 ResizeObserver RAF 完成。使用双 RAF:await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)))。

UI 测试(tests/ui)

  • tests/ui 中的测试必须通过 AppLoader 渲染完整应用,并从用户视角驱动交互(点击、输入、导航)。
  • 使用 renderReviewPanel() 辅助函数或类似模式渲染 <AppLoader client={apiClient} />。
  • 不要在此测试孤立组件或工具函数——它们应作为单元测试放在实现旁边(*.test.ts)。
  • 切勿直接调用后端 API(例如 env.orpc.workspace.remove())来触发正在测试的操作——始终模拟用户操作(点击删除按钮等)。直接调用 API 会绕过导航、状态更新和错误处理等前端逻辑,而这些往往是缺陷藏身之处。
    • 后端 API 调用可用于设置/清理或避免昂贵操作。
    • 若需细粒度测试后端逻辑,考虑将测试移至 tests/ipc。
  • 这些测试中切勿绕过 UI;例如不要调用 updatePersistedState 更改 UI 状态——应通过 UI 触发所需行为。
  • 这些测试需要 TEST_INTEGRATION=1;使用 shouldRunIntegrationTests() 守卫。
  • 仅在确实发起 AI API 调用的测试中调用 validateApiKeys()。纯 UI 交互测试(点击按钮、选择项目)不需要 API 密钥。

happy-dom 限制

  • Radix Portal 内容渲染在 document.body 中——happy-dom 不会将其放在 view.container 下,因此限定在应用根节点的查询会漏掉对话框/弹出框内容。
  • 变通方法: 对于基于 portal 的 UI(Dialog/Popover/Tooltip),查询 view.container.ownerDocument.body(或 within(document.body))并在那里驱动交互。输入优先使用 userEvent 以确保受控输入更新。
  • 若 portal 内容因缺少浏览器 API 仍不出现,优先使用条件渲染({isOpen && <div>...}),如 AgentModePicker 那样,或回退到 tests/e2e(启动时间约 2 分钟)。

测试辅助函数约定

  • 使用非 portal 组件时,在 view.container 内查询元素(而非 document.body)。
  • 使用带明确错误信息的 waitFor() 以辅助调试:if (!el) throw new Error("Element not found")。
  • 辅助函数按用户操作命名:openBaseSelectorDropdown()、selectSuggestion(),而非实现细节。

IPC 测试(tests/ipc)

尽量完全通过 IPC 交互测试后端。避免在此直接断言或修改后端状态。例外包括:

  • 构建大量历史以测试原本昂贵的操作(例如长上下文处理)。
  • 测试可观察副作用属于 API 契约一部分的逻辑,例如 createProject 在项目目录不存在时创建该目录。

确定性

尽量减少测试的竞态。测试运行于多种环境,包括负载沉重的 CI 运行器。优先使用显式同步而非任意 sleep。当显式同步不可行时,使用 waitFor 等模式,它在常见情况下能快速完成。

例外

在某些情况下,由于基础设施或性能限制,可能选择偏离这些指南。此时应确保测试代码(或缺少测试的生产代码)有良好注释,说明例外背后的理由。