-
1 电子教材
-
2 PPT
基本路径测试也是一种常用的白盒测试用例设计方法。在前一章学习的逻辑覆盖的例子只有4条路径,但是在实际工作中,即使一个不太复杂的程序,路径数量往往也是较多的,要在测试中覆盖所有的路径是不现实的。而基本路径测试的方法就是一种简化路径数的测试方法,它是在分析程序控制流图的基础上,通过分析控制构造的环路复杂性,导出基本可执行路径集合,从而设计测试用例的方法。设计出的测试用例要保证程序的每一个可执行语句至少执行一次。
控制流图是描述程序控制流的一种图示方法。控制流图是退化的程序流程图,它是把程序流程图中的每个处理符号都退化成一个结点,原来连接不同处理符号的流线变成连接不同结点的有向弧。这种图仅描述程序内部的控制流程,完全不表现对数据的具体操作以及分支和循环的具体条件。因此,它往往把一个简单的if语句与循环语句的复杂性看成是一样的,把嵌套的if语句与case语句的复杂性看成是一样的。
程序中的基本控制结构对应的图形符号如图17-6所示。
图中的圆圈称为控制流图的一个结点,它表示一个或多个无分支的源代码语句。
在绘制控制流图前,一般先画出程序的流程图,再把流程图映射成控制流图。

图17-6 控制结构示意图
例如,有下面的函数:
void Sort(int iRecordNum,int iType)
1{
2 int x=0;
3 int y=0;
4 while (iRecordNum--> 0)
5 {
6 if(0= =iType)
7 x=y+2;
8 else
9 if(1= =iType)
10 x=y+10;
11 else
12 x=y+20;
13 }
14 }
画出其流程图如图17-7所示。

图17-7 流程图
把流程图映射成对应的控制流图如图17-8所示。

图17-8 控制流图
映射关系如下:
一组顺序处理框可以映射为一个单一结点。控制流图中的箭头表示了控制流的方向,类似于流程图中的流线,一条边必须终止于一个结点,但在选择或多分支结构中分支的汇聚处,即使没有可执行语句也应该绘制一个结点。边和结点圈定的部分叫做区域,当对区域计数时,图形外的区域也应记为一个区域。
如果判断条件表达式复合条件的,即条件表达式是由一个或多个逻辑运算符连接的逻辑表达式,则需要修改复合条件的判断为一系列只有单个条件嵌套的判断。
如下语句:
1 if a or b
2 x
3 else
4 y
则需要像图17-9那样绘制:

图17-9 复合语句的绘制方法
程序的环路复杂性是通过McCabe复杂性度量方法(简称McCabe法)来计算的,程序复杂性度量值称为环路复杂度,也称为圈复杂度。
McCabe方法是基于程序的控制流来计算的,从程序的控制流图可导出程序基本路径集合中的独立路径数,这是确保程序中每个可执行语句至少执行一次所必需的测试用例数目的上界。独立路径是指包括一组以前没有处理的语句或条件的一条路径。从控制流图来看,一条独立路径是只要包含一条在其他独立路径中从没有用过边的路径。
根据前面的控制流图,可得出4个独立的路径:
路径1: 4-14
路径2: 4-6-7-13-4-14
路径3: 4-6-9-10-13-4-14
路径4: 4-6-9-12-13-4-14
一条新的路径必须包含一条新边。例如路径:4-6-9-10-13-4-6-7-14就不能作为一条独立路径,因为它只是已经有的两个独立路径的组合,没有通过新的边。上面的4个独立路径组成了控制流图的一个基本路径集。
那么如何知道控制流图中到底有多少个独立路径呢?有以下两种方法计算圈复杂度,也就是计算独立路径数。
第一种方法流图中区域的数量对应于环型的复杂性,如图17-10所示,是上面例子中的区域,在区域中使用数字标识。注意标记区域时,图形外的区域也应标记为一个区域。

图17-10 控制流图标记区域
第二种方法给定流图G的圈复杂度——V(G),定义为V(G)=E-N+2, E是流图中边的数量,N是流图中结点的数量。
根据算法公式计算例子中的圈复杂度,图中共有10个边,8个结点,则G=10-8+2=4。
注意:有的参考书籍提出可以用判断结点的数量加1得到图复杂度的方法,但是这种方法对于switch语句并不适用。但是在没有switch语句的模块中可以这样计算。
通过计算得到的圈复杂度,可以确定图中包含的独立路径数。只要设计的测试用例能够确保这些基本路径的执行,就可以使得程序中的每个可执行语句至少执行一次,每个条目的取真和取假分支也能得到测试。但是不同的测试人员,得到的独立路径集可能是不同的。
当分支和循环的数目增加时,程序中的环路也随之增加,因此McCabe环路复杂度的度量值实际上是为软件测试的难易程度提供了一个定量度量的方法,同时也间接地表示了软件的可靠性。实验表明,程序中潜在的缺陷数以及为了发现和修改这些缺陷所需的时间与McCabe环行复杂度值有明显的关系。通过实验,可总结如下:
Ø McCabe环路复杂度取决于程序控制结构的复杂度。当程序的分支数目和循环数目增加时,其复杂度也增加。环路复杂度与程序中覆盖的路径条数有关。
Ø 环路复杂度是可加的。多个模块的复杂度是每个模块复杂度的和。
McCabe建议,单个模块的复杂度最好不超过10,当超过时,代码中的缺陷数会成倍增长。
为了确保基本路径集中的每一条路径的执行,根据判断结点给出的条件,选择适当的数据以保证每条路径可以被测试到。满足上面例子基本路径集的测试用例的设计如表17-6所示。
表17-6 所设计的测试用例
通过路径 | 输入数据 | 预期结果 |
4-14 | iRecordNum=0或iRecordNum<0的某个值 | x=0 |
4-6-7-14 | iRecordNum=1,iType=0 | x=2 |
4-6-8-10-13-14 | iRecordNum=1,iType=1 | x=10 |
4-6-8-10-11-13-14 | iRecordNum=1,iType=2 | x=20 |
因为被测试的模块是一个函数,所以要加载测试用例,必须编写一个驱动模块,即由一个主程序来调用该函数。为了采集结果,可以用以下两种方法。
Ø 修改被测试函数,使其返回验证的预期结果。
Ø 添加一个全局变量,在程序中把验证的值赋值给该全局变量。
因为第一种方式对程序影响比较大,我们采用第二种方式。
修改后的源程序模块如下:
void Sort(int iRecordNum,int iType)
1 {
2 int x=0;
3 int y=0;
4 while (iRecordNum-->0)
5 {
6 if (0==iType)
7 x=y+2;
8 else if(1==iType)
9 x=y+10;
10 else
11 x=y+20;
12 }
13 ret=x;
14 }
为了加载测试用例,必须编写一个主程序,调用上面的模块。
主程序如下:
void Sort(int iRecordNum,int iType);
int ret;
int main ( )
{
sort (0,0);
if(0==ret)
printf("pass") ;
else
printf("fail");
return 0;
}
上面计算基本路径集的算法都是手工的方式,对于复杂控制流图的基本路径统计就相当困难了,有一种称为图形矩阵(graph matrix)的数据结构能解决这个问题。
利用图形矩阵实现自动确定一个基本路径集。一个图形矩阵是一个方阵,其行/列数控制流图中的结点数,每行和每列依次对应到一个被标识的结点,矩阵元素对应到结点间的连接(即边)。
在图中,控制流图的每一个结点都用数字加以标识,每一条边都用字母加以标识。如果在控制流图中第i个结点到第j个结点有一个名为x的边相连接,则在对应的图形矩阵中第i行/第j列有一个非空的元素x。
上面的程序例子的图形矩阵如下:
4 | 6 | 7 | 8 | 10 | 11 | 13 | 14 | |
4 | 1 | 1 | ||||||
6 | 1 | 1 | ||||||
7 | 1 | |||||||
8 | 1 | 1 | ||||||
10 | 1 | |||||||
11 | 1 | |||||||
13 | 1 | |||||||
14 |
在上面的矩阵图中,连接权值为“1”表示存在一个连接,不填的结点表示不存在连接,可填充“0”,也可省略。在图中如果一行有两个或更多的元素“1”,则这行所代表的结点一定是一个判定结点,通过连接矩阵中有两个以上(包括两个)元素为“1”的个数,就可以得到确定该图圈复杂度的另一种算法。环形复杂性的算法是判定结点数加1。
该矩阵还可以有其他作用:对每个矩阵项加入连接权值(link weight),图形矩阵就可以用于在测试中评估程序的控制结构,连接权值为控制流提供了另外的信息。最简单的情况是,连接权值是1(存在连接)或0(不存在连接),但是,连接权值可以赋予更有趣的属性:
1) 执行连接(边)的概率。
2) 穿越连接的处理时间。
3) 穿越连接时所需的内存。
4) 穿越连接时所需的资源。
本章主要介绍了白盒测试的相关基础知识,白盒测试技术——逻辑覆盖、循环测试、基本路径测试等,及具体的白盒测试用例设计方法。此外用于白盒测试的测试工具可以分为内存泄漏检查工具、代码覆盖率检查工具、性能测试工具等,由于白盒测试偏重于具体实现,所以并不能够检查出程序中所有的缺陷,黑盒测试也是同样如此。很多时候,将二者结合起来进行用例设计,可以收到更好的效果。

