Cherry Studio 前端测试规范:渲染进程、packages/ui 与 E2E 的层级选择、Mock 边界与评审门禁
2026/9/20 22:01:29 网站建设 项目流程

Cherry Studio 前端测试规范:渲染进程、packages/ui 与 E2E 的层级选择、Mock 边界与评审门禁

【免费下载链接】cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio

本文是 Cherry Studio 开源仓库中针对src/renderer/packages/ui/tests/e2e/的规范性测试指南(原文见 docs/references/testing/frontend-testing.md)。它面向人类开发者与 AI Agent 两类编写者,回答三个核心问题:一个测试什么时候值得写、应该写在哪个层级、断言什么内容。读完本文,你将掌握 Cherry Studio 的测试价值门槛、最低充分层级选择、行为化断言原则、查询与 Mock 边界规则、评审拒绝清单,以及一套可直接执行的 AI-Agent 测试工作流。

0. 指南的定位与权威性

本指南的目标不是最大化测试数量或覆盖率,而是维护一组最小规模的测试集合,使其能对用户可见行为与稳定的公共契约提供强信心。核心判断标准始终是:

  • 保护用户可见行为、已文档化的公共契约,或此前观察到的回归;
  • 生产代码一旦出现真实回归,测试必须失败;
  • 同一行为没有在更合适的层级被重复保护;
  • 测试能够在保持行为不变的前提下经受住内部重构。

该文档是 Cherry Studio 前端测试质量与评审决策的唯一事实来源(single source of truth)。仓库入口文档应链接到它而不是复制其规则;更具体的文档可以描述命令、fixture 或基础设施,但不得重新定义"测试何时有价值、应断言什么、如何评审"。当旧示例与本指南冲突时,以本指南为准——现有测试只代表当前实现历史,不代表被自动认可的范式。

1. 价值门槛(The Value Gate):写测试之前先说出回归

在写任何测试之前,先明确它要捕获的回归。一个测试只有在同时满足全部四个条件时才值得添加:

  1. 它保护用户可见行为、已文档化的公共契约,或此前观察到的回归;
  2. 真实的生产代码回归会让它失败;
  3. 同样的行为尚未在更合适的层级被保护;
  4. 它能在一个保持行为不变的内部重构中存活。

如果无法具体陈述回归,就不要添加该测试。

1.1 通常需要测试的变更

  • 新增或修改的业务规则、状态转换、校验与对账(reconciliation)逻辑;
  • 触发持久化、IPC、导航、剪贴板访问或其他副作用(side effect)的用户交互;
  • 对用户有实质影响的加载、空、错误、权限与恢复状态;
  • 可访问性契约:名称(name)、角色(role)、禁用状态、焦点移动与键盘行为;
  • 跨进程、缓存、序列化、懒加载或 mock 对齐(mock-parity)边界;
  • Bug 修复:添加在修复前必然失败的最小回归用例。

1.2 通常不需要新测试的变更

  • 纯视觉重排(无文档化的布局或可访问性契约);
  • 无自身行为的透传包装(pass-through wrapper)或 re-export;
  • 纯类型变更(TypeScript 已强制),除非类型契约本身就是产品 API;
  • 已被生成器或契约检查覆盖的生成输出;
  • 执行同一生产分支的 prop 排列组合;
  • 针对不可能或不支持输入的防御性 "不抛错"(does not throw)用例。

当"不加测试"的决策不明显时,应在 PR 中解释原因,而不是添加一个象征性(token)测试。

2. 选择最低充分层级(Choose the Lowest Sufficient Layer)

不同行为对应不同的首选测试层级,Cherry Studio 的规范映射如下:

行为首选测试层级断言内容
纯转换、解析器、reducer 或状态机单元测试输入、输出、转换与有意义的边界
带状态或外部效果的 HookHook 测试或小型 harness 测试返回契约与外部可观察效果
渲染进程组件行为组件测试用户能发现、操作与观察的内容
通用@cherrystudio/ui原语/复合组件packages/ui测试(使用真实组件)可访问性、交互与文档化视觉契约
关键跨窗口或跨进程工作流E2E 测试完整的用户结果
编译期公共类型契约类型测试被接受与被拒绝的用法,无需重复运行时测试

不要在每个层级重复同一行为:组件测试不应重新测试已被测试的纯 helper 的每个分支;E2E 测试不应枚举每个组件 prop。

仓库在 vitest.config.ts 中以 Vitest projects 形式落地了这一分层:rendererproject 使用jsdom环境并加载tests/renderer.setup.tsuiproject 把@cherrystudio/ui别名指向packages/ui/src直接测试真实组件,main/shared/preload/aiCore/provider-registry/scripts则分别对应主进程、共享层等其他边界。运行时可使用 package.json 中的命令:pnpm test:rendererpnpm test:pkg:uipnpm test:mainpnpm test:e2e等。

2.1 测试时间确定性

vitest.config.ts 在配置加载阶段强制process.env.TZ = 'UTC',并将监听器上限提高到 64。注释明确说明:CI 运行器默认 UTC,若不固定时区,按本地日分桶 UTC 时间戳的测试(例如话题列表的 "Today/Yesterday")会在 CI 通过、在非 UTC 时区的开发机失败。这一细节提醒我们:层级选择之外,测试环境的确定性同样是规范的一部分

3. 测试行为,而非实现(Test Behavior, Not Implementation)

优先断言:

  • 文本、可访问名称、角色、焦点与禁用状态;
  • 用户操作后的可见状态转换;
  • 返回值与稳定的公共数据形状;
  • 外部效果:IPC 请求、导航、持久化、剪贴板写入;
  • 清理(cleanup)——仅当不清理会造成可观察泄漏或重复效果时。

避免断言:

  • 内部 Hook 调用次数或注册顺序;
  • 私有子组件的 props;
  • 偶然的 DOM 嵌套结构;
  • 非文档化契约的 CSS 类或内联样式;
  • 被 mock 占位符的出现;
  • 实现相关的重渲染次数。

Mock 调用断言仅在 mock 本身就代表外部效果时合适(例如剪贴板写入);当被 mock 的函数是内部协作者时,它不能替代可观察结果。

CSS/class 断言只有在 class 本身即契约时才允许,例如 Electron 拖拽区域标记(drag-region marker)、受维护的 UI 语义 token、或涉及布局机制的回归——此时应添加一行注释说明该契约。这正是 Cherry Studio 的data-ui语义契约存在的原因之一(见第 4 节与 docs/references/components/ui-semantic-contract.md)。

4. 查询与交互优先级(Query and Interaction Priority)

使用用户或辅助技术所用的同一表面来查询。

4.1 Testing Library 查询优先级

  1. getByRole/findByRole+ 可访问名称(accessible name);
  2. getByLabelText
  3. 用户可见文本或其他语义查询;
  4. 文档化的受维护选择器(maintained selector);
  5. getByTestId——仅在不存在有意义的语义选择器时使用。

使用queryBy*做不存在性检查,findBy*等待异步出现。当存在语义查询时,不要使用document.querySelector、DOM 父节点遍历或 CSS 类。

交互方面:常规用户输入(点击、键入、Tab、选择)使用userEvent.setup()fireEvent仅用于userEvent无法充分建模的底层浏览器事件,如定向滚动、resize、拖拽或自定义事件。

4.2 Playwright 定位器与>it('copies the message and announces success', async () => { const user = userEvent.setup() render(<CopyButton textToCopy="hello" />) await user.click(screen.getByRole('button', { name: 'Copy' })) expect(navigator.clipboard.writeText).toHaveBeenCalledWith('hello') expect(toast.success).toHaveBeenCalled() })

剪贴板与 toast 是外部效果,它们的调用即可观察契约。

坏的示例:实现与透传

it('renders the icon and wrapper', () => { const { container } = render(<CopyButton textToCopy="hello" />) expect(container.querySelector('div')).toBeInTheDocument() expect(container.querySelector('.copy-icon')).toBeInTheDocument() })

该测试依赖偶然的 DOM 结构,且不保护复制行为本身。

仓库中的正面实践可对照 useCopyTool.test.tsx:它断言工具列表暴露的copy动作在点击后调用了外部效果onCopySource被调用一次),以及源复制失败时不显示成功反馈——关注用户可观察结果,而非内部实现细节。负面实践(如对container.querySelector的断言)在本规范第 3、6 节中已被明确禁止。

13. 相关文档

  • Test Mocks(测试 Mock 总览)
  • E2E Testing Guide(Electron E2E 基础设施)
  • UI Semantic Contract(data-ui 语义契约)
  • Vitest 配置(分层 projects 与确定性环境)
  • Playwright 配置(E2E 运行参数)
  • 前端测试脚本命令(package.json)

【免费下载链接】cherry-studio🍒 Cherry Studio 是一款支持多个 LLM 提供商的桌面客户端项目地址: https://gitcode.com/CherryHQ/cherry-studio

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询