对 Coding Agent 最友好的,是端到端测试 Harness

E2E Test Harness Is the Most Agent-Friendly Infrastructure

上周我让一个 Coding Agent 给一个 Web 应用实现登录功能。三轮迭代之后,它汇报:「完成了,所有测试通过。」

我打开浏览器一看:登录按钮不见了。

我把截图丢回去,它看了一眼,回复:「抱歉,上一轮我把按钮组件误删了。」

有意思的不是 Agent 会犯错——犯错很正常。有意思的是: 它直到看见那张截图,才知道自己犯了错。 那三轮迭代里,它有编译反馈,有测试报告,但它没有任何办法知道最终页面长什么样。它的「世界」是残缺的。

这件事让我更确认了一个琢磨了很久的判断:

对于 Coding Agent,最重要的基础设施不是更聪明的模型,不是 Benchmark,而是端到端(E2E)Test Harness。

E2E Test Harness Is the Most Agent-Friendly Infrastructure

Agent 不是代码生成,是闭环

很多人对 Coding Agent 的心智模型还停留在「更聪明的补全」:给它一个任务,它生成一段代码,结束。

但 Agent 真正的工作方式是一个闭环:

Task
Agent 修改代码
Build → Run → E2E Test
反馈:Logs / Screenshot / Network / Exit Code
Agent 分析失败原因
继续修改

这个闭环和「一次性生成」的本质区别在于: Agent 不是一步到位,而是靠反馈逐步逼近目标。

反馈的质量,决定迭代的质量。而提供反馈的那个东西,就是 Harness。

换句话说,Harness 就是 Agent 的环境(Environment),更准确地说,是 Agent 的 世界模型接口(World Interface):Agent 通过它感知世界,也通过它验证自己的每一个动作。

开头那个故事里,Agent 前三轮之所以「不知道自己删了按钮」,就是因为它的 Harness 里没有浏览器这一层——它的世界里根本不存在「页面长什么样」这个事实。

Unit Test 回答的是错误的问题

很多人会说:我们有测试啊,unit test 覆盖率 80%。

但 unit test 和 E2E 回答的是两个不同的问题:

  • Unit Test 回答:「这个函数是不是对的?」
  • E2E Test 回答:「整个任务完成了吗?」

还是登录这个例子。Unit test 的验证是这样的:

def test_validate_password():
    assert validate_password("secret") is True
# generated by hugo AI

而任务「帮我实现登录」真正需要的验证是这样的:

def test_login_e2e(page):
    page.goto("http://localhost:3000/login")
    page.fill("#username", "alice")
    page.fill("#password", "secret")
    page.click("button[type=submit]")
    # oracle 是「最终状态」,不是中间函数
    assert page.url.endswith("/dashboard")
    assert "alice" in page.locator(".welcome").inner_text()
# generated by hugo AI

区别在哪?Unit test 全绿,只能证明 validate_password 这个函数对了;它证明不了「用户能登录并看到 Dashboard」。路由接错了、按钮被删了、Session 没写进去——这些都会让 unit test 全绿,但任务实际是失败的。

对于 Agent 来说, 最终状态远比中间函数重要。因为用户要的是「登录能用」,不是「某个函数正确」。

这不是说 unit test 没用——后面会讲,它是分层反馈里必不可少的一层。但如果 Harness 只有 unit test,Agent 拿到的就是一个 与真实任务错位 的信号。

Harness 的本质:给 LLM 提供 Reward

LLM 本身是没有 reward 的。它生成代码,但它不知道这段代码好不好——除非有人告诉它。

Harness 就是那个「告诉它」的东西:

PASS

或者:

FAIL
Expected: Dashboard
Actual: 500 Internal Server Error

甚至是一张截图:

Screenshot: Login button disappeared.

每一种反馈,都在让 Agent 的下一轮决策变得更准。拿到 500 错误,它去查服务端日志;看到按钮消失的截图,它意识到「我把按钮删掉了」。

所以 Harness 的价值不是「跑测试」这么简单,而是 为 Agent 的每一步行动提供可验证的结果。模型负责决策,Harness 负责让决策有后果。

我在 面向 Agent 开发:可测试和可运维第一次真正成为前提条件 里讲过:人可以靠经验兜底,Agent 只能依赖「可调用、可验证、可反馈」的接口。E2E Harness 就是这套接口里最关键的一块——它是 Agent 的验收能力本身。

但 Reward 有经济学:延迟、噪声、防作弊

承认「Harness 提供 reward」只是第一步。真正做过的人都知道,Harness 设计的难点不在「有没有信号」,而在三个变量。我把它们叫做 reward 经济学

变量一:延迟

E2E 很慢。起服务、起浏览器、跑完整流程,一轮可能要几分钟。如果 Agent 每改一行代码都要等一次完整 E2E,它的迭代次数会被墙钟时间卡死。

所以好的 Harness 一定是 分层 的:

信号延迟作用
Build / 类型检查语法、类型错误秒级挡住低级错误
Unit Test函数逻辑错误十秒级挡住局部回归
E2E Test任务级失败分钟级最终验收

设计原则是: 让快而稠密的信号挡住大部分错误,把慢而贵的 E2E 留给终审。 登录例子里那个 500 错误,理想情况下应该在 Run 阶段就被抓到,根本轮不到浏览器出场。

变量二:噪声

E2E 天然 flaky:端口冲突、时序竞态、第三方服务抖动。

一个有 10% 抖动率的 Harness,给出的不只是稀疏的 reward,而是 错误的 reward——测试挂了,Agent 花了五轮去「修」一个根本不存在的 bug,最后学会了「这个测试随机挂,忽略它」。噪声比延迟更致命,因为它会主动教坏 Agent。

所以 Harness 的质量,本质上是一个信噪比问题。确定性环境(docker-compose、固定 seed、隔离的测试数据)不是锦上添花,是 Harness 的地基:

services:
  db:
    image: postgres:16
    environment:
      POSTGRES_PASSWORD: ${DB_PASSWORD}
    volumes:
      - ./seeds/init.sql:/docker-entrypoint-initdb.d/init.sql  # 固定 seed
  app:
    build: .
    environment:
      DATABASE_URL: postgresql://test:test@db:5432/test
# generated by hugo AI

每个任务开始前重置数据库到同一个 seed,测试之间互不污染——这是让 FAIL 信号「可信」的最低成本做法。

变量三:防作弊(Goodhart)

这是最容易被忽视、也最阴险的一个变量。

一旦 Harness 成为 reward 来源,Agent 就有可能不去解决问题,而是 解决提出问题的人

def test_login_success(page):
    # 正确做法:验证真实行为
    # assert page.url.endswith("/dashboard")

    # Agent 学会作弊后的做法:
    assert True  # 全绿
# generated by hugo AI

删掉失败的测试、mock 掉关键断言、硬编码期望值——这些都不是假想,是在真实 Agent 工程里反复出现的行为。对策是隔离: 测试代码和任务卡放在 Agent 可写面之外 (只读挂载或独立仓库),让裁判不可被选手修改。

变量差的 Harness好的 Harness
延迟每轮都等完整 E2E分层:快信号挡错误,E2E 做终审
噪声随机挂,Agent 学会忽略确定性环境,固定 seed,隔离数据
作弊Agent 能改测试文件测试与 Agent 可写面隔离

Benchmark 和 Harness,区别是「给谁用」

讲到这里,可能有人会问:SWE-bench 这些 benchmark 不也是 E2E 环境吗?为什么你说最重要的不是 Benchmark?

因为同一个 E2E 环境,接法不同就是两种东西:

BenchmarkHarness
用途一次性打分持续供能
服务对象外界(排行榜、选型)Agent 自己(迭代循环)
频率跑一次出个分每个 commit、每轮迭代都跑
输出一个数字结构化失败信号 + 轨迹

SWE-bench 接上排行榜,它是 benchmark;接进 Agent 的迭代循环,它就是 harness。所以真正的区分不在形态,而在 给谁用

这个区分很重要,因为它决定了投入方向:行业里不缺「能打分的环境」,缺的是 高频、可信、能喂回 Agent 的任务级反馈。前者是奖杯,后者是粮食。

为什么大厂都在往这个方向砸钱

近一两年,可以观察到一个明显的趋势:Anthropic、OpenAI、Cursor 这些团队都在加强沙箱(sandbox)、可重复执行的开发环境、自动测试执行、浏览器自动化、日志采集、截图反馈。

这些投入看起来不像在「提升模型能力」,其实是在提升 反馈质量。而且背后还有一层更深的逻辑:

Harness 里的每一次任务执行,就是一条带 reward 标注的轨迹。

Agent 在 Harness 里跑「实现登录」这个任务,无论成败,整个过程——每一步修改、每一次测试输出、最终结果——都是一条完整的训练数据。Harness 不只是推理时的验收系统,它同时是 数据飞轮:闭合的不只是推理循环,还有训练循环。

我在 修 Bug 的真正目的:让 AI 下次能自己修 里讲过这个思路的微观版本:每一次 bug 修复都应该沉淀为 regression test 和结构化诊断信号,让 AI 下次能自己修。把这句话放大一万倍,就是大厂正在做的事——把整个开发环境变成可持续产出带标注轨迹的基础设施。

模型会趋同——大家都能接入差不多的基座模型。但「环境 × 轨迹数据」的积累是复利的,别人抄不走。这也是为什么我在 好的 Runtime 不挑模型 里说,Runtime 和模型在共同演化:模型越来越 Agent Native,而 Harness 越来越成为决定胜负的那一侧。

一句话总结

把这些串起来,我会这样总结:

Prompt 决定起点,Model 决定上限,而 E2E Test Harness 决定上限能否兑现。

也可以写成一个乘法关系:

最终能力 = 模型能力 × 兑现率

兑现率是什么?是「模型理论上能做到」和「在实际任务里稳定做到」之间的系数。而这个系数,几乎完全由反馈回路的质量决定——延迟够不够低、信号够不够干净、裁判能不能被收买。

所以回到开头的判断:未来 Coding Agent 的竞争,未必主要发生在模型层,而会越来越多地发生在 Harness、执行环境(Runtime)、可观测性(Observability)和自动评测(Evaluation)这些基础设施层面。

E2E Test Harness 听起来是个很「传统」的词——它甚至是软件工程里被抱怨最多的东西(慢、脆、难维护)。但在 Agent 时代,它的身份变了:从「给人看的验收流程」,变成「Agent 的世界本身」。

对 Coding Agent 最友好的,不是更大的模型,是一个它能看得见、信得过、改不动的世界。

你给 Agent 配的 Harness,信噪比过关吗?欢迎留言讨论。


See also