6. 先让检查按预期失败
第 5 章把「整理正确」裁决成了几条能检查的验收条件。这一章把这些条件变成测试,但顺序是反的:先让测试运行,而且要它失败。
一条测试写完就通过,看起来是好事,其实什么都没证明。它可能只是复述了现有实现的行为,也可能根本没在检查该检查的东西。一条测试值不值得信,先看它在「实现还没做到」的时候会不会红。红不起来的测试,绿了也不算数。
让测试先对着一个空壳跑
Section titled “让测试先对着一个空壳跑”先给解析函数留一个空壳,让测试有对象可跑,但故意什么都不做:
// 还没有实现,先放一个空壳export function parseQuotes(text) { // 故意留空:现在跑测试,应该全红 return [];}把第 5 章那几条验收条件交给 AI,请它照着写成测试。拿到的测试文件大致是这样,每条测试对应一条验收条件:
import { test } from 'node:test';import assert from 'node:assert/strict';import { parseQuotes } from './parse-quotes.js';
// 验收条件一:正常行解析出品名、单位和数字价格test('正常行:解析出品名、单位和数字价格', () => { const rows = parseQuotes('M6 螺母,个,0.18'); assert.deepEqual(rows[0], { name: 'M6 螺母', unit: '个', price: 0.18, missing: false });});
// 验收条件二:缺单位的行,单位标记为「缺失」test('缺单位:单位标记为缺失', () => { const rows = parseQuotes('不锈钢垫片,,1.20'); assert.equal(rows[0].unit, '缺失'); assert.equal(rows[0].missing, true);});
// 验收条件三(第5章新裁决):价格为 0 也算缺失test('价格为 0:价格标记为缺失', () => { const rows = parseQuotes('赠品,个,0'); assert.equal(rows[0].missing, true);});在这两个文件所在目录运行 node --test。空壳返回的是一个空数组,rows[0] 取不到任何东西,三条测试全部失败。这一片红正是这一步要的结果。
红灯也分两种
Section titled “红灯也分两种”不是所有红灯都算数。这一章要的红,是「实现还没做到」这一种;另有几种红是测试自己坏了,得先修掉,不能拿来当验收依据。
| 看到的红 | 属于哪种 | 怎么处理 |
|---|---|---|
| 断言不满足,或取值时说某个字段是 undefined | 目标红灯:实现确实还没做到 | 保留,这就是要的红 |
| 提示找不到模块、import 路径对不上 | 假红:测试连实现都没加载进来 | 先修好文件名和路径,再重跑 |
| 语法错误,或提示缺少某个依赖 | 假红:测试文件或环境本身坏了 | 先修语法、装依赖,再重跑 |
区分的办法很简单:目标红灯的失败信息里,说的是「期望得到 A,实际得到 B」这类断言不符;假红说的是「文件找不到」「语法错」这类还没跑到断言就崩了的问题。只有断言级别的红,才说明测试真的在盯着行为。
把测试文件锁成只读
Section titled “把测试文件锁成只读”红灯确认无误之后,进入下一章让 AI 写实现之前,先立一条规矩:实现阶段,测试文件只读。
原因是这样:让 AI 去改实现让测试变绿时,如果某条测试怎么都过不了,最省事的做法是把那条测试删掉或改松。一旦这样做,等于为了让结果好看,把尺子改短去迁就实现。测试是第 5 章由人裁决出来的「什么算对」,它不能在实现阶段被实现方随手改动。
真的需要改测试怎么办?停下来,回到第 5 章那一步,由人重新裁决验收条件,改完再解锁。改的是「什么算对」,永远由人决定;不是「怎么让它变绿」,那才交给 AI。
留下什么产物
Section titled “留下什么产物”这一章留下两样成对的东西:一份保存下来的红灯输出,和它对应的验收条件。
红灯输出证明每条测试都能在实现缺失时正确地红;验收条件说明每条红各自在守哪一条标准。这两样放在一起,才构成「测试可信」的证据。下一章让 AI 在这套测试的判定下循环,把红一条条变成绿。
这一章可以对 AI 说的一句话
Section titled “这一章可以对 AI 说的一句话”这是我裁决好的几条验收条件:______。请照着写成测试,但先不要写实现。我会在实现还是空壳时先跑一遍,确认每条测试都因为「断言不满足」而失败,而不是因为路径或语法出错。测试写好后请不要再改动它。