用 AI 生成表征测试(characterization tests)捕获遗留代码当前行为,为重构提供安全基准,避免改坏「只有原作者知道的隐藏逻辑」。
考虑这样一个遗留结账服务:只有当服务器时区为 UTC+1 时才应用会员折扣。没有测试覆盖,原开发人员已经离职,代码注释写着"魔法数字没问题"。你需要重构它,因为新的税务规则依赖于相同的逻辑。唯一安全的做法是:在改动任何一行代码之前,先捕获当前行为。
表征测试(Characterization tests)决定了重构是悄无声息地发布,还是凌晨两点把你叫醒。它们不断言代码应该做什么,而是断言代码今天实际做什么。一旦你知道了基线,你就可以做小的、机械式的修改,然后让测试套件告诉你是否破坏了什么。
Disclosure: This article was prepared as part of MonkeyCode's product outreach.
AI 编码工具让每位开发者都变成了审核者,但没有人测试过这个审核者。当你让模型重写一个混乱的函数时,它可能引入微妙的行为变化,因为它不知道哪些怪癖是有意为之的。表征测试套件成为了反馈循环,用来捕捉这些变化。
MonkeyCode 的免费模型访问让你无需消耗自己的 tokens 就能生成测试建议,其免费服务器选项为你提供了一个可丢弃的套件运行环境。下面的工作流程有或没有这些工具都能工作,但有了它们,快到可以在周五下午完成。
在生成任何测试之前,列出每个公共函数、其输入、其输出以及它触及的任何副作用。对于结账服务,你可能识别出 applyDiscount(price, user),它读取当前时间并返回修改后的价格。写下所有已知的边界情况:空购物车、负价格、VIP 等级、接近午夜的时间。
将该接口列表和原始源代码发送给模型,要求生成测试骨架。MonkeyCode 的免费模型访问意味着你可以自由迭代提示词。目标不是完美的测试,而是足够的测试来锁定你能找到的每个分支和边界。
以下是一个使用 Node 内置测试运行器的最小示例:
// cart.js
export function applyDiscount(price, user) {
if (user.tier === 'gold' && new Date().getHours() < 12) {
return price * 0.8;
}
return price;
}
// cart.test.js
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { applyDiscount } from './cart.js';
test('characterization: gold user before noon pays 80%', () => {
assert.equal(applyDiscount(100, { tier: 'gold' }), 80);
});
test('characterization: standard user pays full price', () => {
assert.equal(applyDiscount(100, { tier: 'standard' }), 100);
});
test('characterization: gold user after noon pays full price', () => {
const now = new Date();
if (now.getHours() < 12) {
// Time-dependent; refine later by injecting a clock.
}
});
资深测试人员会注意到这个 flaky 的时间依赖。这是刻意的:表征测试经常揭示隐藏的依赖,而那个发现是第一个重构收获。
你需要在每次微更改后获得快速、可重复的反馈。MonkeyCode 的免费服务器选项为运行你的测试命令提供了干净的环境,不消耗笔记本电脑的电池或你的 CI 预算。运行套件,捕获输出,将任何失败视为行为变化警报。
# safety-net.sh -- run from repo root
node --test test/characterization/
你可以在本地、容器中或像 MonkeyCode 提供的免费服务器上执行这个脚本。关键是让这个命令足够便宜,以至于你可以在每修改十行代码后就运行一次。
现在进行重构。更改一个函数,重命名一个变量,提取一个辅助函数,然后运行套件。如果测试通过,就提交。如果失败,你就确切知道哪个行为滑落了,然后你可以决定新行为是改进还是回归。除非你有文档化的理由,否则不要"修复"测试来让它通过。
在让任何模型或 Agent 编写表征测试时使用此清单:
表征测试保留当前行为,而非期望行为。如果代码包含已知的安全漏洞或业务逻辑 bug,不要用测试把它神圣化。先修复 bug,然后表征更正后的行为。
当测试依赖于时间、随机性或外部服务时,套件可能很脆弱。你应该在编写测试之前注入依赖,或者至少标记 flaky 的案例以便后续重新访问。
对于有完整测试覆盖的新建项目来说,这种方法就过度了。对于原始行为已经严重损坏以至于几乎每个输出都错误的代码库,它也是错误的工具。在这种情况下,先投入产品需求。
表征测试把可怕的遗留重写变成了一系列无聊的、可审查的提交。你不需要更聪明的模型或无限的基础设施;你需要一个安全网和以小步前进的纪律。有了 MonkeyCode 的免费模型访问和免费服务器,你没有理由跳过这张网。
在你最糟糕的文件上尝试这个工作流程,你可能会发现系统中最高风险的代码变成了最无聊的修改。