下面我们来讨论变异充分准则。这个准则,同样是一种基于缺陷的充分准则,但是跟我们前面讨论过的准则相比,思路又完全不同。我们来具体看一看。
首先,它为什么叫“变异”充分准则呢?我们通常说的变异,指的是DNA分子结构的变化。遗传学告诉我们,变异会导致生物个体性状的差异,然后就有了物竞天择,适者生存。所以,变异是生物进化的根本原因。
在信息技术领域,有人借鉴生物进化的过程,设计了遗传算法。00年代的遗传算法,10年代的深度学习,20年代的大模型,号称paper灌水三代神器。遗传算法里也有变异的概念,指的是对个体信息进行一个微小的改动,从而实现局部寻优。
变异充分准则里所谓的“变异”,跟遗传算法里的变异概念非常相像,指的是对被测程序进行一个微小的改动,比如把决策表达式里的“≥”改成“>”、“<”或者“≤”,改动后的程序被称为变异体。
这种变异的物理意义,实际上是模拟开发者容易犯的错误,已有的研究和实践成果已经总结出了很多典型的变异方式,我们称其为变异算子。下表是变异测试开源工具muJava提供的19种典型变异算子:
比如,“数值运算符替换”这个变异算子,模拟的就是“+”被误写为“-”这样的错误;“关系运算符替换”这个变异算子,模拟的就是“≥”被误写为“>”这样的错误。
对被测程序应用这些变异算子之后,可以生成一系列变异体,也就是一系列植入了各种模拟缺陷的程序。接下来,使用已有的测试集来测试原始程序和所有变异体。对于任一变异体来说,如果它的测试结果跟原始程序的测试结果不一样,那就说明,已有测试集能够分辨出这个变异体与原始程序的差异,或者说已有测试集能够检出这个变异体中植入的缺陷,我们说,这个变异体被“杀死”了。但是如果这个变异体的测试结果跟原始程序是一样的,我们就说变异体“存活”下来了。
为什么有的变异体会存活下来?有两种可能:
① 一种可能是,已有测试集不充分,不足以检出这个变异体中植入的缺陷,那么我们就需要对测试集进行补充;
② 另一种可能是,虽然变异体和原始程序存在语法上的差异,但是它们在语义上是等价的。这种变异体叫做“等价变异体”。比如在如下变异体中:
循环条件中的“<”变异成“!=”,但是语义上的循环逻辑没有任何改变,程序的运行时行为也不会有任何改变,所以任何测试集都无法杀死这样的变异体。
排除这种等价变异体之后,被杀死的变异体占的比例越高,就意味着我们的测试集检出各种缺陷的能力越强。我们把这个比例叫做变异得分。
利用变异得分这个指标,我们就可以建立充分准则了。如果我们希望测试集能够检出在未来发生概率比较高的各种典型缺陷,我们可以要求测试集的变异得分达到1。
这样的准则,其实并没有告诉我们怎么做测试选择,它的主要作用是评估一个已有测试集的充分性:变异得分达到1了,说明测试集已经比较充分了;没达到,说明还不太行,需要再补充一些用例。
所以,围绕变异充分准则做测试选择,大致是这样一个过程:
首先针对原始被测程序,使用变异算子生成一系列变异体,从中识别并刨除等价变异体之后,在原始程序和所有变异体上执行测试集,如果有变异体存活下来,就补充用例并再次执行测试集,直到所有变异体都被杀死。
可以看到,尽管变异充分准则是一种基于缺陷的充分准则,但这个准则所关注的,并不是被测对象现在可能有哪些缺陷,而是由变异算子所代表的、在未来发生概率比较高的那些潜在缺陷。我们把这些潜在缺陷主动注入到被测对象里,看测试集能不能检出这些缺陷,目的是为了判断,测试集能不能在下一个版本里过滤掉这些缺陷。所以,变异充分准则主要是用来做回归测试设计的。