首页 > 江南app官方版下载

如何通过变异测试提高测试用例质量

来源:江南网页版登录入口官网下载 网
时间:2023-03-01 17:04:50
热度:

如何通过变异测试提高测试用例质量变异测试评估测试用例的质量。它重新执行已经通过的测试用例,但在更改的测试对象上,并揭示测试用例是否检测到测试对象中的更改。安全关键系统的开发标准(例

变异测试评估测试用例的质量。它重新执行已经通过的测试用例,但在更改的测试对象上,并揭示测试用例是否检测到测试对象中的更改。安全关键系统的开发标准(例如 IEC 61508)建议进行突变测试。在实践中,测试执行和突变生成的自动化是不可避免的。自动化突变测试是TESSY新主版本V4.3重要的新功能,是嵌入式软件单元、模块和集成自动化测试工具。本文展示了如何应用突变测试来提高测试用例的质量;突变测试提出的问题以及如何克服这些问题。

突变测试基础

变异测试重复执行已经通过测试对象(例如软件单元)的测试用例。但是,当重复测试用例时,它们不是使用测试对象的原始源代码执行的,而是使用已更改(“变异”)的代码执行的。变异代码与原始代码不同;更改可能涉及次要细节,例如用逻辑 OR 替换逻辑 AND;但是,更改也可能非常剧烈,例如删除 if 指令的 else 分支。当然,即使在更改之后,测试对象也必须保持可编译性,否则无法重复测试。

当用变异的源代码重复测试时,问题是现有的测试用例是否揭示了变异(技术术语是“杀死”)。如果在重复测试时至少有一个测试用例失败,则突变将被杀死。如果这没有发生,则测试用例不会检测到源代码已被更改,或者换句话说:测试用例还认为原始对象以外的测试对象是正确的。这令人担忧,需要进一步调查。对于这项调查,如果只进行了突变,那将很有帮助。

如果幸存的突变不是等效突变,则测试用例的质量不足。等效突变不会改变测试对象对外部的行为,因此不会被杀死。下面进一步给出等效突变的示例。当然,突变的激进程度很重要。一个细微的突变比几个剧烈的变化更难检测。通常会进行几次具有不同突变的测试运行。这评估了测试用例的质量。

图 1 显示了由 TESSY 自动执行的突变测试过程。原始源代码通过所有现有测试后,才能开始实际的变异测试过程。TESSY 只执行突变并重复所有现有测试,当然会记录突变是否被杀死。然后恢复原来的测试对象,再进行变异。


图 1:TESSY 自动化了整个突变测试过程。(:Hitex)

图 2 显示了 TESSY 自 V4.3 版本以来可以执行的突变。用户可以选择要应用的突变,因此当然也会影响进行突变的次数,进而影响整个突变测试过程的执行时间。


图 2:TESSY V4.3 执行的默认突变。(:Hitex)

突变测试的两个假设

默认情况下,TESSY 执行的更改很微妙,例如关系运算符“<”变为“<=”。这是基于“有能力的程序员”[Liggesmeyer] 的假设,该假设说熟练的软件开发人员只会犯小错误——例如,使用一个循环次数太多或次数太少的循环(“off-by-一个”错误)。为了查明测试用例是否发现了这样的小错误并因此具有良好的质量,突变必须是微妙的。低质量的测试用例也应该揭示根本性的突变,例如删除一条甚至几条指令。另一个经经验证实的假设 [Offut] 指出存在耦合效应:恰好杀死一个突变体的测试用例也会杀死多个突变体。因此只进行一个突变就足够了。

例子

我们考虑一个已通过四个测试用例(图 3)并通过这些测试用例实现 100% 代码覆盖率的测试对象。


图 3:四个通过的测试用例的测试数据应用于原始测试对象。(:Hitex)

如果 TESSY 进行突变测试(突变的标准设置如图 2 所示),结果是一个被杀死的突变体和一个存活的突变体(图 4)。


图 4:TESSY 突变测试的结果。(:Hitex)

在上图(图 4)的左上部分,显示了被杀死的突变(“突变导致测试失败”)。此突变将测试对象的个 if 指令(在图 4 的右上角突出显示)中的关系运算符从“<”更改为“<=”。结果,测试用例失败,这在变异测试中是积极的。因此,此突变标有绿色勾号。

在图 4 的左下部分,您可以看到幸存的突变(“突变在所有测试用例中幸存”);此突变将测试对象的第二个 if 指令(在图 4 的右下方突出显示)中的关系运算符从“>”更改为“> =”。没有测试用例通过失败检测到此更改。这是有问题的,需要调查。

变异分数和测试用例质量

突变得分是杀死的突变与所有突变的比率。


图 5:TESSY 中的突变评分。(:Hitex)

上图(图 5)显示了 TESSY 为四个测试用例确定的突变分数(再次参考四个测试用例的测试数据,编号为 1.1 到 4.1,如前面图 3 所示)。

测试用例 2 杀死了两个突变体中的一个,因为测试用例 2 由于个 if 指令 (v1 < r1.range_start) 从“<”到“<=”的突变而失败。这导致突变得分为 50%。测试用例 2 在 M 列中标有绿色复选标记,因为它杀死了一个突变体。其他三个测试用例没有杀死任何突变体,因此有一个红叉或突变分数为 0%。

测试用例 2 杀死了一个突变体,因此比其他没有杀死任何突变体的测试用例质量更高。这是由于测试用例 2 中变量 v1 的值。这取决于个 if 指令中的关系运算符。在第二个测试用例中,变量 v1 和变量 r1.range_start 的值都是 5,因此个 if 指令中的决定是“5 < 5”,计算结果为“false”。在突变中,决策是“5 <= 5”,其计算结果为“真”。因此,第二个测试用例提供了一个意想不到的结果(“否”而不是正确和预期的“是”),从而杀死了突变体。

测试用例 4 应该在第二个 if 指令 (v1 > r1.range_start + r1.range_len) 的决定中杀死另一个突变(从 '>' 到 '>=')。但这不起作用,因为测试用例 4 中 v1 的值不合适。变量 v1 的值为 9,r1.range_start + r1.range_len 的结果为 5 + 2 = 7。因此,第二个 if 语句中的决定是原始的 '9 > 7' 和突变的 '9 >= 7',两者都评估为“真”。因此,原始和突变体在两种情况下都给出正确的结果“否”;original 和 mutant 都通过了第四个测试用例;这意味着突变体没有被杀死。

测试用例 2 的质量优于测试用例 4,因为测试用例 2 使用了边界值而测试用例 4 没有。测试用例 2 使用边界值 5,它是从 5 开始且长度为 2 的范围的起始值。对于变量 v1 的值为 9,测试用例 4 不使用范围的边界值。

这说明了为什么边界值可以形成良好的测试数据,以及为什么安全关键软件的开发标准建议将边界值作为测试数据。例如,IEC 61508 [61508] 在第 3 部分的表 B.2 和 B.3 中推荐了“边界值分析”方法。在这两个表中,此方法推荐用于 SIL 1,强烈推荐用于 SIL 2 至 4。 ISO 26262 [26262] 还提到第 6 部分表 8 中的方法 1c“边界值分析”作为获取软件单元测试测试数据的过程。该方法推荐用于 ASIL A,强烈推荐用于 ASIL B 至 D。

突变测试还可以评估测试用例集。如果一组测试用例杀死了所有突变体,则称其为足够的。适当的测试用例集越小越好。它还可用于评估测试用例构造方法。

无限循环和崩溃

突变也可能导致无限循环;这意味着测试不会结束。为确保此类突变不会导致整个流程停顿,TESSY 会监控执行时间。如果突变的执行时间超过没有突变的执行时间的十倍,TESSY 将中止测试执行。无限循环或超时杀死突变体。突变也可能导致测试对象崩溃,例如由于被零除。突变测试对象的崩溃也会杀死突变体。超时或崩溃后,如果有更多突变适用,突变测试过程将继续。


图 6:无限循环杀死突变体。(:Hitex)

在上面的示例(图 6)中,count() 函数使用一个测试用例进行测试,该测试用例的参数 x 的输入值为 10,并通过返回值 1 提供正确的结果。该测试用例杀死了上图所示的所有四个突变图 6 的左侧。第三个突变(从 '<=' 到 '>=')并没有像往常一样被测试用例的失败杀死,而是被无限循环和由此触发的超时杀死。TESSY 将此突变评为已杀死。之后,进行第四次突变。


图 7:撞车杀死了突变体。(:Hitex)

在上面的示例(图 7)中,crash_by_divide() 函数使用一个测试用例进行测试,其中参数a和b具有相同的值。这个测试用例在 if 指令的决定中杀死了 '!=' 到 '==' 的突变。

等效突变是有问题的

突变测试的主要问题是等效突变。这些突变不会改变测试对象的外部行为。


图 8:等效突变的示例。(:Hitex)

在上图中(图 8),显示了一个等效的突变。关系比较运算符从“>”到“>=”的突变没有外部可见的效果,因此不能被任何测试用例杀死。但是输入值 0 肯定会导致原始和变异源代码的不同内部程序行为。在原始代码中执行 if 指令的 else 分支,变异执行 then 分支。

因为等效突变不能被测试用例杀死,所以必须手动(由人)检查所有幸存的突变以确定它是否是等效突变。这可能很耗时。然而,如果像 TESSY 一样,只进行一个突变,这将很有帮助。此外,手头的测试对象是软件单元,与整个软件相比是很小的。这减少了检查等效突变的工作量。重要的是,我们可以假设,经过单元测试的安全关键软件比其他软件具有更好的测试用例,因为该软件需要实现高百分比的代码覆盖率。这意味着,只有一小部分(如果有的话)安全关键软件没有被任何测试用例执行。另一方面,没有像安全关键软件那样经过彻底测试的软件可能有很大一部分代码没有被任何测试用例执行。很明显,无法杀死未被任何测试用例执行的软件部分的突变。这意味着更多的幸存突变体和因此更多的努力来决定不充分的测试用例和等效突变体。

等价突变可以看作是被杀死的突变;它们并不表示低质量的测试用例。

避免不必要的突变

在安全关键软件的单元测试期间,幸存的突变(不是等效突变)应该导致更改或额外的测试用例。由于软件需要高度完整性,终目标是杀死所有应用的突变(同样,不包括等效突变)。这可能不是集成测试的目标。集成测试的主要目标是测试单元的正确交互。因此,用于集成测试的测试用例检查单元的交互,而不是检查每个单元是否对每个可能出现的错误条件(例如意外的 NULL 指针)做出正确反应。

在集成测试期间引发错误条件在技术上可能很困难,因此可能会被忽略。这是支持的,因为可以假设在单元测试期间测试了对错误条件的反应。因此,在集成测试期间达到 100% 的代码覆盖率并不是重要的,尤其是代表防御性编程的代码部分(例如,对意外 NULL 指针的反应)可能仍未被发现。很明显,无法杀死未被任何测试用例执行的代码中的突变。应用此类突变会导致手动调查此类突变的人工努力,因为由于该突变是等价突变或低质量测试用例,因此尚不清楚该突变是否存活。此外,应用此类突变会增加突变测试的执行时间。

如果 TESSY 中的代码覆盖率信息可用于变异测试,TESSY 会避免未被任何测试用例执行的代码部分发生变异。此功能在集成测试期间特别有用,因为可能有很大一部分未被覆盖的代码不会也永远不会被任何测试用例执行。尽管用处不大,但 TESSY 还在单元测试期间抑制了未覆盖代码中的突变。


图 9:两个突变被抑制,因为它们无法被杀死。(:Hitex)

在上图中(图 9)测试集成了抽象数据类型“stack”的函数 push() 和 pop()。图 9 的右侧显示了 push() 的源代码。第 15 行中的条 if 指令检查堆栈指针(变量 next_free_element)是否已到达堆栈顶部,表明堆栈溢出。个 if 指令的 then 部分用红色阴影表示,表示它没有被任何测试用例执行。因此,第二个 if 指令(第 17 行)中决策的突变是无法检测到的,并且会继续存在。

利用代码覆盖信息,TESSY 在第二个 if 指令 (error_report_level > 0) 的决策中抑制了关系运算符“>”的两个突变,图 9 右侧以灰色阴影显示。在左侧在图 9 中,相同的决定以灰色阴影显示,在其下方显示了两个可能的突变(从“>”到“<”以及从“>”到“>=”)。两种突变均未应用。这由“结果”列中的破折号('-')表示。

如果 TESSY 在没有事先进行代码覆盖测量的情况下执行突变测试,TESSY 会应用这两个突变来决定第二个 if 指令。当然,他们都没有被杀。与单元测试相反,集成测试可能没有必要添加测试用例来检查错误情况(在我们的例子中是堆栈溢出)是否得到正确处理。通过避免这些突变,TESSY 为人类和计算机计算节省了大量时间。

标准突变测试

IEC 61508 将突变测试描述为“从错误播种执行测试用例”,并建议将其用于安全完整性等级 (SIL) 2 至 4(在第 3 部分的表 B.2 中)。IEC 61508 还指出(在第 7 部分的 C.5.6 节中)可以根据测试套件在原始测试对象中发现的错误数量和该测试套件杀死的突变数量来估计错误总数(预测). 杀死的突变体与突变体总数的比值等于原始测试对象中发现的错误与原始测试对象中错误总数的比值。这种估计自然假设突变的类型和位置以及实际错误具有相同的统计分布;例如,如果实际错误是错误的计算但没有使用算术突变,

ISO 26262 仅在第 6 部分表 7 中的“故障注入测试”方法(方法 1l)的注释中提到了“代码突变”,其中列出了软件单元验证的方法。

结论

突变测试可以揭示不充分的测试用例。改进它们会增加在测试软件中发现错误的机会。因此,变异测试不仅评估测试用例的质量,还有助于提高被测软件的质量。变异测试的执行在 TESSY 中是自动执行的,因此执行不需要任何更大的努力。

然而,即使没有 TESSY,每个处理测试项目的人都可以手动执行一些突变并重新执行测试,看看测试用例是否杀死了突变。

术语

播种错误

这就是 IEC 61508 所说的突变测试(参考第 7 部分的 C.5.6 节)

耦合效应

如果通过一组测试用例发现具有单个突变的突变体,则同时也发现了多个突变。

强突变试验

仅从外部(黑匣子)考虑突变体,并且突变体仅通过测试用例发现,从外部提供与原始结果不同的结果。

弱突变试验

测试用例确保突变体内部的行为与原始行为不同。但是,这种其他行为在外部是不可见的。

足够的测试用例/足够的测试用例集

如果一个测试用例杀死了一个不等价的突变体,那么它就被称为足够的。如果一组测试用例杀死了所有非等价的突变体,则称其为足够的。

突变分数

被杀死的突变体与突变体数量的比率。通常以百分比给出。

故障注入

外部错误被注入到未变异的测试对象中,以测试健壮性。ISO 26262 提到“代码突变”作为故障注入测试的例子。

Baidu
map