我们设计并执行了大量测试数据,但没有发现程序有什么问题,执行结果都是正确的。这时有两种可能:程序太完美与测试数据质量太差
程序变异: 程序变异通常只是一种轻微改变程序的操作。例如,有程序段 P1,可以用“>”来替换程序中的“>= ”,产生变异程序 P2。
变异测试: 有时也叫做“变异分析”,指先对程序进行变异,然后再来执行测试,以检验测试数据集是否有效。
如果一个已知的修改被植入到程序中,而测试结果不受影响,则说明测试不充分或者测试无效。 变异测试是一种对测试数据集的有效性、充分性进行评估的技术,以便指导我们创建更有效的测试数据集。
变异测试基本思想:给定一个程序P,通过对程序P进行微小的合乎语法的改变,得到一组变异体M1,M2,...; 对程序P和变异体M都使用测试集T进行测试,如果某Mi在某个测试输入t上与P产生不同的结果,即P(t)≠Mi(t),称T不能区别P和Mi,称该变异体Mi被杀死; 若某Mi在测试数据集T的所有测试数据t上都与P产生相同的结果,即T中所有的测试数据t都使得P(t)=Mi(t),则称T不能区别P和Mi,称Mi没有被杀死。
变异体无法被杀死的原因:测试数据集还不够充分; 变异体再功能上等价于原始程序,称这类变异体为等价变异体(equivalent mutant).
等价变异举例:
变异分数的计算:
测试集T的变异分数记为MS(T), 其中:|D|表示:杀死的变异体数 |E|表示:等价的变异体数 |M|表示:生成的所有变异体数
如何进行变异?
做法:基于良好定义的变异操作,对程序进行微小的合乎语法的修改,得到源程序的变异程序。变异是一种轻微改变程序的操作。 良好定义的变异操作可以是模拟典型的应用错误。 例如模拟操作符使用错误,把大于等于改写成小于等于; 强制出现特定数据,以便对特定的代码或者特定的情况进行有效地测试,例如使得每个表达式都等于0,以测试某种特殊情况; ... ...
变异算子:
变异算子是一种产生变异体的机制。 设计变异算子来模拟程序员可能出现的简单错误。当变异算子作用于原始程序时,产生的变异体在语法上必须是正确的; 一个变异算子可能产生一个或者多个变异体。 某个变异算子一可能不产生任何变异体。 变异算子对编程语言有依赖性,已经开发的、与语言相关的变异算子有 Fortran, C, Ada, Lisp 和 Java等。
算子类别: 操作数替换算子类:将一个操作数替换为另一个合法的操作数; 表达式修改算子类:用新的的运算符或替换运算符来修改表达式; 语句修改算子类:修改整条语句,如删除动态内存释放语句来模拟内存泄漏故障。
对于面向对象的语言,传统的变异算子不能够检测到和类相关的缺陷,因此需要另外设计关于类的变异算子。类变异算子和类的特性相关,比如继承、多态和动态绑 定以及方法重载等。
变异测试的成本
变异体数量庞大:即使是对一个小规模的软件模块,也将产生大量的变异体。 仅有4行代码的Min函数经过Mothra变异系统可以生成44个变异体 30行代码的三角形分类程序能产生951个变异体 变异测试需要运行大量的变异体,每一变异体至少需要执行一个测试用例以便将其杀死,这就需要大量的计算能力。正是由于难以接受的巨大计算开销阻碍了变异测试在软件工业界的应用。
变异测试的优缺点
变异测试工具:muJava