testkit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesetestkit
testkit
The suite you build for code that already works. testkit reads a codebase with no tests, or a handful of stale ones, ranks what is worth covering, crowns one slice, and writes tests that have each been observed to fail before they were kept.
Two modes. ranks the untested surface and writes nothing but a ledger. writes the tests.
auditcover专为已可运行代码打造的测试套件。testkit会读取无测试用例或仅有少量过时测试用例的代码库,对值得覆盖的内容进行排序,选定一个测试切片,并编写每个在保留前都被观察到执行失败的测试用例。
两种模式:模式会对未测试范围进行排序,仅生成一份记录文档,不编写任何测试用例;模式则负责编写测试用例。
auditcoverThe failure it exists to prevent
它旨在避免的问题
An agent asked to "write tests for this project" reliably produces coverage theater: a pile of tests that mirror the implementation line for line, assert that mocks were called, pass on the first run, and pin whatever the code does today, bugs included.
It looks exactly like a real suite. Same layout, same green checkmarks, same coverage number. It is worse than no suite, because it charges maintenance rent forever, detects nothing, and hands you a green checkmark that is now evidence in arguments it cannot support.
Two properties separate a real brownfield test from that pile, and both are structurally absent unless you force them:
- The test has been watched to fail. Code written before the test means red-then-green never happens on its own. The failure has to be manufactured deliberately, or it does not happen at all.
- The expectation came from outside the implementation. A test whose expected value was read off the function it tests is a photograph of current behaviour, not a claim about correct behaviour.
Every rule below exists to make those two mandatory rather than aspirational.
当要求Agent「为这个项目编写测试用例」时,通常会产生覆盖率假象:一堆与实现逐行镜像的测试用例,仅断言mock被调用,首次运行就通过,并且固化了当前代码的所有行为(包括bug)。
这类测试用例看起来和真正的测试套件毫无区别,有着相同的目录结构、绿色勾选标记和覆盖率数值,但实际上比没有测试套件更糟糕——它会永久占用维护成本,无法检测任何问题,还会给你一个毫无说服力的绿色勾选标记,甚至在争议中成为无效证据。
真正的遗留系统测试与这类无效测试的区别在于两个核心特性,而这两个特性必须强制实现,否则会结构性缺失:
- 测试用例必须被观察到执行失败:先有代码后有测试的情况下,自然不会出现「红转绿」的过程。必须人为制造失败场景,否则无法验证测试的有效性。
- 预期结果来自实现外部:如果测试的预期值直接取自被测函数本身,那它只是当前行为的快照,而非对正确行为的断言。
以下所有规则的存在,都是为了让这两个特性成为强制要求,而非可选目标。
When this fires
触发场景
"Write tests for this", "this repo has no tests", "add test coverage", "set up testing", "what should I test first", "our coverage is terrible", "/testkit".
Boundaries against the kits that sit closest:
- Not test-driven development on new work. A build skill's TDD mode owns red-then-green for code that does not exist yet. testkit's entire premise is that the code is already there and already runs.
- Not a manual QA plan. A QA skill writes steps a human executes by hand. Nothing testkit produces is run by a person.
- Not browser proof for a pull request. A visual-verification skill drives a browser once and captures images. testkit writes specs that a test command reruns forever.
- Not diagnosis. A debugging skill chases one symptom to its cause and may produce one failing test as a reproduction. testkit builds a suite.
当用户提出「为这个编写测试用例」「这个仓库没有测试用例」「增加测试覆盖率」「搭建测试环境」「我应该先测试什么」「我们的覆盖率很差」或输入「/testkit」时触发。
与同类工具的边界区分:
- 不适用于新代码的测试驱动开发(TDD):构建类工具的TDD模式负责处理尚未存在的代码的「红转绿」流程,而testkit的核心前提是代码已存在且可运行。
- 不是手动QA测试计划:QA类工具会编写人工执行的测试步骤,testkit生成的内容无需人工运行。
- 不是PR的浏览器验证:视觉验证类工具会驱动浏览器一次并捕获截图,testkit编写的是可通过测试命令永久重复运行的测试用例。
- 不是问题诊断:调试类工具会追踪单个症状找到根源,可能生成一个失败测试用例作为复现场景,而testkit负责构建完整的测试套件。
It never fixes, and it never restructures
绝不修复代码,也绝不重构代码
Two hard boundaries, both of which will feel wrong in the moment:
Retrofitting tests uncovers bugs. testkit reports them and does not fix them. When observed behaviour contradicts the external expectation, do not edit the source to match the test, and do not edit the test to match the source. Write the test asserting the intended behaviour, mark it skipped or expected-to-fail with a one-line pointer, and report it. A skill that finds a defect and also lands the cure has decided the question before writing the report.
Some code cannot be tested without restructuring it. testkit does not restructure it. No extracted interface, no injected dependency, no "small seam while I'm in here." Untestable code becomes a testability blocker in the ledger, routed onward. A refactoring skill's untested coupling pattern is precisely this finding with a proposal attached.
The only source edits testkit ever makes are the temporary mutations of The failure gate, and every one is reverted.
两条硬性边界,尽管在执行时可能会让人觉得不合理:
适配测试用例时会发现bug,但testkit仅报告bug,不修复bug:当观察到的行为与外部预期不符时,既不要修改源代码以匹配测试用例,也不要修改测试用例以匹配源代码。编写断言预期行为的测试用例,标记为跳过或预期失败,并附上一行指向问题的说明,然后上报。如果一个工具发现缺陷的同时直接修复,相当于在提交报告前就自行做出了判断。
有些代码不重构就无法测试,但testkit绝不重构代码:不会提取接口、注入依赖,也不会「顺便开个小口子」。无法测试的代码会在记录文档中标记为可测试性障碍,并转交给其他工具处理。重构类工具的「未测试耦合」模式正是针对这类问题提出解决方案的。
testkit唯一会对源代码进行的修改是失败验证机制中的临时变更,且所有变更都会被还原。
Mode selection
模式选择
Take the first tier that answers:
- The user said a mode, as in or
/testkit audit. Explicit always wins./testkit cover src/billing - A ledger exists → . No ledger →
cover.audit
A request against a repo with no ledger does not bounce. Run the ranking inline, crown a slice, say which path the run took, and proceed. Refusing to write a test until a survey document exists is bureaucracy, and it is the kind that gets a skill uninstalled.
coverAn optional scope argument narrows what gets ranked in either mode. It never changes what gets written.
按以下优先级确定模式:
- 用户指定模式:例如输入或
/testkit audit,显式指令优先。/testkit cover src/billing - 已有记录文档 → 使用模式;无记录文档 → 使用
cover模式。audit
如果对无记录文档的仓库发起请求,不会拒绝:会直接执行排序,选定测试切片,说明执行路径后继续。要求必须先有调研文档才能编写测试用例属于官僚作风,会导致工具被卸载。
cover可选的范围参数可以缩小两种模式下的排序范围,但不会改变最终编写的内容。
The ledger
记录文档
One file per repository: , where the date is its creation date and stays fixed forever. creates it. Every run after that updates it in place. A scoped run appends under a scoped heading in the same file, and it never spawns a second one.
docs/tests/testplan-<repo>-YYYY-MM-DD.mdauditOne file is what makes run N+1 cheap. A brownfield retrofit does not finish in one session, and a skill that writes a fresh dated survey per slice leaves a pile of surveys and no resumable state at all.
It carries:
- the ranked untested surface, stamped ;
ranked against <sha> on <date> - what each run covered, with its date and its declared-versus-actual count;
- what was deferred, and why;
- testability blockers, meaning code that cannot be tested without restructuring;
- unproven tests, meaning pre-existing tests that survived a mutation they should have caught.
Durable and committable. testkit never commits it.
When the repo already has a home or naming scheme for test-planning documents, that convention wins, so say that you followed it. No writable filesystem (a browser-based agent) means printing the ledger as a codeblock under its canonical path instead.
每个仓库对应一份文档:,其中日期为文档创建日期,永久固定。模式负责创建文档,后续每次运行都会更新该文档。带范围的运行会在同一文档的对应标题下追加内容,不会生成新文档。
docs/tests/testplan-<repo>-YYYY-MM-DD.mdaudit单份文档的设计是为了让第N+1次运行更高效。遗留系统的测试适配无法在一次会话中完成,如果每个测试切片都生成一份新的日期调研文档,最终会留下一堆调研文档,且无法恢复之前的状态。
记录文档包含以下内容:
- 已排序的未测试范围,标记为「基于<sha>在<date>排序」;
- 每次运行覆盖的内容,包含日期和声明数量与实际数量的对比;
- 被推迟的内容及原因;
- 可测试性障碍:即不重构就无法测试的代码;
- 未验证测试用例:即已存在但未通过突变测试的测试用例。
文档是持久且可提交的,但testkit不会自动提交它。
如果仓库已有测试计划文档的存放位置或命名规范,则遵循该规范,并说明已遵循该规范。如果没有可写入的文件系统(如基于浏览器的Agent),则将记录文档作为代码块打印在其规范路径下。
Mode: audit
audit模式:audit
auditRead-only. It writes the ledger and nothing else: no test file, no source edit.
只读模式。仅生成记录文档,不编写任何测试文件或修改源代码。
1. Derive the untested surface
1. 确定未测试范围
Find what source exists, what tests exist, and what those tests actually reach. Read a coverage report only if one is already on disk. Never generate one, and never install a tool to produce evidence, because probing for an analysis tool and parsing its output couples the skill to a format that changes on a minor release.
Exclude before ranking, not after: generated code, vendored trees, thin configuration, and pure delegation. They inflate a count and prove nothing.
找出已存在的源代码、已有的测试用例,以及这些测试用例实际覆盖的范围。仅读取已存在于磁盘上的覆盖率报告,绝不生成覆盖率报告,也绝不安装工具来生成证据,因为查找分析工具并解析其输出会让工具依赖于可能随小版本更新而变化的格式。
在排序前排除以下内容:生成代码、第三方依赖代码、简单配置代码和纯委托代码。这些内容会夸大统计数量,且没有实际测试价值。
2. Rank
2. 排序
One read of the history gives both signals at once: yields how often each file changes and which files keep changing together.
git log --format= --name-only --since=<about a year>Rank on four signals:
- Churn. How often it changes. Code nobody touches breaks nobody.
- Fan-in. How many modules depend on it. A break here is a break everywhere.
- Failure cost. Money, authentication, data loss, migrations, anything irreversible.
- Testability cost. Divide by this. A behaviour that needs three services standing up costs more than its rank suggests.
No git history, meaning a shallow clone or not a repository, means no ranking. Scan by structure instead and say plainly that the prioritisation was skipped, so nobody reads the coverage claim as more than it is.
只需读取一次历史记录即可获取两个信号: 可以得出每个文件的变更频率,以及哪些文件经常一起变更。
git log --format= --name-only --since=<about a year>基于四个信号进行排序:
- 变更频率:文件变更的次数。无人修改的代码不会导致问题。
- 依赖度:有多少模块依赖该文件。此处出现问题会影响所有依赖模块。
- 失败成本:涉及金钱、认证、数据丢失、迁移等不可逆的后果。
- 可测试成本:排序结果需除以该值。需要启动三个服务才能测试的行为,其实际优先级低于排序结果显示的数值。
如果没有git历史记录(如浅克隆或非仓库项目),则不进行排序。改为按结构扫描,并明确说明跳过了优先级排序,避免用户将覆盖率数据视为完整结果。
3. Crown one slice
3. 选定一个测试切片
Group the top of the ranking into coherent slices along the co-change clusters, then crown exactly one, with runners-up in order. Crown a slice rather than a file: a coherent behaviour rarely lives in one file, and a file is not a unit of meaning.
Crowning one is the work. A list of five equal-looking candidates is the state the user was already in.
将排序靠前的内容按共同变更集群分组为连贯的切片,然后仅选定一个,并列出候选顺序。选定切片而非单个文件:一个连贯的行为很少只存在于一个文件中,单个文件也不是有意义的测试单元。
仅选定一个切片是关键工作。列出五个看似同等重要的候选只会让用户回到最初的困惑状态。
4. Write the ledger
4. 生成记录文档
Every run carries a coverage line, in the terminal and in the file: "ranked 1,240 files, read the top 40 across 3 slices." A cap nobody can see reads as completeness.
An audit that finds no meaningful untested surface writes no file and says so. A survey obliged to produce findings will manufacture them, and manufactured advice about what to test reads exactly like the real thing.
每次运行都会在终端和文档中显示一条覆盖率统计行:「已排序1240个文件,查看了3个切片中的前40个」。设置一个不可见的上限,避免用户误以为已覆盖全部内容。
如果审计发现没有有意义的未测试范围,则不生成文档并告知用户。如果强制要求生成调研结果,工具会编造内容,而编造的测试建议看起来和真实建议毫无区别。
Mode: cover
cover模式:cover
coverWrites the tests.
编写测试用例。
1. Take the slice, and check the ranking is still true
1. 获取测试切片,并验证排序是否仍然有效
Take it from the ledger, from the user, or from an inline ranking when neither exists, and say which.
The ledger's ranking is stamped with the commit it was computed against. Compare . Re-rank when the commits since that sha touched files in the ledger's top slice. Otherwise trust it out loud, naming the sha you trusted. A durable ranking outlives the code it ranked, so an August ordering will happily drive a November run at whatever used to be hot.
HEAD从记录文档、用户指定或直接执行内联排序获取测试切片,并说明来源。
记录文档的排序结果标记了计算时对应的提交记录。与进行对比:如果自该sha以来的提交修改了记录文档中排名靠前的切片中的文件,则重新排序。否则,明确说明信任该排序结果,并指出所信任的sha。持久化的排序结果可能会过时,例如8月的排序结果可能会在11月仍用于测试曾经的热点代码。
HEAD2. Stand up a runner, if there is none
2. 搭建测试运行器(如果尚未存在)
Skip this when the repo already has one.
- Inherit the ecosystem's default. A project with no tests has no opinion to honour, so the choice least likely to be relitigated is whatever the language's own documentation reaches for. When the choice is genuinely contested, route to a research skill rather than deciding it here.
- Wire it into the repo's existing entry point, such as ,
npm test, ormake test, so the suite is reachable by the command someone would already try. A suite reachable only by an incantation in a chat log is not a suite.pytest - Test-file placement follows the same rule. Where an ecosystem carries two live conventions (colocated specs versus a tree), follow whatever the repo's existing layout already implies rather than importing a preference.
tests/ - Ask once before installing a dependency. A dev dependency is a durable change to somebody else's project.
- Land one green smoke test before writing anything real. A retrofit that opens with forty tests against an unproven harness debugs the harness through the tests.
When no standard runner can be wired up, because the language, build system, or dependency situation defeats it, report the specific obstacle and stop. Never improvise a harness. A hand-rolled test loop is something nobody else can run, maintain, or replace, and it would be the most durable thing this skill ever left behind.
如果仓库已有测试运行器,则跳过此步骤。
- 继承生态系统的默认选择:无测试用例的项目没有明确的偏好,因此选择该语言官方文档推荐的工具最不容易引发争议。如果选择存在争议,则转交给调研类工具处理,而非自行决定。
- 接入仓库现有的入口命令:如、
npm test或make test,确保测试套件可通过用户已习惯使用的命令访问。仅能通过聊天记录中的咒语访问的测试套件毫无意义。pytest - 测试文件的位置遵循相同规则:如果生态系统存在两种常用规范(与源代码同目录的测试用例与单独的目录),则遵循仓库现有布局暗示的规范,而非引入新的偏好。
tests/ - 安装依赖前询问一次:开发依赖是对他人项目的持久变更。
- 在编写实际测试用例前,先编写一个可通过的冒烟测试:如果一开始就针对未经验证的测试框架编写40个测试用例,会导致调试测试框架的过程混入测试用例的编写中。
如果无法接入标准测试运行器(如语言、构建系统或依赖情况不支持),则报告具体障碍并停止。绝不自行编写测试框架:手动编写的测试循环无法被他人运行、维护或替换,会成为工具留下的最持久的无效内容。
3. Declare the size before writing anything
3. 编写测试用例前声明数量
State it before the first test file exists: "N behaviours in this slice, this run covers M."
You pick the numbers. They go on the record in advance, and the hand-off reports actual against declared. A run that wants to exceed its declaration stops and says so rather than drifting.
Nothing else bounds a run. Three tests and thirty both read as compliant afterwards, and the failure gate's cost scales linearly with a number nobody wrote down. A fixed cap would be wrong for both a 200-line utility and a payments module; a question every run would make an unattended run impossible.
在创建第一个测试文件前明确说明:「该切片包含N个行为,本次运行将覆盖M个」。
由你确定具体数值。这些数值会提前记录在案,后续交接时会对比声明数量与实际数量。如果运行想要超出声明数量,需停止并告知用户,而非自行增加。
没有其他规则限制运行次数。无论是3个还是30个测试用例,后续都会被视为合规,且失败验证机制的成本与未记录的数量成正比。固定上限对200行的工具类代码和支付模块都不适用;每次运行都询问数量会导致无人值守的运行无法进行。
4. Write the tests
4. 编写测试用例
Source every expectation from outside the implementation, meaning the docstring, the README, the issue, the type signature, the caller's actual usage, or the domain. Where nothing outside speaks, the test is a characterization test: a change-detector that locks current behaviour and makes no claim about correctness.
Every test carries a one-line provenance comment, whichever kind it is:
// per README: "amounts are stored in cents"
// characterization: no docstring, no issue, no caller assertionMaking both sides cost the same is the entire mechanism. A free label gets stamped on everything until the distinction means nothing. A label that costs more than the alternative pushes you to invent an external expectation to dodge it, which is the exact dishonesty the rule exists to catch. A citation on both sides removes the dodge in both directions.
Fake only at the process boundary, meaning network, clock, randomness, filesystem, or third-party service, and only where the real thing is unavailable or nondeterministic. Never fake a collaborator that lives inside the boundary. Never assert that a call happened. A test whose only assertion is a call count or a spy argument is checking that the implementation is the implementation.
One behaviour per test, named for the behaviour rather than for the function it happens to enter through.
A contradiction becomes a skipped test plus a report, never a source edit, never a weakened assertion. See It never fixes, and it never restructures.
所有预期结果均来自实现外部:即文档字符串、README、issue、类型签名、调用方的实际使用场景或领域知识。如果没有外部参考,则编写特性测试用例:即锁定当前行为的变更检测器,不对正确性做出断言。
无论哪种测试用例,都需包含一行来源注释:
// per README: "amounts are stored in cents"
// characterization: no docstring, no issue, no caller assertion让两种测试用例的编写成本一致是核心机制。如果可以免费添加标签,所有内容都会被随意标记,导致区分失去意义。如果标签的成本高于替代方案,会促使你寻找外部预期来避免添加标签,而这正是该规则旨在防止的不诚实行为。为两种测试用例都添加引用,可以避免双向的规避行为。
仅在流程边界进行模拟:即网络、时钟、随机数、文件系统或第三方服务,且仅在真实资源不可用或不确定时进行。绝不模拟边界内的协作对象。绝不断言调用是否发生:仅断言调用次数或间谍参数的测试用例,只是在验证实现与自身一致。
每个测试用例对应一个行为,以行为命名,而非以其进入的函数命名。
如果出现矛盾,则标记为跳过的测试用例并上报,绝不修改源代码,也绝不弱化断言。详见绝不修复代码,也绝不重构代码。
5. Verify every target before running against it
5. 运行前验证所有目标
Nothing runs against a datastore or service testkit has not verified as disposable.
Qualifying targets: , a container testkit or the user started, or an explicitly named test URL. A value inherited from an ambient never qualifies, however the file is named. A config called test is a claim, not a fact, and trusting it is exactly the assumption that destroys somebody's data.
localhost.envWhere no disposable target exists, ask once to scaffold one. If the answer is no, defer the integration work to the ledger as a blocker and carry on with the unit tier.
This is the one rule here whose failure is unrecoverable, which is why it is written as a refusal rather than a caution.
绝不针对testkit未验证为可丢弃的数据源或服务运行测试。
符合要求的目标:、testkit或用户启动的容器,或明确命名的测试URL。从环境变量继承的值绝不视为符合要求,无论该文件如何命名。名为「test」的配置只是一个声明,而非事实,信任它可能会导致数据丢失。
localhost.env如果没有可丢弃的目标,询问一次是否搭建。如果用户拒绝,则将集成工作推迟到记录文档中作为障碍,并继续进行单元测试。
这是唯一一条失败后无法恢复的规则,因此采用拒绝而非警告的方式编写。
6. The e2e tier, when it applies
6. 端到端(e2e)测试层(适用时)
Opt-in and narrow. It runs only when the app already launches non-interactively and a driver is installed or the user approves installing one. Cap it at a handful of critical-path specs, such as the sign-in and the one transaction that matters, never a mirror of the UI.
testkit never opens a browser as a session action. It authors the spec; the only thing that ever drives a browser is the repo's own test command executing that spec. The failure gate therefore applies to an e2e spec exactly as it does to a unit test, through the runner, with no exemption for the tier most likely to be written wrong.
That is the seam against a visual-verification skill, and it is stated as who invokes the driver rather than as artifact lifetime, because both skills legitimately want the same launch command, the same fixtures, and the same driver config, and only the first framing survives that overlap.
可选且范围狭窄。仅当应用已支持非交互式启动且已安装驱动或用户同意安装驱动时运行。将其限制在少数关键路径用例中,如登录和核心交易流程,绝不镜像整个UI。
testkit绝不会在会话中打开浏览器:它负责编写测试用例,驱动浏览器的唯一方式是仓库自身的测试命令执行该用例。因此,失败验证机制同样适用于e2e测试用例,通过运行器执行,没有任何例外——而e2e正是最容易编写错误的测试层级。
这是与视觉验证类工具的边界,通过「谁调用驱动」而非「产物生命周期」来区分,因为两类工具都可能需要相同的启动命令、 fixtures和驱动配置,只有前者的定义能在重叠场景下保持清晰。
The failure gate
失败验证机制
Every test testkit keeps has been observed red. A test never watched to fail has demonstrated nothing about whether it is connected to the code at all.
The code already exists, so red-then-green is unavailable. The substitute: break the behaviour, run the test, watch it fail, restore.
A valid break is a semantic mutation, never a deletion. Change a returned value, flip a comparison, drop a branch, skip a write. Deleting the function or the file makes everything fail, including a test that asserts nothing, so it proves the import path resolves and nothing else. Use the smallest edit that changes the behaviour the test claims to check.
Run the narrowest selection the runner supports. The target test plus the others in its file. Never the full suite. Confirm the expected one goes red and its neighbours stay green, because that second half is free, and it catches an assertion that reaches too far. Where the runner cannot select a file or a pattern, lower the declared behaviour count and say why.
The gate is never skipped for slowness. A slow suite is the condition that makes the gate valuable, so an exemption would open in precisely the situation that most tempts you through it. Full-suite runs happen exactly twice, at the done-gate, and nowhere else.
A test testkit wrote this run that stays green under its mutation is deleted, and the deletion is reported.
A pre-existing test that stays green is recorded, not deleted. Log it in the ledger as unproven, with the mutation that missed it, and route it to the user. The delete rule is scoped to tests testkit authored this run. Deleting somebody else's test on evidence from a mutation aimed at something else is a scope this skill has not earned, and it converts a helpful signal into an unrecoverable one.
testkit保留的每个测试用例都必须被观察到执行失败。未被观察到失败的测试用例无法证明其与代码的关联有效性。
由于代码已存在,自然的「红转绿」流程不可用。替代方案:破坏行为,运行测试,观察失败,恢复代码。
有效的破坏是语义变更,而非删除:修改返回值、翻转比较条件、跳过分支、跳过写入操作。删除函数或文件会导致所有测试用例失败,包括无断言的测试用例,只能证明导入路径有效,无法证明其他内容。应使用最小的修改来改变测试用例声称要验证的行为。
运行运行器支持的最窄范围:目标测试用例及其所在文件中的其他测试用例。绝不运行完整套件。确认预期的测试用例失败且其他测试用例保持通过,因为这一步可以免费完成,且能发现断言范围过大的问题。如果运行器无法选择文件或模式,则降低声明的行为数量并说明原因。
绝不因速度慢而跳过验证机制:测试套件速度慢正是验证机制有价值的场景,因此在最容易诱惑你跳过的场景下,绝不能开例外。完整套件仅在完成验证时运行两次,其他场景绝不运行。
如果本次运行编写的测试用例在变更后仍通过,则删除该测试用例并上报。
如果已存在的测试用例在变更后仍通过,则记录但不删除:在记录文档中标记为「未验证」,并附上未检测到的变更,转交给用户。删除规则仅适用于本次运行中testkit编写的测试用例。基于针对其他内容的变更证据删除他人编写的测试用例,超出了该工具的权限范围,会将有用的信号转化为不可恢复的损失。
The mutation ledger
变更记录文档
Mutations are edits to tracked source. The user may have had uncommitted work when the run started, and reverting a mutation must never revert it.
- Snapshot first. Take before the first mutation. It writes an unreferenced commit object and touches no ref and no file, which is what makes it free, and also what makes it invisible, so print the SHA and its
git stash createrecovery line in the hand-off on every run. Git prunes unreachable objects on its own schedule; say so rather than overselling the net.git stash apply <sha> - Record every mutation, meaning the file, the diff, and the behaviour it was testing.
- Revert by reverse-applying the recorded diff. Never restore a file. is the reflex move and the one that silently destroys a pre-existing uncommitted edit. The ban has no exception for files that looked clean at baseline, because a ban with exceptions to reason about is not a ban.
git checkout -- <file> - On a reverse-apply conflict, stop the run and report. Do not force. Do not fall back to a restore.
- Verify the tree matches the baseline before declaring done. Name by path anything deliberately left in place.
- Never revert, stash, or discard a change testkit did not make.
变更是指对已跟踪源代码的修改。用户在运行开始时可能有未提交的工作,还原变更时绝不能还原这些工作。
- 先快照:在第一次变更前执行。该命令会创建一个无引用的提交对象,不会修改任何引用或文件,因此是无成本的,但也是不可见的。因此,在每次运行的交接信息中打印该SHA及其恢复命令
git stash create。Git会自行清理不可达对象,需告知用户这一点,避免过度承诺。git stash apply <sha> - 记录所有变更:包括文件、差异和测试的行为。
- 通过反向应用记录的差异来还原变更,绝不直接恢复文件:是本能反应,但会无声地销毁已存在的未提交修改。该禁令无例外,即使基线时文件看起来是干净的——因为有例外的禁令不是真正的禁令。
git checkout -- <file> - 如果反向应用差异时发生冲突,则停止运行并上报:绝不强制操作,绝不恢复文件。
- 声明完成前,验证代码树与基线一致:列出所有故意保留的路径。
- 绝不还原、暂存或丢弃testkit未做出的变更。
The done-gate
完成验证
"Done" means green, twice, shuffled, and clean. Discover the commands from the repo itself ( scripts, , , , CI config) rather than guessing.
package.jsonMakefilepyproject.tomljustfile- The new suite passes.
- Every kept test was observed red, against a named mutation.
- The suite runs twice in a row and passes both times, in randomised order where the runner supports it. Order dependence is the signature flakiness of a retrofitted suite, because tests written against existing shared state inherit that state.
- The repo's own build/typecheck still passes.
- The source tree carries no leftover mutation.
If the gate fails, fix your own output and re-run, bounded to roughly three attempts. Then stop. Never declare done on a failing gate, and never loop. The tempting fix here is to weaken the assertion; that is not a fix.
「完成」意味着测试套件通过两次随机运行,且代码树干净。从仓库自身的配置(脚本、、、、CI配置)中获取命令,而非猜测。
package.jsonMakefilepyproject.tomljustfile- 新测试套件通过;
- 每个保留的测试用例都被观察到执行失败,并对应明确的变更;
- 测试套件连续两次运行都通过,如果运行器支持,则随机化执行顺序。顺序依赖是遗留系统测试套件不稳定的典型特征,因为针对现有共享状态编写的测试用例会继承该状态;
- 仓库自身的构建/类型检查仍通过;
- 源代码树无遗留变更。
如果验证失败,修复自身输出并重新运行,最多尝试三次。然后停止。绝不在验证失败时声明完成,也绝不循环运行。此时最容易做出的错误修复是弱化断言,但这不是真正的修复。
Coverage numbers
覆盖率数值
Never a goal, never a stopping condition. Report a percentage only as a before-and-after fact, and only when the toolchain already produces one.
The stopping condition is the declared count. A target percentage is the most reliable way to manufacture exactly the pile this skill exists to prevent, because every rule above is expensive and a number can be reached without any of them.
绝不是目标,也绝不是停止条件。仅当工具链已生成覆盖率数据时,才将其作为前后对比的事实报告。
停止条件是声明的数量。设定覆盖率目标是制造该工具旨在避免的无效测试用例的最可靠方式,因为上述所有规则都需要成本,而仅靠数值可以无需遵守任何规则就能达到。
Hand off
交接
Write this section in the procedural register: one instruction per sentence, active voice, present tense, no metaphor.
After . Report the coverage line, the crowned slice, and the runners-up in order. Give the ledger path. Name the testability blockers and the unproven pre-existing tests, if the run found any. Say there is no test code yet. Then crown one next move: run on the crowned slice. When the audit wrote no file, say the surface is already covered and state that there is no next step.
auditcoverAfter . Report:
cover- the mode and where the slice came from, whether the ledger, the user, or an inline ranking;
- declared against actual, as in "declared 8 behaviours, covered 8";
- the files created and changed, including the runner wiring;
- the gate result, meaning the commands that ran, that every kept test was observed red, and that the suite passed two consecutive runs;
- deletions, meaning tests testkit wrote and then removed because they stayed green;
- unproven pre-existing tests, by path;
- the baseline snapshot: ;
baseline snapshot: <sha> · recover with git stash apply <sha> - tree state, meaning the source is clean of mutations, or the paths that still carry one.
Leave every change unstaged. Do not . Do not commit. Do not draft a commit message.
git addThen crown one next move:
- Deferred work remains in the ledger → run again on the next slice.
cover - The run found a contradiction → route it to a debugging skill when one is installed, otherwise say plainly that somebody must decide whether the behaviour or the expectation is wrong.
- The run found testability blockers → route them to a refactoring skill when one is installed, otherwise say the code needs a seam before it can be tested.
- Nothing remains → say the slice is done and name committing as the next move. Suggest a message such as for the user to run. Never commit automatically.
test(<area>): cover <slice>
本节采用流程化表述:每句一个指令,主动语态,现在时态,无隐喻。
auditcovercover- 模式和测试切片的来源(记录文档、用户指定或内联排序);
- 声明数量与实际数量对比:如「声明覆盖8个行为,实际覆盖8个」;
- 创建和修改的文件:包括运行器的接入配置;
- 验证结果:即运行的命令、每个保留的测试用例都被观察到失败、测试套件连续两次通过;
- 删除的内容:即testkit编写后因仍通过而删除的测试用例;
- 未验证的已有测试用例:按路径列出;
- 基线快照:;
baseline snapshot: <sha> · recover with git stash apply <sha> - 代码树状态:即源代码无遗留变更,或列出仍有变更的路径。
所有变更保持未暂存状态。不执行,不提交,不生成提交信息。
git add然后指定下一步操作:
- 记录文档中仍有推迟的工作 → 对下一个测试切片再次运行模式;
cover - 运行中发现矛盾 → 如果已安装调试类工具,则转交给该工具,否则明确说明需有人决定是行为还是预期存在错误;
- 运行中发现可测试性障碍 → 如果已安装重构类工具,则转交给该工具,否则说明代码需要调整后才能测试;
- 无剩余工作 → 说明该切片已完成,并建议用户提交,可使用如的提交信息。绝不自动提交。
test(<area>): cover <slice>
Notes
注意事项
- Never green-wash. A declared "done" always means the gate actually passed. Red after the bounded fixes is reported as red.
- Zero permanent source mutation. Test files, the runner wiring, and the ledger are the entire write surface. Every mutation is temporary and reverted.
- Read-only analysis. Git history, file reads, greps. Never install or run an analysis tool to generate evidence. Read an artifact that is already on disk; never produce one.
- Route, don't launch. Name the next kit and its one-line invocation; do not invoke it.
- Route, don't require. Every recommendation degrades to a plain action when the named kit is not installed. testkit is useful in a bare repository with nothing but git.
- Follow the repo over these defaults. An established test layout, a documented convention, or a stated policy in the repo's agent-guide file (or an equivalent) wins, so say that you followed it.
CLAUDE.md - No filesystem or shell? You cannot write files, run a gate, or mutate anything, so the failure gate cannot run, and no test may be presented as verified. Print the proposed tests as fenced blocks with their paths, print the ledger as a codeblock under its canonical path, and list the gate commands the user must run themselves.
- 绝不粉饰结果:声明「完成」意味着验证机制确实通过。经过有限次修复后仍失败的,需如实报告为失败。
- 无永久源代码变更:测试文件、运行器接入配置和记录文档是唯一的写入内容。所有变更都是临时的,且会被还原。
- 只读分析:仅读取git历史、文件内容和执行grep。绝不安装或运行分析工具来生成证据。仅读取已存在于磁盘上的产物,绝不生成新产物。
- 转交而非启动:指定下一个工具及其一行调用指令,绝不自行启动。
- 转交而非强制:如果指定的工具未安装,所有建议都会降级为普通操作。testkit在仅有git的裸仓库中也能发挥作用。
- 仓库规范优先于默认规则:如果仓库已有测试布局、文档化规范或代理指南文件(如或等效文件)中的明确政策,则遵循该规范,并说明已遵循。
CLAUDE.md - 无文件系统或shell?:无法写入文件、运行验证机制或进行任何变更,因此无法运行失败验证机制,也不能将测试用例视为已验证。将建议的测试用例作为代码块打印,并附带路径;将记录文档作为代码块打印在其规范路径下;列出用户需自行运行的验证命令。