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.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 或状态机 | 单元测试 | 输入、输出、转换与有意义的边界 |
| 带状态或外部效果的 Hook | Hook 测试或小型 harness 测试 | 返回契约与外部可观察效果 |
| 渲染进程组件行为 | 组件测试 | 用户能发现、操作与观察的内容 |
通用@cherrystudio/ui原语/复合组件 | packages/ui测试(使用真实组件) | 可访问性、交互与文档化视觉契约 |
| 关键跨窗口或跨进程工作流 | E2E 测试 | 完整的用户结果 |
| 编译期公共类型契约 | 类型测试 | 被接受与被拒绝的用法,无需重复运行时测试 |
不要在每个层级重复同一行为:组件测试不应重新测试已被测试的纯 helper 的每个分支;E2E 测试不应枚举每个组件 prop。
仓库在 vitest.config.ts 中以 Vitest projects 形式落地了这一分层:rendererproject 使用jsdom环境并加载tests/renderer.setup.ts,uiproject 把@cherrystudio/ui别名指向packages/ui/src直接测试真实组件,main/shared/preload/aiCore/provider-registry/scripts则分别对应主进程、共享层等其他边界。运行时可使用 package.json 中的命令:pnpm test:renderer、pnpm test:pkg:ui、pnpm test:main、pnpm 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 查询优先级
getByRole/findByRole+ 可访问名称(accessible name);getByLabelText;- 用户可见文本或其他语义查询;
- 文档化的受维护选择器(maintained selector);
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),仅供参考