持续集成是敏捷软件开发的核心实践。在我的项目中,我实践了一种与持续集成类似的测试活动:
Rolling Testing:每个小时,从源代码管理系统中获得当前版本,进行完整的构建(build)。在一台干净(clean)的机器上,部署新构建的系统,执行端到端(end to end)的系统测试。最后,用电子邮件将测试结果发送给开发团队。整个过程是无人值守的。
按照《持续集成》的定义,Rolling Testing不是严格意义上的“持续集成”(Continuous Integration)。不过,它所发挥的作用与持续集成非常接近。
Why
- Rolling Testing可以尽快地发现集成错误。开发者在自己的开发环境中工作,他所看到的系统是他最后一次检出(check-out)的结果。他根据这一版本的“知识”来开发新的功能。虽然两个开发者的都基于相同的“知识”来进行开发,但是不能保证他们所构造的新“理论”是相互一致的。往往,两部分的代码都通过了单元测试,但是检入(check-in)之后却引发了集成错误。Rolling Testing用端到端的系统测试弥补了单元测试的不足,能够提供快速的系统级的测试反馈。
- Rolling Testing可以节省大量的开发时间。集成错误往往会阻碍全面的系统测试。例如,模块A不能正确解释模块B的输出,它抛出异常并停止工作。这时,系统测试将无法覆盖模块A及其后续模块的代码。这将严重影响测试效率,甚至使得每日运行的全面回归测试变得毫无效果。Rolling Testing可以在检入之后的一个多小时内捕获到此类严重的错误,那么开发者就可以在第一时间修正错误,清除测试障碍(题外话:清除障碍并给予人们完成工作所需要的东西,是Scrum方法论的核心)。有时开发者的修正会引入的新的集成错误,Rolling Testing的安全网会再次捕捉到它,于是我们有机会保证:在下班回家的时候,没有明显的集成错误。
- Rolling Testing可以发现环境错误。所有的开发者都应该听从John Robbins的忠告:不要在开发机上使用Administrator权限。不过久经世故的Cem Kaner也说过:本书介绍的测试,假定的前提是你的同伴现在没有、将来也不会并且也没有必要遵循这些规定。由于开发者在开发机上拥有Administrator权限,他们安装了各式各样的工具和运行库,我们无法保证所开发出的系统能够在其他环境中以合适的权限正常工作。Rolling Testing在干净的机器上部署被测试系统,能够发现许多在开发环境中无法发现的错误。所谓干净的机器,并不要求重新安装操作系统,只是要求完整地卸载了旧版本的系统。
- Rolling Testing保证了被测试版本的基本质量,使测试团队更有信心进行大规模的功能测试和压力测试。
How
- 基本方法是利用Windows Schedule在每个整点触发流程,从源代码管理系统中获得完整的最新版本,然后利用构建工具执行完整的构建。如果构建失败,则无需继续:构建失败(build break)要求更紧急的修复。如果构建成功,在一台干净的机器上部署整个系统。然后,调用测试工具或测试框架执行系统测试。最后,汇总测试结果,生成测试报告,并用邮件发送给负责Rolling Testing的测试和开发人员。
- 自动化是Rolling Testing的关键。从以上流程可以看出,Rolling Testing所使用的工具无非是:操作系统自带的服务、源代码管理工具、构建工具、自动化测试框架、内部邮件系统,这些都是开发团队的常备工具(如果没有源代码管理和构建工具,那么立即部署合适的工具是开发人员的第一要务)。因此,用合适的方法将工具连接起来,构建出完整的流程,是架设Rolling Testing的主要任务。在Windows平台上,DOS Shell是最常用的脚本技术,可以满足大部分的要求。此外,PowerShell和IronPython能够直接利用.NET framework的强大功能,往往能事半功倍。
- Rolling Testing要能够在30分钟内完成(在我的项目中,Rolling Testing大约持续25分钟)。如果Rolling Testing发现了错误,开发人员需要在Rolling Testing环境中调查故障原因。为了不影响下一次的Rolling Testing,一般要留下30分钟的时间给开发人员进行诊断。
Tips
- 要部署完整的系统。对于一些分布式系统而言,将所有的子系统部署在一台机器上有些“不合情理”。但是,部署所有的子系统可以测试到所有的子系统的部署功能,也能够检查出由于子系统冲突造成的部署失败。部署是任何系统的核心功能,是Rolling Testing必不可少的测试内容。此外,在部署最新版本之前,Rolling Testing会反安装旧版本的系统。这也测试了系统的反安装功能。
- 测试用例集包含最重要的端到端系统测试。Rolling Testing的主要目的是发现集成错误,因此测试执行应该能够“从输入到输出”覆盖到所有的子系统。考虑到用户价值,测试用例应该能够体现最重要的用户情景(main user scenario)。由于Rolling Testing的时间有限,测试用例应该“贵精不贵多”,用较少的用户情景覆盖所有的子系统。
- 测试应该覆盖所有的子系统。有些子系统难以用常见的用户情景来覆盖,这时可以构建独立的测试来检验它不存在严重的错误。Rolling Testing的目的不是检查出所有的错误,而是发现那些会阻碍进一步测试的错误(blocking bug)。因此,不需要构建完备、严厉的测试用例集,只需要验证子系统的基本功能可以工作即可。
- 确保所有的测试用例是正确的。有时系统的设计会发生变更,这会导致部分测试用例需要更新。更新测试的工作有可能需要几个工作日才能完成。这时,应该将这些测试用例从Rolling Testing中排出出去。否则,当Rolling Testing报告测试执行失败时,测试人员不能立即判断是被测试系统的问题还是测试的问题,这将严重降低Rolling Testing的报警作用。特别是,如果测试人员“默认”测试失败是测试本身的问题,那么他有可能会漏过严重的集成错误。
- 及时地维护Rolling Testing测试用例集。系统会增加新的子系统、用户情景会发生变化、测试工具也需要更新,这都要求Rolling Testing的负责人及时地维护Rolling Testing的测试用例集,以确保:测试用例集可以覆盖到所有的子系统,且不包含错误的测试用例。
- 及时地响应测试报告。Rolling Testing的威力在于根据测试结果采取及时的行动。如果对于每小时一次的错误报告采取漠视的态度,那么之前所花费的精力将毫无价值。我的做法是利用Outlook的邮件规则,对测试报告邮件生成一个通知(alert)。当测试报告到达时,Outlook会弹出一个对话框,我只需要按一下“回车(Enter)”,就可以看到报告内容。如果有问题,则进行调查;如果没有问题,按两下”取消(Esc)”就可以关闭邮件、关闭对话框。此外,我还设置了另一条邮件规则,将所有的测试报告归档到一个单独的文件夹中。