第 2 章 运行与验证循环
学习目标
- 解释本地运行、开发服务器、端口之间的关系,准备好首次拿到的仓库,使其能够运行。
- 区分测试、类型检查、lint、构建各自能发现和会遗漏的问题。
- 从失败输出中读出什么、在哪里、为什么出错,选出应最先查看的错误。
- 逐个检验假设,缩小失败原因范围。
- 通过浏览器检查和证据要求验证“完成”声明,明确说明未能验证的内容。
引言
“修改已经完成,所有测试均已通过。”与编程智能体一起工作时,一天之内常常会看到好几次这句话。然而,单凭这句话,无法知道测试是否真的执行过、所执行的测试是否涉及这次变更,以及界面上的问题是否也已经消失。
一位业余项目运营者相信了智能体的报告,将应用部署出去,结果第二天早晨就收到了用户咨询。测试确实全部通过了,只不过这些测试仅涉及计算函数,而所修复的bug却出现在界面上。这是一个只要打开浏览器检查一次就能发现的问题。
本章讲解如何把“已经做好了”这句话,转变成“确实做好的证据”。我们从应用究竟在哪里运行讲起,依次学习如何准备本地运行、各类检查工具分别承担什么角色、如何阅读错误信息,以及如何逐步缩小原因范围。随后再讨论让验证循环不必依靠人时刻记住也能执行的机制,以及浏览器检查、索要证据和整理上下文的方法。
核心概念
应用运行在哪里:浏览器、服务器、存储
Web应用通常在三个地方工作。用户的浏览器绘制界面并接收按钮点击;服务器接收浏览器请求,计算并返回响应;存储(数据库或文件)保存刷新后也必须保留的数据。以餐厅为例,浏览器是前厅,服务器是厨房,数据库是仓库。
区分它们很重要,因为错误出现的位置各不相同。
| 问题位置 | 错误显示位置 |
|---|---|
| 浏览器(界面代码) | 浏览器开发者工具的控制台 |
| 浏览器与服务器之间(请求、响应) | 开发者工具的网络标签页、服务器输出 |
| 服务器代码 | 启动开发服务器的终端;部署后则是服务器日志 |
| 数据库连接 | 服务器输出中的连接错误(例如表示连接被拒绝的ECONNREFUSED,可能是数据库未启动或地址、端口不正确) |
“界面提示已保存”与“数据确实保存”是不同的事实。保存结果要通过刷新或在另一台设备重新打开来确认。
本地运行:开发服务器与端口
本地运行,是指将应用发布到互联网之前,先在自己的电脑上运行并试用,就像餐厅正式开业前,先邀请家人来试吃。负责启动应用的程序叫作开发服务器。开发服务器会打开一个带有编号的入口,也就是端口(port),并等待请求。http://localhost:3000的意思就是“自己电脑上的3000号入口”。
启动开发服务器时,如果遇到EADDRINUSE(address already in use)错误,就表示这个端口已经被另一个进程使用。常见情况是,之前启动的开发服务器仍然在另一个终端里运行。应对方法是关闭那个进程,或者改用其他端口启动。关闭时,要先确认占用端口的进程编号(PID)(macOS与Linux中可用lsof -i :3000),再用kill <PID>只结束那个进程。根据名称模式一次性关闭多个进程的命令,可能连无关的程序也一起关闭,因此很危险。这个错误通常属于端口占用问题,重新安装并不能解决。少数情况下,也可能是代码或配置让同一个服务器被启动了两次。
准备首次拿到的仓库
初次拿到别人创建的仓库,或智能体创建的仓库时,人们很想立刻输入npm run dev。然而,应用只有在完成几项准备后才能运行。按顺序逐项确认,可以减少无目的摸索的时间。
- 阅读README与指南文件。 运行命令与前置准备通常写在这里。
- 对齐运行时版本。 仓库有
.nvmrc等版本文件,就用指定版本的Node.js。 - 按lock文件安装。 如果仓库中有
package-lock.json,就使用npm ci,严格按照该文件记录的版本进行安装。npm文档说明,缺少lock文件时,这条命令无法运行;如果lock文件的内容与package.json不一致,它会报告错误并停止,而不会为了消除差异自动修改文件。此外,执行这条命令既不会修改package.json,也不会修改lock文件本身。 - 补齐环境变量。 根据
.env.example确认所需名称,在.env.local等本地文件中填入测试值。 - 准备数据库。 有迁移命令就运行,以创建表。
- 启动开发服务器,亲自打开一个核心页面。
跳过这些步骤,出现的错误通常有规律:缺少环境变量就回看第4步,Cannot find module回看第3步,数据库连接被拒绝回看第5步。
检查工具地图
自动检查代码的工具有多种,各自发现的问题不同。
| 工具 | 比喻 | 能发现的问题 | 不能发现的问题 |
|---|---|---|---|
| 测试 | 试吃团评价口味 | 给定输入是否得到预期结果 | 未编写测试的行为 |
| 类型检查 | 核对设计图尺寸 | 函数参数的类型和数量、使用不存在的属性 | 值的形式正确但计算逻辑错误 |
| lint | 厨房卫生检查表 | 未使用变量、危险代码模式、违反代码规范 | 功能是否按需求运行 |
| 构建 | 开业前的最终组装 | 仅打包为部署版本时才出现的错误 | 运行中的逻辑错误 |
在TypeScript项目中,可以使用tsc --noEmit这类命令,仅执行类型检查,而不输出编译产物。TypeScript文档说明,noEmit这一选项的作用就是不输出编译结果,只将TypeScript用作类型检查器。例如,为一个函数新增一个必填参数后,运行一次类型检查,就能够找出调用方式不再符合要求的位置。不过,对于通过any等方式绕过类型检查的调用,这种检查仍有可能遗漏。
关键在于每种工具的“通过”含义不同。测试通过,不代表未覆盖的界面正确;类型检查通过,也不代表折扣计算正确。因此应按改动性质选择检查,从轻量检查开始运行。
拆解错误信息
错误信息很长,并不是为了吓人。通常会告诉你三件事。
- 什么:错误类型(
TypeError、失败测试的名称)。 - 在哪里:文件名与行号。
- 为什么:预期值与实际值,或缺少了什么。
TypeError: Cannot read properties of undefined (reading 'filter')
at ClassSchedule (src/components/ClassSchedule.tsx:31:18)
at renderWithHooks (node_modules/react-dom/cjs/react-dom.development.js:15486:18)
第一行的含义是:“试图对一个尚未取得值(undefined)的对象使用filter。”下面的堆栈跟踪(stack trace)按照由内向外的顺序,列出发生错误时所经过的函数。位于node_modules中的行属于第三方编写的依赖库,位于src/中的行才属于本项目代码。通常应当先打开本项目代码中位置最靠上的那一行;在这个例子中,就是ClassSchedule.tsx的第31行。这里很可能是在数据尚未加载完成之前,就尝试绘制列表。
错误一下涌出几十条时,先看第一个错误。一个模块找不到,所有使用它的地方都可能产生连锁错误。修好最初原因,后面大多数错误会一起消失。
最可靠地说明命令成功或失败的,是退出码(exit code),而非提示语。0表示成功,非0表示失败。即使满屏警告,退出码为0就代表命令成功;即使最后一行没有报错信息,非0也代表命令失败。终端用echo $?查看上一条命令的退出码。程序通常把结果写入标准输出(stdout),把错误写入标准错误(stderr)。输出太长而要完整交给智能体时,可用npm test > test.log 2>&1将标准输出写入文件(>),并把标准错误合并到同一处(2>&1),避免遗漏。
根据失败输出缩小原因范围
查找原因的工作,就像侦探办案:先收集线索,再逐个核查嫌疑人,不会仅凭未经确认的猜测就逮捕一个人。
- 复现。 确认按照同样的步骤,是否会出现同样的失败。如果不能复现,也就无法知道问题是否已经修好。
- 区分失败发生的阶段。 安装、启动、构建、运行,失败发生在哪个阶段,决定了应该查看什么位置。
- 选择最先出现的相关错误。 暂时将后续连锁错误放在一旁。
- 提出一个假设。 将它写成能够确认的句子,例如“这个import路径已经改变”。
- 只验证这个假设。 如果同时修改多个地方,就无法知道究竟哪一项修改起了作用。
- 修改之后,再次运行相同的检查。 使用最初失败的那一条命令确认结果。
向智能体反馈失败报错时,应当附上完整输出,并明确要求:“修复根本原因,不要掩盖错误。”Claude Code最佳实践同样建议,将构建失败交给智能体处理时,应附上原始错误信息,要求它确认修复后的成功结果,同时强调不要压制错误,而要解决根本原因。掩盖错误的典型做法包括:添加关闭类型检查的注释、跳过测试,以及把预期值改成与实际输出相同的值。如果预期值本来就符合需求,那么出错的是代码,应当修复代码。
验证循环与强制机制
验证循环是修改、运行检查、读取结果,重复直到检查通过的流程。把检查命令告诉智能体,它便能自行循环,但人每次都要说,智能体偶尔也会忘。因此要把循环的一部分固定为机制。
| 机制 | 作用 | 例子 |
|---|---|---|
| 任务指令 | 告知本次任务应运行的检查 | “实现后运行npm test并展示输出” |
| 指南文件 | 告知所有任务共用的检查命令 | “任务结束后运行npm run check” |
| 钩子 | 在规定时点无例外执行检查 | 每次修改文件后运行格式化工具,结束前运行检查脚本 |
| CI必需检查 | 阻止未通过检查的改动被合并 | 每次拉取请求都测试,失败时禁用合并按钮 |
Claude Code文档解释,钩子与指南文件不同,会确定性地执行,保证相应行为发生。例如为PostToolUse事件设置Edit|Write条件的钩子,会在智能体每次通过这些编辑工具修改文件后运行格式化工具。它捕捉不到shell命令修改的文件,因此结束时还需再检查一次以免遗漏。把钩子写入项目的.claude/settings.json并提交,团队即可共享。交互会话需先批准信任该目录,钩子才会执行,也能通过设置关闭。Stop钩子在智能体准备结束回答时运行检查脚本,失败则让任务继续。但文档指出,若没有工具调用而连续阻止八次,下一次便会允许结束,因此不能无条件保证检查通过。
在GitHub的保护分支(protected branch)设置中,开启“合并前必须通过状态检查”后,只有指定的必需检查以成功(或跳过、中立)状态结束,改动才能合并到这个分支。即使审查者一时忘记检查,这道机制也能阻止存在问题的改动被合入。默认设置下,管理员不受这条规则约束;如果希望规则无例外地适用,还需要开启禁止绕过的选项。CI(持续集成,即每次变更都会自动运行检查的系统)中,应使用npm ci安装依赖。npm文档也明确说明,这条命令面向CI等自动化环境。
在浏览器中像用户一样检查
修改界面的任务,必须在浏览器亲自检查才算结束。Anthropic的长时间运行智能体实验也发现,智能体常在未充分测试功能时将其标为完成;明确要求用浏览器自动化像真实用户一样测试后,它便发现了仅读代码会遗漏的bug。Claude Code最佳实践也建议,对界面修改截取结果,与设计稿比较并修正差异。
亲自检查时查看以下内容。
- 完整执行点击、输入流程,刷新后结果是否保留
- 缩窄到手机宽度(例如360px),是否有截断或重叠
- 空数据、错误输入、响应缓慢等第1章定义的状态
- 开发者工具的控制台:是否有红色JavaScript错误
- 开发者工具的网络标签页:请求是否真正发出,响应状态码是什么
状态码用数字概括响应结果。200段表示成功,400段表示请求侧问题(错误输入、无权限、地址不存在),500段表示服务器侧问题。点击按钮无反应时,网络标签页完全没有请求就看界面代码;请求以500失败,就看服务器输出。
区分声明与证据
智能体的“已完成”是声明。证据是执行的命令及原始输出、截图、检查步骤与结果。Claude Code最佳实践建议,不要只让智能体断言成功,而要展示测试输出、执行命令及结果、结果界面等证据。审查证据比重新验证更快,也适用于无人旁观的会话。
好的完成报告,既包含已确认的内容,也包含未能确认的内容。
已执行检查:npm test(42项通过,0项失败),npx tsc --noEmit(无错误)
界面检查:附手机、桌面宽度截图。确认刷新后保存结果保留
未能确认:实际短信发送(测试中仅确认发送记录)
在同一上下文中完成工作的智能体,有可能重复同一个误解,并据此给出“已经确认”的结论。因此,Claude Code文档介绍了一种方法:让处于全新上下文中的子智能体尝试反驳现有结果,从而避免由执行任务的智能体为自己的工作评分。
上下文混乱时重新开始
正如第1章所述,对话的上下文中,也会不断积累失败的尝试。反复修改同一个问题时,堆积着失败方法的上下文,可能再次引出相同的错误。Claude Code最佳实践建议,如果在同一个会话中,对同一问题纠正超过两次仍然出错,就用/clear清空上下文,再用一份更具体、包含此前所学经验的指令重新开始。干净的会话配合更好的指令,几乎总是优于一段不断积累纠正内容的冗长会话。
重新开始之前,要记住两件事。第一,即使清空了对话,磁盘上的代码仍然保持原样。如果失败尝试造成的变更还留在那里,就要先决定是否回退到最后一次经过验证的提交(第3章)。第二,要留下交给新会话的交接备忘。写清楚曾经尝试过什么、什么尝试失败了,以及哪些假设已经排除,才不会再次沿着同一条错误路径前进。
舍不得丢掉对话,可用/compact <指令>围绕指定内容进行摘要。
深入阅读
通过的测试与有用的测试
测试通过这一事实,并不能证明这项测试有用。没有用处的测试,即使代码中存在bug,也照样会通过。典型的情况有以下四种。
| 无用的测试 | 表现 | 核实方法 |
|---|---|---|
| 照抄实现的测试 | 预期值以与代码相同的方法计算 | 查看预期值是否独立来自需求 |
| 断言过弱 | 只检查“结果存在” | 查看是否比较具体数值或状态 |
| 从未失败的测试 | 修复前也通过 | 暂时还原修改,看是否失败 |
| 按结果改过的测试 | 失败后改变预期值 | 在diff中查找测试文件的预期值修改 |
最有力的确认方法,是先看到测试失败。修复bug时,先编写能够复现问题的测试,并确认它在修复之前确实失败。Claude Code最佳实践也给出了这样的委派示例:描述症状之后,要求智能体“先编写能够复现问题的失败测试,然后再修复”。只有修复前失败、修复后通过的测试,才能证明这次修改确实解决了问题。
另一个类似的陷阱,是批量更新快照测试(保存界面或输出以前的样子,再与当前结果作比较的测试)。如果把所有失败的快照一次性覆盖成“新的基准”,那么有意作出的变化和无意造成的变化,就会一起变成正确答案。更新之前,应逐一检查失败的快照,确认它所反映的变化是否确实是本次任务想要改变的内容。
偶尔失败的测试
未改代码却时而通过、时而失败的测试,叫作不稳定测试(flaky test)。重跑通过后很想直接放过,但大多数都有真实原因。
- 测试之间共享状态。 如果使用同一个测试数据库或全局变量,前一项测试留下的数据,就可能破坏后一项测试。单独执行时通过,全部一起执行时却失败,就是典型信号。Playwright文档强调,各项测试必须完全隔离、独立运行,才能提高可复现性,并避免失败接连发生。
- 等待方式错误。 如果界面元素需要稍后才出现,却只检查一次它“是否存在”,检查结果就会取决于执行检查的那个瞬间。Playwright的Web优先断言(
await expect(locator).toBeVisible())会持续等待并重试,直到满足条件;如果直接使用isVisible()检查,则不会等待,而是立即返回当时的结果。采用固定等待时长也不能解决这个问题:在运行较慢的环境中仍可能失败,在运行较快的环境中又会白白浪费时间。 - 依赖外部因素。 结果会随着真实外部API或当前时间而改变。测试中应将响应与时间固定下来。
设置重试次数只能掩盖症状,不能消除原因。CI中偶尔失败的测试,必须取得失败当时的信息,才能修复。因此,Playwright文档建议,分析CI失败时,应使用trace viewer(一种将记录下来的执行过程逐步回溯查看的工具),而不只是视频或截图,并设置在失败测试的第一次重试时记录trace。
自己电脑与其他环境结果不同时
“在我自己的电脑上可以运行啊”,是开发现场流传最久的玩笑。如果结果不同,原因就在两个环境的差异之中。这种差异通常属于下面几种情况之一。
| 差异 | 例子 | 对齐方法 |
|---|---|---|
| 运行时版本 | 本机与CI的Node.js主版本不同 | 添加版本文件,让两边使用同一版本 |
| 已安装依赖 | npm install安装了不同的下级依赖版本 |
提交lock文件,CI使用npm ci |
| 环境变量 | 值只存在于本机本地文件 | 记录所需名称,也加入CI与部署设置 |
| 操作系统 | macOS的APFS若未特意选择区分大小写格式,则不区分文件名大小写,Linux则区分 | import路径大小写与实际文件名完全一致 |
| 执行方式 | 只检查开发服务器,没跑部署构建 | 部署前也在本地运行构建命令 |
环境变量尤其容易出现陷阱。根据Next.js文档,原则上应通过.gitignore将.env*.local文件排除在仓库之外;在测试环境(NODE_ENV=test)中,也不会读取.env.local。这样设计是为了让所有人的测试都得到相同结果。因此,如果测试依赖的是自己电脑上.env.local中的值,那么它在其他环境失败,正是正常的结果。
性能也一样。开发服务器与部署构建的代码执行方式不同,因此修改前后要在相同、接近部署的条件下测量,才有可比性。
工作场景
盐面包店:无法保存的上午9点
社区面包店“盐面包店”的预约应用收到顾客反馈:“即使选择了预约时间,也保存不了。”运营者闵经理没有立刻修改,而是先复现症状。选择下午时段可以保存,但选择上午9点时段,即使点击保存按钮也什么都不发生。开发者工具的控制台没有报错,网络标签页却留下了请求已经发出、随后以400响应返回的记录。问题出在请求的内容上,而不是界面上。
闵经理把复现步骤交给智能体,要求:“先增加一个能捕捉这个症状的测试,并展示它在修复之前如何失败。”智能体提出立刻修改格式校验代码,但闵经理拒绝了。没有先看到失败,就无法相信修复后的通过结果。测试果然如预期般失败,输出中显示Expected: "09:00" / Received: "9:00"。时间字符串缺少开头的0,因此被服务器的格式校验拦截了。
智能体修改了生成时间的函数之后,新测试通过了。虽然智能体报告“完成”,闵经理却没有在这里停下来。他运行了全部测试与类型检查,又在浏览器中选择上午9点并保存,随后在网络标签页确认请求以200响应成功,并确认刷新页面后预约仍然存在。直到这时,才创建提交。
正是因为复现了症状,才能编写测试;因为看到了修复前的失败,才能信任这项测试;因为在浏览器中进行了确认,才能说症状确实已经消失。
绿星植物园:面对47条错误
运营会员植物养护应用的“绿星植物园”开发者池工程师,让智能体把分散各处的植物辅助函数整理到src/lib/plants/文件夹中。任务完成后运行npm run build,一下出现了47条错误。
智能体提出了两个方案:一个是在每一行出错的位置添加跳过类型检查的注释,另一个是将整个文件夹整理工作全部回退。池工程师两个都没有接受,因为第一个方案只是掩盖错误,根本原因仍然存在;第二个方案则会连原本要求的整理工作一起丢弃。
池工程师从头开始阅读完整输出。最先出现的错误,是src/app/watering/page.tsx中的Cannot find module '@/lib/plant-utils'。之后的大部分错误,看起来都是因为找不到原先从这个模块导入的函数与类型,于是在每个使用这些函数与类型的文件中接连产生了连锁错误。池工程师只提出一个假设:“仍有文件在使用旧路径进行导入。”随后,他将任务交给智能体,要求它只验证这一个假设。
找出所有导入旧路径 '@/lib/plant-utils' 的文件,改为新路径。
不改其他代码。修改后重跑 npm run build ,展示完整输出。
再次运行构建后,错误减少到了两条。剩下的两条由另一个原因造成:整理过程中,浇水计算函数被修改为额外接收一个季节值,但调用它的两个位置仍然采用原来的方式。池工程师进一步检查,这项修改是否属于原来的任务范围。原本只要求整理文件夹,改变函数的接口已经超出范围。让智能体将函数恢复为原来的接口之后,构建通过了。
池工程师随后运行了类型检查与测试,并在浏览器中打开浇水日程界面,确认日期是否与之前一致。47条错误,其实只有两个原因。正是因为从第一个错误开始,逐个验证假设,才得以既不掩盖错误、也不丢弃工作,而是修好问题。
易错点
- “测试通过了,工作就结束了。” 通过看起来像是终点。然而,测试只能确认它所涉及的范围。如果修改了界面,却没有界面测试,那么在完成浏览器检查之前,工作就还没有结束。
- “测试失败了,修改预期值就可以。” 只需要改一个数字,似乎很快。然而,如果预期值本来就与需求一致,那么出错的就是代码。当智能体提出修改测试或跳过测试时,正是最需要警惕的时候。
- “重新运行后通过,就没有问题了。” 重跑通过会让人安心。然而,偶尔失败通常有共享状态、等待方式错误等真实原因;如果每次都在同一个值上失败,则属于逻辑错误。重新运行不能代替原因分析。
- “错误的数量多,就说明问题很多。” 数字很大时,容易让人无从下手。然而,几十条错误往往是由同一个原因接连引发的。应当从最先出现的错误开始看。
- “智能体说它很确定。” 充满信心的报告会让人愿意相信。然而,自信并不是证据。应要求它展示命令、输出、界面等证据,并询问还有哪些内容未能确认。
- “清空对话,工作也就回到最初的状态。” 新会话让人感觉像重新出发。然而,即使清空上下文,磁盘上对代码作出的变更仍然存在。是否需要回退,必须另外通过Git来决定。
要点速览
| 主题 | 应记住的内容 |
|---|---|
| 错误位置 | 界面看控制台,请求看网络标签页,服务器看终端与日志 |
| 端口冲突 | EADDRINUSE表示端口已被占用,确认PID后只关闭该进程 |
| 首次运行 | README → 运行时版本 → npm ci → 环境变量 → 迁移 → 运行 |
| 检查工具 | 测试检查行为,类型检查形式,lint检查模式,构建检查部署组装 |
| 读错误 | 什么、在哪里、为什么;自己代码最上面一行、首条错误、退出码 |
| 缩小原因 | 复现 → 区分阶段 → 首条错误 → 一个假设 → 检验 → 重跑同一检查 |
| 强制机制 | 从弱到强:指令 < 指南文件 < 钩子 < CI必需检查 |
| 浏览器 | 完整流程、窄屏、刷新后保留、控制台与状态码 |
| 证据 | 命令、原始输出、截图,以及未能确认的内容 |
| 整理上下文 | 纠正超过两次仍不行,就/clear并改进指令;代码另行回退 |
查阅官方文档
- Claude Code: Best practices — 提供验证手段、修复根因、索要证据、管理上下文
- Claude Code: Automate actions with hooks — PostToolUse格式化钩子、Stop钩子、钩子配置文件位置
- Playwright: Best practices — Web优先断言、测试隔离、CI失败与trace viewer
- npm: npm ci — 基于lock文件的安装与自动化环境
- TypeScript TSConfig: noEmit — 不输出产物、仅做类型检查
- GitHub: About protected branches — 合并前的必需状态检查
- Next.js: Environment variables —
.env读取顺序与测试环境
自测
2-1. 启动开发服务器后出现Error: listen EADDRINUSE: address already in use :::5173。这是什么意思,应如何应对?再举一种应避免的做法。
2-2. 智能体报告“测试失败0项”,打开输出却是Tests: 0 passed, 0 total。为什么不能视为通过?应先怀疑什么原因?
2-3. 咨询表单点击“发送”无反应。应在浏览器开发者工具的控制台、网络标签页分别查看什么?如何根据结果区分问题位置?
2-4. 以下输出中,最先应打开哪个文件、哪一行?为什么?
TypeError: Cannot read properties of null (reading 'price')
at sumLines (src/checkout/summary.ts:47:12)
at OrderPanel (src/components/OrderPanel.tsx:18:9)
at renderWithHooks (node_modules/react-dom/cjs/react-dom.development.js:15486:18)
2-5. 构建命令输出几十行黄色警告,最后一行没有报错信息。确认命令是否成功,最可靠的方法是什么?
2-6. 智能体修复折扣计算bug并添加一个新测试,测试通过。说明一个简单方法,确认该测试是否真正能捕捉这个bug。
2-7. MacBook上正常的应用,在Linux CI中以Module not found: Can't resolve './components/header'失败。实际文件名是Header.tsx。原因是什么,如何修复?
实操练习
练习
围绕失败输出完成一轮循环(45分钟)
- 在练习应用的一个函数中故意引入bug(例如求和时遗漏最后一项)。
- 先写捕捉该bug的测试,亲自确认失败。
- 在纸上从失败输出记录什么、在哪里、为什么出错。
- 把完整输出交给智能体,要求“修复原因,不改测试,展示全量测试输出与退出码”。
- 暂时还原修复,确认测试再次失败,再恢复修复。
完成标准: 自己写下的原因与智能体修复位置一致,同时保留修复前的失败输出和修复后的通过输出(退出码0),测试文件未被修改。
练习
用机制固定验证(50分钟)
- 在指南文件中写一行任务结束时执行的检查命令(例如
npm run check)。 - 用所用工具的钩子功能,设置每次修改文件后运行格式化工具或lint,并将配置文件提交仓库(Claude Code为
.claude/settings.json中的PostToolUse)。 - 交给智能体一项小界面修改,确认钩子确实执行的痕迹。
- 在浏览器以手机和桌面宽度检查,记录控制台是否有错误、网络请求状态码。
- 按“已执行检查、界面检查、未能确认”三栏整理完成报告。
完成标准: 有已提交的钩子设置,也有无需额外指令便运行钩子的记录;三栏完成报告的“未能确认”栏不为空,或说明了为空的理由。