(四川大学计算机学院,成都610065)
单元测试是软件测试中非常基础和重要的方式,能够尽早发现程序缺陷,保障软件质量。手动单元测试是一项耗时又有挑战性的工作,自动化单元测试能有效提升测试效率、降低成本。
EvoSuite使用遗传算法来进化生成一组具有最优代码覆盖的Junit测试用例[5]。它首先初始化一组由随机测试用例组成的测试套件,然后迭代地使用搜索、变异和交叉等搜索算子(searchingoperators)来进化它们。搜索过程中存在一个基于覆盖率的适应度函数(fitnessfunction)来指导这种进化,例如最基本的是分支覆盖。当搜索结束后,选取具有最高代码覆盖的测试套件,并在保持覆盖率的前提下去除冗余测试用例。最后会添加回归测试断言,让生成的测试套件能够应用于回归测试场景。
实验将从以下几个方面来探究EvoSuite的性能:
(3)自动生成的测试用例与手工测试用例的效果比较。面对相同的被测类,将EvoSuite生成的效果最好的测试用例与手工测试用例进行比较,如果覆盖率和揭错能力差异较小,则说明该工具有可能替代手工测试用例编写,以用于实际软件测试。
表1实验项目及被测类信息
为了方便进行实验,这里借助了SBST公开的最新版本竞赛框架(v1.0.1)。按照使用要求,将EvoSuite和被测类放在指定目录,并设定好必要的环境变量和参数,该框架就可以自动执行完整的实验流程,无需人工干预。
实验基本流程如图1所示。
图1实验基本流程
具体步骤如下:
(1)前期准备。准备运行环境,选取被测类文件,配置EvoSuite并做调试。
(3)测试用例检查。如前所述,EvoSuite可能会生成不可编译和不稳定的测试用例,并在生成测试用例的最后阶段进行检查和处理。实验框架也包含了检查测试用例是否可用的环节,会记录并注释掉不可编译和不稳定测试。
(5)结果分析讨论。根据上述步骤获得的效果指标、生成测试套件时是否超时、生成的可用测试比例等多种数据,结合具体的被测项目,进行分析讨论。
表2实验结果汇总(部分)
(1)EvoSuite运行的稳定性。
查看所有运行结果,发现在面对13个被测类时,共有102次生成失败,占总次数的6.54%,基本情况如表3所示。
表3生成失败情况汇总
总体来看,EvoSuite在超过93%的运行中都生成了测试套件,稳定性在可接受的范围。具体分析那些生成失败的情况。对于被测类JXPATH-7,EvoSuite每次运行都未能生成任何测试用例。通过过程数据发现,每次都会出现运行时异常java.lang.VerifyError。分析异常信息,发现该异常出现在字节码插装阶段。由于EvoSuite使用字节码插装来模拟外部对象,增加了被插装方法的大小,使其超出了Java对方法字节码大小的限制(64KB)。EvoSuite在被测类OKHTTP-5、DUBBO-2和WEBMAGIC-4上也未生成任何测试用例。这是由于这些类缺失依赖项,EvoSuite会中止运行并抛出错误来通知用户。对于REDISSION项目中的被测类,大多数生成失败是由于运行超时,目前还未找到具体原因。
(3)自动生成的测试用例与手工测试用例的效果比较。
图3两类测试用例的分支覆盖率和变异分数的分布差异
以上结果表明,对于分支覆盖,EvoSuite生成的测试套件与手工测试套件的差异很小,有时候生成测试套件的分支覆盖率甚至会超过手工测试套件;但是对于变异分数,二者的差异很大。变异分数较高的测试套件更容易发现因修改代码而引入的新的错误。因此,EvoSuite生成的测试套件在揭错能力上还与手工测试套件有较大差距。
经过实验与分析讨论,这里对EvoSuite的使用和改进提出一些建议。
首先,在生成测试用例时,用户要保证被测类的依赖项都存在,否则会生成失败。其实可以使用更加灵活的策略来提高运行稳定性,例如对于那些缺失依赖的类,EvoSuite也可以在执行时不直接中止,而是忽略此类错误,并尝试利用可以找到的依赖项尽量生成不完全的测试用例。另外,当面对少数很“庞大”的方法,EvoSuite可以在字节码插装时进行一些判断,一个方案是动态识别代码易产生不稳定测试用例的部分进行插装,并监测被插装方法大小,在达到大小限制之前停止。在测试用例精简阶段,EvoSuite可以考虑其他更高效的精简方法,例如delta-调试[12]。