首页
/ ECC 测试规则实战:在 Cursor 约束下落地 80% 覆盖率与 TDD 工作流

ECC 测试规则实战:在 Cursor 约束下落地 80% 覆盖率与 TDD 工作流

2026-09-06 11:01:30作者:龚格成

本文以 ECC(agent harness performance optimization system)仓库中的 Cursor 规则文件 common-testing.md 为主体,系统讲解 ECC 如何把"80% 最低覆盖率、三类测试全覆盖、强制 TDD"这三条测试纪律写成可被 Agent 自动执行的项目级约束,并结合仓库中的 tdd-guide 代理、tdd-workflow 技能、test-coverage 命令以及 tests/ 目录下的真实测试组织方式,讲清这条规则从"文字约定"到"实际执行"的完整链路。读完后你可以掌握:如何在 Cursor 等 AI 编码工具中配置强制测试规则、如何按 RED-GREEN-IMPROVE 流程驱动开发,以及 ECC 仓库自身如何用自己的测试基础设施印证这套规则。

一、规则文件定位:common-testing.md 是什么

.cursor/rules/common-testing.md 是 ECC 为 Cursor 环境准备的"通用测试规则"文件,与 golang-testing.mdpython-testing.mdtypescript-testing.md 等按语言细分的规则文件并存。它的 frontmatter 只有两个关键字段:

---
description: "Testing requirements: 80% coverage, TDD workflow, test types"
alwaysApply: true
---
  • description 用一句话概括规则主题,便于工具在会话中检索引用;
  • alwaysApply: true 表示该规则对当前仓库的所有对话无条件生效,而不依赖 Agent 自行判断是否"与测试相关"。这是测试类规则的合理选择:覆盖率门槛和 TDD 流程属于硬约束,宁可始终在上下文中,也不能等到写测试时才被发现。

同一份规则在仓库中还有语言无关的"母版" rules/common/testing.md,它在 common-testing.md 的全部内容之上额外定义了 AAA(Arrange-Act-Assert)测试结构偏好和描述性命名规范,例如:

test('returns empty array when no markets match query', () => {})
test('throws error when API key is missing', () => {})
test('falls back to substring search when Redis is unavailable', () => {})

命名要求用"行为句"而非"函数名句"描述被测行为——这一点在 skills/tdd-workflow/SKILL.md 的最佳实践清单("Descriptive Test Names - Explain what's tested")中同样出现,三处文档相互印证。

二、核心测试要求:80% 覆盖率与三类测试全覆盖

规则文件的第一节设定了两条硬性指标:

1. 最低测试覆盖率:80%。 这不是"建议值",而是与 TDD 工作流第 6 步"Verify coverage (80%+)"直接挂钩的验收门槛。

2. 三类测试全部必需(ALL required):

测试类型 覆盖对象 依据
单元测试 单个函数、工具函数、组件 规则原文"Individual functions, utilities, components"
集成测试 API 端点、数据库操作 规则原文"API endpoints, database operations"
E2E 测试 关键用户流程(框架按语言选择) 规则原文"Critical user flows (framework chosen per language)"

"framework chosen per language" 这一措辞很关键:ECC 不强制绑定 Playwright 或 Pytest 等具体框架,而是把框架选择权交给各语言子规则(如 golang-testing.md 等)。这一点与 commands/test-coverage.md 中"Step 1: Detect Test Framework"的设计一致——先探测再执行,而不是假设:

探测指标 覆盖率命令
jest.config.*package.json 中的 jest npx jest --coverage --coverageReporters=json-summary
vitest.config.* npx vitest run --coverage
pytest.ini / pyproject.toml 中的 pytest pytest --cov=src --cov-report=json
Cargo.toml cargo llvm-cov --json
pom.xml 且含 JaCoCo mvn test jacoco:report
go.mod go test -coverprofile=coverage.out ./...

也就是说,80% 这个门槛在 ECC 里是跨语言统一的,而"如何度量 80%"则由每个技术栈各自的覆盖率工具承担。

三、强制 TDD 工作流:RED → GREEN → IMPROVE 六步法

规则文件将 TDD 定义为 MANDATORY workflow(强制工作流),共六步:

  1. 先写测试(RED):写出描述预期行为的失败测试;
  2. 运行测试——它必须失败:证明测试确实覆盖了未实现的行为;
  3. 写最小实现(GREEN):只写让测试通过所需的代码,不多写;
  4. 运行测试——它必须通过
  5. 重构(IMPROVE):在测试保持绿色前提下降重复、改命名、做优化;
  6. 验证覆盖率(80%+)

agents/tdd-guide.md 把这个流程落实到了具体命令(L29-L49):

# Step 2: 验证失败
npm test

# Step 6: 验证覆盖率
npm run test:coverage
# Required: 80%+ branches, functions, lines, statements

注意这里 80% 门槛同时约束 branches、functions、lines、statements 四个维度,而不是只看行覆盖——分支覆盖率是最容易被"高行覆盖"掩盖的缺口,tdd-guide 把它一并纳入硬性指标。

仓库中的 skills/tdd-workflow/SKILL.md 则把六步法扩展成更工程化的 8 步流程(Step 0 检测运行器 → Step 1 写用户旅程 → Step 2 生成测试用例 → Step 3 运行确认 RED → Step 4 最小实现 → Step 5 确认 GREEN → Step 6 重构 → Step 7 验证覆盖率 → Step 8 编写 TDD 证据报告),其中两处细节值得注意:

RED 门槛是真实的。 Step 3 明确写道:"A test that was only written but not compiled and executed does not count as RED"(只写了但没编译执行的测试不算 RED)。RED 必须是"测试编译成功、实际运行、结果为失败、且失败由预期的业务逻辑缺陷导致",排除语法错误、依赖缺失等假失败。这一条把 TDD 从仪式变成了可验证的门禁。

每一步都有 Git 检查点。 推荐的最小提交序列是:一个 commit 对应"失败测试加入且 RED 已验证"(test: add reproducer for <feature>),一个 commit 对应"最小修复且 GREEN 已验证"(fix: <feature>),可选第三个 commit 对应重构完成(refactor: clean up after ...)。如果后续会 squash 合并,要求把 RED/GREEN 摘要复制进 PR 描述或 squash commit body,保证评审方仍能看到验证证据。

Step 0:不要假设 npm test 这是 tdd-workflow 与 tdd-guide 的一个实际差异:tdd-guide 直接示例 npm test,而 tdd-workflow 要求先运行 ECC 自带的检测器 scripts/setup-package-manager.js

node scripts/setup-package-manager.js --detect

该脚本按 CLAUDE_PACKAGE_MANAGER 环境变量、.claude/package-manager.jsonpackage.jsonpackageManager 字段、lockfile 的优先级依次解析 npm / pnpm / yarn / bun(见 scripts/setup-package-manager.js#L95-L98 中引用的检测顺序与脚本头部注释 L9-L13)。技能文档还专门区分了包管理器 ≠ 测试运行器:一个项目可能用 Bun 装依赖却跑 Jest;bun test(原生运行器)与 bun run test(执行 package.json 的 test 脚本)是两回事,选错在 ESM-only 项目中会导致常见故障。其运行器命令矩阵:

Runner <test> <test-watch> <coverage>
npm npm test npm test -- --watch npm run test:coverage
pnpm pnpm test pnpm test --watch pnpm test:coverage
yarn yarn test yarn test --watch yarn test:coverage
Bun(脚本跑 jest/vitest) bun run test bun run test --watch bun run test:coverage
Bun(原生 bun:test bun test bun test --watch bun test --coverage

四、测试失败排查清单与 tdd-guide 代理

common-testing.md 给出了一条四步排查链,其核心原则是第 4 条:修实现,不要改测试(除非测试本身错了)——防止 Agent 为了让 CI 变绿而弱化断言:

  1. 使用 tdd-guide 代理介入;
  2. 检查测试隔离(test isolation);
  3. 核实 mock 是否正确;
  4. 修实现而非测试。

规则文件末尾的 Agent Support 一节强调:tdd-guide 在开发新功能时应**主动(PROACTIVELY)**启用,而不是等失败后再召唤。

tdd-guide 代理的完整职责包括:

  • 必须测试的 8 类边界情况(L59-L68):Null/Undefined 输入、空数组/空字符串、非法类型、边界值(min/max)、错误路径(网络失败、数据库错误)、竞态条件、大数据量(1 万项以上)、特殊字符(Unicode、emoji、SQL 字符);
  • 必须避免的测试反模式(L70-L75):测试内部实现细节而非行为、测试间依赖共享状态、断言过弱("能通过但不验证任何东西")、外部依赖(Supabase、Redis、OpenAI)不 mock;
  • 质量清单(L77-L87):公开函数有单测、API 端点有集成测试、关键用户流有 E2E 测试、覆盖 80%+ 等 9 项;
  • v1.8 Eval 驱动扩展(L91-L100):在实现前定义 capability + regression evals,先跑基线并记录失败签名,实现最小通过变更后重新运行,报告 pass@1 与 pass@3,发布关键路径以 pass^3 稳定作为合并前提。

这套清单与 tdd-workflow 技能中的"Common Testing Mistakes"章节(FAIL/WRIGHT 对照代码示例,如"测试 component.state.count 内部状态"对照"断言 screen.getByText('Count: 5') 用户可见行为")构成互补:agent 定义"判什么",skill 提供"怎么判"的代码模式。

五、覆盖率缺口闭环:test-coverage 命令

test-coverage 命令定义了把"低于 80%"变成"回到 80% 以上"的标准动作:

  1. 探测框架(见第二节表格);
  2. 分析报告:列出低于 80% 的文件并按差距排序,对每个文件定位未测函数、缺失分支(if/else、switch、错误路径)和"虚增分母"的死代码;
  3. 按优先级生成缺失测试:Happy path → 错误处理 → 边界情况(空数组、null、0、-1、MAX_INT)→ 分支覆盖;
  4. 验证:全量测试必须通过,重跑覆盖率确认提升,仍不达标则回到第 3 步;
  5. 报告:输出 before/after 对比表(如 src/services/auth.ts 45% → 88%)。

其测试生成规则与 common-testing.md 的隔离要求呼应:测试文件紧邻源码(foo.tsfoo.test.ts)、复用项目既有测试模式、mock 数据库/文件系统等外部依赖、每个测试独立无共享可变状态、命名描述行为(test_create_user_with_duplicate_email_returns_409)。

六、规则落地验证:ECC 仓库自己的测试组织

规则说 80% 覆盖率与"单元/集成/E2E 三层"是硬要求,仓库自身就是最好的验证样本:

  • 单元/模块测试主体tests/lib/ 下按源码模块一一对应存放了上百个 *.test.js(如 install-plan-boundary.test.jsgithub-coordination.test.js),符合 tdd-workflow 中"tests adjacent to source / 项目约定"的组织原则;
  • 集成测试tests/integration/ 目录包含 hooks.test.jsplan-canvas-e2e.test.js,对应规则中"API/数据库级交互"与"关键用户流程"两类对象;
  • Python 侧并行存在tests/ 下的 test_executor.pytest_selector.py 等 pytest 风格文件与 tests/conftest.py,印证了"框架按语言选择"的设计;
  • 统一入口与测试隔离tests/run-all.js 递归发现 tests/**/*.test.js 并逐个以子进程执行。其中一处实现细节直接对应 common-testing.md 第 2 条"检查测试隔离"(L76-L83):runner 会为每个测试子进程剥离 GIT_DIRGIT_WORK_TREEGIT_INDEX_FILE 等继承的 git 环境变量——因为套件可能在 pre-push 等 git hook 内运行,而 hook 环境中的这些变量会让测试里的 git -C <dir> 操作劫持到宿主仓库而非测试夹具。从源码结构看,这是"测试间无共享状态、测试不依赖执行环境"原则在 CI/hook 场景下的具体工程化解法。

七、小结:一份规则文件背后的三层执行体系

回到 common-testing.md 本身,它只有约 30 行,但每一行都能在仓库中找到执行者,构成"规则 → 代理 → 技能/命令 → 自证"的三层体系:

层面 载体 职责
规则层 common-testing.mdalwaysApply: true 80% 门槛、三类测试、六步 TDD、排查清单,对会话无条件生效
代理层 tdd-guide 边界情况、反模式、质量清单,主动介入新特性开发
执行层 tdd-workflow + test-coverage 运行器探测、RED/GREEN 门禁、Git 检查点、证据报告、覆盖率缺口闭环
自证层 tests/ + tests/run-all.js 三层测试实际分布、hermetic 子进程隔离,规则在仓库自身落地

对使用 ECC 的开发者而言,这套配置的可复用要点是:把覆盖率门槛写进 alwaysApply 规则使其成为硬约束;用代理定义边界情况与反模式清单;用技能文件固化 RED 必须"编译并真实失败"、GREEN 之后才允许重构、证据报告可追溯的执行细节;最后用仓库自己的测试结构证明规则不是空文。

登录后查看全文
热门项目推荐
相关项目推荐