[论文解读] Relating Code Coverage, Mutation Score and Test Suite Reducibility to Defect Density

Relating Code Coverage, Mutation Score and Test Suite Reducibility to Defect Density

简介

论文标题

  • Relating Code Coverage, Mutation Score and Test Suite Reducibility to Defect Density
  • 代码覆盖率、突变分数和测试套件可缩减性与缺陷密度的关系
  • 2016

摘要

评估现有测试套件的总体质量(对于特定目的的充分性)是一项复杂的任务。它们的代码覆盖率对于这个目的来说是一个简单而强大的属性,因此突变分析的额外好处可能并不总是证明相对较高的计算成本和复杂性是合理的。突变测试方法和工具慢慢开始达到成熟度水平,在这个水平上,它们在日常工业实践中的使用成为可能,然而,在哪些情况下,它们为测试套件的各种质量属性提供了额外的洞察力,目前还不完全清楚。本文报告了一个在四个开源系统的测试套件上进行的实验,从代码覆盖率、突变得分和测试套件的可还原性(缩减的测试套件会降低测试充分性)的角度对它们进行了比较。比较的目的是找出不同属性何时提供关于缺陷密度的额外洞察力,缺陷密度是用于估计真实故障的单独计算的属性。我们证明,在某些情况下,代码覆盖率可能是预期缺陷密度的充分指示器,但在大多数情况下,突变和可还原性更好

介绍

测试用例充分性的度量和预测是测试实践者和研究人员之间经常争论的话题。一组特定测试需求的充分性级别可能被视为底层测试套件的质量指示器,因此它非常重要。在白盒测试中,一个简单但非常有用的质量属性是代码覆盖率,但是也表明代码覆盖率并不总是足以预测测试套件的缺陷检测能力[1]。作为一种基于故障的技术,突变分析[2]已经被引入,希望在这方面提供更多的洞察力。然而,在哪些情况下,突变分析将提供发现真正错误的能力的更可靠的估计,或者实际上,何时代码覆盖本身是足够好的估计器,仍然是未知的。此外,突变分析仍然被广泛认为是一项非常昂贵的技术,在工业实践中更广泛地采用它还有很多障碍[2],[3]。

与此同时,业界对更可靠的突变检测方法和工具的需求正在增长,幸运的是,有很有希望的趋势来满足这一需求[4]。例如,在敏捷高保证方法论中,Binder提出了Done的3向覆盖定义,其中包括代码覆盖、来自静态分析器的干净报告和最小突变得分[5]。此外,还有一些变异工具在工业项目中也被证明是有用的,而不仅仅是在研究实验室中,例如PIT工具[6]。

然而,测试社区仍然需要找出在什么情况下突变测试是值得努力的。我们还可以走得更远:除了代码复盖率和突变分数之外,我们是否可以使用进一步的充分性标准来更好地预测测试套件在查找真正故障方面的有效性?我们的测试套件评估方法[7]、[8]、[9]试图涉及不同的充分性标准,并将它们组合成一个模型来描述测试套件的不同质量方面。该模型目前主要依赖于基于代码覆盖率的信息,但额外标准的参与是一项持续的工作。

本文是我们在四个非平凡开源系统的测试套件上进行的一次实验中,不同测试套件充分性准则组合使用的早期报告。目标是将标准与为项目报告的实际故障进行比较。对于标准,我们使用了代码覆盖率、突变分数和测试套件可缩减性。对于后者,我们考虑了在不降低代码覆盖率和突变分数的情况下可以减少测试套件的数量。为了估计系统中的实际故障,我们使用了从各自的缺陷报告系统计算的缺陷密度度量(作为每个系统大小的确认缺陷数量)。在这一阶段,我们使用了两个带有手动验证突变体的突变操作符。我们的结果是有希望的:我们发现有迹象表明,代码覆盖率本身很少是缺陷预期数量(因此测试套件质量)的良好指示器,而其他两个方面提供了有用的额外洞察力。

论文的下一部分详细介绍了研究的目标和我们使用的方法,而第三部分则报告了结果。在第四节中,我们概述了相关工作,然后在第五节中结束。

目标和方法

我们的长期目标是研究涉及各种测试充分性标准的测试用例质量评估方法,并在现有的测试用例上进行验证。我们的目标是比较测试套件,找出它们之间的差异,并找到这些差异与其他指标的关系,最重要的是测试套件发现真正故障的能力。

特别是,我们的问题是,在评估和比较测试套件期间,突变分析(根据突变得分)何时为代码覆盖率提供了额外的信息。此外,我们还测试了测试套件的简约性,这个概念用于描述在不降低给定充分性标准(如代码覆盖率或突变分数)的情况下,测试套件可以缩减到什么程度,或者-从不同的角度-给定测试套件的缩减子集,测试套件的充分性比整个测试套件的充分性差多少。如果测试套件的一小部分缩减部分不够用–或者仅仅比完整的测试套件稍差一点,我们就说测试套件比相反的情况更可精简(更冗余)。在我们的研究中,我们的问题是可还原性是否在代码覆盖率和突变得分上提供了额外的洞察力。

我们将这三个标准与有问题的项目的缺陷密度DD[10]进行比较,其本质上被视为实际测试套件质量的代理。缺陷密度表示为系统中确认的缺陷数量除以系统大小,通常用于表征项目或过程本身。我们将此概念应用于发布后的缺陷来表征测试套件:我们使用这样的假设:如果在项目中使用质量较低的测试套件,则在测试过程中会发现较少的缺陷,因此在发布后会观察到较高的缺陷密度。显然,其他方面可能会影响实际的DD值,不仅是测试套件质量,我们目前也依赖于这个估计。

[论文解读] Relating Code Coverage, Mutation Score and Test Suite Reducibility to Defect Density

下面将对测量这些视点的方法进行综述。图1显示了在我们的实验中使用右侧视点的整个过程。通过运行和分析源代码和测试,可以直接计算代码覆盖率、突变分数和测试可还原性。另一方面,我们从项目问题管理历史中考虑缺陷密度,这在比较中作为单独的维度。

a)覆盖率:测试覆盖率是一个基本指标,在测试套评估过程中很容易衡量和理解。我们的观点是,任何更深层次的分析都需要根据覆盖率进行解释,因为低测试覆盖率会影响许多其他因素:例如,如果突变没有被测试用例覆盖,它可能永远不会被杀死。我们将主要根据其他因素如何扩展这一充分性标准来调查代码覆盖率

b)突变分析:我们的突变分析方法是随机抽取有限数量的突变点,并通过源代码注释和基于脚本的代码后处理来手动实现突变。我们没有使用突变工具,所以我们可以手动控制和验证突变的产生和分析过程。我们还将变异操作符限制为两个。我们选择了可能会被测试套件杀死的变异操作符。一些作者将类似的操作符称为“琐碎”操作符,然而,我们认为,如果测试套件在覆盖这些突变体的同时没有杀死它们,这可能表明存在严重的缺陷。我们选择这两个操作符,以便第一个操作符主要干扰程序的数据流,而另一个操作符影响控制流。王和马图尔[11]也提出了类似的方法,他们也主张非常少的操作符。我们定义的第一个运算符在Boolean和Number返回类型(称为Return
Negation,RN)的情况下求非返回语句的结果,而第二个运算符在IF语句中求非整个条件表达式(称为IF
Negation,IN)。带注释的源代码用于生成两种类型的输出。首先,生成并运行检测代码,以测量每个突变点的覆盖范围。其次,生成并运行所有突变体以获得测试结果。基于覆盖数据和测试结果,可以进行详细的突变分析。然而,为了计算突变得分,我们需要处理等价的突变体。幸运的是,在人工调查中,我们只发现了几个同等的突变体。

c)测试套件缩减:测试套件缩减方法寻求通过相对于充分性标准(例如代码覆盖)消除冗余测试用例(永久地或仅用于测试执行)来最小化测试套件的大小。我们使用传统的基于代码覆盖率的缩减,其中计算测试套件的一个可能的最小子集,以达到整个测试套件的代码覆盖率。减少的子集由基于贪婪添加具有最高附加覆盖率的测试用例的启发式算法来计算[12]。然后,评估所获得的精简测试集的突变得分。作为指标,我们使用约简集的相对大小和结果分数与未约简测试集分数的差值。在第一种情况下,从代码覆盖率的角度来看,大大减少的测试套件可能表示冗余,而第二个概念背后的直觉是,如果分数差异很大,仅最大覆盖率本身并不表明测试套件是冗余的,因为消除的测试用例能够杀死一些变种。

d)缺陷密度:使用来自各个问题跟踪系统的主题项目的历史数据来计算缺陷密度。我们使用GitHub上提供的开源项目,这允许我们处理问题报告的状态和历史。缺陷密度并不直接依赖于失败的测试,而是取决于项目合作者和用户发现和报告的问题。虽然我们没有单独验证每个报告的缺陷,但通常会为系统的发布版本报告这些缺陷,这意味着在测试过程中没有发现这些缺陷。因此,我们假设较高的缺陷密度比率是不成功测试的指示器,并最终降低测试套件的质量。

实验结果

Properties of Subjects

在实验中,我们使用了四个正在积极开发的中型开源Java程序,其中包括定期维护的测试套件。表I显示了有关主题程序的基本数据。它们属于不同的领域:MapDB是一个嵌入式数据库引擎,Netty是一个事件驱动的网络应用框架,OrientDB是一个分布式图形数据库项目,而Oryx是一个实时的大规模机器学习引擎。这些项目的代码总行数约为450K,从31K到229K LOC不等。这些程序的测试套件不是微不足道的,因为需要测试客户端-服务器组件和分布式数据库的正确处理。表中还显示了方法的数量和测试用例的数量,以及整个测试套件在方法级别上的总覆盖率。这里使用了Coverer工具,该工具基于源代码插装来计算覆盖率。不同项目的覆盖率不同,这使得它们适合我们的目标

[论文解读] Relating Code Coverage, Mutation Score and Test Suite Reducibility to Defect Density

变异体生成

如前所述,突变是使用源代码注释手动添加的。考虑到手动生成突变所需的大量工作,只实现了随机选择的可能突变点的一部分。表II报告了两个操作符的第二列和第四列中的候选突变点的数量,而第三列和第五列包含了实际注释的突变体的数量。我们将使用符号M0表示生成的突变体的基本集合。

为了确定可能的突变点,使用grep工具扫描源代码(对于RN,使用RETURN关键字,对于IN,在搜索中使用IF关键字)。然后将获得的列表的顺序随机化。在手动注释过程中,随机列表之后是排除误报的,例如当在评论中发现关键字时。在RN注释的情况下,也手动检查给定方法的返回类型,因为只有布尔类型和Number类型被否定。最后,所有突变体都被成功构建,但在12个案例中,它们未能产生测试结果,因为测试框架因超时而停止。这些案例被排除在最终列表之外,该列表显示在表的最后一栏中。总体而言,在实验中,我们使用了667个突变。在论文的剩余部分,我们总结了这两个算子的结果。

[论文解读] Relating Code Coverage, Mutation Score and Test Suite Reducibility to Defect Density

突变体分类

根据突变体的运行时行为,基本上可以将其分为三组:未覆盖突变体、死亡突变体和活突变体。

在本研究中,我们区分了未覆盖突变体的两个子组:方法完全未覆盖的突变体和方法已覆盖但突变体本身未覆盖的突变体。所有突变体M0的集合因此被分成以下四个组:

M1:其方法未被覆盖的突变体集合, 突变体的所有测试用例中的方法都没有

M2:其方法被覆盖但突变体未被覆盖的突变体集合, 突变体的有测试用例中的方法,但突变部分没有测试用例

M3:被覆盖且测试结果没有改变的突变体的存活集合

M4:被覆盖且至少一个先前通过的测试未通过的突变体的死亡集合

[论文解读] Relating Code Coverage, Mutation Score and Test Suite Reducibility to Defect Density

表III显示了每个项目的相关数字,而相同的数据在图2a中以标准化形式显示。从数据中观察到的第一件事是关于未覆盖的突变体。将它们分离到M1和M2可以深入了解突变过程。可以观察到,在最后3个项目的情况下,|M1|/|M2|比率较大,即总覆盖率较低的项目。换句话说,总体覆盖率较低对其他数据的解读有负面影响。这一观察结果导致了本节稍后讨论的改进的突变分数定义。

[论文解读] Relating Code Coverage, Mutation Score and Test Suite Reducibility to Defect Density

突变分析的关键部分是记录致死突变体的比率,这将导致计算突变得分。该数据可以从表III的最后两列和图2a中读取。如果我们观察在突变总数中死亡的突变体的总数,就会发现MapDB在这方面表现最好,其他三个要差得多,但彼此之间具有可比性。然而,这个信息具有误导性,因为在这个基本的计算中,一个突变体可能是活的,原因有两个:要么它从未被覆盖,要么它被覆盖了,但测试套件未能杀死它。与其他主题相比,MapDB的覆盖率较高,因此这可能解释了前面提到的关系。为了消除总覆盖率的偏差,我们在图2b中给出了所覆盖突变的死亡/存活比率。从这个角度来看,Oryx项目的测试套件在杀死突变方面是最好的,尽管它的总覆盖率最低。

突变分数

突变得分是突变分析中测试集的传统充分性度量。它是杀死的突变体数量与非等价突变体总数的比率,其中也包括未覆盖的突变体[2]。这个分数的分母有两个值得注意的问题。第一个是排除同等的突变体。在本研究中,我们没有应用系统的方法来检测等效突变体,因为基于我们的人工验证,它们的数量可以忽略不计,这是由于所使用的突变算子的性质所致。因此,我们将基本突变分数定义如下:
S=M4/M0 S=\left|M_{4}\right| /\left|M_{0}\right|
分数计算的另一个问题是关于包含未覆盖的突变体,这是大多数突变方法和工具所遵循的方法。然而,为了消除未覆盖突变体在低覆盖率对象中的主导地位,我们通过省略未覆盖突变体来定义突变得分的修改版本:
SCOV=M4/M3+M4 S_{C O V}=\left|M_{4}\right| /\left|M_{3}+M_{4}\right|
表IV的列2-3汇总了主题程序的突变分数值。可以清楚地看到低覆盖率是如何扭曲突变得分的,只有MapDB对这两种得分类型具有相似的值。事实上,受试者根据其分值的相对顺序发生了很大的变化。

[论文解读] Relating Code Coverage, Mutation Score and Test Suite Reducibility to Defect Density

表IV:当测试减少达到最大覆盖率时,突变分数、套件大小和分数都会降低

测试缩减

测试约简算法已经应用于从单个测试用例到整个未约简测试集的固定约简大小的对象。这样,我们就可以比较减少的测试用例集的覆盖率和突变得分逐渐增加的特点。图3显示了oryx的覆盖率和S分值的变化。我们可以观察到,在覆盖率达到最大值后,突变得分仍在增加。在这种情况下,通过选择37%的测试用例实现了最大覆盖率,同时减少了14%的S。

表IV的最后两列显示了与完整测试套件相比,所有受试者的尺寸减小和突变分数降低。我们使用这两个值作为还原性指标。其他程序的增长曲线表现出与oryx相似的特征,除了分数下降值较低的受试者表现得更为平坦外,其他程序的增长曲线则表现出与oryx相似的特征。

[论文解读] Relating Code Coverage, Mutation Score and Test Suite Reducibility to Defect Density

缺陷密度

我们将DD值计算为标记为缺陷的已确认和已关闭问题报告的数量除以分析时系统的实际大小。这些缺陷是GitHub上各自的问题跟踪数据库中获得的,并考虑到了项目的整个生命周期。表V显示了识别的缺陷数量、用于在所有问题报告中确定缺陷的标签,以及计算出的缺陷密度值。对于归一化,我们使用了每千行程序代码的比率,该比率也如表中所示。

[论文解读] Relating Code Coverage, Mutation Score and Test Suite Reducibility to Defect Density

结果分析

在本研究中,我们使用代码覆盖率、突变得分和测试可还原性作为测试套充分性的不同指标,目的是将其与缺陷密度进行比较,缺陷密度是测试套质量的一个独立维度。在图4中,所有这些观点都是并排呈现的,其中主题按照缺陷密度的升序列出。除缺陷密度外,数据取值范围为0−1,但在此总结中,实际值并不是非常重要,只是它们的相对顺序。理想的情况是,如果所有指示器曲线都显示出与缺陷密度相反的陡度,因为在每种情况下,较大的值表示较高的质量,这反映为较低的缺陷密度。

[论文解读] Relating Code Coverage, Mutation Score and Test Suite Reducibility to Defect Density

我们将我们的观察总结如下:

  • 覆盖率本身不足以推断缺陷密度:只有在主体MapDB和Netty之间才能观察到期望的关系。
  • 考虑到传统的突变得分S,改进了与缺陷密度的关系。它给了oryx一个更高的排名,更符合预期,但是这个指示器的特征仍然与整体代码覆盖率非常一致。
  • 图2a和2b提到的覆盖偏差的消除极大地改进了缺陷密度估计。除了Orientdb-MapDB对之外,顺序与SCOV的缺陷密度对齐。
  • 最后,测试可缩减性 强调了附加充分性标准的有用性:它正确地对MapDB进行了分类,而其他指标都没有对其进行管理。

[论文解读] Relating Code Coverage, Mutation Score and Test Suite Reducibility to Defect Density

覆盖顺序: m n ori ory

这些结果的汇总见表VI,其中包括根据受试者与相应指标的相对顺序是否遵循DD给出的相对顺序,对受试者进行成对比较。该表包含每行中的对,如果顺讯对齐,则指示器的列中会有一个1。可以看到,代码覆盖率只在六种情况中的一种情况下是一个很好的指标,传统的突变得分在一半的情况下匹配,而被覆盖的突变体的得分在6种情况中有5种匹配。有趣的是,除了一种情况之外,所有情况下的缩减大小都与DD一致,并且该指标不依赖于突变分析。要强调的一个特殊情况是 oryx,它具有最低的代码覆盖率(只有27.51%),仍然具有最低的缺陷密度比率。当然,除覆盖率之外的所有充分性标准都能够预测此属性。

未来研究的一个有前途的方向可能是找到模型来预测在哪些情况下将是适合于测试套件质量评估的特定指示器;换句话说,什么时候代码覆盖率本身就足够了,什么时候需要突变或可还原性分析

相关工作

尽管在该领域进行了几十年的研究,突变分析的概念仍然太顽固,无法离开学术研究实验室,成为工业测试实践中的日常技术[3]。研究人员确定了采用的几个关键障碍,包括识别等效突变体、测试用例生成、可靠的工具,以及与预期收益相比非常高的(人和机器)资源需求。贾跃亭和哈曼发表了一篇关于该领域的优秀文献综述[2]。

因此,一个非常重要的研究领域是找出在什么情况下突变分析的结果可以作为成功的测试充分性标准。以前的研究使用各种基于突变的充分性标准,如控制流路径覆盖来评估所谓的类突变[13]、分布式系统的基于模拟的模型[14],并探索突变的替代方案以在不显著降低其强度和有效性的情况下降低其成本[15]。在我们的研究中,我们寻求组合的测试充分性标准,它使用代码覆盖率、突变分数以及测试集的可还原性。从本质上说,我们正在研究一种通用的测试套件评估方法,其中突变分析将扮演重要的角色。测试质量的其他方法包括对测试套件代码应用通用源代码度量[16],或者从测试过程的角度评估测试[17]。

有几个积极的证据表明,突变体是真实故障(遵循耦合效应)的有效替代品[18],[19],然而,这在一般的上下文中很难验证,并推广到不同的系统、测试套件、突变技术和工具。高阶突变体是增加耦合效应的另一种方法[20],[21]。在我们的工作中,我们使用了一种特定的缺陷密度测量来验证突变分析的充分性。

选择性突变是一种流行的降低成本的方法[2]。我们的方法是只使用两个变异操作符,这两个操作符应该能够充分表明测试套件的充分性。Delamaro等人。[22]发现仅使用删除操作符是一种经济高效的方法。另一个方向是找到一组“足够的运算符”,从统计上讲,这组运算符并不比完整的运算符差太多,但却有更多的限制[23]。例如,Mathur和Wong只使用了两个运算符[11]-与我们的方法相似。正如Navanti等人所展示的那样,使用一小部分特定于目标的操作符也是一个很有前途的方向。[24]。突变故障已经用于测试用例的优先级排序。Usaola等人。[25]提出了一种基于变异分数的测试约简方法。Offut等人提出了减少基于突变的回归测试集的方法。[26]。与等效突变体相关的问题已经被广泛研究(有关评论,请参见[27])。虽然有一些非常创新的方法(例如参见Papadakis等人的作品。[28],Kintis和Malevris[29]),这个问题在很大程度上仍然没有得到解决。

结论

本文提供的实验强调了代码复盖率本身很少成为系统中预期缺陷数量的指示器。另一方面,我们使用的另外两个充分性标准大大提高了预测:突变得分和测试用例集的可还原性。由于我们使用缺陷密度和发布后为系统报告的真实故障,因此我们将这些指标的组合视为测试套件质量的指示器,从而向更通用的测试套件评估模型迈进了一步。目前,我们依赖于小样本的程序,而且实验还有其他限制(如基于样本的突变生成和有限的操作符集合)。因此,我们没有得出太多笼统的结论,但我们观察到的趋势是有希望的。

不幸的是,仅仅知道代码覆盖率有时本身就是测试套件质量的良好指标是不够的。我们的结果还没有解释在什么情况下突变分析是多余的,以及可还原性实际上增加了什么。这些主题对未来的研究是开放的,因为我们认为,在不必要的时候应该避免进行更复杂的分析,而应该在任何需要的时候使用。类似于Jalbert和Bradbury[30]对突变分数的启发式预测的方法可能有助于构建模型来决定这些问题。我们还计划继续对更多的受试者进行实验,使用一组不同的突变操作符并使用专用的突变工具。