摘要:引用:Henry Coles, Thomas Laurent, Christopher Henard, Mike Papadakis, & Anthony Ventresque. (2016). PIT: a practical mutat...
引用:
Henry Coles, Thomas Laurent, Christopher Henard, Mike Papadakis, & Anthony Ventresque. (2016). PIT: a practical mutation testing tool for Java (demo). International Symposium.
摘要
变异测试引入了人为缺陷以衡量测试的充分性。如果候选的测试可以将变异体的行为与原始程序的行为区分开则认为它们的质量很高,否则开发人员需要设计新的测试。尽管这种方法已经被证明是有效的,但由于所需要的数量庞大的变异体和测试执行,工业规模的代码对它的适用性提出了挑战。在本文中我们介绍了 PIT,这是一种用于 Java 的适用变异测试工具,适用于真实的代码库。由于 PIT 对字节码进行操作并优化了变量执行,因此它的速度很快。PIT 也很健壮并且和开发工具能很好地集成在一起,因为它可以通过命令行界面、Ant 或 Maven 调用。PIT 也是开源的,因此可以在http://pitest.org/上公开获取。
关键词
变异测试,PIT,自动化工具
1 引言
软件测试目的在于训练软件的行为、明确软件的正常(预期)行为并且展现出表明存在错误的异常行为(当测试无法正常运行时)。相反地,如果测试通过了,则假定程序具有符合测试期望的行为。由于其简单性和实用性,软件测试已成为业界主要的软件质量保证技术之一。但是,充分地测量测试质量是困难的。研究人员提出了几种度量标准,其中大多数度量标准都依赖于代码覆盖率的概念,该概念描述了测试覆盖(即仅执行)了多少源代码。覆盖率指标指示着覆盖率越高越好。这个概念虽然得到广泛应用,但有一个主要缺点。它仅检查行或者指令是否经过测试,但不检查通过测试的有多好。
变异测试是一种可以更好地了解测试对被分析程序执行情况的技术。变异会以少量代码修改的形式引入缺陷,当通过测试进行执行时,这些缺陷会导致异常行为。如果测试未能暴露出缺陷,则测试人员或开发人员可以合理地推断出该测试并未检查每种可能的行为,并且需要对其进行改进。
本文介绍了 PIT,一种用于 Java 的变异测试系统。PIT 的速度相当快,因为它可以处理字节码,并且只运行有机会杀死使用过的变异体的测试(即执行该变异体所在的指令的测试)。PIT 的主要优势在于它功能强健、易于使用并且与开发工具很好地集成在一起。本文旨在描述该工具及其在支持的变异体上的最新改进。PIT 的早期版本具有有限的算子集,我们将其扩展为所谓的扩展的变异算子集。该集合被展示以提高变异过程的效率,并且对执行时间的影响有限。而且扩展的算子集符合变异测试员的标准和信念,因此使得该工具更加支持将来的研究。
2 变异测试
变异分析会产生正在分析的程序的几种变体,称为变异体。变体是根据称为变异算子的简单语法规则创建的,该规则会转换程序的语法,例如将表达式“ a + b”转换为“ ab”。通过观察和比较未变异和变异程序的运行时行为,变异体可以用来衡量我们的测试的质量。这是根据程序输出执行的,因此,变异会衡量测试将句法程序的更改投射到其行为的能力,即识别语义差异。当变异体表现出行为差异时,它们被称为被杀死了。而当变异体没有行为差异时,它们被称为存活着。变异测试是指使用变异分析作为量化测试过程彻底程度的一种方法。因此,它测量了被杀死的变异体的数量,并计算了它们在变异体总数中的比率。该比率代表充足性度量标准,被称为变异得分。
变异被证明在缺陷显示和模拟实际缺陷的行为方面非常有效。但是实际上,变异对其使用的下层突变很敏感。换句话说,已实现的算子集对技术的可扩展性和高效性产生了重大的影响。因此必须为变异工具配备一套全面的变异体,这些变异体可以充分测量测试的完全性,同时又很实用。先前的研究提出将变异算子限制为一个被我们称作扩展集的小集合,并在以下部分中对其进行描述。
最受欢迎的 Java 变异测试工具是 MuJava 和 Major。不幸的是这些工具是为支持研究项目而构建的,因此其实际使用受到限制。PIT 具有以下三个优点:a)它是开源的,b)由于它提供了 Maven 插件,因此与开发工具很好地集成在一起,c)它的稳定性非常的健壮并得到了积极的维护(在 Java 的最新版本上运行)。有关工具的详细信息,请参见 Delahaye 的工作。
3 PIT:真实的变异
PIT 是用于 Java 的变异测试框架,以支持实际代码库上的日常开发。这意味着 PIT 旨在:
- 与构建工具(例如 Maven,Ant,Gradle)、集成开发环境(IDE 例如 Eclipse 或 IntelliJ)和静态代码分析工具(例如 SonarQube)有着良好的集成。
- 快速。PIT 使用三种技术来获得结果:处理字节码而不是源代码、选择要针对变异体运行的测试、并最小化变异体执行的次数。
- 明确报告测试执行情况。通过高亮未被杀死的变异体使得在源代码和变异体之间的导航变得容易。
3.1 运行 PIT
PIT 已完全集成到各种构建工具、IDE 和静态代码分析工具中。因此,当使用这些通用工具之一时,不需要额外的努力去获取 PIT。
要将 PIT 与 Ant 或 Maven 结合使用,我们需要向其构建文件中添加任务(或插件)以便配置 PIT 的行为。PIT 的配置很简单,而且可以限制为我们要测试的特定类。我们还可以配置输出目录(用于测试报告)。许多其他参数也被提供了(例如变异算子,超时来推断遇到了无限循环)。PIT 的扩展版本提供了一个附加选项: 测试矩阵的生成。该矩阵为每个变异体报告了是哪个测试杀死它的。为了避免归因于变异体和/或重复变异体的实验偏差,这一点尤其重要。
一旦 PIT 任务被配置了,就可以使用构建工具而无需任何其他考虑。这意味着 PIT 不会更改工作流程,它将进行处理并生成报告而不会受到任何用户的干扰。该工具既不会留下任何工件,也不会更改已编译代码中的任何内容(变异体仅被 PIT 使用)。
3.2 变异体的生成和执行
PIT 通过字节码操作生成变异体。与编译变异文件相比,此方法提供了显着的性能优势,因为它实际上将变异产生的成本降低到了零。而且,PIT 避免了输入输出操作并降低了内存开销。变异体的字节码表示不需要在磁盘上写入任何程序,而是将其保留在内存中(为减少内存开销,一次仅将一个变异体保留在内存中)。
变异体的产生是两个阶段的过程。初始扫描在主控制过程中被执行。检查被测系统中的所有类别,可能的变异点(称为 MutationIdentifiers)并将被检查和存储在内存中。变异的字节码实际上是由该扫描过程生成的。但它被立即被丢弃,只有 MutationIdentifiers 被存储。
一个 MutationIdentifier 由变异的精确定位和变异算子的名称组成。该定位由方法和类的名称、方法签名以及发生变异的指令指定。这些很少的信息足以重建每个变异体。 因此,可以通过主要过程将数百万个变异体的描述保存在内存中。
为了通过对每个变异体运行测试来评估每个变异体,子 JVM 进程将会被创建。MutationIdentifier 和要针对该变异体运行的所选测试的名称由控制过程传递给子级。然后,将在子进程内生成变异字节码,并使用 Java 工具 API 将其插入到运行的 JVM 中。
创建 JVM 是一项代价很大的操作,因此 PIT 会尽量减少所创建的数量。尽管有着一个单一的子进程使 JVM 可以用于评估所有变异体,对变异体进行测试的过程可能会使 JVM 的状态不同于刚启动的 JVM(例如值可以被设置在静态变量中或者 JVM 的内存可能不足)。这可能会在运行其他变异体时影响测试结果。因此需要在性能和隔离状态之间进行权衡。在默认情况下,PIT 启动新的 JVM 来评估与每个类相关的变量。因此它提供了强有力的保证,即不同类别的变异体之间不会有干扰,但不能保证同一类别的变异体不会相互干扰。PIT 可以被配置来以性能为代价提供更强的保证(最多包括每个变异启动 JVM)。
3.3 变异算子的扩展
在当前版本中,PIT 支持少量的变异算子,其目的是限制变异体的数量和执行时间。但是如先前的一些研究所指出的那样,存在产生一组低质量变异体的风险。我们最近提出了扩展变异算子的列表以提高该工具的高效性。下一节介绍了两组变异体的结果。表 1 列出了所有基本的变异算子(在当前版本的 PIT 中)以及扩展列表中的那些算子。请注意,当前发布的 PIT 每个关系算子只有一个变异算子:即 PIT 将<变异为<=,而还有其他的可能情况如<→>,<→=>,这种由变异算子的扩展列表负责处理。
表 1:PIT 的基础变异算子
3.4 变异报告
PIT 生成的 HTML 报告使用颜色代码显示行覆盖率和变异得分。请参见图 1,该图显示了示例应用程序上的报告。浅绿色的行对应于没有生成任何变异体的代码覆盖范围:本示例中的第 11 行。深绿色对应于已执行测试且未通过的行(这意味着变异体被杀死):这表示变异覆盖范围,例如示例中的第 9、12 和 13 行。浅粉色显示没有代码覆盖的行(超出测试范围):示例中的第 4 行。深粉红色用于显示指示有关哪些变异体已生成且未被测试(存活的变异体)检测到:第 8 行。
图 1:PIT 的输出示例
在每个行号旁边,嵌入内部 Web 链接的另一个数字给出了为该行生成的变异体的数量。例如在图 1 中,我们可以看到有 6 个变异体在第 8 行被生成了(深粉红色)。单击链接将焦点移到变异体列表上(位于 HTML 页面的底部),其中的颜色代码显示为第 8 行生成的 6 个变异体中有 3 个被杀死(深绿色),有 3 个幸存了(深粉红色)。
4 演示结果
为了证明 PIT 的适用性和性能,我们选择了 5 个 Java 项目(记录在表 3 中),这些项目经常在学术评估中被使用。表 2 记录了:版本、代码行(使用 JavaNCSS 工具计算),类的数量(对于那些存在着测试套件的类)和测试的数量。Jodatime 是一个日期和时间处理库。Jfreechart 是一个流行的用于创建图表和绘图的库。Jaxen 是用于评估 XPath 表达式的引擎。Commonslang 为 Java 的 commons 类提供了一组工具方法。最后,commonscollections 是一组用于 Java 的数据结构。
表 3: 测试程序、代码行(LoC)、类的数量和测试用例数
表 2: PIT 的扩展变异算子列表
表 4 和表 5 记录了从选定程序获得的结果。这些证明 PIT 适用于真实的程序,并且扩展的算子集是可行和实用的。该结果还表明当使用两组集合时结果有所不同,说明在可能的情况下有使用扩展的变异体的需要。
表 4:基本算子和扩展算子列表的变异体数量、可杀死的和变异的评分(MS)
表 5:基本算子和扩展算子列表的执行时间(以秒为单位)
5 结论
本文介绍了 PIT,一种用于 Java 的变异测试工具。PIT 是一款功能强大且易于使用的变异测试工具。它非常流行并且已经被广泛使用。总的来说,PIT 具有以下主要优点:它同时支持 Ant 和 Maven,因此与开发工具很好地集成在一起;它是开源的,并被积极地维护;它具有可扩展性,并支持符合当前变异测试研究实践的变异算子。
致谢
本文由南京大学软件学院 2020 级硕士生唐昊杰翻译转述。
感谢国家重点研发计划(2018YFB1003900)和国家自然科学基金(61832009,61932012)支持!