<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>断言 on 老朱的赛博工位</title><link>https://blog.zjkun.top/tags/%E6%96%AD%E8%A8%80/</link><description>Recent content in 断言 on 老朱的赛博工位</description><generator>Hugo</generator><language>zh-CN</language><lastBuildDate>Mon, 19 Oct 2026 12:30:00 +0800</lastBuildDate><atom:link href="https://blog.zjkun.top/tags/%E6%96%AD%E8%A8%80/index.xml" rel="self" type="application/rss+xml"/><item><title>AI 写代码自称做完？断言 4 步拆穿假完成</title><link>https://blog.zjkun.top/posts/ai-code-assertion-4-steps/</link><pubDate>Mon, 19 Oct 2026 12:30:00 +0800</pubDate><guid>https://blog.zjkun.top/posts/ai-code-assertion-4-steps/</guid><description>晚上 11 点半 AI 丢下一句「测试全过」你就睡了，早上打开页面按钮根本没反应。假完成的根源是断言写得太松。4 步设计法：用例五件事、红灯要坏得对、逻辑用替身逐字段断言、效果真模型多跑几轮。</description><content:encoded><![CDATA[<p>晚上 11 点半，AI 丢下一句「测试全过 ✅」，你关了电脑睡觉；早上打开页面，新加的按钮根本没反应。</p>
<p>这种「假完成」我上周遇到三次——AI 一口气报 5 次「全过」，3 次是假的，全是旧数据被覆盖这类无声坑。它是真觉得自己完成了，因为它的测试压根抓不住。问题不出在测试数量，出在<strong>断言设计</strong>。</p>
<p>这是 AI 编程避坑系列第 2 篇，承接<a href="/posts/tame-coding-agent-5-steps/">《5 步驯服 Coding Agent》</a>：那篇讲流程，这篇讲第三步「验收检查怎么写」里最容易翻车的一环。</p>
<h2 id="先掰词">先掰词</h2>
<p><strong>断言 = 测试里写死的一句「什么才算对」。</strong> 像验货对着清单逐项打勾，不是快递说「到了」就算到。断言只写「成功了没」，没写「它把旧数据覆盖了没」，测试全绿也拦不住假完成。</p>
<h2 id="断言设计-4-步">断言设计 4 步</h2>
<h3 id="第-1-步预期和不应发生都要写">第 1 步：预期和「不应发生」都要写</h3>
<p>一条完整用例 = 前置条件 + 输入操作 + 预期结果 + <strong>不该出现什么</strong> + 怎么检查。</p>
<p>拿「导入 2 条新消息」举例：断言总数加 2 之外，还必须断言<strong>旧记录的原文、发布者一个字没变</strong>。关键字段逐个比，别只数条数。我练习题里的真实写法：</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">import_messages</span>(existing: list[dict], batch: list[dict]) <span style="color:#f92672">-&gt;</span> list[dict]:
</span></span><span style="display:flex;"><span>    seen <span style="color:#f92672">=</span> {(m[<span style="color:#e6db74">&#34;room_id&#34;</span>], m[<span style="color:#e6db74">&#34;msg_id&#34;</span>]) <span style="color:#66d9ef">for</span> m <span style="color:#f92672">in</span> existing}
</span></span><span style="display:flex;"><span>    result <span style="color:#f92672">=</span> list(existing)          <span style="color:#75715e"># 不改动已有记录（原文/发送者/采集时间保留）</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">for</span> m <span style="color:#f92672">in</span> batch:
</span></span><span style="display:flex;"><span>        key <span style="color:#f92672">=</span> (m[<span style="color:#e6db74">&#34;room_id&#34;</span>], m[<span style="color:#e6db74">&#34;msg_id&#34;</span>])
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> key <span style="color:#f92672">not</span> <span style="color:#f92672">in</span> seen:          <span style="color:#75715e"># 身份 = 房间 + 消息ID，不是文本</span>
</span></span><span style="display:flex;"><span>            seen<span style="color:#f92672">.</span>add(key)
</span></span><span style="display:flex;"><span>            result<span style="color:#f92672">.</span>append(dict(m))   <span style="color:#75715e"># 拷贝存入，避免外部对象后续被改</span>
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> result
</span></span></code></pre></div><p>配套断言不能只写 <code>len(result) == 2</code>，必须逐字段比对已存在的 M01 原文未被改写——只检查「成功」，就永远发现不了「它把错误内容覆盖进去了」。</p>
<h3 id="第-2-步红灯要坏得对">第 2 步：红灯要坏得对</h3>
<p>TDD 先红再绿，但<strong>红的失败原因必须是「目标行为还没实现」</strong>。缺依赖、模块导不进来，那是考场坏了，不算验收——先修运行条件，这条测试还没开始检查业务规则。</p>
<p>测试第一次就直接绿：别故意改坏代码造假红灯。要么这个行为早已实现（保留测试即可），要么你的断言漏了关键结果。后者更常见。</p>
<h3 id="第-3-步查逻辑固定假数据--模型替身">第 3 步：查逻辑——固定假数据 + 模型替身</h3>
<p>去重、筛选、引用越界这类题有标准答案，像数学题。喂固定假数据，把模型换成替身（它自己记录被调用几次），逐字段断言。数数是程序干的活，不归模型：</p>
<ul>
<li>快照为空 → 断言 <code>call_count == 0</code>，模型一次都不许被调</li>
<li>引用越界 → 断言被拒，返回越界的 ID 清单</li>
</ul>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">validate_answer</span>(answer: dict, input_ids: set[str]) <span style="color:#f92672">-&gt;</span> tuple[bool, list[str]]:
</span></span><span style="display:flex;"><span>    bad <span style="color:#f92672">=</span> [i <span style="color:#66d9ef">for</span> i <span style="color:#f92672">in</span> answer[<span style="color:#e6db74">&#34;cited_ids&#34;</span>] <span style="color:#66d9ef">if</span> i <span style="color:#f92672">not</span> <span style="color:#f92672">in</span> input_ids]
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> (len(bad) <span style="color:#f92672">==</span> <span style="color:#ae81ff">0</span>, bad)
</span></span></code></pre></div><p>替身的价值是<strong>稳定制造</strong>「没调用 / 乱调用」这些边界，真模型反而演不出来。</p>
<h3 id="第-4-步看效果真模型多跑几轮">第 4 步：看效果——真模型多跑几轮</h3>
<p>摘要准不准、有没有编——程序能校验「引用没越界」，校验不了「引用真的支撑了回答」。这类必须真实调用 + 多样本 + 语义标准：<strong>允许措辞变，固定住「意思必须保留」</strong>。比如「爬长城很累吗」和「爬到一半能返回吗」被概括时，两条的差异有没有被揉掉——这是语义检查，替身做不了。</p>
<p>一次通过不等于稳定，同一条至少跑几轮看一致性。</p>
<h2 id="速查表">速查表</h2>
<ol>
<li>用例五件事：前置｜输入｜预期｜<strong>不应发生</strong>｜检查方法</li>
<li>关键字段逐个比，不只数条数</li>
<li>红灯 = 目标行为没实现，不是考场坏了；第一次就绿先查断言漏没漏</li>
<li>逻辑用替身逐字段断言，效果用真模型多样本多跑几轮</li>
</ol>
<h2 id="验收时多看一眼">验收时多看一眼</h2>
<p>AI 报告「测试全部通过」时，去它的汇报里找两行：真实模型调用过没有？页面实际操作过没有？我那 3 次假完成，两次都是单元测试全绿、但「测试全过」覆盖的只是程序逻辑那一半——模型效果和用户流程根本没被执行过。</p>
<p>一句话收束：<strong>AI 写代码算不算做完，不能它嘴上说了算，断言说了算。</strong></p>
<blockquote>
<p>本篇最早于 2026 年 10 月 19 日发布于小红书，此为博客完整版。</p>
</blockquote>
]]></content:encoded></item></channel></rss>