agentic-tdd
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseRed, green, refactor: against the running app
Red, green, refactor:针对运行中的应用
Unit tests drive the units. They cannot say "clicking Deploy posts to , moves the store to , and shows the banner": that outcome only exists when the whole app runs. So the loop stalls exactly where the interesting bugs are, and the agent falls back to writing code and hoping.
/api/deploydeployingThis runs the same discipline one level up, using Reticle to drive the real app. Not installed? , then the skill.
RETICLE_INSTALL_SOURCE=npx_skill npx @reticlehq/server@latest initinstall-and-verify单元测试驱动单元级开发。它们无法描述“点击Deploy按钮后向发送POST请求、将状态切换为并显示横幅”这类场景——这类结果只有在整个应用运行时才存在。因此,在最容易出现有趣bug的环节,测试循环会停滞,开发人员只能转而编写代码并寄希望于它能正常工作。
/api/deploydeploying我们将同样的准则提升一个层级,使用Reticle来驱动真实应用。还未安装?执行,然后使用技能。
RETICLE_INSTALL_SOURCE=npx_skill npx @reticlehq/server@latest initinstall-and-verifyWhy this is TDD and not just testing afterwards
为什么这是TDD而非事后测试
The whole value of test-first is that the oracle is written while you still do not know the answer. An expectation written after seeing the result can always be adjusted into agreeing with whatever happened, and an agent is especially good at that adjustment. It will find a reading of the output under which the code it just wrote is correct.
reticle_act_and_wait({ ref, action, until })until测试先行的核心价值在于,在你还不知道答案的时候就编写验证逻辑。在看到结果之后编写的预期总是可以被调整为与实际发生的情况一致,而开发人员尤其擅长这种调整。他们会找到一种对输出的解读方式,让自己刚写的代码看起来是正确的。
reticle_act_and_wait({ ref, action, until })until1. RED: write the expectation, watch it fail
1. RED:编写预期,观察测试失败
Before you write the feature, state what the app must do:
reticle_act_and_wait({ sessionId, ref, action: "click", until: { kind: "allOf", predicates: [
{ kind: "net", method: "POST", urlContains: "/api/deploy", status: 200 },
{ kind: "signal", name: "deploy:started" },
{ kind: "element", query: { testid: "deploy-banner" } },
{ kind: "console", level: "error", absent: true },
]}})You want here. A red you did not see is a test you cannot trust. If this comes back before you have written anything, the expectation is not specific enough to the change. Tighten it until it fails for the right reason.
verified: "no"yesverified: "unknown"在编写功能之前,先声明应用必须实现的行为:
reticle_act_and_wait({ sessionId, ref, action: "click", until: { kind: "allOf", predicates: [
{ kind: "net", method: "POST", urlContains: "/api/deploy", status: 200 },
{ kind: "signal", name: "deploy:started" },
{ kind: "element", query: { testid: "deploy-banner" } },
{ kind: "console", level: "error", absent: true },
]}})此时你希望得到的结果。没看到红状态的测试是不可信的。如果在编写任何代码前就返回,说明预期对变更的针对性不够强。需要细化预期,直到它因正确的原因失败。
verified: "no"yesverified: "unknown"2. GREEN: implement until the same call passes
2. GREEN:实现功能直到同一调用通过
Write the smallest change that makes it hold, then re-run the same call, unchanged. That last word is the discipline: editing the predicate to match what you built converts TDD into narration. If the assertion has to change, say out loud why the original expectation was wrong.
Prefer re-asserting over re-driving when the verdict was / : . Re-driving repeats a side effect that already happened.
unknownunsettledreticle_assert({ predicate, since, timeout_ms: 8000 })编写最小的变更使其满足预期,然后重新运行完全相同的调用。最后这点是准则的核心:修改断言以匹配你构建的内容会将TDD变成事后说明。如果必须修改断言,要大声说明原始预期错误的原因。
当结果为/时,优先使用断言而非重新驱动:。重新驱动会重复已经发生过的副作用。
unknownunsettledreticle_assert({ predicate, since, timeout_ms: 8000 })3. REFACTOR: the expectation is the safety net
3. REFACTOR:预期是安全网
Now change the implementation freely and re-run. Predicates are bound to behaviour (a request, a signal, a state path), not to markup, so a refactor that preserves behaviour stays green while a DOM-shaped test would go red for no reason.
现在可以自由修改实现并重新运行测试。断言与行为(请求、信号、状态路径)绑定,而非与标记语言绑定,因此保留行为的重构会保持绿状态,而基于DOM的测试会毫无理由地变红。
4. Keep the loop for the next change
4. 为下一次变更保留循环
A journey worth writing test-first is a journey worth re-running forever. Save it once:
reticle_run({ tool: "reticle_flow_save", sessionId, args: { flowName: "deploy" } })
reticle_run({ tool: "reticle_verify", sessionId, args: { action: "flows" } }) // every saved flow, no model per flowThat turns the red-green loop into a regression suite you never hand-wrote.
值得测试先行开发的流程也值得永久重复运行。只需保存一次:
reticle_run({ tool: "reticle_flow_save", sessionId, args: { flowName: "deploy" } })
reticle_run({ tool: "reticle_verify", sessionId, args: { action: "flows" } }) // every saved flow, no model per flow这会将红-绿循环变成一套你从未手动编写过的回归测试套件。
What to assert on, in order of strength
断言的优先级,按强度排序
- A signal the app fires itself (): the app declaring success in its own vocabulary. Strongest available.
{ kind: "signal" } - State (): what the app believes. Catches a UI that moved while the store did not.
reticle_state - Network: the request, method and status. Catches a mock standing in for the real thing.
- An element appearing: necessary, never sufficient. Anything can render.
- Absence of console errors: always include it, never rely on it alone. Absence-only predicates pass on a control wired to nothing.
- 应用自身触发的信号 ():应用用自身的词汇声明成功,是最强的验证方式。
{ kind: "signal" } - 状态 ():应用所认为的状态。可以捕获UI已变化但状态未更新的情况。
reticle_state - 网络请求:请求、方法和状态码。可以捕获用模拟对象替代真实服务的情况。
- 元素出现:必要但绝不充分。任何内容都可以被渲染。
- 控制台无错误:始终要包含,但绝不能单独依赖它。仅基于“不存在”的断言会在控件未连接任何逻辑时通过。
Honesty
诚信原则
Never weaken a check to turn a verdict green. In this loop that is not a small sin: it is the loop running backwards, and it produces a green suite over a feature that does not work.
Predicate reference: . Everything else: .
curl https://docs.reticle.sh/predicates.mdcurl https://docs.reticle.sh/llms.txt绝不要为了让测试变绿而削弱检查。在这个循环中,这不是小过错:这会让循环反向运行,最终得到一套绿状态的测试套件,但对应的功能却无法正常工作。
断言参考:。其他内容参考:。
curl https://docs.reticle.sh/predicates.mdcurl https://docs.reticle.sh/llms.txt