您还未登录! 登录 | 注册 | 帮助  

您的位置: 首页 > 软件测试技术 > 测试用例 > 正文

白盒测试用例设计方法

发表于:2022-10-31 作者:半道出家的lv 来源:知乎

白盒测试常见的用例设计方法有:代码检查法、静态结构分析法、静态质量度量法、逻辑覆盖法、基本路径覆盖测试法、域测试、符号测试。

(一)代码检查法


代码检查包括桌面检查、代码审查和走查等,主要检查代码和设计的一致性,代码对标准的遵循、可读性,代码逻辑表达的正确性,代码结构的合理性等方面;发现违背程序编写标准的问题,程序中不安全、不明确和模糊的部分,找出程序中不可移植部分、违背程序编程风格的内容,包括变量检查、命名和类型审查、程序逻辑审查、程序语法检查和程序结构检查等内容。

代码检查方法:
1、代码检查法
(1)桌面检查:这是一种传统的检查方法,由程序员检查自己编写的程序。程序员在程序通过编译之后,对源程序代码进行分析、检验,并补充相关文档,目的是发现程序中的错误。由于程序员熟悉自己的程序及其程序设计风格,桌面检查由程序员自己进行可以节省很多的检查时间,但应避免主观片面性
(2)代码审查

由若干程序员和测试员组成一个审查小组,通过阅读、讨论和争议,对程序进行静态分析的过程。代码审查分两步:第一步,小组负责人提前把设计规格说明书、控制流程图、程序文本及有关要求、规范等分发给小组成员,作为审查的依据。小组成员在充分阅读这些材料后,进入审查的第二步,召开程序审查会。在会上,首先由程序员逐句简介程序的逻辑。在此过程中,程序员或其他小组成员可以提出问题,展开讨论,审查错误是否存在。实践表明,程序员在讲解过程中能发现许多原来自己没有发现的错误,而讨论和争议则促进了问题的暴露。

在会前,应当给审查小组每个成员准备一份常见错误的清单,把以往所有可能发生的常见错误罗列出来,供与会者对照检查,以提高审查的失效。这个常见的错误清单也成为检查表,它把程序中可能发生的各种错误进行分类,对每一类错误列出尽可能多的典型错误,然后把它们制成表格,供再审查时使用
(3)走查

与代码审查基本相同,分为两步,第一步也是把材料分给走查小组的每个成员,让他们认真研究程序,然后再开会。开会的程序与代码审查不同,不是简单地读程序和对照错误检查表进行检查,而是让与会者“充当”计算机,即首先由测试组成员为所测试程序准备一批有代表性的测试用例,提交给走查小组。走查小组开会,集体扮演计算机角色,让测试用例沿程序的逻辑运行一遍,随时记录程序的踪迹,供分析和讨论用。
人们借助测试用例的媒介作用,对程序的逻辑和功能提出各种疑问,结合问题开展热烈的讨论和争议,能够发现更多的问题。
代码检查应在编译和动态测试之前进行,在检查前,应准备好需求描述文档、程序设计文档、程序的源代码请当、代码编译标准和代码缺陷检查表等。在实际使用中,代码检查能快速找到缺陷,发现30%~70%的逻辑设计和编码缺陷,而且代码检查看到的问题本身而非征兆。但是代码检查非常耗费时间,而且代码检查需要知识和经验的积累。代码检查可以使用测试软件进行自动化测试,以利于提高测试效率,降低劳动强度,或者使用人工进行测试,以充分发挥人力的逻辑思维能力
2、代码检查项目
变量交叉引用表;标号的交叉引用表;检查子程序、宏、函数;等价性检查;常量检查;标准检查;风格检查;比较控制流;选择、激活路径;补充文档
根据检查项目可以编制代码规则、规范和检查表等作为测试用例,如编码规范、代码检查规范、缺陷检查表等
3、编码规范
编码规范是指程序编写过程中必须遵循的规则,一般会详细制定代码的语法规则、语法格式等
4、代码检查规范
在代码检查中,需要依据被测软件的特点,选用适当的标准与规则规范。在使用测试软件进行自动化代码检查时,测试工具一般会内置许多的编码规则。在自动化测试基础上使用桌面检查、代码走查、代码审查等人工检查的方法仔细检查程序的结构、逻辑等方面的缺陷
5、缺陷检查表
在进行人工代码检查时,代码缺陷检查表是我们用到的测试用例。
代码缺陷检查表中一般包括容易出错的地方和在以往的工作中遇到的典型错误
 

(二)静态结构分析法


程序的结构形式是白盒测试的主要依据。研究表明程序员38%的时间花费在理解软件系统上,因为代码以文本格式被写入多重文件中,这是很难阅读理解的,需要其它一些东西来帮助人们阅读理解,如各种图表等,而静态结构分析满足了这样的需求。

在静态结构分析中,测试者通过使用测试工具分析程序源代码的系统结构、数据结构、内部控制逻辑等内部结构,生成函数调用关系图、模块控制流图、内部文件调用关系图、子程序表、宏和函数参数表等各类图形图标,可以清晰地标识整个软件系统的组成结构,使其便于阅读和理解,然后可以通过分析这些图标,检查软件有没有存在缺陷或错误。

其中函数调用关系图通过应用程序中各函数之间的调用关系展示了系统的结构。通过查看函数调用关系图,可以检查函数之间的调用关系是否符合要求,是否存在递归调用,函数的调用曾是是否过深,有没有存在独立的没有被调用的函数。从而可以发现系统是否存在结构缺陷,发现哪些函数是重要的,哪些是次要的,需要使用什么级别的覆盖要求......

模块控制流图是与程序流程图相类似的由许多节点和连接节点的边组成的一种图形,其中一个节点代表一条语句或数条语句,边代表节点间控制流向,它显示了一个函数的内部逻辑结构。模块控制流图可以直观地反映出一个函数的内部逻辑结构,通过检查这些模块控制流图,能够很快发现软件的错误与缺陷

(三)静态质量度量法


根据ISO/IEC
9126质量模型作为基础,我们可以构造质量度量模型,用于评估软件的各个方面。该模型从上到下分为3层:质量因素(Factors)、分类标准(Criteria)和度量规则(metrics)。其中质量因素对应ISO
9126质量模型的质量特性,分类标准对应ISO 9126质量模型的子特性,度量规则用于规范软件的各种行为属性。以下例子按照可维护性进行分析。
1、度量规则
度量规则使用了代码行数、注释频度等参数度量软件的各种行为属性
2、分类标准

软件的可维护性采用以下四个分类标准来评估:可分析性(ANALYZABILITY)、可修改性(CHANGEABILITY)、稳定性(STABILITY)、可测性(TESTABILITY)。每个分类标准由一系列度量规则组成,各个规则分配一个权重,由规则的取值与权重值计算出每个分类标准的取值。
function_TESTABILITY_DRCT_CALLS+LEVL+PATH+PARA
3、质量因素
质量因素的取值与分类标准的计算方式类似:依据各分类标准取值组合权重方法计算.
function_MAINTAINABILITY=function_ANALYZABILITY
+function_CHANGEABILITY
+function_ATABILITY
+function_TESTABILITY

(四)逻辑覆盖法


逻辑覆盖是以程序内部的逻辑结构为基础的设计测试用例的技术。
根据覆盖目标的不同和覆盖源程序语句的详尽程度,逻辑覆盖又可分为:

1. 语句覆盖(SC)
2. 判定覆盖(DC)
3. 条件覆盖(CC)
4. 条件/判定覆盖(CC)
5. 条件组合覆盖(MCC)
6. 修正判定条件覆盖(MCDC)
7. 点覆盖
8. 边覆盖
9. 路径覆盖

几种逻辑覆盖标准发现错误的能力呈由弱至强的变化。

下面我们来逐一举例详解:

1语句覆盖(SC):


语句覆盖是指选择足够的测试用例,使得运行这些测试用例时,被测程序的每一个语句至少执行一次,其覆盖标准无法发现判定中逻辑运算的错误.

我们看下面的被测试代码:
int foo(int a, int b)
{
return a / b;
}
假如我们的测试人员编写如下测试案例:
TeseCase: a = 10, b = 5

测试人员的测试结果会告诉你,他的代码覆盖率达到了100%,并且所有测试案例都通过了。然而遗憾的是,我们的语句覆盖率达到了所谓的100%,但是却没有发现最简单的 Bug,比如,当我让b=0时,会抛出一个除零异常。

简言之,语句覆盖,就是设计若干个测试用例,运行被测程序,使得每一可执行语句至少执行一次。这里的“若干个”,意味着使用测试用例越少越好。

语句覆盖率的公式可以表示如下:
语句覆盖率=可执行的语句总数/被评价到的语句数量 x 100%

2判定覆盖(DC)


判定覆盖是设计足够多的测试用例,使得程序中的每一个判断至少获得一次“真”和一次“假”,即使得程序流程图中的每一个真假分支至少被执行一次。
但若程序中的判定是有几个条件联合构成时,它未必能发现每个条件的错误。

例:
int a,b;
if(a || b)
执行语句1
else
执行语句2

要达到这段程序的判断覆盖,我们采用测试用例:
1)a = true , b = false;
2)a = false, b = false

3条件覆盖(CC)


条件覆盖是指选择足够的测试用例,使得运行这些测试用例时,判定中每个条件的所有可能结果至少出现一次,但未必能覆盖全部分支.
例:
int a,b;
if(a || b)
执行语句1
else
执行语句2

要达到这段程序的条件覆盖,我们采用测试用例:
1)a = true , b = false ;
2)a = false, b = true

4判定/条件覆盖(CDC)


判定/条件覆盖是使判定中每个条件的所有可能结果至少出现一次,并且每个判定本身的所有可能结果也至少出现一次。
例:
int a,b;
if(a || b)
执行语句1
else
执行语句2

要达到这段程序的判定/条件覆盖,我们采用测试用例:
1)a = true , b = true;
2)a = false, b = false

5条件组合覆盖(MCC)


选择足够的测试用例,使得每个判定中条件的各种可能组合都至少出现一次。显然,满足“条件组合覆盖”的测试用例是一定满足“判定覆盖”、“条件覆盖”和“判定/条件覆盖”的。

例:
int a,b;
if(a || b)
执行语句1
else
执行语句2

要达到这段程序的判定/条件覆盖,我们采用测试用例:
1)a = true , b = true;
2)a = false, b = false
3)a = true, b = false
4)a = false, b = ture

6修正判定条件覆盖(MC/DC)


MC/DC首先要求实现条件覆盖、判定覆盖,在此基础上,对于每一个条件C,要求存在符合以下条件的两次计算:
1)条件C所在判定内的所有条件,除条件C外,其他条件的取值完全相同;
2)条件C的取值相反;
3)判定的计算结果相反。

核心意思是每个条件都要独立影响判定结果。为什么说“两次计算”,而不是“两个用例”呢?当循环中有判定时,一个用例下同一判定可能被计算多次,每次的条件值和判定值也可能不同,因此,一个用例就可能完成循环中判定的MC/DC。



MC/DC是条件组合覆盖的子集。条件组合覆盖要求覆盖判定中所有条件取值的所有可能组合,需要大量的测试用例,实用性较差。MC/DC具有条件组合覆盖的优势,同时大幅减少用例数。满足MC/DC的用例数下界为条件数+1,上界为条件数的两倍,例如,判定中有三个条件,条件组合覆盖需要8个用例,而MC/DC需要的用例数为4至6个。如果判定中条件很多,用例数的差别将非常大,例如,判定中有10个条件,条件组合覆盖需要1024个用例,而MC/DC只需要11至20个用例。

下面是MC/DC的示例:

代码:
int func(BOOL A, BOOL B, BOOL C)
{
if(A && (B || C))
return 1;
return 0;
}

用例:
 

 


对于条件A,用例1和用例2,A取值相反,B和C相同,判定结果分别为1和0;
对于条件B,用例1和用例3,B取值相反,A和C相同,判定结果分别为1和0;
对于条件C,用例3和用例4,C取值相反,A和B相同,判定结果分别为0和1。

9路径覆盖(PC)


MC/DC被称为“最严格的标准”,但这种说法是将条件组合覆盖和路径覆盖排除在外为基础的。MC/DC显然不如条件组合覆盖严格,但是条件组合覆盖需要太多用例,实际应用中难以做到,所以排除,那么,路径覆盖是否也难以做到?使用先进的工具,对于一般的代码,实现路径覆盖还是可能的。另外,路径代表了从函数入口到出口的所有可能的代码组合,这些组合会不会出问题?只有路径覆盖能发现,这与MC/DC侧重于判定内的条件的组合关系是完全不同的。

MC/DC与路径覆盖的侧重点不同,两者都有其优势和局限性,如果组合起来,优势互补,形成“MC/DC-路径覆盖”,就是真正意义上的“最严格的标准”了。

有些程序,路径数量可能大得惊人,可用以下规则和方法减少路径数量:
计算路径时,不考虑循环的次数,将循环结构视为循环体“至少执行一次”和“从不执行”两个分支;
不考虑条件的计算结果只考虑判定的计算结果,条件间的组合关系由条件覆盖、C/DC和MC/DC负责;
一个分支如果不可达,通过该分支的所有路径也不可达,可以让工具自动排除;
当代码很复杂时,理想的处置方式是将部分代码独立为函数,如果做不到,可以让工具来模拟,即在逻辑结构图中,将部分代码临时屏蔽,被屏蔽的代码视为一个函数调用。交替屏蔽可以既减少路径数量,又保证路径覆盖的效果。

对于一般复杂度的代码,采用以上规则和方法后,路径数量和用例数量可以维持在一个现实可覆盖的的范围内。



路径覆盖的主要缺陷是:不相关的逻辑块会组合出大量没有意义的路径。一个函数的路径,可能达到几万条甚至几百万条。如果路径超过100条,通常路径覆盖就没有意义了。对于一般企业来说,建议用MC/DC作为统一的覆盖标准,只有特别关键的代码,才要求完成“MC/DC-路径覆盖”。

路径覆盖要求设计足够多的测试用例,在白盒测试法中,覆盖程度最高的就是路径覆盖,因为其覆盖程序中所有可能的路径。
对于比较简单的小程序来说,实现路径覆盖是可能的,但是如果程序中出现了多个判断和多个循环,可能的路径数目将会急剧增长,以致实现路径覆盖是几乎不可能的。

(五)基本路径测试法


基本路径测试法是在程序控制流图的基础上,通过分析控制构造的环路复杂性,导出基本可执行路径集合,从而设计测试用例的方法。
  设计出的测试用例要保证在测试中程序的语句覆盖100%,条件覆盖100%。
  在程序控制流图的基础上,通过分析控制构造的环路复杂性,导出基本可执行路径集合,从而设计测试用例。包括以下4个步骤和一个工具方法:
  1.程序的控制流图:描述程序控制流的一种图示方法。
  2.程序圈复杂度:McCabe复杂性度量。从程序的环路复杂性可导出程序基本路径集合中的独立路径条数,这是确定程序中每个可执行语句至少执行一次所必须的测试用例数目的上界。
  3.导出测试用例:根据圈复杂度和程序结构设计用例数据输入和预期结果。
  4.准备测试用例:确保基本路径集中的每一条路径的执行。
  工具方法:
  图形矩阵:是在基本路径测试中起辅助作用的软件工具,利用它可以实现自动地确定一个基本路径集。
  程序的控制流图:描述程序控制流的一种图示方法。
  圆圈称为控制流图的一个结点,表示一个或多个无分支的语句或源程序语句

 


流图只有二种图形符号:
  图中的每一个圆称为流图的结点,代表一条或多条语句。
  流图中的箭头称为边或连接,代表控制流
  任何过程设计都要被翻译成控制流图。
  如何根据程序流程图画出控制流程图?
  在将程序流程图简化成控制流图时,应注意:
  1)在选择或多分支结构中,分支的汇聚处应有一个汇聚结点。
  2)边和结点圈定的范围叫做区域,当对区域计数时,图形外的区域也应记为一个区域。
如下图所示



3)如果判断中的条件表达式是由一个或多个逻辑运算符 (OR, AND, NAND, NOR)连接的复合条件表达式,则需要改为一系列只有单条件的嵌套的判断。
  例如:
  1 if a or b
  2 x
  3 else
  4 y
  对应的逻辑为:


独立路径:至少沿一条新的边移动的路径


基本路径测试法的步骤:
  第一步:画出控制流图
  流程图用来描述程序控制结构。可将流程图映射到一个相应的流图(假设流程图的菱形决定框中不包含复合条件)。在流图中,每一个圆,称为流图的结点,代表一个或多个语句。一个处理方框序列和一个菱形决测框可被映射为一个结点,流图中的箭头,称为边或连接,代表控制流,类似于流程图中的箭头。一条边必须终止于一个结点,即使该结点并不代表任何语句(例如:if-else-then结构)。由边和结点限定的范围称为区域。计算区域时应包括图外部的范围。


画出其程序流程图和对应的控制流图如下


第二步:计算圈复杂度
  圈复杂度是一种为程序逻辑复杂性提供定量测度的软件度量,将该度量用于计算程序的基本的独立路径数目,为确保所有语句至少执行一次的测试数量的上界。独立路径必须包含一条在定义之前不曾用到的边。
  有以下三种方法计算圈复杂度:
  流图中区域的数量对应于环型的复杂性;
  给定流图G的圈复杂度V(G),定义为V(G)=E-N+2,E是流图中边的数量,N是流图中结点的数量;
  给定流图G的圈复杂度V(G),定义为V(G)=P+1,P是流图G中判定结点的数量。


第三步:导出测试用例
  根据上面的计算方法,可得出四个独立的路径。(一条独立路径是指,和其他的独立路径相比,至少引入一个新处理语句或一个新判断的程序通路。V(G)值正好等于该程序的独立路径的条数。)
  ü路径1:4-14
  ü路径2:4-6-7-14
  ü路径3:4-6-8-10-13-4-14
  ü路径4:4-6-8-11-13-4-14
  根据上面的独立路径,去设计输入数据,使程序分别执行到上面四条路径。
  o第四步:准备测试用例
  为了确保基本路径集中的每一条路径的执行,根据判断结点给出的条件,选择适当的数据以保证某一条路径可以被测试到,满足上面例子基本路径集的测试用例是:



举例说明:
  例:下例程序流程图描述了最多输入50个值(以–1作为输入结束标志),计算其中有效的学生分数的个数、总分数和平均值。


步骤1:导出过程的流图。


步骤2:确定环形复杂性度量V(G):
  1)V(G)= 6 (个区域)
  2)V(G)=E–N+2=16–12+2=6
  其中E为流图中的边数,N为结点数;
  3)V(G)=P+1=5+1=6
  其中P为谓词结点的个数。在流图中,结点2、3、5、6、9是谓词结点。
  步骤3:确定基本路径集合(即独立路径集合)。于是可确定6条独立的路径:
  路径1:1-2-9-10-12
  路径2:1-2-9-11-12
  路径3:1-2-3-9-10-12
  路径4:1-2-3-4-5-8-2…
  路径5:1-2-3-4-5-6-8-2…
  路径6:1-2-3-4-5-6-7-8-2…
  步骤4:为每一条独立路径各设计一组测试用例,以便强迫程序沿着该路径至少执行一次。
  1)路径1(1-2-9-10-12)的测试用例:
  score[k]=有效分数值,当k < i ;
  score[i]=–1, 2≤i≤50;
  期望结果:根据输入的有效分数算出正确的分数个数n1、总分sum和平均分average。
  2)路径2(1-2-9-11-12)的测试用例:
  score[ 1 ]= – 1 ;
  期望的结果:average = – 1,其他量保持初值。
  3)路径3(1-2-3-9-10-12)的测试用例:
  输入多于50个有效分数,即试图处理51个分数,要求前51个为有效分数;
  期望结果:n1=50、且算出正确的总分和平均分。
  4)路径4(1-2-3-4-5-8-2…)的测试用例:
  score[i]=有效分数,当i<50;
  score[k]<0, k< i ;
 期望结果:根据输入的有效分数算出正确的分数个数n1、总分sum和平均分average。


连接权为“1”表示存在一个连接,在图中如果一行有两个或更多的元素“1”,则这行所代表的结点一定是一个判定结点,通过连接矩阵中有两个以上(包括两个)元素为“1”的个数,就可以得到确定该图圈复杂度的另一种算法。
 

(六)域测试法


域测试是一种基于程序结构的测试方法,基于对程序输入空间(域)的分析,选择测试点进行测试。
域测试主要测试如下错误:
1)域错误:程序的控制流存在错误,对于某一特定的输入可能执行的是一条错误路径,这种错误称为路径错误,也叫做域错误。
2)计算型错误:对于特定输入执行的路径正确,但赋值语句的错误导致输出结果错误,称为计算型错误。
3)丢失路径错误:由于程序中的某处少了一个判定谓词而引起的丢失路径错误。

(七)符号测试


符号测试的基本思想是允许程序的输入不仅仅是具体的数值数据,而且包括符号值,符号值可以是基本的符号变量值,也可以是符号变量值的表达式。