以自动化测试撬动维护阶段的遗留系统(2)

发表于:2013-01-23来源:程序员作者:胡振波点击数: 标签:自动化测试
这样,我们就让测试程序和具体的测试数据得到了解耦,缓解了测试的不可重复执行性,使其更加稳定,维护成本也得到降低。除了上述方法,还有其他方

  这样,我们就让测试程序和具体的测试数据得到了解耦,缓解了测试的不可重复执行性,使其更加稳定,维护成本也得到降低。除了上述方法,还有其他方式可以避免测试程序和测试数据的耦合。

  功能测试程序,是在用一种自动化的方式代替人的手工执行,但同时也一定程度上丢失了手工执行的灵活性。让功能测试程序保持灵活应变是我们在编写测试程序时应该考虑的一个重点。

  为遗留系统写单元测试

  为遗留系统写单元测试会面临和写功能测试不一样的挑战,在复杂度及对人的能力要求上也可能会更高一些。原因并不在于测试,而在于遗留系统自身。遗留系统内部 的强耦合(依赖)及每个单元的高复杂度使得测试难以开展。例如最近我接触的几个遗留系统,线程池逻辑和业务逻辑交织在一起,SQL拼装逻辑、ORM逻辑和 业务逻辑也交织在一起,一个方法往往几百上千行,而且有很深的调用链。

  为这样的系统编写单元测试,我们普遍遇到了这样几个问题。

  1. 私有方法如何测试:我经常被问到的一个问题是“这个私有方法怎么测”?对于私有方法的测试,可能的答案是——不要对私有方法进行测试,只要测试公有方法, 就能覆盖到私有方法。这个答案可能正确,但在遗留系统中,往往是错误的。很多时候,我会反问“为什么要对私有方法进行测试?”

  下面这个例子(如图2所示),是一个有较复杂逻辑的线程。但主要的逻辑存在于组装和发起HTTP请求和解析返回的XML上。

  图2 一个有较复杂逻辑的线程

  当想对私有方法进行测试时,往往意味着类过于复杂、私有方法承载着太多的职责,通过公有方法覆盖私有方法的测试成本过高,难度太大。因此,更好的解决办法是 分离职责、分而治之、单独测试。通过分离职责,单独对各部分逻辑进行测试,测试就会简单很多,如图3所示;另一个例子如图4所示。

图3 单独对各部分逻辑进行测试

  图4 另一个案例

  如果在不改变代码的前提下为这样的代码写测试,首先要花很多时间理解它,理清各个分支,分别为它们建立复杂的测试准备状态等,成本很高,有时甚至为不可能完成的任务。

  2. Mock是否是遗留系统单元测试的“银弹”:当对遗留系统进行单元测试时,Mock作为“银弹”时不时地出现了。面对遗留系统中有较深依赖链的一些方法, 把那些难以建立的依赖统统Mock掉是最快的手段。但经验告诉我:对Mock保持警惕,一旦引入它,就容易被人滥用。当然它本身无错,错在使用它的人,如 果一定要怪Mock,就怪有些Mock工具过于强大吧。滥用Mock会导致:

  测试真实性的减弱,降低测试程序作为测试本身的价值;

  滥用Mock的“交互验证(Verification)”,使得测试和实现紧密耦合,降低测试的稳定性。

  我专门回顾了之前做的几个系统,发现用到Mock的时候微乎其微,大多使用在对不受控依赖建立测试替身上。在保证测试执行速度的前提下,这是我推荐的做法。但面对遗留系统时,我们很容易把“难以建立依赖测试状态”作为使用Mock的借口,大量滥用。

  单元测试,相对于功能测试等高层次的测试,是代码级别的白盒测试。但之于它的测试对象而言,我们仍然应当把单元测试视为黑盒测试——单元级别的黑盒测试。例 如对一个排序方法而言,不管它采用什么排序算法实现,大多情况下我们只在乎它是否可以将一个无序数组排序。它的测试也只要基于输入(无序数组)和输出判断 (有序数组)即可,就算排序算法改变,测试仍然不受影响。从这个排序方法的角度看,测试对它的内部实现不关心,是黑盒的;当内部实现改变时,只要外部行为 不变,测试就不会受到影响。滥用Mock很容易让测试违反这个特质。

  此种情况下,我们选择的方案是:选择从依赖链的末端开始测试,从这里开始逐渐完备测试状态准备机制。在保证测试速度(运行速度和编写速度)的前提下,尽量避免使用Mock。当然,如果你的代码依赖复杂、混乱,那么首先可能要重构,重新组织分配职责,简化依赖关系。

  简而言之,面对遗留系统进行单元测试时,有几个值得关注的实践:

  分离职责,分而测之,优于私有方法进行测试;

  逐步完备测试状态准备机制,优于使用Mock。

  之前,我个人对于在遗留系统上实施测试自动化建设是总体悲观的:遗留系统庞大,测试的效果并不会在短期内得以体现;而团队,恰恰经常认为遗留系统足够稳定 (没有什么大需求,而且90%的代码可能短期不需要改动),没有动刀的必要。因此,大多数团队很有可能会在看到测试带来的效果之前就放弃了。

  但请相信我,这一切都是假象。故障频发的正是这些遗留系统:

  稳定是假象,遗留系统里隐藏着很多故障和漏洞,只是暂时没有爆发而已;

  稳定是假象,粗劣的设计让任何一个需求的变化都会像霰弹一样影响整个系统,遗留系统大都是极其脆弱的。

  而单元测试,除了它本身带来的测试价值之外,对于遗留系统而言,更大的部分在于以下三个方面:

  驱动遗留系统的重构,提升架构设计和内部质量(遗留系统虽庞大,但坏味道其实都雷同)。这是对遗留系统而言最关键但一般情况下最没有可能去做的事情;

  暴露并解决系统中的缺陷和漏洞,在这个过程中,我们发现并处理了很多漏洞和缺陷,包括多线程处理上、业务逻辑等;

  更重要的是提升团队的重构和设计能力以及团队质量意识,特别是内部质量。

原文转自:http://www.programmer.com.cn/14624/