步骤五:具有可延续性的设计
在开篇的故事中,我们看到由于自动化工程师把注意力仅仅集中在如何使自动化运转起来,导致测试自动化达不到预期的效果。自动化测试是一个长期的过程,为了与产品新版本的功能和其他相关修改保持一致,自动化测试需要不停的维护和扩充。自动化测试设计中考虑自动化在未来的可扩充性是很关键的,不过,自动化测试的完整性也是很重要的。如果自动化测试程序报告测试用例执行通过,测试人员应该相信得到的结果,测试执行的实际结果也应该是通过了。其实,有很多存在问题的测试用例表面上执行通过了,实际上却执行失败了,并且没有记录任何错误日志,这就是失败的自动化。这种失败的自动化会给整个项目带来灾难性的后果,而当测试人员构建的测试自动化采用了很糟糕的设计方案或者由于后来的修改引入了错误,都会导致这种失败的测试自动化。失败的自动化通常是由于没有关注自动化测试的性能或者没有充分的自动化设计导致的。
性能:提高代码的性能往往增加了代码的复杂性,因此,会威胁到代码的可靠性。很少有人关心如何对自动化本身加以测试。通过我对测试套性能的分析,很多测试套都是花费大量的时间等候产品的运行。因此,在不提高产品运行性能的前提下,无法更有效的提高自动化测试执行效率。我怀疑测试自动化工程师只是从计算机课程了解到应该关注软件的性能,而并没有实际的操作经验。如果测试套的性能问题无法改变,那么应该考虑提高硬件的性能;测试套中经常会出现冗余,也可以考虑取出测试套中的冗余或者减少一个测试套中完成的测试任务,都是很好的办法。
便于分析:测试自动化执行失败后应该分析失败的结果,这是一个棘手的问题。分析执行失败的自动化测试结果是件困难的事情,需要从多方面着手,测试上报的告警信息是真的还是假的?是不是因为测试套中存在缺陷导致测试执行失败?是不是在搭建测试环境中出现了错误导致测试执行失败?是不是产品中确实存在缺陷导致测试执行失败?有几个方法可以帮助测试执行失败的结果分析,某些方法可以找到问题所在。通过在测试执行之前检查常见的测试环境搭建问题,从而提高测试套的可靠性;通过改进错误输出报告,从而提高测试自动化的错误输出的可分析性;此外,还可以改进自动化测试框架中存在的问题。训练测试人员如何分析测试执行失败结果。甚至可以找到那些不可靠的、冗余的或者功能比较独立的测试,然后安全地将之删除。上面这些都是减少自动化测试误报告警、提高测试可分析性的积极有效的方法。另外,有一种错误的测试结果分析方法,那就是采用测试结果后处理程序对测试结果自动分析和过滤,尽管也可以采用这种测试结果分析方法,不过这种方法会使自动化测试系统复杂化,更重要的是,后处理程序中的BUG会严重损害自动化测试的完整性。如果由于自动化测试本身存在的缺陷误把产品中的正常功能视为异常,那该怎么办?我曾经看到测试小组花费开发测试自动化两倍的时间来修改测试脚本,并且不愿意开展培训过程。那些仅仅关注很浅层次测试技术的测试管理者对这种方法很感兴趣,他们排斥采用隐藏测试复杂度的方法。
综上所述,应该集中精力关注可以延续使用的测试套,从以下几方面考虑,测试的可检视性、测试的可维护性、测试的完整性、测试的独立性、测试的可重复性。
可读性:很多情况下,在测试人员开始测试项目之前,公司已经有了一套测试脚本,并且已经存在很多年了。我们可以称之为“聪明的橡树”(wise oak tree)[Bach 1996]。大家很依赖它,但是并不知道它是什么。由于“聪明的橡树”类型的测试脚本缺乏可读性,在具体应用中,那些脚本常常没有多大的实用价值,越来越让人迷惑。因此,测试人员很难确定他们实际在测试什么,反而会导致测试人员对自身的测试能力有过高的估计。测试人员能够检视测试脚本,并且理解测试脚本究竟测试了什么,这是很关键的。好的文档是解决问题的手段之一,对测试脚本全面分析是另外一个手段。由上面两种方法可以引申出其它的相关方法,我曾经在一个项目中使用过引申之后的方法。在测试脚本中插桩,把所有执行的产品相关的命令记录到日志里。这样,当分析日志确定执行了哪些产品命令,命令采用了何种参数配置时,可以提供一个非常好的测试记录,里面记录了自动化测试执行了什么,没有执行什么。如果测试脚本可读性不好,很容易变得过分依赖并没有完全理解的测试脚本,很容易认为测试脚本实际上做的工作比你想象中的还要多。测试套的可读性提高后,可以更容易的开展同行评审活动。
可维护性:在工作中,我曾经使用过一个测试套,它把所有的程序输出保存到文件中。然后,通过对比输出文件内容和一个已有的输出文件内容的差别,可以称已有的输出文件为“标准文件”(“gold file”)。在回归测试中,用这个方法查找BUG是不是明智之举。这种方法太过于敏感了,它会产生很多错误的警告。随着时间的推移,软件开发人员会根据需要修改产品的很多输出信息,这会导致很多自动化测试失败。很明显,为了保证自动化测试的顺利进行,应该在对“标准文件”仔细分析的基础上,根据开发人员修改的产品输出信息对之做相应的修改。比较好的可维护性方法是,每次只检查选定的产品的某些特定输出,而不是对比所有的结果输出。产品的接口变动也会导致原来的测试无法执行或者执行失败。对于GUI测试,这是一个更大的挑战。研究由于产品接口变化引起的相关测试修改,并研究使测试修改量最小的方法,测试中采用库是解决问题的方法。当产品发生变化的时候,只需要修改相关的库,保证测试与产品的变动同步修改即可。
完整性:当自动化测试执行后,报告测试执行通过,可以断定这是真的吗?这就是我称之为测试套的完整性。在前面的故事中,当没有对自动化测试完整性给予应有的关注的时候,发生了富有喜剧性的情况。我们应该在多大程度上相信自动化化测试执行结果?自动化测试执行中的误报告警是很大的问题。测试人员特别讨厌由于测试脚本自身的问题或者是测试环境的问题导致测试执行失败,但是,对于自动化测试误报告警的情况,大家往往无能为力。你期望自己设计的测试对BUG很敏感、有效,当测试发现异常的时候,能够报告测试执行失败。有的测试框架支持对特殊测试结果的分类方法,分类包括PASS,FAIL和第三种测试结果NOTRUN或者UNRESOLVED。无论你怎么称呼第三种测试结果,它很好的说明了由于某些测试失败导致其他测试用例无法执行而并非执行失败的情况。得到正确的测试结果是自动化测试完整性定义的一部分,另一部分是能够确认执行成功的测试确确实实已经执行过了。我曾经在一个测试队列中发现一个BUG,这个BUG引起测试队列中部分测试用例被跳过,没有执行。当测试队列运行完毕后,没有上报任何错误,我不得不通过走读代码的方式发现了这个BUG。如果,我没有关注到这个BUG,那么可能在认识到自动化测试已经出现问题之前,还在长时间运行部分测试用例。