ECC 测试规则实战:在 Cursor 约束下落地 80% 覆盖率与 TDD 工作流
本文以 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.md、python-testing.md、typescript-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(强制工作流),共六步:
- 先写测试(RED):写出描述预期行为的失败测试;
- 运行测试——它必须失败:证明测试确实覆盖了未实现的行为;
- 写最小实现(GREEN):只写让测试通过所需的代码,不多写;
- 运行测试——它必须通过;
- 重构(IMPROVE):在测试保持绿色前提下降重复、改命名、做优化;
- 验证覆盖率(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.json、package.json 的 packageManager 字段、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 变绿而弱化断言:
- 使用 tdd-guide 代理介入;
- 检查测试隔离(test isolation);
- 核实 mock 是否正确;
- 修实现而非测试。
规则文件末尾的 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% 以上"的标准动作:
- 探测框架(见第二节表格);
- 分析报告:列出低于 80% 的文件并按差距排序,对每个文件定位未测函数、缺失分支(if/else、switch、错误路径)和"虚增分母"的死代码;
- 按优先级生成缺失测试:Happy path → 错误处理 → 边界情况(空数组、null、0、-1、MAX_INT)→ 分支覆盖;
- 验证:全量测试必须通过,重跑覆盖率确认提升,仍不达标则回到第 3 步;
- 报告:输出 before/after 对比表(如
src/services/auth.ts 45% → 88%)。
其测试生成规则与 common-testing.md 的隔离要求呼应:测试文件紧邻源码(foo.ts → foo.test.ts)、复用项目既有测试模式、mock 数据库/文件系统等外部依赖、每个测试独立无共享可变状态、命名描述行为(test_create_user_with_duplicate_email_returns_409)。
六、规则落地验证:ECC 仓库自己的测试组织
规则说 80% 覆盖率与"单元/集成/E2E 三层"是硬要求,仓库自身就是最好的验证样本:
- 单元/模块测试主体:tests/lib/ 下按源码模块一一对应存放了上百个
*.test.js(如install-plan-boundary.test.js、github-coordination.test.js),符合 tdd-workflow 中"tests adjacent to source / 项目约定"的组织原则; - 集成测试:tests/integration/ 目录包含 hooks.test.js 与 plan-canvas-e2e.test.js,对应规则中"API/数据库级交互"与"关键用户流程"两类对象;
- Python 侧并行存在:tests/ 下的
test_executor.py、test_selector.py等 pytest 风格文件与 tests/conftest.py,印证了"框架按语言选择"的设计; - 统一入口与测试隔离:tests/run-all.js 递归发现
tests/**/*.test.js并逐个以子进程执行。其中一处实现细节直接对应 common-testing.md 第 2 条"检查测试隔离"(L76-L83):runner 会为每个测试子进程剥离GIT_DIR、GIT_WORK_TREE、GIT_INDEX_FILE等继承的 git 环境变量——因为套件可能在 pre-push 等 git hook 内运行,而 hook 环境中的这些变量会让测试里的git -C <dir>操作劫持到宿主仓库而非测试夹具。从源码结构看,这是"测试间无共享状态、测试不依赖执行环境"原则在 CI/hook 场景下的具体工程化解法。
七、小结:一份规则文件背后的三层执行体系
回到 common-testing.md 本身,它只有约 30 行,但每一行都能在仓库中找到执行者,构成"规则 → 代理 → 技能/命令 → 自证"的三层体系:
| 层面 | 载体 | 职责 |
|---|---|---|
| 规则层 | common-testing.md(alwaysApply: true) |
80% 门槛、三类测试、六步 TDD、排查清单,对会话无条件生效 |
| 代理层 | tdd-guide | 边界情况、反模式、质量清单,主动介入新特性开发 |
| 执行层 | tdd-workflow + test-coverage | 运行器探测、RED/GREEN 门禁、Git 检查点、证据报告、覆盖率缺口闭环 |
| 自证层 | tests/ + tests/run-all.js | 三层测试实际分布、hermetic 子进程隔离,规则在仓库自身落地 |
对使用 ECC 的开发者而言,这套配置的可复用要点是:把覆盖率门槛写进 alwaysApply 规则使其成为硬约束;用代理定义边界情况与反模式清单;用技能文件固化 RED 必须"编译并真实失败"、GREEN 之后才允许重构、证据报告可追溯的执行细节;最后用仓库自己的测试结构证明规则不是空文。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0624
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00