
inbox-zero 单元测试编写指南聚焦业务逻辑的 Vitest 测试方法论与仓库实践【免费下载链接】inbox-zeroThe worlds best AI personal assistant for email. Open source app to help you reach inbox zero fast.项目地址: https://gitcode.com/GitHub_Trending/in/inbox-zero导读本文围绕 inbox-zero 仓库中的测试编写技能文档.claude/skills/testing/write-tests.md由.claude/skills/write-tests/SKILL.md作为入口指向展开系统讲解该开源项目只测逻辑、不测 UI、Mock 一切外部依赖、测试与源码同目录的单元测试方法论。你将掌握如何为工具函数、数据转换、条件分支等后端与纯逻辑代码编写高质量的 Vitest 测试理解server-only、Prisma、测试 Helper 三类 Mock 模式的实际用法并学会用仓库内的命令一键运行与覆盖率分析。全文结合 helpers.ts、prisma mock 等源码佐证可直接复用于该仓库的日常开发与代码评审。一、技能文档的定位与适用场景inbox-zero 是一个帮助用户快速清空收件箱的开源 AI 邮件助手代码量庞大且大量逻辑集中在apps/web/utils与后端 API 中。为了让 AI 编码代理与人类开发者写出风格统一、边界清晰的测试仓库在.claude/skills/testing/下沉淀了一套分级测试指南类型文档适用时机单元测试unit.md框架设置、Mock 方式、测试与源码同目录编写测试write-tests.md该测什么、不该测什么、完整工作流LLM 测试llm.md调用真实 LLM 的测试pnpm test-aiEval 套件eval.md跨模型对比、LLM-as-judge集成测试integration.md基于模拟器Emulator的测试pnpm test-integration数据库测试apps/web/__tests__/db/README.md真实 Postgres用于唯一约束与updateMany计数守卫pnpm test-dbE2E 测试e2e.md来自 inbox-zero-e2e 仓库的真实邮件工作流测试本文的主体是 write-tests 这份文档它适用于工具函数、后端逻辑、数据转换、业务规则、前端抽离出的纯函数与状态机等不依赖渲染环境的代码。二、核心规则先定边界再写测试write-tests 文档以五条Critical Rules定调这也是理解整个仓库测试风格的关键只测逻辑—— 工具函数、数据转换、业务规则是唯一目标绝不测 UI—— 不写组件渲染、不写 renders correctly 这类断言Mock 一切外部依赖—— Prisma、API、server-only 标记、第三方服务全部隔离测试与源码同目录——foo.ts旁边放foo.test.tsco-locate遵循测试总纲—— 复用.claude/skills/testing/SKILL.md中的既有模式与 Helper。第 4 条在仓库中有大量实例例如 action-display.ts 与 action-display.test.ts 同目录存放condition.ts 与 condition.test.ts、useToggleSelect.ts 与 useToggleSelect.test.ts 亦是如此。这种布局让测试与实现一一对应评审与重构时无需跨目录跳转。总纲还强调了一个重要取向Prefer behavior-focused assertions; avoid freezing prompt copy or internal call shapes unless those exact values are the contract under test优先行为导向的断言除非精确值本身就是被测契约否则不要冻结提示词文案或内部调用形态。这与下文测行为而非实现的检查清单互为呼应。三、该测什么高优先级目标清单文档给出六个高优先级测试目标可归纳为有分支、有转换、有失败路径的代码业务逻辑与条件流——if/else、switch构成的多分支走向数据转换与解析—— 输入到输出的映射、格式化、序列化边界情况与错误处理—— 空输入、异常输入、抛错路径输入校验逻辑—— 参数合法性的判定复杂工具函数—— 有明确入参出参的纯函数前端逻辑—— 从组件中抽离出的 reducer、状态机、纯函数。从源码结构看该仓库的apps/web/utils下 150 个 actions 文件、apps/web/hooks下的状态管理 Hook如useToggleSelect、useLabels、usePremium均有同名.test.ts文件正是按此清单覆盖的典型区域。四、该跳过什么不写测试的黑名单write-tests 文档用一张表格明确列出SKIP清单避免测试沦为为了覆盖率而测试的噪音跳过项反例React 组件渲染component renders without crashingUI 外观button has correct class/style图标/标签映射newsletter group uses newspaper icon静态配置值default timeout is 5000简单类型再导出测试某个 type alias 是否存在琐碎 gettergetName() { return this.name }简单 Zod schemaz.object({ name: z.string() })其中 Zod 一项值得注意只有包含refine/superRefine这类复杂逻辑时才测试 schema纯字段声明型 schema 属于框架/库代码测试它们没有收益。这四条判断测行为、能抓到真实 bug、不重复、不测框架代码也直接构成了文档末尾的测试质量检查清单。五、Mock 模式三大标准套路文档给出了三个仓库级的标准 Mock 写法全部有源码对应。5.1 server-only 标记Next.js 的server-only包用于标记仅服务端模块在测试中必须桩掉否则导入即报错vi.mock(server-only, () ({}));5.2 Prisma 深度 Mock仓库在 apps/web/utils/mocks/prisma.ts 中预置了基于vitest-mock-extended的深度 Mock// https://www.prisma.io/blog/testing-series-1-8eRB5p0Y8o#why-mock-prisma-client import type { PrismaClient } from /generated/prisma/client; import { beforeEach } from vitest; import { mockDeep, mockReset } from vitest-mock-extended; const prisma mockDeepPrismaClient(); beforeEach(() { mockReset(prisma); }); export default prisma;使用方式为在测试中导入该 Mock 并替换真实 Prisma 模块import prisma from /utils/__mocks__/prisma; vi.mock(/utils/prisma); describe(example, () { beforeEach(() { vi.clearAllMocks(); }); it(test, async () { prisma.group.findMany.mockResolvedValue([]); }); });注意两个细节mockDeep会为PrismaClient的所有模型方法生成可配置的 Mock 函数因此测试内可直接mockResolvedValue指定返回值而该 Mock 文件自身的beforeEach(mockReset)与测试内的vi.clearAllMocks()配合保证了每个用例之间互不污染。5.3 既有测试 Helper文档推荐复用 apps/web/tests/helpers.ts 中预置的工厂函数import { getEmail, getEmailAccount, getRule } from /__tests__/helpers;这批工厂函数覆盖了邮件链路的核心实体从源码可见其完整清单节选getEmail(...)—— 构造EmailForLLM邮件对象支持from/to/subject/content/replyTo/cc/date/listUnsubscribe覆盖getEmailAccount(overrides)—— 构造EmailAccountWithAI内置默认userId: user1、sensitiveDataPolicy: ALLOW、draftReplyConfidence: MEDIUM、provider: googlegetRule(instructions, actions, name)—— 构造规则对象默认automate: true、conditionalOperator: LogicalOperator.ANDgetAction(overrides)—— 构造 Action默认类型ActionType.LABELgetMockMessage(...)/getMockExecutedRule(...)—— 构造 Gmail 消息与已执行规则记录getCalendarConnection/getMockOrganizationMembership—— 构造日历连接与组织成员关系测试中间件辅助addTestAuth/addTestEmailAccountAuth/createWithErrorTestMiddleware等用于在Request上挂载测试身份与 logger。这些工厂函数普遍采用默认值 overrides 参数设计如getEmailAccount的overrides.email || usertest.com让测试用例只需声明与默认不同的字段大幅降低样板代码。六、完整工作流从定位范围到交付总结Step 0确定测试范围文档建议按优先级自动探测变更范围git diff --cached --name-only # 已暂存的文件 # 或 git diff main...HEAD --name-only # 与主干分支的差异其次是指定的文件即用户显式要求覆盖的目标。Step 1识别测试目标在范围内的文件中重点寻找包含以下特征的函数条件逻辑if/else、switch、数据转换、错误处理路径、多返回值场景。这与第三节的高优先级清单一致。Step 2创建测试文件测试文件必须紧挨源码放置utils/example.ts→utils/example.test.ts。文档给出的标准骨架import { describe, it, expect, vi, beforeEach } from vitest; import { yourFunction } from ./example; vi.mock(server-only, () ({})); describe(yourFunction, () { beforeEach(() { vi.clearAllMocks(); }); it(handles happy path, () { // Test main success case }); it(handles edge case, () { // Test boundary conditions }); it(handles error case, () { // Test error paths }); });骨架刻意对每个用例仅标注意图happy path / edge case / error case与总纲行为导向断言的要求呼应。Step 3运行测试仓库统一通过 pnpm 脚本运行 Vitest根目录 package.json 与各子包 vitest.config.mts 已配置好文档明确要求不要使用沙箱执行测试命令pnpm test path/to/file.test.ts # 运行单个测试文件 pnpm test # 运行全部单元测试相关的分级命令来自 testing/SKILL.md还有pnpm test-integration # 集成测试基于模拟器 pnpm test-db # 数据库测试需指向一次性数据库的 DATABASE_URL pnpm test-ai ai-regression/your-feature # 调用真实 LLM 的回归测试 EVAL_MODELSall pnpm test-ai eval/your-feature # 跨模型 EvalStep 4交付总结写完测试后文档要求附上一段简短摘要说明覆盖范围与有意跳过的内容及原因例如Tests written for utils/example.ts: Covered: - validateInput: null handling, invalid format, valid input - transformData: empty array, nested objects, error case Not covered (and why): - getConfig: static values only, no logic to test - CONSTANTS export: no behavior to test Run coverage? (y/n)Not covered and why 这一节尤其重要——它把静态值、常量导出无需测试的决定显式化防止后续被盲目的覆盖率指标推翻。七、测试质量检查清单文档要求每个用例在交付前过一遍四道关卡测行为而非实现—— 断言输入输出的行为契约不断言内部调用顺序等实现细节逻辑变更时能抓到真实 bug—— 如果被测逻辑被改坏该用例必须失败不与其他测试重复—— 同一场景只测一次不测框架/库代码—— 框架行为与第三方库不是被测对象。这条清单与仓库总纲中的偏好完全一致仓库要求避免freezing prompt copy or internal call shapes除非精确值本身就是契约例如 action-display.test.ts 中若展示文案是产品契约则可作为断言对象。八、可选覆盖率分析当需要定位覆盖盲区时文档提供了针对apps/web包的覆盖率命令cd apps/web pnpm test --run --coverage -- path/to/file.test.ts注意覆盖率应作为发现遗漏的辅助手段而非写测试的目的——SKIP 清单中的静态配置、简单 getter、声明式 schema 即使零覆盖也属正常这正是 Step 4 摘要中Not covered (and why)要记录的内容。九、仓库中的测试生态小结结合 unit.md 与 SKILL.md 可看到write-tests 是这套测试生态的执行层文档其上还有两条补充纪律每个测试相互独立使用描述性命名测试间清理 Mock不要 Mock Logger—— 日志是排查手段保留真实 loggerhelpers 中提供了createTestLogger()工厂AI 类测试放在__tests__目录且默认不运行它们调用真实 LLM。对于正在 inbox-zero 仓库中开发或为其贡献代码的工程师推荐的落地顺序是先用git diff圈定改动范围 → 按高优先级清单筛选出有逻辑的目标 → 用三件套 Mock 编写同目录测试 →pnpm test验证 → 按四道关卡自检 → 输出带 Not covered and why 的摘要。这套方法论既保证了邮件自动化这类强外部依赖逻辑的可测性也守住了测试服务于逻辑正确性的本心。【免费下载链接】inbox-zeroThe worlds best AI personal assistant for email. Open source app to help you reach inbox zero fast.项目地址: https://gitcode.com/GitHub_Trending/in/inbox-zero创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考