代码改变世界

实施组合测试

2010-07-25 15:02 liangshi 阅读(...) 评论(...) 编辑 收藏

组合测试(Combinatorial Test)是一种测试用例生成方法。它将被测试应用抽象为一个受到多个因素影响的系统,其中每个因素的取值是离散且有限的。两因素(Pairwise)组合测试生成一组测试用例集,可以覆盖任意两个因素的所有取值组合,在理论上可以暴露所有由两个因素共同作用而引发的缺陷。多因素(N-way,N>2)组合测试可以生成测试用例集,以覆盖任意N个因素的所有取值组合,在理论上可以发现由N个因素共同作用引发的缺陷。由于两因素组合测试在测试用例个数和错误检测能力上达到了较好的平衡,它是目前主流的组合测试方法。

本文总结了我在组合测试实践中获得的一些经验。权当是抛砖引玉,希望读者不吝赐教。

1. 不要使用正交表,要使用PICT

有一些文章介绍了利用正交表构造组合测试用例集的方法。虽然可行,但是正交表并不是理想的组合测试工具。

首先,正交表的性质并不适用于软件测试。正交表是为正交试验服务的,它求对任意两个因素的取值组合实施“等概率”覆盖,以便实验样本“均匀”地分布在样本空间。“等概率覆盖”有助于在正交试验中确定各个因素对实验结果的贡献,但是对于组合测试提高错误检测能力并没有帮助。受到“等概率覆盖”的约束,正交表往比“覆盖即可”的组合测试工具生成更多的测试用例,提高了测试成本。

第二,利用正交表构造组合测试用例并不方便。使用正交表构造测试用例,要寻找正确的正交表、对其进行剪裁、替换参数,方能获得测试用例集。如果使用微软提供的组合测试工具PICT,生成测试用例会非常简单。第一步,在文本文件中描述被测试应用的模型(Model),即该软件有哪些因素、每个因素有哪些取值。该文本文件被称为模型文件(Model File)。下面的文本是一个为配置测试所准备的模型文件。其中,以"#"开始句子是注释,正文部分每一行代表一个因素,":"之前是因素名,之后是因素的可能取值。

#
# Different machine configurations
#
PLATFORM: x86, ia64, amd64
CPUS:     Single, Dual, Quad
RAM:      128MB, 1GB, 4GB, 64GB
HDD:      SCSI, IDE
OS:       NT4, Win2K, WinXP, Win2K3
IE:       4.0, 5.0, 5.5, 6.0

第二步,在命令行上运行"pict.exe model.txt > test_cases.txt"。所生成的文件test_cases.txt是两因素组合测试用例集,其内容如下。

PLATFORM	CPUS	RAM	HDD	OS	IE
amd64	Single	4GB	SCSI	Win2K	4.0
amd64	Dual	128MB	IDE	Win2K3	6.0
x86	Quad	64GB	SCSI	Win2K3	5.0
...

可见,使用PICT生成组合测试用例非常方便。输入文件和输出文件是易读、易理解的文本文件,测试者可以轻松地理解并修改,测试工具可以方便地解析并运行

第三,现实世界中的程序是复杂的,它们为组合测试的实施提出了许多挑战。面对这些困难,PICT较正交表有明显的优势。下文将详细介绍组合测试面临的挑战和解决之道。

2. 要定义因素之间的约束关系

在组合测试的基础理论中,各个因素的取值是相互独立的,即因素A的取值不会影响因素B的取值。但是,大多数被测试应用的因素之间存在约束关系。以配置测试为例,当因素PLATFORM的取值是x86时,因素RAM的取值就不能是64GB,因为x86 CPU最大只支持4GB RAM。即便你试图将组合(PLATFORM: x86, RAM: 64GB)作为负面(negative)测试用例,也是不可行的,因为x86的主板根本插入不了64GB的内存。

如果不考虑约束关系,组合测试用例集将包含大量的无效测试用例。这些无效的测试用例,包含一些无效的取值组合,也有可能包含一些有效的取值组合。仅仅删除无效测试用例,会导致最终的测试用例集不能实现两因素或多因素组合覆盖。面对因素之间存在约束关系的被测试应用,应该明确定义约束关系,让组合测试工具根据约束来生成有效的测试用例集。

在PICT的模型文件中,加入如下的约束(Contraint)语句,就可以定义出因素之间的约束关系。

IF [PLATFORM] = "x86" THEN [RAM] <> "64GB";
IF [OS] = "Win2K3" THEN [IE] >= 6.0;

第一条约束是,当CPU是x86时,内存不能是64GB。第二条约束是,当操作系统是Windows 2003时,IE的版本号要大于6.0。当PICT读取模型文件时,它会解析约束规则,并将其应用于测试用例生成过程。生成的测试用例集既满足对有效取值组合的覆盖,又不包含无效取值组合。

3. 小心卫哨(Guard)语句

许多软件用卫哨语句来“过滤”无效的输入。例如,在如下代码中,if语句会“过滤”掉所有A<=0的输入。

int func(int A, int B, int C)
{
    if (A <= 0) return ERROR;
    
    ...
}

如果我没有读过代码,没有仔细分析规格说明,我可能会制定如下的模型文件。在该模型中,A的取值是-1, 0, 1。

A: -1, 0, 1
B: -1, 0, 1
C: -1, 0, 1

利用上述模型,所生成的测试用例集包含9条测试用例。

A       B       C
0       1       -1
1       -1      1
-1      0       -1
1       1       0
-1      -1      0
0       0       1
1       -1      -1
-1      1       1
0       -1      0
1       0       0

在这9条测试用例中,有6条测试用例会被if语句过滤掉,因为其中A<=0。只有3条测试用例,能够执行后续逻辑,这意味着只有1/3的B和C的取值组合被真正地覆盖。这个例子表明,如果忽视了卫哨语句对执行流的中断,组合测试用例集将不能达成两因素或多因素覆盖的目标 。

面对此类问题,测试人员要仔细阅读规格说明或源代码,发现会导致执行流中断的“负面”(Negative)取值。我通常将负面取值从模型中排除,将因素的取值置于正常执行流的范围。例如,对于上述被测试函数func,将模型文件定义为:

A: 1, 10, 100
B: -1, 0, 1
C: -1, 0, 1

在生成测试用例集之后,再加入一条的测试用例(A: 0,B: 0, C: 0)。原模型生成的测试用例可以“通过”卫哨语句,覆盖因素A、B、C的两两取值组合;附加的测试用例可以覆盖卫哨语句的“过滤”功能。

另一种方法,是在PICT的模型中,用特殊符号"~"标记出非法(invalid)值。例如,在如下模型中,参数A的取值0被标记为非法。

A: ~0, 1, 10
B: -1, 0, 1
C: -1, 0, 1

PICT会保证所有有效值的取值组合都会被覆盖,此外任意非法值与有效值的组合也会被覆盖。以上模型将生成如下测试用例集。

A       B       C
1       1       -1
1       0       1
10      -1      -1
1       -1      0
10      0       -1
10      -1      1
10      0       0
10      1       0
1       1       1
~0      0       -1
~0      -1      0
~0      1       1

如果很清楚被测试对象的实现逻辑,使用第一种方法可以生成规模较小的测试用例集,因为它几乎不考虑非法值与有效值的组合。如果只是从规格说明中了解到程序可能存在卫哨语句,那么用第二种方法可以生成更“安全”的测试用例集。

4. 考虑采用多因素组合测试

在软件测试过程中,最先找到的缺陷往往处于程序的“主干”上,程序执行很容易覆盖相应的语句或状态;被遗漏的缺陷常常位于程序的“末枝”,程序执行需要满足特殊条件才能覆盖相应的语句或状态。这是软件测试的经验之谈,也提示两因素组合测试也许不能发现隐藏在“末枝”中的缺陷。一些路径需要多个因素满足一定取值组合才能被覆盖,然而两因素组合测试不能保证测试用例集可以覆盖这些组合。因此,在测试资源允许的情况下,引入多因素组合覆盖有可能进一步提高错误发现率。

微软的软件测试之道》建议从两因素组合测试开始,逐渐提高组合维度,直至6因素组合测试,因为有研究表明6因素组合测试可以发现绝大多数的程序缺陷。但是,随着组合维度的提高,测试用例数呈爆炸式增长。除非测试用例是由测试先知(Test Oracle)自动化执行,否则几乎没有团队能够完成6因素组合测试。在测试实践中,3因素组合测试可能是比较实际的选择。

在PICT中,有两种方法引入多因素组合测试。第一种方法是在命令行上使用参数"/o:N"。例如,在命令行上执行"pict.exe model.txt /o:3",就可以生成三因素组合测试用例集。另一种更加实用的方法是在模型文件中定义“子模型”(Sub-Models)。在PICT的帮助文件中有如下的模型文件定义。

PLATFORM:  x86, ia64, amd64
CPUS:      Single, Dual, Quad
RAM:       128MB, 1GB, 4GB, 64GB
HDD:       SCSI, IDE
OS:        NT4, Win2K, WinXP, Win2K3
IE:        4.0, 5.0, 5.5, 6.0
APP:       SQLServer, Exchange, Office
 
{ PLATFORM, CPUS, RAM, HDD } @ 3
{ OS, IE } @ 2

根据该模型文件,PICT将对PLATFORM, CPUS, RAM, HDD实施3因素组合覆盖,具体的生成策略可参加下图。PICT在两因素组合覆盖的基础上,对某些因素实施多因素组合覆盖,这有助于实现更灵活的测试策略。

Clipboard01
 

5. 考虑在回归测试中引入随机种子

有些团队将组合测试用例集加入回归测试,在软件生命周期中复地执行。我建议,不妨每次生成新的组合测试用例集,一方面满足两因素或多因素覆盖的测试标准,另一方面扩大测试对程序状态空间的覆盖。如果每次都使用相同的测试用例,测试用例可能只是反复执行相同的路径,覆盖相同的状态空间,也许不能发现隐藏的缺陷。如果每次都用新的测试用例,随着回归次数的增长,测试执行可以执行更多的路径,覆盖更广大的状态空间,发现隐藏缺陷的概率也会提高。

在PICT中,参数"/r[:N]"可以为测试用例生成引入随机种子(N是作为随机种子的整数),以生成不同的测试用例。例如,执行如下命令,就可以当前日期为种子,生成测试用例集。第一条语句从当前日期中获得年、月、日信息(%date的格式随Windows系统设置而变化,该语句只适用于特定Windows系统),放入变量seed中。第二条语句以变量seed为种子,生成测试用例。于是,测试用例集的具体内容随日期变化,在保证两因素覆盖的前提下,扩大了对被测试应用的状态覆盖。

set seed=%date:~4,2%%date:~7,2%%date:~10,4%
pict model.txt /r:%seed%

 

6. 要覆盖最重要的取值组合

关于组合测试,James Bach写过一篇非常好的文章:Pairwise Testing: A Best Practice That Isn't。他指出组合测试可能会错过最重要的取值组合。

image

上图是Word 2010“高级”设置的局部。为了测试Word在不同设置下的行为,可以将一个单选框视为一个因素,对所有因素生成组合测试用例集。但是该测试用例集很可能没有覆盖Word的默认设置。不幸的是,大多数用户几乎不修改默认配置,测试用例集没有覆盖最常用、也是最重要的取值组合。这揭示了组合测试的一个潜在风险:如果测试人员不仔细分析被测试对象,只依赖组合测试工具,他可能错过有价值的测试用例。

为了避免漏测,测试人员应该利用领域知识和测试技能,发掘出一批他认为必须测试的取值组合。然后,基于这些取值组合生成组合测试用例集。对于Word的“高级”设置,可以考虑如下方案。

  1. 将默认设置对应的因素取值组合加入核心用例集。
  2. 对于默认设置,每次修改其中一个单选框,获得一批取值组合。将它们加入核心用例集。
  3. 考虑到大多数用户不修改默认设置或只修改1个设置,核心用例集已经“足够好”,可以将其用于测试执行。
  4. 如果还有测试资源,可以将核心用例集作为“种子”,利用PICT生成组合测试用例集。例如,命令"pict.exe model.txt /e:seedrows.txt"生成的组合测试用例集就包含种子文件(seedrows.txt)所指定的取值组合。

7. 组合测试只是测试工具箱中的一员

组合测试最适用的场景是配置测试,包括硬件兼容性测试、浏览器兼容性测试等。在配置测试中,待组合的配置项天然就是可枚举的离散值,不存在划分等价类、从等价类中选择可用值等手工操作,避免了测试者引入的错误。在配置测试中,大部分缺陷是由两个配置项不兼容所导致的,所以组合测试的错误检测能力较强。

对于功能测试,我倾向于将组合测试作为探索式测试的补充。在一轮测试中,我建议按照如下步骤应用组合测试。

  1. 实施探索式测试,迭代地展开测试活动:阅读规格说明,运行被测试程序,应用不同的测试策略反复尝试,标记已经探索过的区域,记录已发现的缺陷。在这一过程中,可以获得一批高质量的测试用例,形成回归测试用例集。通常这批回归用例包括两类测试用例。第一类是正面(Positive)用例,它们检查了程序在主流场景(Scenario)下的行为。只要它们通过,我就有自信认为程序可以满足正常使用的需要。第二类是负面(Negative)用例,它们往往来自暴露了程序缺陷的探索式测试。这些用例检查了程序在极限或罕见场景中的行为,是程序在现实世界中正常工作的保证。
  2. 实施组合测试。基于探索式测试,我已经知晓某些因素的取值组合是很重要的。我可以将其传递给PICT,让它基于已有的测试用例生成组合测试用例集。例如,在命令行上执行"pict.exe model.txt /e:rows.txt",就可以基于文件rows.txt,生成测试用例集,以完成两因素组合覆盖。
  3. 通过阅读代码或运行测试覆盖率工具,以发现测试遗漏。寻找未被发现的卫哨语句,修改测试策略,再次测试。寻找未被测试覆盖的语句或分支,增加新的测试用例,以提高代码覆盖率。

组合测试只是测试工具箱中的一员。只有将它和其他测试方法结合,将它与被测试软件的特点结合,才能发挥其全部功效。