杨正卉,洪 玫,郭 丹,王 潇,刘 芳,黄小丹
(四川大学 计算机学院(软件学院),成都 610065)
软件测试是发现软件缺陷的过程,是保障软件质量的重要手段.由于被测系统的复杂性以及测试成本的限制,软件测试自动化已经成为必然.随着测试技术的进一步发展,测试流程的不断规范,大量的单元测试用例生成工具涌现.这些工具以覆盖率作为测试标准.有研究者发现,测试用例的覆盖率是越高,捕获代码缺陷的可能性就越大.研究自动化单元测试用例生成工具生成的测试用例的覆盖率和检错率具有重要意义,直接影响着这些测试工具是否能在业界发挥更好的作用.在国际上的一些单元测试工具竞赛中[1-6],Evosuite多次获得第一名;在2019年的竞赛中,Randoop 工具和手工编写的测试用例被作为其余工具对比的基线.因此,本文选择Evosuite,Randoop 两个具有代表性的工具为例,采用了2016年单元测试工具竞赛中使用的数据集:Defects4J,研究自动化单元测试工具的性能,拟回答以下问题:
(1)在不同的生成时间下,Evosuite,Randoop 生成的测试用例在数量上、方法覆盖率、分支覆盖率等方面的表现如何?
(2)哪些因素影响了Evosuite、Randoop 工具生成的测试用例的覆盖率?
为了分析自动化测试用例生成工具的性能,Fraser等进行了手工测试和自动化测试的对比实验,邀请49 名参与者,从多个方面分析了手工测试用例和自动化工具Evosuite 所生成的测试用例的特定[7].Shamshiri等在Defects4J 数据集上研究了Evosuite、Randoop 工具,以及商用工具AgitarOne 的性能,回答了如何使自动化单元测试工具能够查找到更多的缺陷的问题[8].Almasi 等使用工业界的项目——LifeCalc 对Evosuite和Randoop 工具的有效性进行了评估[9].Kotelyanskii等对基于搜索的单元测试用例工具Evosuite 的参数调优进行了研究,其研究结果为本文提供了参考[10].在这些研究中,主要关注的是工具的缺陷查找能力,但作为自动化单元测试,更重要的是其对代码的覆盖能力,David Schuler 确定了覆盖率是判断测试用例质量的一个可靠指标,覆盖率越高,缺陷查找率可能越高[11].本文详细分析了工具生成的测试用例对源代码的方法和分支的覆盖率,进一步探索了工具的特点和存在的缺陷.
为了回答上述研究问题,本文设计了比较实验,实验过程如图1所示.应用自动化单元测试用例生成工具Evosuite 和Randoop,在Defects4J 数据集上,针对不同的被测软件,分别生成测试用例.工具生成测试用例的时间分别设置为10 s,20 s,30 s,60 s,120 s,180 s,300 s.由于Evosuite 和Randoop 都是随机生成测试用例,每次产生的结果有一定随机性.因此,在实验设计上,同一生成时间重复运行Evosuite 和Randoop 3 次,并以3 次运行结果的平均值作为实验分析数据.
Randoop[12]是基于反馈的面向对象单元测试的随机测试用例生成工具,对于被测类,Randoop 创建一系列方法调用和构造函数,依次创建和更改对象状态.执行所有创建的序列,并用执行的生成来创建捕获系统行为的断言.Randoop 的输入是一组要测试的Java 类,一个时间限制和一组可选的规范检查器,生成是一组对应的JUnit 测试用例.Randoop 的随机种子在缺省情况下为0,对于相同被测类,每次运行时需更改随机种子的值.另外,Randoop 工具在生成过程中易产生片状测试,导致Randoop 工具无法生成更多的测试用例.为了解决该问题,需将flaky-test-behavior 参数设置为OUTPUT.
Evosuite[13]是基于搜索的单元测试用例生成工具,该工具被集成于Eclipse,IntelliJ,Maven 等环境下,可以生成Java 类的JUnit 测试用例.该工具采用遗传算法,从一组随机测试用例开始,迭代地应用选择、突变和交叉等搜索操作符来进化测试用例,进化由基于覆盖标准的适应度函数指导,一旦搜索结束,测试用例就会被最小化,并添加测试断言.在本实验中,除了禁止Evosuite 使用单独的类加载器,更改生成时间以外,其余均采用默认配置.
图1 实验过程设计
实验被测软件采用Defects4j 数据集[14]中的5 个开源项目的多个版本.这些项目版本中有357 个真实缺陷:JFreeChart (26 个缺陷)、Google Closure compiler(133 个缺陷)、Apache Commons Lang (65 个缺陷)、Apache Commons Math (106 个缺陷) 和Joda Time(27 个缺陷).Apache Commons Lang 是处理Java 基本类方法的工具类包,弥补了Java.Lang API 基本处理方法上的不足;Apache Commons Math 是轻量级自容器的数学和统计计算方法类包,包含大多数常用的数值算法,外部依赖较少.JFreeChart 是为Applications,Applets,Servlets 以及JSP 等使用所设计的,可生成多种图表,产生PNG 和JPEG 格式的生成.对于项目版本的每一个缺陷,Defects4j 提供了:(1)该缺陷对应的缺陷版本和已修复版本;(2)开发人员手工编写的可以揭露缺陷的测试用例;(3)缺陷类的列表.
本实验选择JFreeChart,Apache Commons Lang,Apache Commons Math 作为被测软件,既满足了多样性的需求,也保证了所选择的软件的代表性.实验将3 个软件每个版本中已修复缺陷的类作为被测类.当某个类在缺陷类列表中多次出现时,使用该类第一次出现缺陷时对应的已修复版本.本实验共筛选出71 个被测类.
本实验在Windows 64 位操作系统下采用SUN Java 8 SE,Eclipse Europa IDE 编译被测项目.在JUnit 4 框架下运行测试用例,使用EclEmma 插件计算测试用例覆盖率.编程自动统计覆盖率结果,并采用Excel工具对结果进行分析.
(1)编译被测类
Apache Commons Lang 和Apache Commons Math是Maven 构建的项目,编译该项目时,需确保Eclipse中已配置Maven 环境.
(2)测试用例生成
在命令行中使用Evosuite-1.0.5.jar 和Randoop-4.0.4 生成测试用例.在生成测试用例时,生成时间设为:10 s,20 s,30 s,60 s,120 s,180 s,300 s.Evosuite 和Randoop 工具分别在每个生成时间下运行3 次.每次运行时,Evosuite 参数保持不变.Randoop 随机种子分别设置为123,234,456,其余参数保持不变.
(3)测试用例执行与覆盖率计算
测试用例在Eclipse 中使用JUnit 4 框架分别执行.测试覆盖率由eclEMMA 插件统计,结果在测试用例执行结束后得到.Randoop 生成的测试用例分为回归测试用例和揭错测试用例,这两种测试用例都应被执行.考虑测试的稳定性,若测试用例不能执行或执行中断,则丢弃该测试用例.
本实验从以下几个角度统计覆盖率结果:
① 以类为单位,统计Evosuite 和Randoop 测试用例对被测类的方法覆盖率和分支覆盖率.
② 以方法为单位,统计Evosuite 和Randoop 测试用例在每个方法中的分支覆盖率.
(4)实验结果处理
实验中存在无法生成测试用例或测试用例无法正常运行的情况,比如个别类或者方法不存在分支,此时默认分支覆盖率为100%.
(1) Evosuite 和Randoop 工具生成的测试用例数量.
图2展示了Evosuite 和Randoop 工具生成的测试用例数量与生成时间的关系.无论生成时间如何设置,Evosuite 工具生成的测试用例数量相对稳定.Randoop工具生成的测试用例数量随着生成时间的增加而增加.当生成时间为60 s 时,明显看出Randoop 测试用例数量多于Evosuite.图3展示了在不同项目中 Evosuite和Randoop 生成的测试用例数量对比,当生成时间为10 s,20 s 时,Randoop 生成的测试用例数量依旧高于Evosuite.因此在所有生成时间设置下,Randoop 工具生成的测试用例数量都多于Evosuite.因为Evosuite 工具会对生成的测试用例进行最小化操作,保留符合覆盖率标准的测试用例.而Randoop 工具则输出生成的所有测试用例.
(2) Evosuite 和Randoop 工具生成的测试用例的方法覆盖率.
图4展示了Evosuite 和Randoop 工具生成测试用例对被测类的方法覆盖率与分支覆盖率.在3 个被测软件中,Evosuite 测试用例的方法覆盖率随着生成时间的增加而增加.Randoop 测试用例的方法覆盖率在生成时间为10~120 s 时存在递增现象,并在120 s 时达到最大值.在Chart 项目中,当生成时间为300 s 时,Randoop 测试用例的方法覆盖率有所降低;在Lang 项目上,当生成时间超过120 s 后,Randoop 测试用例的方法覆盖率保持平稳;对于Math 项目,当生成时间超过120 s 后,Randoop 测试用例的方法覆盖率逐渐降低.当且仅当生成时间为10 s 时,对于Chart 项目,Randoop测试用例的方法覆盖率高于Evosuite.当生成时间达到20 s 后,无论在哪个项目上,Evosuite 测试用例的方法覆盖率都高于Randoop.总体来看,Evosuite 测试用例的方法覆盖率高于Randoop.
图2 Evosuite 和Randoop 工具生成的测试用例数量与生成时间的关系
图3 在不同的项目中,Evosuite 和Randoop 生成的测试用例数量对比
图4 Evosuite 和Randoop 测试用例对被测类的方法覆盖率与分支覆盖率比较
(3) EvosuiteRandoop 工具生成的测试用对被测类的分支覆盖率.
在Chart 和Math 项目中,Evosuite 工具生成的测试用例对被测类的分支覆盖率随着生成时间的增加逐渐上升.在Lang 项目中,当生成时间为30 s 时,Evosuite工具生成的测试用例的分支覆盖率较生成时间比20 s 时略有降低.但在其余生成时间上,Evosuite 工具生成的测试用例的分支覆盖率依旧随着生成时间的增加而增加.当生成时间为10~120 s 时,Randoop 工具生成的测试用例的分支覆盖率随着生成时间的增加而增加.当生成时间达到120 s 后,在Chart 和Lang 项目上,Randoop 工具生成的测试用例的分支覆盖率达到最优值,并保持稳定.在Math 项目上,Randoop 工具生成的测试用例的分支覆盖率逐渐下降.当且仅当生成时间为10 s 时,在Chart 项目上Randoop 工具生成的测试用例分支覆盖率与Evosuite 工具生成的测试用例分支覆盖率相当,当生成时间达到20 s 后,Evosuite 测试用例的分支覆盖率高于Randoop 测试用例.整体来看,Evosuite 对被测类的分支覆盖率同样优于Randoop.
(4) 影响Evosuite 对被测类的分支覆盖率高于Randoop 的因素.
从上述结论可知,Evosuite 对被测类方法覆盖率高于Randoop.原因之一是Randoop 工具的随机性,不能覆盖部分方法,就不能覆盖到方法中的分支,而Evosuite可实现更多的方法覆盖,可覆盖到方法中的分支.图5展示了同时被Evosuite 和Randoop 测试用例覆盖的方法的分支覆盖率.从图5可见,当Evosuite 和Randoop同时覆盖某个方法时,Evosuite 测试用例对该方法的分支覆盖程度高于Randoop.因此,Evosuite 对被测类分支覆盖率高于Randoop 不仅因为Evosuite 覆盖了更多的方法,还因为Evosuite 测试用例较Randoop 可以覆盖到方法中的更多分支.
图5 同时被Evosuite 和Randoop 测试用例覆盖的方法的分支覆盖率
通过分析被测软件,发现软件版本中存在抽象类,而Randoop 工具在执行时,会忽略这些抽象类以及接口.所以,即使抽象类中存在着具体的方法(例如:Math_12,Lang_15 等),Randoop 工具也没有对这些方法生成测试用例.可以认定,被测软件中存在的抽象类是影响Randoop 工具生成的测试用例覆盖率的因素之一.在Randoop 工具改进中,建议考虑对抽象类的处理.
图6展示了Randoop 工具所生成的一个测试用例的样例.在执行该用例时,发现深色标注的代码未执行,从而未覆盖该代码所调用的被测方法.从这一测试用例中可以看出,Randoop 在生成测试用例时,随机模拟被测方法中所需要的参数来调用方法.当Randoop 工具不确定测试代码是否能成功执行时,将会以trycatch 语句包裹该代码.实验证明,若测试用例中某些语句被try-catch 包裹,则该语句基本无法正常执行,从而导致无法覆盖其中被调用的被测方法.因为Randoop不能成功模拟调用该被测方法所需要的参数.Randoop的设计者也提到:目前Randoop 对于参数的选择是随机的.因此,有效模拟被测方法所需参数是影响Randoop覆盖率的因素之一.当Randoop 需要模拟某些参数来调用方法时,可以依照上下文关系获取该参数的值,而不是重新定义随机值[15].
在图4中,当生成时间足够长时,Evosuite 测试用例的方法覆盖率达到90%以上,方法覆盖程度并不是影响该工具分支覆盖率的重要因素.图7展示了 Evosuite工具对某被测模块的覆盖情况的样例,Evosuite 工具所生成的测试用例虽然覆盖了该方法,但当方法中出现分支时,Evosuite 无法覆盖该分支中所包含的语句,该分支的运行依赖于其他方法的执行结果.因此,Evosuite工具对被测方法相关的依赖方法结果的构造是影响分支覆盖的因素之一.
图6 Randoop 工具生成的一个测试用例样例
图7 Evosuite 工具对某被测模块的覆盖情况的样例
内部有效性,Evosuite 和Randoop 工具都具有一定的随机性,若要评估工具测试用例的覆盖率,应生成尽可能多的测试用例.本实验在多个项目、多个版本、多个生成时间上分别生成3 组测试用例,实验数据具有一定的统计意义.在实验过程中,Randoop 工具每次运行所生成的测试用例的方法覆盖率和分支覆盖率波动较小;Evosuite 覆盖率波动情况较Randoop 更大,若以工具的最优值作为衡量标准,Evosuite 测试用例的覆盖率结果将更优.但就Evosuite 运行情况来看,采用最小值、最大值、平均值完整体现工具的真实情况,衡量工具覆盖率更为合理.
外部有效性,从实验结果来看,Evosuite 工具生成的测试用例的方法覆盖率和分支覆盖率随着生成时间的增加而增加.Randoop 工具在生成时间达到120 s 后,对于不同的项目方法覆盖率和分支覆盖率具有不同的特征.但Lang 和Math 项目是Apache Commons 中的基础工具包,使用频率较高,Chart 项目充分体现了绘图程序的基本特征,因此,本实验选择的被测项目既满足了多样性的需求,也保证了所选择的软件的代表性.
本实验通过对Evosuite 和Randoop 工具所生成的测试用例的覆盖率分析发现:① 即使生成时间低至10 s,Radnoop 所生成的测试用例数量依旧高于Evosuite.并且当生成时间到达20 s 后,单元测试工具Evosuite生成的测试用例的方法覆盖率和分支覆盖率覆盖率高于Randoop.② 影响Randoop 工具覆盖率的原因包含如何处理抽象类以及如何模拟被测方法所需要的参数等.Evosuite 工具提高覆盖率的关键问题是如何构造与被测方法相关的依赖方法结果,以保证能够覆盖更多的分支.在未来的工作中,可以研究是否可以采用符号化执行与随机测试相结合的方法提高Randoop 工具的参数模拟能力.