同时,选择合适粒度的测试也很重要。各类测试自己的优点,例如集成测试在功能保护上体现效果更快;而单元测试却会驱动内部质量的提升。如果条件允许,选择多 种粒度的测试结合,别忘了之前提到的测试金字塔。我们无法为整个系统一下子建立完善的测试,但为某一个区域,是有可能的。
为遗留系统写功能测试
功能测试处于测试金字塔的上端,它的稳定性相对较低,维护成本也较高。因此写功能测试一定要关注提升它的稳定性,并降低维护成本,遗留系统在这几个方面遇到的挑战可能会更大。
最近我对一个Web系统建立基于WebDriver的功能进行测试,其中面临的一个很大问题就是HTML页面缺乏语义、很多元素的定位都得依靠位置等极不可 靠方式,一旦页某些布局发生变化,就会影响到测试,维护成本很高。但事情总有两面性,正是这些测试,让页面的重构和优化得到了团队的重视。
影响功能测试稳定性的另一个重要因素是测试数据。对于团队控制范围内的系统,我的建议是随着测试的建立逐步创建一套可靠的、覆盖各种典型场景的测试数据准备脚本。由此,我们每次都重新建立干净的测试数据,让测试更加稳定和可控。
但在遗留系统中,有时会碰上更严峻的问题,系统依赖于第三方或其他不在控制范围内的测试系统。功能测试会影响到测试数据,因此我们的测试很有可能无法重复执 行。当然,建立一个测试替身系统是一种选择方案,但有时并不容易,至少短期之内。面对这种情况,我们的解决方案是让测试程序和测试数据解耦。想象一下,如 果同样的测试由一个测试人员手工执行,每次执行时不需要选择相同的数据,而只需选择“符合同样要求”的数据。
例如一个电商系统,它出售数量 有限的商品,售完即止。测试数据库中有大量不同商品,但每种商品数量所剩无几。如果我们的商品购买测试程序针对某个特定商品,那么在运行几次之后,商品就 会卖完,测试就不再具备可执行性。但测试人员不会这么傻,他每次都可以选择还有剩余的商品进行购买测试。既然如此,我们的测试程序也同样可以做到:只要根 据商品页面上的信息识别出哪些商品有剩余,随机或者有策略地选择其中某个商品进行购买即可。
这样,我们就让测试程序和具体的测试数据得到了解耦,缓解了测试的不可重复执行性,使其更加稳定,维护成本也得到降低。除了上述方法,还有其他方式可以避免测试程序和测试数据的耦合。
功能测试程序,是在用一种自动化的方式代替人的手工执行,但同时也一定程度上丢失了手工执行的灵活性。让功能测试程序保持灵活应变是我们在编写测试程序时应该考虑的一个重点。
为遗留系统写单元测试
为遗留系统写单元测试会面临和写功能测试不一样的挑战,在复杂度及对人的能力要求上也可能会更高一些。原因并不在于测试,而在于遗留系统自身。遗留系统内部 的强耦合(依赖)及每个单元的高复杂度使得测试难以开展。例如最近我接触的几个遗留系统,线程池逻辑和业务逻辑交织在一起,SQL拼装逻辑、ORM逻辑和 业务逻辑也交织在一起,一个方法往往几百上千行,而且有很深的调用链。
为这样的系统编写单元测试,我们普遍遇到了这样几个问题。
1. 私有方法如何测试:我经常被问到的一个问题是“这个私有方法怎么测”?对于私有方法的测试,可能的答案是——不要对私有方法进行测试,只要测试公有方法, 就能覆盖到私有方法。这个答案可能正确,但在遗留系统中,往往是错误的。很多时候,我会反问“为什么要对私有方法进行测试?”
下面这个例子(如图2所示),是一个有较复杂逻辑的线程。但主要的逻辑存在于组装和发起HTTP请求和解析返回的XML上。
图2 一个有较复杂逻辑的线程
当想对私有方法进行测试时,往往意味着类过于复杂、私有方法承载着太多的职责,通过公有方法覆盖私有方法的测试成本过高,难度太大。因此,更好的解决办法是 分离职责、分而治之、单独测试。通过分离职责,单独对各部分逻辑进行测试,测试就会简单很多,如图3所示;另一个例子如图4所示。
原文转自:http://www.uml.org.cn/Test/201305025.asp