实战:让 Agent 补测试与文档
系列入口:如何使用 Agent。M9 第 4 篇。
一、为什么这两类活最容易"假完成"
补测试和写文档是 Agent 的拿手好戏——它能瞬间产出大量结构整齐的用例和成体系的说明。但也正因为产出快、形式感强,最容易出现两类"假完成":测试数量涨了却测不到点(注水测试),文档写全了却和代码对不上(过时文档)。
这两者的共同病根是只追求形式覆盖、不面向真实目的。测试的目的是给改变行为提供可靠信号,文档的目的是回答读者真实问题。让 Agent 做这两类活,必须始终用"目的"而不是"数量/篇幅"来驱动和验收。

二、补测试:先搞清"测什么、为什么测"
不要让 Agent 笼统地"给这个文件补测试"——那会得到一堆同质、只验证 happy path 的用例。先和它一起确定测试目标:哪些是关键逻辑和高风险分支、历史上哪里出过 bug、哪些是容易错的边界。
让它按测试设计的几个角度构造:正常主路径、边界值(空、零、临界、最大最小)、异常与非法输入、组合与顺序。对每类,断言必须是具体、可证伪的——检查确切的结果、状态变化或错误,而不是"没报错""返回了东西"。
判断测试有没有价值,用两个动作:一是变异检查(故意改坏实现看它是否变红),二是追问"这个测试在防什么具体错误"。答不上来或改坏了也不红的,就是注水。优先把测试预算花在高风险、易变化的逻辑上,而不是为覆盖率数字去给 getter/setter 也写一遍。
三、遗留代码先写特征测试
如果是给缺乏测试的旧代码补测试,注意区分:特征测试记录"现状",不是验证"应然"。
让 Agent 先跑代码、观察实际输入输出,再据此写测试锁定当前行为——包括那些看起来奇怪、可能是历史包袱的地方,先如实记录、不擅自"纠正"。这样得到的安全网让你日后敢改;等改动时再决定奇怪行为是保留还是修。若让 Agent 凭"应该怎样"写测试,可能把它的臆测固化成标准答案,反而锁死真实行为。
四、写文档:面向读者,基于事实
让 Agent 写文档前,先明确为谁写、解决什么问题。是新人上手、接口使用方、还是运维?读者不同,内容取舍完全不同。一份好的文档应回答真实问题:怎么开始、怎么用、常见坑、出错怎么办,而不是把代码结构机械复述一遍。
更关键的是事实准确。让文档中的关键陈述(函数签名、参数、行为、命令)都来自真实代码,最好就近放置、由代码或自动化生成能生成的部分。这与AGENTS.md 互操作讲的"单一事实来源"一致:同一件事只在一个权威处维护,别处引用,避免多处各写一份、一改就全过时。宁可有一份短而准、会更新的文档,也不要一套厚而错、没人信的文档。
五、让测试和文档随代码一起活
这两类资产最大的敌人是腐化。要在流程上让它们跟着代码一起维护:
- 改动即更新:修改行为时,要求同时更新对应测试和文档,作为完成定义的一部分。
- CI 把关:测试自动运行;对文档可用链接检查、示例可执行性等手段减少腐烂。
- 就近与生成:能从代码生成的(API 参考、类型)尽量生成,手写部分聚焦解释与指引。
- 定期复核:过时文档和失效测试应及时删改,别让错误信息长期误导。
让 Agent 在每次代码变更后主动检查"相关测试和文档要不要同步",比事后集中补更省力、也更不容易遗漏。
六、一个具体的对照例子
抽象原则之外,看一对正反例能更清楚地把握"有效"与"注水"的分界。
假设要给"计算会员等级"的函数补测试。注水版本是这样的:构造一个用户、调用函数、断言"返回不为空"、再断言"没有抛异常"。无论等级算得对不对,这些断言永远成立——它们给的是"函数能运行"的安慰,而非"函数算得正确"的保证,改坏折扣规则也照样绿。有效版本则针对业务规则:累计消费恰好卡在两档临界点的用户应归入哪一级、差一元时应归入下一档、无消费记录的新用户应是最低级、同一会员在边界金额两侧结果应不同——每条断言都检查确切等级,且把规则改坏时至少一条会变红。
文档同理。注水版本把函数签名、字段名、代码目录结构复述一遍,这些信息代码里都有、且一改版就过时,回答不了"我该怎么用、什么时候会出错"。有效版本面向真实使用场景:给一个最小可运行的调用示例、说明返回值在几种业务情形下的含义、列出两个常见坑(如"金额单位是分不是元""未登录时返回默认等级而非报错")、并写明排查路径。判断标准始终是:这段内容若被删掉,读者会不会因此失去某个从代码本身难以获得的东西。 答案为否,就是该删的注水。
七、怎么验收这两类产出
验收测试:随机改坏实现看是否变红;检查断言是否具体、覆盖了关键边界;确认不是为凑覆盖率的同质用例。验收文档:抽样把关键陈述回到代码核实;以读者身份走一遍文档里的步骤能否成功;确认信息有单一来源、不是别处的过时重复。两者都不看数量,只看是否达成各自目的。
七、常见问题
Q:要不要追求 100% 覆盖率? 不必。覆盖率是"哪些代码没被测"的线索,不是质量本身;为凑数字给琐碎代码堆测试会稀释价值。应追求关键逻辑和高风险路径的有效覆盖。
Q:Agent 写的测试会不会照抄实现、实现错它也错? 这正是要防的。让测试依据需求和外部可观察行为来写,用变异检查验证,并保留独立视角,避免测试只是把实现逻辑重述一遍。
Q:文档总过时,是不是干脆别写? 问题不在写不写,而在维护机制。控制篇幅、就近放置、能生成就生成、纳入改动流程,少量可信文档远胜于无。
Q:注释算文档吗? 注释应解释"为什么",而不是复述"代码在做什么";让 Agent 补注释时同样要求它只在有非显而易见的意图处落笔,避免噪音。
八、为长期维护定一份轻量规矩
测试与文档一旦补起来,若没有维护规矩,很快又会腐化回原点。关键是定一份大家愿意遵守的轻量约定,并尽量自动化。
把同步纳入"完成定义":一个改动只有在相关测试和文档一并更新后才算完成,而不是事后想起来再补,从源头避免脱节。能生成的不手写:API 参考、类型说明等尽量从代码自动生成,把手工维护压到最少;手写部分集中在代码无法表达的"为什么"和"怎么用"。就近放置:文档尽量贴近所描述的代码,改动时自然看得到,也更容易想起更新。
定期轻扫:每隔一段时间删改失效测试和过时文档,结合链接检查、示例可运行性等自动化手段发现腐烂。规矩要少到记得住、并由 CI 承担大部分执行,否则写得再全也会被忽略。目标不是建立一套庞大制度,而是让"测试和文档随代码一起活"成为几乎零额外成本的默认习惯,这样前期的投入才不会随时间归零。
九、如何让 Agent 不"应试"你的代码
补测试时一个隐蔽风险是:Agent 可能照被测实现的内部逻辑反推用例,使测试与实现错误"同构",实现错、测试也跟着错。需要主动切断这种应试关系。
依据需求而非源码定预期:先从业务规则列出应有的行为和边界,再让它据此写断言,而不是先读实现再"把实现走过的路标为正确"。保留独立视角:可让另一个 Agent 或人只凭接口契约评审用例,看是否遗漏了合理场景。用变异检查兜底:随机改坏实现,若没有任何测试变红,说明用例只是在复述实现。
警惕硬编码凑值:若它写的测试看起来在"为了通过而填特定数字",追问该值的业务依据。这样得到的测试才真正独立于实现、能在行为偏离需求时报警,而不是充当现有代码(包括其中 bug)的保镳。
十、注释与代码内文档的分寸
让 Agent 补注释时最容易制造噪音——把代码本身已表达的东西再用自然语言复述一遍,反而增加维护负担。
注释应回答"为什么":解释非显而易见的意图、约束、取舍或踩过的坑,而不是复述"这行在做什么"。过时注释比没有更危险:把注释也纳入随代码更新的范围,错误的解释会直接误导后来者。克制数量:能用清晰命名和结构表达的就不必加注释,让真正重要的信息不被淹没。
十一、面向不同读者裁剪文档
同一份内容对不同读者价值完全不同,让 Agent 动笔前先确定对象,避免一份谁都不合适的"大杂烩"。
给新人的侧重快速上手与最小示例;给使用方的侧重接口契约、参数语义、错误处理;给运维的侧重部署、配置、监控与排障。读者决定取舍,也决定示例和深度。判断好坏的标准始终如一:它是否真实、是否回答了目标读者真正会问的问题,而不是篇幅多全、结构多漂亮。
十二、用读者视角做最终验收
这两类产出的最终验收都应跳出作者视角、回到使用者立场,否则形式齐整仍可能无用。
验测试:以"未来修改者"身份问"改坏哪类错误时它会拦住我",并实际做变异检查。验文档:完全照文档从零走一遍,看能否成功、卡在哪;抽样核对每个关键陈述与代码一致。删冗余:凡是读者从代码本身就能轻易得到、或不回答真实问题的内容,果断删。标准始终是"它是否独立地提供了可靠价值",而不是写了多少。这样验收过的测试与文档才真正可被信任、也愿意被后来者使用,前期投入才转化成了长期资产而非一次性的交差。
Q:时间紧,只补测试或只写文档选哪个? 优先补关键路径上能真正失败的测试——它直接降低改动风险;文档可先写最关键的一小段(如何运行、已知坑),日后再扩,切忌为了交差两头都铺一层薄而无用的内容。
十三、检查清单
- 测试针对关键逻辑与边界,断言具体、可证伪。
- 已做变异检查,确认测试能真正失败、对应具体风险。
- 遗留代码用特征测试如实记录现状。
- 文档面向真实读者问题,关键陈述回源核实。
- 信息有单一事实来源,测试与文档随代码变更同步维护。
总结
让 Agent 补测试和文档,成败都在"是否达成目的"而非数量:补测试要先锁定关键逻辑和高风险边界、写具体可证伪的断言,用"改坏实现是否变红"和"在防什么错"来识别注水,旧代码则用记录现状的特征测试打底;写文档要先想读者和问题、让所有关键陈述忠于真实代码并坚持单一事实来源;最后把两者纳入"改代码即更新"的流程和 CI,让它们持续可信,而不是交差式的覆盖率和一堆很快过时的漂亮文字。