时间:2024-05-04
岳鑫
(四川大学计算机学院,成都610065)
单元测试是软件测试中非常基础和重要的方式,能够尽早发现程序缺陷,保障软件质量。手动单元测试是一项耗时又有挑战性的工作,自动化单元测试能有效提升测试效率、降低成本。
EvoSuite 是一款先进的JUnit 测试工具,由Gordon Fraser 和Andrea Arcuri 公开发表于2011 年[1],作为一个开源软件,源码存管于GitHub①https://github.com/EvoSuite/evosuite,并得到不断地改进和维护。基于搜索的软件测试研讨会(Search-Based Software Testing,简称SBST②http://www.searchbasedsoftwaretesting.org)从2013 年开始增加了JUnit 测试工具竞赛环节,评测当前流行的自动化工具的性能。为了提升竞赛的公正性和效率,SBST 从2017年开始实现了一个简单易用的开源框架③https://github.com/PROSRESEARCHCENTER/junitcontest来自动执行竞赛流程。近三年(2017-2019)竞赛都是EvoSuite 取得了最高分[2-4]。
EvoSuite 使用遗传算法来进化生成一组具有最优代码覆盖的Junit 测试用例[5]。它首先初始化一组由随机测试用例组成的测试套件,然后迭代地使用搜索、变异和交叉等搜索算子(searching operators)来进化它们。搜索过程中存在一个基于覆盖率的适应度函数(fitness function)来指导这种进化,例如最基本的是分支覆盖。当搜索结束后,选取具有最高代码覆盖的测试套件,并在保持覆盖率的前提下去除冗余测试用例。最后会添加回归测试断言,让生成的测试套件能够应用于回归测试场景。
作为一款有代表性的JUnit 测试工具,可以说Evo-Suite 代表了该类工具的最先进水平,那么它目前的实际表现究竟如何?本文借助SBST 会议中JUnit 测试工具竞赛所使用的框架,选取了15 个来自真实世界的Java 项目,共计130 个被测类,实证探究最新版本Evo-Suite 的性能,包括其运行稳定性,不同生成时间下生成的测试用例的可用性、覆盖率和揭错能力,以及与手工测试用例效果的差异。本研究评估了目前EvoSuite 的实际表现,并为该工具的使用和改进提供了有价值的建议,对于其他同类工具的改进和该领域内的相关研究也有借鉴意义。
实验将从以下几个方面来探究EvoSuite 的性能:
(1)EvoSuite 运行的稳定性。稳定性是指在不同的生成时间下,面对不同的被测类,该工具总是能正确运行,最终生成测试用例。
(2)生成时间对测试用例的可用性、覆盖率和揭错能力的影响。在运行EvoSuite 前,可以设定生成时间。可用性是指生成的测试用例语法正确,能够通过编译(没有不可编译测试),并能在生成之后的任意时间正确执行(没有不稳定测试)。EvoSuite 在生成一些断言时,被测类中的某些与外部因素(如系统时间、文件系统等)关联紧密的代码会被执行到,这可能导致将来执行包含这些断言的测试用例时会报错。这种测试用例叫做不稳定测试,需要特别处理。这些测试套件的覆盖率和揭错能力可以通过计算行覆盖率、分支覆盖率和变异分数这些指标来量化。已有研究表明,具有更高变异分数的测试套件也会有更好的揭错效果[6]。
(3)自动生成的测试用例与手工测试用例的效果比较。面对相同的被测类,将EvoSuite 生成的效果最好的测试用例与手工测试用例进行比较,如果覆盖率和揭错能力差异较小,则说明该工具有可能替代手工测试用例编写,以用于实际软件测试。
实验使用了当前最新版本的EvoSuite(1.0.6)④https://github.com/EvoSuite/evosuite/releases。参考之前的研究[7]及SBST 比赛中EvoSuite 的参数设置[8],禁用了使用变异分析来过滤断言的功能,因为这需要花费很长时间。除了将生成时间作为自变量,其余都保持默认配置,因为这能达到最好的效果[7]。另外需要说明的是,由于EvoSuite 在生成测试套件时包含若干个阶段:初始化、搜索、精简测试、断言生成、编译检查、移除不稳定测试[5]。因此,对于某一给定的生成时间,每个阶段的时间分配策略是:搜索阶段分配50%,其他阶段各分配10%。对于任意一个阶段,如果超时,就立即停止并保留当前的结果,执行下一个阶段。
在选取实验所用的项目时,遵循了以下原则:能够代表真实世界中的软件;考虑到获取的难易程度,一般使用开源项目;项目要尽量覆盖多个应用领域;项目中最好包含手工测试套件;项目中的代码足够复杂;程序的输入类型多样。另外,在选择项目中的被测类时,会先使用CKJM⑤http://gromit.iiar.pwr.wroc.pl/p_inf/ckjm/扩展库,利用McCabe 圈复杂度来计算所有类文件的圈复杂度,排除那些只包含圈复杂度低于3 的方法的类,保证被测类包含至少有2 个分支的方法。考虑到实验需要花费的时间,不会使用所有的类文件,而是从满足复杂度的类文件中随机采样,选取最终的被测类。使用上述方法,最终从GitHub 中选取了15 个关注度较高、使用广泛且应用领域类型丰富的Java 开源项目,经过采样后最终得到130 个被测类。具体信息如表1 所示。
表1 实验项目及被测类信息
为了方便进行实验,这里借助了SBST 公开的最新版本竞赛框架(v1.0.1)。按照使用要求,将EvoSuite 和被测类放在指定目录,并设定好必要的环境变量和参数,该框架就可以自动执行完整的实验流程,无需人工干预。
考虑到实验所需时间以及排除无关因素的影响,本实验搭建了一台安装Ubuntu 16.04.6 LTS 操作系统的虚拟机,分配3 个CPU 内核(物理主机CPU 为第八代Intel Core i5-8500B 六核处理器,3.0GHz),8G 内存,60G SSD,并且只安装必要软件。
实验基本流程如图1 所示。
图1 实验基本流程
具体步骤如下:
(1)前期准备。准备运行环境,选取被测类文件,配置EvoSuite 并做调试。
(2)测试用例生成。针对每个被测类,设置不同的生成时间,多次运行EvoSuite,每次生成一组包含若干测试用例的测试套件。参考近3 年SBST 比赛的时间设定和计算资源,本文选择10、60、120、240 秒这4 种生成时间,针对每个被测类执行3 次EvoSuite。同时,实际运行EvoSuite 可能会超过设定的生成时间,为了不强制终止其运行,允许最多有比设定的生成时间多一倍的实际运行时间。
(3)测试用例检查。如前所述,EvoSuite 可能会生成不可编译和不稳定的测试用例,并在生成测试用例的最后阶段进行检查和处理。实验框架也包含了检查测试用例是否可用的环节,会记录并注释掉不可编译和不稳定测试。
(4)计算效度指标。这里会同时计算由工具生成的经上一步骤处理后的测试套件和可用的手工测试套件的效果指标,具体来说:使用JaCoCo⑥https://github.com/EvoSuite/evosuite/releases计算测试套件的代码覆盖率,包括行覆盖和分支覆盖;使用PITest⑦http://gromit.iiar.pwr.wroc.pl/p_inf/ckjm/对这些测试套件进行变异分析。
(5)结果分析讨论。根据上述步骤获得的效果指标、生成测试套件时是否超时、生成的可用测试比例等多种数据,结合具体的被测项目,进行分析讨论。
本实验使用多种手段保证有效性。实验环境为全新搭建的虚拟环境,只安装必要的实验相关软件,保证实验过程不受其他程序影响。选择被测项目时,在条件允许的情况下,选取了尽量多的项目,并保证项目的多样性。选取被测类时,在满足复杂度的条件下,各个项目随机选取一定数量的被测类,确保整体上分布均匀。实验中使用的框架经过了近几年SBST 竞赛的使用而不断完善,实验过程基本上都是自动化执行,避免了人为干扰。在计算测试套件的覆盖率和变异分数时,使用了常用的比较成熟可靠的工具[9,10],保证计算结果的准确。
实验中,EvoSuite 共运行1560 次来生成测试用例(4 种生成时间,130 个被测类,每个被测类上运行3次)。对于每个被测类,整理并记录了不同生成时间下EvoSuite 的实际运行时间和生成的测试用例的行覆盖率、分支覆盖率和变异分数,结果汇总如表2 所示。其中“-”表示未能生成任何测试用例。这里只展示了每个项目中选取的前两个被测类的数据,完整数据可以在GitHub 仓库⑧https://github.com/HsingYue/EvoSuite-evaluation中查看。
表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 项目中的被测类,大多数生成失败是由于运行超时,目前还未找到具体原因。
(2)生成时间对测试用例的可用性、覆盖率和揭错能力的影响。
表4 显示了设定不同生成时间时,EvoSuite 实际的运行时间情况(不考虑生成失败的情况)。其中,超时次数(严格)是指超过了设定的生成时间,而超时次数(宽容)是指在原设定时间的基础上增加6 秒后,实际运行超过该时间的次数。之所以这样考虑,是因为如前面所说,EvoSuite 会将运行分成6 个阶段,连续两个阶段的运行之间还需要一定的切换时间。根据之前的研究[5],这些切换时间总共约6 秒,而目前的EvoSuite未将切换时间计入设定的分配时间中,因此考虑宽容的超时次数和超时率更合理。可以发现,设定生成时间为10 秒时,EvoSuite 极容易出现运行超时的情况,超时率(宽容)高达61.3%。当生成时间设定为60 秒及以上时,超时率(宽容)大幅下降,均不到5%。
表4 EvoSuite 实际(成功)运行时间
生成成功的测试用例中有一些不可用测试用例(包括不可编译测试和不稳定测试),占比不到1%,即EvoSuite 成功运行生成的测试用例的可用性高达99%。这是由于EvoSuite 使用了多种技术手段来避免许多不稳定测试的出现[14],又在生成测试用例的最后阶段进行检查,注释掉不可用测试,所以最终输出的测试用例中包含的不可用测试比例非常低.。之所以还会出现,一般都是由于运行超过了分配的时间而停止检查所致。例如,FREEHEP-7,JXPATH-10 和FASTJSON-4 是在一次运行中出现最多数量的不稳定测试(分别为12、14 和12 个)的被测类。对于这三个项目,测试用例生成的精简测试阶段都会超时,因此会遗留数量较多的测试用例,这可能导致更大概率出现不稳定测试。
竞赛框架最终计算得到了不同生成时间下,Evo-Suite 生成的测试套件的行覆盖率、分支覆盖率和变异分数,这些效果指标的整体变化情况(以均值表示)如图2 所示。
图2 不同生成时间下的测试套件整体效果指标
观察图2 中三个效果指标与生成时间的变化关系,明显看出代码行覆盖率、分支覆盖率和变异分数均随生成时间增长而增长,这也符合预期。从表5 中可以看出,当生成时间从10 秒增加到60 秒时,三个指标均增长了15.5%左右,但相比于10 秒时的数值,变化率非常大,尤其是变异分数,变化率高达71.8%;从60秒增加到120 秒时,三个指标均有约8%的增长,相比于10 秒到60 秒的时间变化,增长率小了很多,但增长率仍然很可观;当从120 秒增加到240 秒时,增长明显放慢,三个指标的增长量均为3.5%左右,增长率也都不超过7%。这说明在本文选择的4 个生成时间下,120 秒的时间收益较高,而继续增加生成时间虽然也会带来效果指标的提升,但要放缓许多。另外随着代码覆盖率的提高,变异分数也在提高,这也与已有研究中的经验证据一致[11]。
表5 生成时间变化时测试用例各效果指标的变化
通过上述分析可以发现,在面对大多数项目时,分配给EvoSuite 生成测试用例的时间不能太短(尤其是时间要求相对严格的情况下),至少应该有1 分钟,这样才能保证生成测试用例的每个阶段都完全执行,得到的测试用例的可用性、覆盖率和变异分数也会更好。
(3)自动生成的测试用例与手工测试用例的效果比较。
为了比较EvoSuite 生成的测试用例与手工编写的测试用例的差距,实验中也对可用的手工测试用例进行了效果指标的计算。在所选项目中,包含手工测试用例的被测类共有36 个。同时,也选择了这些被测类在生成时间为240 秒时,3 次运行EvoSuite 中某一次生成的测试用例来进行比较,因为根据前面的实验结果,此时得到的测试用例效果相对最优。图3 展示了手工测试用例和生成的测试用例的分支覆盖率(相比于行覆盖率,在逻辑上更有意义)和变异分数的分布差异。可以发现,对于分支覆盖率,EvoSuite 生成的测试用例更为分散一些;对于变异分数,手工测试用例整体的效果要明显好于生成的测试用例了。
图3 两类测试用例的分支覆盖率和变异分数的分布差异
经过计算,对于分支覆盖率,手工测试套件的均值为66.2%,对应的生成的测试套件均值为63.4%;对于变异分数,手工测试套件的均值为61.9%,对应的生成的测试套件均值为46.7%。这里使用Vargha-Delaney A 检验方法[3]进行度量。Vargha-Delaney A 检验是一种非参数统计检验方法,可以定量的对两类测试套件的效果差异的显著性进行评估。也就是说,通过该方法可以分析生成测试套件的效果与手工测试套件的效果相比是否有明显差异,如果有的话,能达到什么程度。在这里,零假设是指生成测试与手工测试相比效果无差异;备择假设则是生成测试与手工测试相比效果有差异。在显著性水平为0.05 时,如果A12 偏离0.5 越远,表示生成测试与手工测试的效果差异越大。根据经验[11],Vargha 和Delaney 建议,A12 大于0.64 或小于0.36 时,表示差异达到中等程度,如果大于0.71 或小于0.29,表示差异很大。基于之前的分析,这里选取生成时间为240 秒的分支覆盖和变异分数,因为在这个时间点,二者的值都趋于稳定。在实验中,对于分支覆盖率,生成的测试套件与手工测试套件的A12 为0.45,p为0.367,无重大差异;对于变异分数,生成测试套件与手工测试套件的A12 为0.31,p 值小于0.001,差异很大,手工测试套件的变异分数优于生成测试套件很多。
以上结果表明,对于分支覆盖,EvoSuite 生成的测试套件与手工测试套件的差异很小,有时候生成测试套件的分支覆盖率甚至会超过手工测试套件;但是对于变异分数,二者的差异很大。变异分数较高的测试套件更容易发现因修改代码而引入的新的错误。因此,EvoSuite 生成的测试套件在揭错能力上还与手工测试套件有较大差距。
经过实验与分析讨论,这里对EvoSuite 的使用和改进提出一些建议。
首先,在生成测试用例时,用户要保证被测类的依赖项都存在,否则会生成失败。其实可以使用更加灵活的策略来提高运行稳定性,例如对于那些缺失依赖的类,EvoSuite 也可以在执行时不直接中止,而是忽略此类错误,并尝试利用可以找到的依赖项尽量生成不完全的测试用例。另外,当面对少数很“庞大”的方法,EvoSuite 可以在字节码插装时进行一些判断,一个方案是动态识别代码易产生不稳定测试用例的部分进行插装,并监测被插装方法大小,在达到大小限制之前停止。在测试用例精简阶段,EvoSuite 可以考虑其他更高效的精简方法,例如delta-调试[12]。
然后,在实际使用EvoSuite 时,最优的时间选择是120 秒,能在相对较短的时间内获得效果相对很好的测试套件;如果时间比较紧迫,想在较短的时间内生成效果最好的测试套件,那就尽量让生成时间达到60 秒或以上;如果在特殊情况下时间过于紧迫,生成时间达不到60 秒,那就分配尽量长的时间;如果时间充裕,可以选择240 秒,能得到接近最优的测试套件;如果时间非常充裕,也可以设定生成时间在240 秒以上,虽然说生成时间越长,得到的单元测试套件效果越好,但此时生成时间增加带来的效果提升非常有限。另外,在设定小于60 秒这样较短的生成时间时,EvoSuite 容易超时。为保证正常生成测试套件,即使超时也尽量等待程序自己运行结束,而不要外部强制终止。考虑到超时问题,还是建议选取120 秒左右的生成时间。
最后,如果只是想在较短时间内生成具有较高行覆盖率和分支覆盖率的测试套件,EvoSuite 足够胜任,甚至比开发人员做得更好。但考虑到揭错潜力,这些生成的测试套件还远不能替代手工测试套件,而考虑到EvoSuite 生成的测试用例是应用于回归场景的,这点尤为重要了。
关于EvoSuite 的研究,还有很多值得进一步做的工作。对于EvoSuite 的使用,可以设定更多、更细粒度的生成时间来找到更精确的最优时间。在数据集上,可以选取更大样本的被测类,如果有条件,甚至可以与企业合作,在真实的商业软件上使用EvoSuite。这样可以增加实验的外部效度,使结论的适用范围更加广泛。对于实验中发现的一些问题,EvoSuite 还需继续完善,例如说,以更聪明的方式进行字节码插装;生成测试时使用更高效的精简测试的算法;进一步提升生成测试的变异分数,减少与手工测试的效果差异等。
我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自各大过期杂志,内容仅供学习参考,不准确地方联系删除处理!