测试体系是重构的基石、是强大的 Bug 侦测器,并且是提升开发速度的关键因素。
如果没有一套稳固可靠的测试集,重构就无法正确地进行。

测试体系作为重构的基石

重构(Refactoring)的严格定义是 在不改变代码外在行为的前提下,对代码做出修改,以改进程序的内部结构。为了确保“不改变代码外在行为”这一前提,测试体系是必不可少的保障。
  1. 安全保障机制:重构手法本身虽然旨在最大限度地减少引入错误,但程序员毕竟是人,有可能犯错。程序越大,修改不小心破坏其他代码的可能性就越大。可靠的测试集充当 Bug 侦测器,能够保护程序员不被自己犯的错误所困扰。
  1. 小步前进的保障:重构的精髓在于 小步修改,每次修改后就运行测试。做完一次修改就运行测试,这样即使犯错,也只需要考虑一个很小的改动范围,使得查错与修复问题易如反掌。如果改动了太多东西,犯错时就可能陷入麻烦的调试。因此,频繁运行测试 是开展高效有序重构的关键心得之一。
  1. 重构的前提条件:重构前,先检查自己是否有一套可靠的测试集。测试必须具有 自我检验能力。永远不应该在测试失败时进行重构。

测试体系对开发速度的提升

尽管编写测试需要花费时间,但 构筑测试体系能够极大提高编程速度。
  1. 减少调试时间:在软件开发中,花费在调试上的时间是很多的。如果测试运行得很频繁,并且引入的 Bug 可被现有测试捕捉到,程序员可以很快定位到 Bug,因为 Bug 必定是在前一次测试运行后修改代码引入的。以前需要一小时甚至更长时间才能找到的 Bug,现在只需要几分钟。
  1. 验证理解:在添加功能或重构时,通过编写测试,程序员需要将“想要达成的目标”写两遍——代码里写一遍,测试里再写一遍。这能够二次确认工作,降低犯错的概率。同时,测试能够帮助程序员 深入理解代码的所作所为。
  1. 测试驱动开发(TDD):先写测试的习惯被提炼成 测试驱动开发(TDD)。TDD 的编程方式依赖于一个短循环:“先编写一个(失败的)测试,编写代码使测试通过,然后进行重构以保证代码整洁”。这种良好的节奏感使得编程工作能够高效、有条不紊地开展。

构筑测试体系的实践原则

构筑测试体系的几个关键实践点:
  1. 自动化和自我检验:确保所有测试都 完全自动化,让它们检查自己的测试结果。这使得运行测试就像执行编译一样简单。
  1. 测试夹具的独立性:在编写多个测试时,应避免使用 共享测试夹具,因为这可能导致测试之间产生交互,使测试结果不确定,从而滋生难以发现的 Bug。推荐在每个测试开始前,构建各自独立的测试夹具(例如使用 beforeEach)。
  1. 风险驱动的测试:测试应该是一种 风险驱动的行为。测试的重点应该是那些 最担心出错的部分。编写未臻完善的测试并经常运行,远胜于对完美测试的无尽等待。
  1. 应对 Bug 的策略:每当你收到 Bug 报告,请先写一个单元测试来暴露这个 Bug。只有当测试通过时,才视为 Bug 修完。
  1. 边界条件和“公敌”思维:好的做法是把测试推到条件的边界处,检查操作出错时软件的表现。程序员应扮演 “程序公敌” 的角色,积极思考如何破坏代码。

测试不足是重构的主要挑战

缺乏可靠的测试集是重构的最大障碍。
  • 风险与遗留代码:如果没有自测试的代码,重构引入 Bug 的担忧是合理的。面对庞大而又 缺乏测试 的遗留系统,很难安全地重构清理它。
  • 自动化重构的局限:虽然自动化重构工具在没有完备测试套件的情况下也能提供帮助,但程序员仍会因此损失很多好用的重构手法。
  • 测试是持久化设计的关键:只有具备自测试能力,软件设计才能持续演化,保持开发的高效和低风险。
💡
简而言之,在重构的背景下,测试体系就是软件工程师的“安全网”。有了这个网,我们才能自信地进行大胆而频繁的结构性修改(重构),确保代码的设计持续改进,从而最终实现更快的开发速度和更高的软件质量。
 
学习笔记——《重构改善现有代码2》:封装学习笔记——《重构改善现有代码2》:代码的坏味道