Harness + TDD:后 Vibe Coding 时代,工程能力正重新被看见
技能的地板确实降了,但天花板被炸开了。TDD 与 Harness 正成为 AI Coding 时代维持意图和代码一致性的工程结构。
来源链接:王人书《Harness + TDD:后 Vibe Coding 时代,工程能力正重新被看见》
发布时间:2026年5月22日 08:17
大家好,我是Alden,LLM开发工程师。这是LLM工程的第7篇。2026 年重读 TDD 经典,读出了强烈的时差感。Vibe Coding 让代码生成的门槛降到接近零,但也让代码腐烂的速度前所未有地快。TDD + Harness,是目前唯一能让 AI 在确定性轨道上跑的物理/工程结构。把工程判断编码进测试和规格,AI 在框架里跑,速度是人类的百倍,方向来自Harness,速度来自AI——这是乘法,不是加法。结合读后想法,以及日常在 @觉察流 社区看到同学的讨论分享,写下这篇。字数 5391,预计阅读 22 分钟,建议先转后看
Vibe Coding 很爽,然而代码三个月后可能没人敢动。Harness Engineering 是工程社区今年给出的那个词,让AI在确定性约束的轨道上跑。OpenAI 五个月三个人合并了 1500 个 PR、写出百万行代码,Bun 依赖 Anthropic 用1次PR提交100万行代码完成rust重写,靠的就是这套东西。就在这个时间节点,我翻出了 Percival 的 Python TDD 旧书,读出了从没有过的紧迫感。
这本书写成的时候,TDD 的主要论敌是懒惰的工程师。测试被当作开销,是在给未来的自己挖坑。那大半篇幅都在说服:写测试是一种纪律,短期看慢,长期看快。彼时最需要克服的摩擦,是人类的惰性。
同一本书,在 2026 年读,语境完全不同了。那些关于”约束”的章节,现在读起来像是一份针对 AI Coding 的操作手册。测试能防止你走弯路,这个判断依然成立,但它的受众已经不只是偷懒的程序员,而是每秒生成几百行代码、却不理解代码的 LLM。
摩擦是信息
Python 的 mock 能力有一个刻薄但精准的评价:它”太方便了”。动态 monkeypatch 让你随手替换任意依赖,几行代码把整个外部系统架空,数据库、消息队列、第三方 API,统统可以换成一个假的。表面看是优势,冷静看这是一种消除:便利性把你被迫思考接口设计的动机一并消除掉了。
反过来看 Java 或 Go 的程序员。他们没有 Python 这套动态能力,测试有依赖的代码,必须先把依赖抽象成接口,再实现一个干净的测试替身。这条路麻烦,但走完之后,依赖结构通常比 Python 版本整洁得多。
摩擦是信息。当你感到”写这个测试好难”,那种痛感不是偶然的,它在告诉你这段代码的耦合度太高,边界划得不对。把摩擦去掉,信号也跟着消失了。
这条规律的射程比测试框架远得多。把 Python 的 monkeypatch 替换成 AI 代码生成,规律依然成立。AI 可以在几秒内为任意耦合度的代码生成结构完整、覆盖率好看的 mock 链条,那种”这段代码太难测试,说明设计有问题”的反馈彻底消失了。
CI 依然绿,覆盖率依然好看,生产环境依然崩。
Mock 廉价化的代价,是失去了测试作为架构侦探的功能。mock 当然有用,但它的价值来自于生成它的痛苦。当这个痛苦消失,晴雨表也就失灵了。
抽象会泄漏
3GL 兴起时有一个经典的观察:C 和 Fortran 最顺手的,往往是通晓底层机器码的程序员。当抽象开始泄漏,底层知识决定了你能不能快速诊断和修复。AI 编码是更高一层的抽象,幻觉和低质量代码是这层抽象最显眼的泄漏点。能高效驾驭 AI 的工程师,是那些清楚知道”什么是好代码”的人,他们能在 AI 生成的结果里辨认坏味道(smell),知道哪里该推倒重来。
抽象层级每上升一次,”懂底层的人”的稀缺性上升。这个命题的穿透力很强,它说的其实是:工程能力的底层有一些东西是不随工具演化的,那些东西正是驾驭新工具的基础。(见一般系统定律及其内容)
社区同学有切身教训。做一套系统时,前三天换了三套架构:第一天一套,第二天一套,第三天一套。每套都是 AI 按照我当时的意图完整交付的,每套都合理,每套都比上一套更贴合业务状态。但三天后回头看,那不是演进,是我没想清楚就开工的代价。这是 Vibe Coding 的经典失效路径:代码里没有任何东西在记录”什么是对的”,一致性靠主观感受维持,每次迭代都是在漂移的基础上继续漂移。
模型没有做错什么。是我传递的”战略意图”一直在漂移、夹具没能夹紧。
后来总结出一个很简单的规律:当你把一件复杂的事拆解得足够清晰,模型几乎可以一次到位。当你表达模糊,它就开始自己”胡思乱想”。它也在做某种 TDD,只不过是围绕一个错误的目标在转圈,然后你会发现它忽然产出了一套精心设计的、完全不解决真实问题的系统。
这个时候需要人从架构层级介入,重新定义问题,而不是在局部继续打补丁。
人可以偷懒,但不能不知道。执行可以委托,判断委托不了。(见黑客、语言囚徒与技术时代的共构)
由外向内
Outside-In TDD 是 TDD 里被严重低估的部分。
由外向内的核心逻辑:从用户最终会接触的那一层开始写测试,根据测试的失败逐层向内驱动实现。每一个内部组件的存在,都由外层的实际调用来证明。书里把这个思路溯源到 SICP 的”许愿编程”(programming by wishful thinking):在高层写代码,暂时搁置尚不存在的底层细节,先把你期望底层能做什么写出来,再去实现底层。TDD 是这种思维方式的可执行形式。
对立面是由内向外:先建模型层,加好属性,再接视图,再接路由。这条路感觉踏实,每一步都踩在实实在在的地基上。但它有一个隐藏的问题:你是在猜测。上层还没建起来,你并不清楚外层真正需要什么。结果要么是内层 API 为自身的内部逻辑设计,跟外层调用的需求格格不入;要么是过度泛化,做了一堆外层根本用不上的抽象。这种舒适感是认知陷阱,你以为在打地基,实际上是在架空。
AI 在这方面有系统性的偏向:它们在人类优秀代码库上训练,天然偏爱复杂、高度抽象的设计模式。你让它实现一个简单的配置读取,它可能给你一个插件系统。原因很简单:它见过太多”扩展性设计”的例子,加上没有人类因疲倦产生的天然惰性,它会乐此不疲地堆砌当前无用但”未来可能有用”的冗余代码。
Outside-In 强制每一层的存在都有来处。在 AI 辅助开发里,这是让 YAGNI(You Aren’t Gonna Need It)从原则变成物理约束的唯一可靠机制。你从外部行为开始写测试,任何无法从这条路径回溯到的内部代码都是冗余,CI 可以用覆盖率工具把它揪出来。这个机制在以人为主的开发里靠自觉执行,在 Agent 里要靠硬编码。
Outside-In 有一个具体的执行形态叫”双循环”:先写一个失败的功能测试,再进入单元测试的内循环。外循环是用户视角的验收边界,内循环是组件视角的实现边界。两个循环之间的张力,迫使每一层的实现都有明确的上家在等它交付结果。
在 AI 辅助的工作流里,这个结构可以被显式拆分:功能测试由人来写,定义”完成”的边界;单元测试可以由 AI 辅助生成,但必须在功能测试的约束下运作。外循环不能交给 AI,这是人类保持系统整体视角的结构性手段,也是分工的天花板。
活的规范
“功能测试可作为应用程序的规范文档”,这句话的含义比字面深。传统意义上,代码随需求变化,文档总在追赶代码;以功能测试写成的规范,随代码演进自动保持准确,你不需要记得更新它,因为一旦它过时,它就会变红。文档和代码之间的主从关系,就这样悄悄反转了。
这一点,任何静态规格文档都做不到。你把规格(spec)写下来,它描述系统应该怎样,却不能强制系统真的是那样。可执行的测试没有这个问题:它说话,就是通过或失败,没有灰色地带。
Given/When/Then 和 Arrange-Act-Assert 的选择,乍看是风格问题,其实是”你在为谁建模”的问题。Arrange-Act-Assert 是程序员的视角:我设置什么,调用什么,验证什么。Given/When/Then 是用户行为的视角:在什么前提下,发生什么操作,出现什么结果。写测试时用哪套语法,决定了这段代码在文档化实现细节,还是在文档化行为边界。
对 AI Agent来说,这个区别更尖锐。Given/When/Then 在语义上刚好映射了一个状态机:前置条件锁定初始状态,操作描述触发,Then 给出唯一的收敛判据。实践上,给模型一个 GWT 结构,比给它同等内容的自然语言描述,通常能得到更收敛、更少歧义的输出。原因不复杂:Given 锁死了前提,When 限定了操作,Then 是唯一可检验的预期,模型能发散的方向变少了。
但前提是:测试由人来写,或者至少由人深度控制。如果你允许 AI 同时写测试和业务实现,就会掉进死循环:AI 写的测试会完美适配 AI 写的代码,无论那些代码是否正确。测试在验证 AI 自己的逻辑,而不是你的业务意图。更极端的情况有人记录到:Agent 在遭遇测试失败时,直接把测试删了。保持对测试写作的主权,是你在 AI 自主迭代链条中保留控制权的最后防线。
刺破黑盒
回到 mock 上,有一条边界值得单说。”测试行为,而非测试实现”,这几乎是测试领域的黄金准则,但这条规则有例外,书里对此诚实地标注了出来:当某个具体的实现方式本身就是关键决策时,基于 mock 的测试可以成为这个决策的档案。
这条例外在 AI 时代被放大到一个新的量级。Agent 是功利性的执行者,只要外部验收测试变绿,它不在乎用什么手段。2026年有许多个类似于下面的案例:一个 Agent 在遭遇凭证冲突时,为了让部署通过测试,自主调用了平台 API 删除了生产环境的数据卷。外部行为测试通过了,代价是数据没了。
在涉及系统物理边界、安全壁垒和关键架构控制点的地方,你必须测试实现,而不只是测试行为。Controller 层能不能绕过业务层直接写数据库,加密实现里用的是不是已知有漏洞的算法,高危操作前有没有强制走人工审批的阻断,这些都不能只靠外部行为测试覆盖。ArchUnit 这类工具可以在 CI 里断言包依赖方向,在编译阶段就把 AI 的越界行为打回去,比等到运行时发现要好得多。
一条规则,你得先搞清楚它在什么条件下成立,才算真的理解了它。
三种答案
那么,2026 年 TDD 的春天到底指什么?
过去一年,工程社区围绕 AI 编码的失效模式,从三个方向给出了各自的答案,核心问题其实是同一个:意图和代码之间,靠什么维持一致性?
Vibe Coding 的回答是靠运行时的感觉。前面说的那三天三套架构,就是这条路的完整叙事。Karpathy 造这个词时的原话,”forget that the code even exists”,是一种认识论立场,不是玩笑:代码是可抛弃的输出,意图才算数。在可逆性高的场景里,这条路没有问题,原型期尤其好用。但”代码没有记忆”这个属性,让所有依赖历史状态的工程决策悬在空中。规模大了、时间长了,系统靠感觉维持的一致性就开始垮。
SDD(Spec-Driven Development)的回答是靠结构化规格文档。把意图写成显式契约,分阶段执行,每个节点有人工检查点。GitHub 今年推的 Spec-Kit 拿到 90k+ star,早期用户报告首次通过率提升了 3-10 倍,是对 Vibe Coding 失控的真实回应,方向是对的。但规格是静态的:AI 每次提交都可能悄悄移动一点边界,文档不会提出异议。”维护规格准确性”这个成本,在 AI 高频迭代的节奏里会被系统性地忽视,直到规格和代码彻底脱节。
TDD 的回答是:规格必须能自己说话。测试就是规格,但这份规格有自执行能力,不需要人去维持同步,因为一旦失步它就变红。这是 TDD 比 SDD 更彻底的地方。但 TDD 还有一层这两者都没有的东西:它是判断力的容器。一套写得好的测试,藏着工程师对边界条件、失效模式、架构约束的多年判断。这些判断被编码成可执行形式之后,AI 在框架内执行,速度是人类的百倍,但方向来自那个框架。
AI 让 TDD 从工程选项变成了工程前提。
在以人为主的开发方式里,TDD 是纪律。懒惰的工程师可以绕过它,代价是慢慢累积的技术债,但系统不会立刻崩。AI 的生成速度是人类的几个数量级,技术债的累积速度同比放大。一个失控的 Agent 可以在几分钟内生成大量表面合理但内部逻辑已经腐败的代码。先写代码后补测试,AI 只会进行字符拼图游戏,生成完美贴合现有代码(包括其中所有错误逻辑)的套娃式测试。你以为在补安全网,实际上在给错误镀金。
Harness Engineering 把这个逻辑往前推了一步。今年 OpenAI 发的一篇复盘里提到:三个人,一套 Harness 系统,AI 自主合并了 1500 个 PR,写出百万行代码。这件事的关键在 Harness,不在 AI 有多聪明。TDD 的红绿循环在 Harness 里被固化成确定性的流水线:AI 在沙盒里写完代码之后,必须通过测试套件,全绿才能推进。没有这套机制,AI 可以随时宣布任务完成,而那种完成只存在于它自己的评估里。
Harness 内部还有一个值得单说的机制。当 Agent 连续运行几个小时、调用了几百次工具、上下文窗口快撑满的时候,AI 会开始走捷径,甚至假装代码写完了来提前结束任务。成熟的 Harness 框架会把臃肿的错误日志离线写入文件,只在上下文里保留关键报错摘要,同时把测试的红绿状态持久化到磁盘。一旦 AI 因上下文爆满崩溃,Harness 清空上下文窗口,重启一个干净的 Agent 实例,读取磁盘上的状态和测试断言,从上一个失败的单测继续跑。TDD 让每一步都有确定的测试状态,这个状态成了 Harness 的检查点(checkpoint)。没有 TDD 提供的离散红绿状态,Harness 根本无处断点续传。
两者的关系是因果,也是互补:没有 TDD 的 Harness,是一个高速运行但没有真值的自动化外壳,AI 在里面因为没有明确的对错准则,会在幻觉里无限转圈。没有 Harness 的 TDD,在 AI 每秒几百行的生成速度面前,人类根本来不及手动验证每一步。TDD 是控制思想,Harness 是执行系统。Harness 是 TDD 在 AI 时代的物理形态。
Percival 在写这本书时,大概没预料到它会在 Agent 能自主合并 PR 的年代被这样读。但那些关于约束、摩擦和由外向内的判断,在新语境里变得更对了。
约束迫使你提前面对复杂性,而不是延迟到运行时。这个逻辑在 AI 时代的适用性只升不降。AI 最擅长在约束清晰的空间里快速收敛,在约束模糊的空间里疯狂发散。你如果跟它说”给我写个系统”,它会写出一套让你开心三分钟、让你头痛三周的东西。你如果跟它说”先让这条 Given/When/Then 测试通过,其他什么都不要碰”,它会给你一段干净的、可审查的、边界清晰的实现。
工程师的角色在这个过程里已经改变了。从代码的编写者,变成了约束的设计者。这件事说起来简单,做起来需要一个心理上的转变:很多人用 AI 工具时,第一反应是把自己想象成一个更高效的程序员,但效率上去了之后,代码越来越难驾驭,因为他们在扮演的角色错了。能写出好约束的工程师,才是 2026 年真正稀缺的那类人。
还有一种流行的叙事:AI 让编程民主化了,任何人五分钟建一个网站,工程经验的壁垒在消失。这是真的,也只是故事的表层。AI 把代码生成的成本降到接近零,同时把维护成本的差距拉开了。靠 Vibe Coding 堆出来的系统,三个月后可能连作者本人都不敢动;靠 TDD + Harness 管着的代码库,可以让 AI 连续合并 1500 个 PR,系统还在正常跑。技能的地板确实降了,但天花板被炸开了。
对有工程基础的人来说,Vibe Coding 时代不是威胁,是杠杆。他们知道 mock 在哪里成了谎言,知道 Outside-In 比 Inside-Out 省了多少浪费,知道哪种操作必须刺破行为黑盒直接检查实现。把这些判断编码进测试和规格,AI 在那个框架里跑,方向的准确性来自判断的质量,速度来自 AI。这不是加法,是乘法。
好的工程原理就是这样:时代越新,它越对。
关联阅读
- Cursor 团队是怎么把 SKILL 写成协议、骨架和契约的
- Harness engineering:在智能体优先世界中发挥 Codex 的力量
- 熵增、刘慈欣与 LLM:声明式架构是对抗混乱的唯一解吗?
- 被 Claude 吞噬的应用层:从 MCP、Skills 到 PTC 的跨维度打击
- LLM 推理引擎选型指南:Transformers、llama.cpp 与 vLLM 该怎么选?
- 当Docker开始接管LLM部署,Ollama 的护城河还在吗?
- AI for Coding:从 Vibe Coding 到规范驱动开发
- LLM重塑系统边界:二阶序与认知工具的知识平权
- 黑客、语言囚徒与技术时代的共构
- Vibe coding:LLM 时代的“没有银弹”回响
- 跨域的结构同构性
- 澄明之镜:Manus AI 与人的存在主义觉醒