
Cal.diy 测试覆盖率工程规范新代码 80% 覆盖率的 CI 落地与单测实践【免费下载链接】cal.diyScheduling infrastructure for absolutely everyone.项目地址: https://gitcode.com/GitHub_Trending/ca/cal.diy本篇文章以 Cal.diy 仓库的工程规则文档 agents/rules/testing-coverage-requirements.md 为主体结合仓库真实的 Vitest/Playwright 测试基建系统讲解这条被标为HIGH 影响的规则任何 PR 新增或修改的代码都必须达到接近 80% 以上的测试覆盖率并由 CI 自动强制。读完你不仅能理解覆盖率门槛为什么存在、卡在什么数值还能掌握如何为一段新逻辑写出完整、有语义的单测以及如何在本仓库中用正确的命令本地验证覆盖率避免提交被打回。规则速览这是一条 CI 强制执行的硬门槛该规则文件带有结构化的 YAML frontmatter是仓库内所有工程规则agents/rules/目录的统一元数据格式便于人类工程师与 AI Agent 共同解析元数据字段值含义titleMaintain 80% Test Coverage for New Code规则标题为新增代码保持 80% 覆盖率impactHIGH影响级别高与testing-分区的默认级别MEDIUM-HIGH相比更高impactDescriptionPrevents bugs and enables confident refactoring规则的价值防止 Bug 扩散、支撑大规模重构tagstesting, coverage, quality, ci归属维度测试、覆盖率、质量、CI规则正文的核心约束是每一个 PR 都必须在它引入或修改的代码上达到接近 80% 以上的测试覆盖率且这一要求在 CI 流水线中自动执行。规则给出两个具体判断标准如果你新增了 50 行代码这 50 行就必须被测试覆盖如果你修改了某个已有函数你的改动本身也必须被测试。也就是说门槛不是落在整个仓库总覆盖率上而是落在**增量代码diff 引入的行**上。这是一条防回退 促增量的双向规则仓库整体质量可以通过既有测试基线托底而每次提交带来的新逻辑则必须立刻被验证。三档覆盖率目标的拆解规则将覆盖率进一步拆成三个层次其优先级与严格程度各不相同指标目标定位整体覆盖率Overall test coverage新增代码 80%PR 级硬性门槛CI 强制执行单元测试覆盖率Unit test coverage接近 100%结合 AI 辅助生成测试后应追求的目标全局覆盖率追踪Global coverage tracking作为关键指标随时间持续改善团队的长期质量方向盘三者的关系可以理解为台阶80% 是每一行新代码必须跨越的红线在单元测试层面规则明确主张接近 100%而不是停在 80%——因为单测成本低、反馈快覆盖缺口大多可以用 AI 快速补齐而全局覆盖率则被当作一项持续追踪、只升不降的健康指标避免团队在个别 PR 上达标却在整体上缓慢退步。反例与正例什么才算带测试的新代码反例无测试的新函数违规规则的Incorrect示例非常直白——新增了一个包含复杂逻辑的业务函数却没有任何对应的测试文件// New function with no tests export function calculateAvailability(user: User, date: Date): TimeSlot[] { // Complex logic here... // No corresponding test file }这段代码的问题不在于函数写得不好而在于没有任何可执行的验证CI 无法确认它对/错后续重构无法依赖它任何改动都只能靠人工肉眼排查。正例实现与测试成对交付合规规则的Correct示例给出了生产实现与测试文件结对的形态// calculateAvailability.ts export function calculateAvailability(user: User, date: Date): TimeSlot[] { // Complex logic here... } // calculateAvailability.test.ts describe(calculateAvailability, () { it(returns empty array for user with no schedule, () { const user createMockUser({ schedules: [] }); expect(calculateAvailability(user, new Date())).toEqual([]); }); it(excludes busy times from available slots, () { const user createMockUser({ schedules: [mockSchedule], busyTimes: [mockBusyTime], }); const slots calculateAvailability(user, new Date()); expect(slots).not.toContainEqual(expect.objectContaining({ start: mockBusyTime.start, })); }); it(handles timezone conversions correctly, () { // Test timezone edge cases }); });细看这三个用例其实对应着可用性时段计算这类领域函数在 Cal.diy 中与 packages/features/slots 等模块的职责同源必须覆盖的三类行为空输入 / 边界基准用户没有任何日程时返回空数组锁定函数的零输入契约核心业务分支日程中的 busy times 必须从可用时段中剔除断言用not.toContainEqual(expect.objectContaining({ start: mockBusyTime.start }))只关心冲突时段起点是否混入结果而不与具体 Slot 对象强耦合——这正是对TimeSlot[]这类结构进行部分匹配断言的典型写法跨时区转换时区边界处理夏令时、跨日换算等往往单独开用例钉死因为这类 Bug 只在特定TZ环境下方可复现。这段示例还透露了仓库测试的通用骨架用describe/it组织用例、用createMockUser(...)这类工厂函数构造最小可用的输入、用expect(...).toEqual(...)做结构性断言。仓库中真实的单测也完全遵循这一形态例如 packages/lib/array.test.ts 对uniqueBy的测试同样覆盖了单键去重、多键去重、空数组、单元素数组四条路径与示例中空输入 主分支 边界的枚举思路一致。回应覆盖率不能说明一切为什么仍要定 80%规则原文专门预留了一段对常见质疑的正面回应是的我们知道覆盖率不能保证测试是完美的我们知道可以写出每行都命中、却什么也不验证的无意义测试我们知道覆盖率只是众多指标之一。但瞄准一个高百分比总好过对自己身在何处毫无概念。这段话其实是给团队立了一个度量哲学覆盖率不是质量本身而是可观测的方向标。没有方向标代码质量的讨论就退化为我觉得没问题的主观判断有了 80% 的红线至少保证了每次迭代都在可量化、可对比的轨道上前进剩下的质量深化断言的语义强度、Mock 的真实度再由 agents/rules/testing-mocking.md 等其他规则去约束。用 AI 补足单测把接近 100%从口号变成现实规则的落点非常明确AI 可以快速且智能地构建完整的测试套件纯手工测试正在越来越成为过去式。这条主张与仓库的整体定位是一致且自洽的——agents/目录下的整套规则体系本身就被设计为机器可读供 AI 编码 Agent 解析执行见 agents/rules/README.md。实操含义是当一个 PR 中 80% 红线已满足、但想把单测逼近 100% 时正确姿势是把代码片段与领域规则交给 AI 生成用例骨架再人工补充断言语义、修正 Mock。需要特别注意与之配套的 Mock 纪律详见后文配套纪律小节AI 生成的测试如果不遵守仓库的 Mock 接口约定反而会引入类型兼容问题。Cal.diy 仓库里的测试基建规则背后的真实支撑覆盖率规则要落地离不开仓库测试体系的支撑。Cal.diy 的单测全部运行在 Vitest 之上关键配置可以从仓库中直接核对测试入口命令根 package.json 的test脚本为TZUTC vitest run默认强制 UTC 时区另有tddvitest watch用于开发循环、type-check:ci用于类型闸门。覆盖率提供器vitest.config.mts 的test.coverage显式配置了provider: v8配合 devDependencies 中的vitest/coverage-v8。本地需要量化某个文件/某个 diff 的覆盖率时可直接追加--coverage参数运行# 本地跑覆盖率UTC 时区 v8 提供器与 CI 同源 TZUTC vitest run --coverage环境与别名同文件还配置了 jsdom 环境、forks线程池、calcom/*源码内联解析以及指向 packages/testing/src/setupVitest.ts 的 setup 文件——后者负责在 React 组件渲染前预置window.matchMedia等 jsdom 缺失的浏览器 API。这意味着仓库内大量纯逻辑函数如 packages/lib/array.ts、packages/lib/slugify.ts、packages/lib/crypto.ts等各自都有同名.test.ts成对存在与UI/交互逻辑都能在一个测试体系下被覆盖。特殊测试模式的编排vitest.workspace.ts 通过VITEST_MODE环境变量切分出integration、timezone如VITEST_MODEtimezone时要求必须显式提供TZ否则直接抛错、packaged-embed等 workspace并把命名为*.timezone.test.ts、*.integration-test.ts的文件归入对应模式。这直接支撑了规则中处理时区边界用例那类测试的可复现性。结合 agents/rules/testing-timezone.md 可以看到仓库在这方面的共识时区 Bug 极难复现因此测试环境必须时刻保持一致——本地开发机是Asia/Shanghai、CI 是UTC同一个日期断言就可能一个绿一个红。仓库的解法就是命令层面统一TZUTC已在package.json的test脚本中固化把不确定性从源头掐掉。让覆盖率转化为真实质量的四条配套纪律80% 覆盖率解决的是有没有测而下面几条同目录规则解决的是测得好不好、能不能稳定通过先类型、后测试agents/rules/ci-type-check-first.md 规定修复顺序是yarn type-check:ci --force先行——类型错误往往是测试失败的根因先消类型错误能切断级联失败若报缺失枚举/类型如CreationSource.WEBAPP先跑yarn prisma generate从 Prisma schema 重新生成类型。逐文件增量修复agents/rules/testing-incremental.md 建议一次只解决一个文件把每个文件的用例跑绿再进入下一个避免被多文件失败压垮。Mock 遵守接口而非照抄结构agents/rules/testing-mocking.md 指出Mock 日历服务时应实现统一的Calendar接口因为所有日历服务都实现该接口并被存入 map而不是按某个具体服务类型逐个加属性对 app-store 资源则优先实现简洁接口而非堆叠mockDeep的深层结构——不良 Mock 是 flaky 测试与误报的源头。E2E 与单测分层、本地先行agents/rules/testing-playwright.md 规定端到端测试统一走PLAYWRIGHT_HEADLESS1 yarn e2e [test-file.e2e.ts]自带时区、虚拟显示与仓库 e2e runner禁止直接调用yarn playwright test且强调 E2E 必须先本地跑通再推送CI 仅在 PR 打上ready-for-e2e标签时执行。单测的 80% 保证每个函数的行为被锁定E2E 保证跨模块链路集成正确二者结合才构成规则开篇impactDescription所说的防止 Bug 支撑自信重构。提交前的覆盖率自查清单综合规则与仓库实践一个合规 PR 在推送前应通过以下检查新增/修改的代码行都有对应测试文件且能单独运行TZUTC vitest run path/to/file.test.ts用例覆盖空输入、核心业务分支、时区/边界等关键路径而非只覆盖 happy path本地覆盖率TZUTC vitest run --coverage达到 80%单测尽量逼近 100%yarn type-check:ci --force无新增类型错误缺失枚举已用yarn prisma generate修复测试在TZUTC下运行Mock 遵守目标接口而非复制深层结构E2E 改动已用PLAYWRIGHT_HEADLESS1 yarn e2e在本地验证通过。把这六条落实到位80% 的覆盖率红线就不再是 CI 上的一次红色告警而会成为团队基础设施几乎从不失败这一工程哲学里最基础的一环。【免费下载链接】cal.diyScheduling infrastructure for absolutely everyone.项目地址: https://gitcode.com/GitHub_Trending/ca/cal.diy创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考