变异测试是一种 fault-based 的软件测试技术。这项技术已经广泛研究并使用了三十余年。它为软件测试贡献了一系列方法,工具,和可靠的结果。本文将对变异测试进行深入的调查,分析它的优势和不足之处,并对比几种不同的变异测试方法,提出一些改进的建议。
下面用一个例子来解释什么是变异测试,考虑以下代码片段:
if(a && b) c = 1;
else c = 0;
条件运算符如果用||来替换&&,就会产生以下变异:
if(a || b) c = 1;
else c = 0;
为了杀死这个突变,需要满足以下条件:
(1)测试数据必须对突变和原始程序引起的不同状态覆盖。如:a=1,b=0可以达到目的。
(2)c的值应该传播到程序输出,并被测试检查。
弱突变覆盖需满足(1),强突变覆盖需满足(1)(2)。
下面进行正式的内容。
1 变异测试理论
1.1 两个基本假设
变异测试旨在找出有效的测试用例,发现程序中真正的错误。在一个工程中,潜在BUG的数量是巨大的,通过生成突变体来全面覆盖所有的错误是不可能的。所以,传统的变异测试旨在寻找这些错误的子集,能尽量充分地近似描述这些BUG。这个理论基于两条假设:Competent Programmer Hypothesis(CPH) 和 Coupling Effect(CE)。
CPH是指:假设编程人员是有能力的,他们尽力去更好地开发程序,达到正确可行的结果,而不是搞破坏。它关注的是程序员的行为和意图。而CE(耦合效应)更加关注在变异测试中错误的类别。一个简单的错误产生往往是由于一个单一的变异(例如句法错误),而一个庞大复杂的错误往往是由于多出变异所导致。复杂变异体往往是由诸多简单变异体组合而成。
1.2 变异测试流程
在变异测试中,对于被测程序p,设定一个测试用例集合T。首先根据被测程序特征设定一系列变异算子;随后通过在原有程序p上,执行变异算子生成大量变异体;接着从大量变异体中识别出等价变异体;然后在剩余的非等价变异体上执行测试用例集T中的测试用例,若可以检测出所有非等价变异体,则变异测试分析结束,否则对未检测出的变异体,需要额外设计新的测试用例,并添加到测试用例集T中。
基于上述传统变异测试分析流程, 对其中的基本概念依次定义如下。
定义 1(变异算子) 在符合语法规则前提下, 变异算子定义了从原有程序生成差别极小程序(即变异体) 的转换规则。表 1给出了一个典型的变异算子, 该变异算子将“+” 操作符变异为 “-” 操作符。选择被测程序 p 中的条件表达式 a + b > c 执行该变异算子, 将得到条件表达式 a - b > c , 并生成变异体 p′ 。
Offutt和King在已有研究工作的基础上,于 1987年针对 Fortran77首次定义了 22种变异算子,这些变异算子的简称和描述如表 2 所示。
这22种变异算子的设定为随后其他编程语言变异算子的设定提供了重要的指导依据。在完成变异算子设计后,通过在原有被测程序上执行变异算子可以生成大量变异体M,在变异测试中,变异体一般被视为含缺陷程序。根据执行变异算子的次数,可以将变异体分为一阶变异体和高阶变异体,并分别定义如下。
定义 2 (一阶变异体) 在原有程序 p 上执行单一变异算子并形成变异体 p′ ,则称p′为p的一阶变异体。
定义 3 (高阶变异体) 在原有程序 p 上依次执行多次变异算子并形成变异体 p′ ,则称 p′ 为 p 的高阶变异体。若在 p 上依次执行 k 次变异算子并形成变异体 p′ , 则称 p′ 为 p 的 k 阶变异体。
高阶变异体实例
定义 4 (可杀除变异体)若存在测试用例 t,在变异体 p′ 和原有程序 p 上的执行结果不一致, 则称该变异体 p′ 相对于测试用例集 T 是可杀除变异体。
定义 5(可存活变异体) 若不存在任何测试用例t , 在变异体 p′ 和原有程序 p 上的执行结果不一致, 则称该变异体 p′ 相对于测试用例集 T 是可存活变异体。一部分可存活变异体通过设计新的测试用例可以转化成可杀除变异体, 剩余的可存活变异体则可能是等价变异体。本文对等价变异体定义如下。
定义 6 (等价变异体)若变异体 p′ 与原有程序 p 在语法上存在差异, 但在语义上与 p 保持一致, 则称p′ 是 p 的等价变异体。
等价变异体实例
1.3 等价变异体检测
等价变异体检测是一个不可判定问题,因此需要测试人员借助手工方式予以完成。等价变异体在语法层次上有微小的差别,但是在语义层次上是一致的。有研究人员发现,在生成的大量变异体中,等价变异体所占比例一般介于10%~40%。在等价变异体的检测上,主要有两类方法。
1.3.1 等价变异体静态检测法
该方法基于如下猜测:源代码在编译时借助优化规则可以生成语义等价代码。
1.3.2 等价变异体动态检测法
Adamopoulos 等人提出一种基于遗传算法的协作演化法(即测试用例和变异体同时进行演化)来检测可能的等价变异体。他们通过设置合理的适应值函数, 确保当变异体是等价变异体时, 该函数可以返回一个很小的适应值。基于该适应值函数, 群体在演化过程中可以有效淘汰部分等价变异体, 同时将那些难以检测的变异体和检测能力强的测试用例均保留下来。
2.1 变异体选择优化
变异体选择优化策略主要关注如何从生成的大量变异体中选择出典型变异体。
2.1.1 随机选择法
随机选择法尝试从生成的大量变异体中随机选择出部分变异体。具体来说, 首先通过执行变异算子生成大量变异体 M ; 然后定义选择比例 x ; 最后从变异体 M 中随机选择出 |M| ´ x% 的变异体, 剩余未被选择的变异体则被丢弃。
2.1.2 聚类选择法
具体来说, 首先对被测程序p 应用变异算子生成所有的一阶变异体; 然后选择某一聚类算法根据测试用例的检测能力对所有变异体进行聚类分析, 使得每个聚类内的变异体可以被相似测试用例检测到; 最后从每个聚类中选择出典型变异体, 而其他变异体则被丢弃。
2.1.3 变异算子选择法
与上述两类方法不同, 这类方法从变异算子选择角度出发, 希望在不影响变异评分的前提下, 通过对变异算子进行约简来大规模缩小变异体数量, 从而减小变异测试和分析开销。结果表明, 变异算子选择法相对于随机选择法来说并不存在明显优势, 随机选择法值得研究人员继续深入研究。
2.1.4 高阶变异体优化法
高阶变异体优化法基于如下推测:(1)执行一个k 阶变异体相当于一次执行 k 个一阶变异体;(2) 高阶变异体中等价变异体的出现概率较小。实证研究表明, 采用二阶变异体可以有效减少50%的测试开销, 但却不会显著降低测
试的有效性。
4 结束语
变异测试作为一种面向软件缺陷的测试技术,得到国内外研究人员的关注, 并取得了大量研究成果。本文从变异测试原理、 优化和应用 3个角度对已有的研究工作进行了系统总结。虽然目前针对变异测试的研究已经取得了大量
的成果, 但该领域仍存在很多研究点值得关注。可以考虑的研究点包括:
(1)变异测试中仍存在大量开放性问题。例如等价体变异检测问题, 已有研究工作主要集中于从生成的变异体中识别出等价变异体。在将来的研究工作中可以考虑通过设计变异算子和分析被测程序特征, 提出有效策略, 在变异体生成过程中避免等价变异体的生成。
(2)在变异测试分析优化中, 即变异体的生成、编译和执行过程中存在很多研究点。例如, 在变异算子选择中可以考虑将变异算子按照变异体检测能力进行排序; 借助硬件的发展和多核 CPU的广泛使用, 深入研究并行变异测试等。
(3)重点关注面向变异测试的测试用例生成技术。已有研究工作大部分关注于变异体的生成技术, 但面向变异测试的测试用例生成研究还在起步阶段, 并亟需成熟工具进行支持。可以进一步考虑将动态符号执行法和 SBST进行有效组合, 以提高测试用例的生成效率。
(4)进一步考虑变异测试研究成果与工业界测试流程的结合。目前的研究一般采用对照试验方式进行有效性评估, 所得结论很难适用于软件企业的大型软件产品。其次研究人员提供的原型工具并未考虑与软件企业目前已有的开发和测试流程紧密结合, 若要让企业接受学术界的研究成果, 需要进一步提高测试工具的自动化程度, 改善工具用户界面, 并提高工具的鲁棒性。
参考文献
An Analysis and Survey of the Development of Mutation Testing, IEEE TRANSACTIONS ON SOFTWARE ENGINEERING, VOL. 37, NO. 5, SEPTEMBER/OCTOBER 2011, Yue Jia, Mark Harman