agentic-tdd

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Red, green, refactor: against the running app

Red, green, refactor:针对运行中的应用

Unit tests drive the units. They cannot say "clicking Deploy posts to
/api/deploy
, moves the store to
deploying
, 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.
This runs the same discipline one level up, using Reticle to drive the real app. Not installed?
RETICLE_INSTALL_SOURCE=npx_skill npx @reticlehq/server@latest init
, then the
install-and-verify
skill.
单元测试驱动单元级开发。它们无法描述“点击Deploy按钮后向
/api/deploy
发送POST请求、将状态切换为
deploying
并显示横幅”这类场景——这类结果只有在整个应用运行时才存在。因此,在最容易出现有趣bug的环节,测试循环会停滞,开发人员只能转而编写代码并寄希望于它能正常工作。
我们将同样的准则提升一个层级,使用Reticle来驱动真实应用。还未安装?执行
RETICLE_INSTALL_SOURCE=npx_skill npx @reticlehq/server@latest init
,然后使用
install-and-verify
技能。

Why 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 })
enforces the order structurally:
until
is an argument to the action, so the consequence is named before the action runs. That is the red-green loop, made unfakeable.
测试先行的核心价值在于,在你还不知道答案的时候就编写验证逻辑。在看到结果之后编写的预期总是可以被调整为与实际发生的情况一致,而开发人员尤其擅长这种调整。他们会找到一种对输出的解读方式,让自己刚写的代码看起来是正确的。
reticle_act_and_wait({ ref, action, until })
从结构上强制了顺序:
until
是action的参数,因此在动作执行前就必须明确预期结果。这就是红-绿循环,无法被伪造。

1. 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
verified: "no"
here. A red you did not see is a test you cannot trust. If this comes back
yes
before you have written anything, the expectation is not specific enough to the change. Tighten it until it fails for the right reason.
verified: "unknown"
is not a red. It means Reticle could not tell, so the loop has no signal at all. Fix that before writing code, usually by naming a consequence the app can actually produce.
在编写功能之前,先声明应用必须实现的行为:
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"
的结果。没看到红状态的测试是不可信的。如果在编写任何代码前就返回
yes
,说明预期对变更的针对性不够强。需要细化预期,直到它因正确的原因失败。
verified: "unknown"
不是红状态。这意味着Reticle无法判断结果,测试循环完全没有信号。在编写代码前修复这个问题,通常需要明确一个应用实际能产生的预期结果。

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
unknown
/
unsettled
:
reticle_assert({ predicate, since, timeout_ms: 8000 })
. Re-driving repeats a side effect that already happened.
编写最小的变更使其满足预期,然后重新运行完全相同的调用。最后这点是准则的核心:修改断言以匹配你构建的内容会将TDD变成事后说明。如果必须修改断言,要大声说明原始预期错误的原因。
当结果为
unknown
/
unsettled
时,优先使用断言而非重新驱动:
reticle_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 flow
That 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

断言的优先级,按强度排序

  1. A signal the app fires itself (
    { kind: "signal" }
    ): the app declaring success in its own vocabulary. Strongest available.
  2. State (
    reticle_state
    ): what the app believes. Catches a UI that moved while the store did not.
  3. Network: the request, method and status. Catches a mock standing in for the real thing.
  4. An element appearing: necessary, never sufficient. Anything can render.
  5. Absence of console errors: always include it, never rely on it alone. Absence-only predicates pass on a control wired to nothing.
  1. 应用自身触发的信号 (
    { kind: "signal" }
    ):应用用自身的词汇声明成功,是最强的验证方式。
  2. 状态 (
    reticle_state
    ):应用所认为的状态。可以捕获UI已变化但状态未更新的情况。
  3. 网络请求:请求、方法和状态码。可以捕获用模拟对象替代真实服务的情况。
  4. 元素出现:必要但绝不充分。任何内容都可以被渲染。
  5. 控制台无错误:始终要包含,但绝不能单独依赖它。仅基于“不存在”的断言会在控件未连接任何逻辑时通过。

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:
curl https://docs.reticle.sh/predicates.md
. Everything else:
curl https://docs.reticle.sh/llms.txt
.
绝不要为了让测试变绿而削弱检查。在这个循环中,这不是小过错:这会让循环反向运行,最终得到一套绿状态的测试套件,但对应的功能却无法正常工作。

断言参考:
curl https://docs.reticle.sh/predicates.md
。其他内容参考:
curl https://docs.reticle.sh/llms.txt
。