12. 故意改坏实现,看测试能否发现
覆盖率统计的是有多少行代码在测试里被运行过。「被运行过」和「被检查过」是两回事:第 4 章那组回声测试把代码全跑了一遍,覆盖率好看,却什么都没验证。
这一章换一个更狠的问法:故意把实现改坏,测试抓得住吗?抓得住,说明断言真的在守这处行为;抓不住,说明这处行为没人看着。
给实现植入几处小改坏
Section titled “给实现植入几处小改坏”拿第 7 章那份已经全绿的解析实现,逐个植入一处小改动,每次只改一处,改完就跑一遍测试,再改回来。改动都很小,像手滑写错:
const missingUnit = !unit;const missingPrice = price === '' || price === undefined || Number(price) === 0;计划植入四处:
- 改动一(反转判断):
!unit改成unit,让缺单位的逻辑反过来。 - 改动二(改错常量):
Number(price) === 0改成Number(price) === 999,让价格为 0 不再算缺失。 - 改动三(删一条分支):删掉
|| Number(price) === 0,价格为 0 的处理整个消失。 - 改动四(去掉一步整理):把分隔后
.trim()去掉,不再清理字段两端的空格。
跑测试,看每处改坏的下场
Section titled “跑测试,看每处改坏的下场”每植入一处就跑一次 node --test,记录结果:
| 植入的改动 | 测试结果 | 说明 |
|---|---|---|
| 反转缺单位判断 | 变红 | 缺单位那条测试抓住了它,被「杀死」 |
| 价格判断改成 999 | 变红 | 价格为 0 那条测试抓住了它,被「杀死」 |
| 删掉价格为 0 分支 | 变红 | 同上,被「杀死」 |
去掉字段整理 .trim() | 仍全绿 | 没有任何测试抓住它,「存活」 |
前三处一改就红,说明有断言在守着对应的行为。第四处改坏了,测试却一片绿,它「存活」了下来。存活不是好消息,它说明「字段两端有空格也要能正确解析」这件事,现在没有任何一条测试在检查。
存活的变异指向要补的验收条件
Section titled “存活的变异指向要补的验收条件”每一处存活的变异,都是一条该补的验收条件。第四处存活的改动,对应的补强是:
字段两端有多余空格时,应当被清理后再解析。
把它写成测试:
// 补强:字段两端的空格要被清理test('字段带空格也能正确解析', () => { const rows = parseQuotes(' M6 螺母 , 个 , 0.18 '); assert.equal(rows[0].unit, '个');});加上这条测试后,再把 .trim() 去掉试一次,这一次测试会变红。原先存活的变异被杀死了,空白补上了。
每一处存活的变异记一行,连同它对应补强的验收条件,攒成一份清单。这份清单把「测试到底严不严」从一句感觉变成了一份具体的账:哪几种改坏能被抓住,哪几种漏了,漏的已经补了哪些。
第三幕到这里收束。判断的依据一路从「AI 说已经完成」,转到了可审查的改动、真实使用者的反应,和这一章的反事实检查。手里的工具不只是能跑,还经得起有人故意找茬。