基础篇
进阶篇
高频篇
精选篇
手写篇
面经篇
AI 篇
原理篇
每日一题
小程序题库
  • 知识卡片碎片时间过考点
  • 场景篇按分类整理的大前端场景考点
  • 历年面经按年份追踪真实考点
  • 算法题库NEW在线编码即时判题
  • 专项自测100 题快速查漏
  • 前端基础
    • HTTP从报文一路讲到 HTTPS
    • 浏览器渲染、事件循环、进程
    • 计算机基础Linux、网络、操作系统
  • 进阶专项
    • 设计模式23 种模式怎么用
    • 前端系统进阶学习大型项目工程化
    • 前端综合文章长期沉淀的实践文
  • 工程与工具
    • Node学习指南从环境搭建到服务端
    • NPM工作流script、依赖与发布
    • Docker容器化部署上手
    • Canvas图形与动画实战
  • 路线与导图
    • 思维导图知识点全景图
    • 学习路线按图索骥不跑偏
  • 动态
    • 公众号动态公众号历史文章
    • 博客动态站长的技术博客
    • 开发者导航常用工具与文档站
    • AI 热点每日 AI 资讯与动向
AI 工作台
简历
旧版
AIHT
内容
精选全部 AI 动态热点榜AI 日报收藏
分类
模型行业编程产品技术观点开源
更多
Agent 接入
返回首页开发者导航
AIHT
精选 / 资讯详情
8.0
热点
AI SCORE
技术实践2026-11-06 07:24

重构前先证明测试能抓住错误

dev.to · AI#软件测试#代码重构#变异测试
Editor brief · 编辑速览

文章要求新增行为刻画测试至少识别一次人为代码变异,例如修改比较边界、替换默认值或移除舍入。通过少量针对性变异,检查测试是否真正约束重构涉及的行为,而非仅让代码执行一遍。

文章思维导图
Knowledge map
拖拽缩放
AI deep read

AI 深度解读

重构前补了测试,改完全部通过,代码就安全了吗?作者提醒:有些测试即使面对分支被删除、边界被改坏,也照样亮绿灯。测试执行过某行代码,不代表它能发现那行代码的行为变化。

他的办法很直接:先故意改坏代码,确认新增测试会失败,再开始重构。这篇值得看的地方,是把「测试有没有保护作用」变成了一次可以运行的检查。不过,检查脚本和结果解释都有边界,不能把它当成正确性证明。

测试通过之前,先证明它能失败

作者针对的是特征测试(characterization test):在整理旧代码前,先记录当前行为,避免重构时无意改变调用方依赖的结果。它回答的是「现在怎么运行」,未必回答「业务上应该怎样」。

问题出在断言太松。例如只检查结果不是 None,即使金额舍入、列表顺序或默认值发生变化,测试仍可能通过。覆盖率也解决不了这个问题,因为执行到一行代码,和检查这行代码造成的结果,是两回事。

作者提出一条规则:每个新增特征测试,至少要识别一个故意引入的变异。所谓变异,就是把 >= 改成 >、替换默认值、删掉舍入调用这类小改动。测试因此失败,才算「杀死」这个变异。

绿色结果只能证明测试通过,故意改坏后出现的失败,才能证明它观察到了某种变化。

范围也要收小。先写清准备保护的函数、公开方法或序列化数据,再明确哪个调用方必须继续正常工作。作者建议每个边界手写 2–6 个变异,无须一开始给整个文件打分。

先固定输入,再记录代码真正返回什么

如果时间、随机 ID 和环境变量每次都不同,变异前后的差异就难以解释。作者建议把这些输入收口到可替换的模块级函数,再通过 monkeypatch 固定。

原文固定了 _now 返回的 2026-03-01T00:00:00Z、_new_id 返回的 id-fixed-001,以及环境变量 BILLING_ROUNDING=half_up。随后连续运行目标测试 5 次,只要结果不同,就先解决确定性问题。

接下来运行现有实现,把真实输出写入断言。这段测试用来锁住指定输入下的完整返回值:

def test_partial_month():
    out = prorate.compute(days_left=1, cents=1234)
    assert out == {"amount_cents": 1234, "label": "1 day"}

要看的是第二行断言:金额和文案都被固定,保护范围比「返回值存在」具体得多。但这仍只是当前行为的记录,不能由此推断计费规则正确。作者也明确说,这些基准值可以作为临时支架,行为得到文档说明后再删除或重新推导。

然后,为调用方真正关心的差异编写变异:

MUTANTS = [
    {"id": "M1", "file": "billing/prorate.py",
     "old": "if days_left >= 1:", "new": "if days_left > 1:",
     "target": "tests/test_prorate.py::test_partial_month"},
    {"id": "M2", "file": "billing/prorate.py",
     "old": "return round(cents, 2)", "new": "return round(cents, 0)",
     "target": "tests/test_prorate.py::test_partial_month"},
]

这里要留意 old 和 target:前者必须在源码中恰好匹配一次,后者必须准确选中测试。匹配零次或多次属于 bad-pattern,不能算作变异存活。原文没有提供完整业务实现,也没有展示这两个变异实际运行后的结果。

结果分类,比写几个替换规则更关键

作者给出的执行器会复制仓库,在临时目录里应用一个变异,然后只运行对应的测试节点。调用形式是 sys.executable -m pytest -q --no-header,通过 subprocess.run 设置 cwd=work、capture_output=True、text=True 和 timeout=300。

按原文分类,退出码 0 是 survived,1 是 killed,5 是 no-tests;2、3、4 分别涉及中断、内部错误和用法错误,统一归入 error。其中 5 尤其容易漏看:测试根本没选中,当然不能证明任何保护作用。

存活结果也不能一律靠补断言解决。如果改动确实改变了调用方能观察到的行为,就收紧断言;如果属于等价变异,也就是改了源码却没有可观察差异,应从列表删除并说明原因。

重构时,作者建议一个提交只动一个边界,前后运行完全相同的变异集合,并比较被杀死的集合。他把新增或丢失的击杀都视为需要调查的信号。

AI 在这里负责提出候选输入、断言和变异。最终裁判仍是运行结果。原文提到 MonkeyCode 的免费模型和免费服务器,同时披露文章属于其产品推广;没有提供模型对比、配额保证或效率实测。

我的判断

我赞成用少量手写变异检查高风险边界。相较覆盖率,它问得更具体:如果这里发生某种错误,测试能否发现?相较直接给整个仓库跑变异测试,它也更容易控制耗时。

但原文脚本标注为未执行的脚手架,不能直接当成可靠工具。尤其是退出码 1,只代表 pytest 出现测试失败,不能单凭它确认是预期断言识别了行为变化。原文要求语法错误算执行器问题,脚本却没有进一步核验失败原因;timeout=300 触发后的异常也未处理。

「前后击杀集合一致」适合作为调查入口,不能机械认定新增击杀就是业务行为被改坏。文本替换可能在重构后失效,失败也可能来自导入或环境问题,需要结合具体输出判断。

这套方法证明的是测试能观察某些变化,不是现有行为正确,更不是重构已经安全。

作者没有给出缺陷拦截率或耗时对照,因此「比人工审大 diff 更便宜」仍是经验判断。它适合保留行为的局部重构;重写边界,或单次测试已经耗时数分钟时,作者也建议跳过。

能拿来做什么

  • 重构前端或 Node 的旧逻辑时,先选一个明确的调用边界,记录实际输出,再针对边界值、默认值或排序设计 2–6 个小变异。
  • 遇到时间、ID、环境变量参与结果计算,先固定输入并连续运行测试 5 次,确认稳定后再评估断言是否有效。
  • 让 AI 同时阅读函数和调用方,要求它为每个边界输入提出一处应改变输出的单行修改,再逐项运行验证。
  • 接入检查脚本前,先补齐失败原因核验、无测试匹配和超时处理,并保留失败输出,让每次「杀死变异」都有可检查的依据。
Original source

本文由 AI 通读原文后重新编写,观点部分为站内判断,细节以原文为准,来源 dev.to · AI,原文版权归原作者所有。

阅读原文
上一篇
英伟达以分层存储缓解智能体显存压力
下一篇
用可执行检查划定 AI 文档生成边界

资讯速览

dev.to · AI · 2026-11-06 07:24

AI 评分 8.0 / 10

相关标签

#软件测试#代码重构#变异测试

继续阅读

  • 百灵新多模态模型开放百万上下文 API
  • 英伟达以分层存储缓解智能体显存压力
  • 用可执行检查划定 AI 文档生成边界
  • 用二分定位智能体补丁引发的测试污染
  • GPT-6 Cyber 发布,强化漏洞验证与修复