课件
测试覆盖标准
测试覆盖标准是衡量软件测试质量的一个重要指标,下面我们先了解一下它的相关概念。
- 测试需求:测试需求可以针对不同的软件制品来进行描述,比如说源代码、需求说明、设计文档等,如果我们要测试源代码的所有语句,那么每一条语句就是一个测试需求。
- 覆盖标准:测试标准是把测试需求施加在一个测试集上的一组规则,例如每一条语句都要执行就是一个覆盖标准。
- 测试覆盖:测试覆盖是指对于每一条测试需求,至少要有一个测试用例可以满足这个需求。
- 覆盖程度:由于完全测试是不现实的,所以我们使用覆盖程度或者覆盖率来说明测试所做的情况,也就是说一个测试用例集能够满足测试需求的比例
以现实生活中的一个例子来进一步解释上面的概念:
假设我们要测试一个装有糖豆的袋子,糖豆有六种口味和四种颜色,那么对于口味来说就有柠檬、开心果、梨子、哈密瓜、橘子和杏这6种口味的测试需求。对于颜色来说,有黄色、绿色、白色和橙色4种颜色的测试需求。我们可以选择口味这个标准,也可以选择颜色这个标准,那么用哪一种覆盖标准更好一些呢?如果我们选择颜色这个标准,那么用每一种颜色各一个糖豆来测试是可以达到百分之百的覆盖,但是这个时候会有2种口味没有覆盖到,如果选择口味这个标准,那么在覆盖了每一种口味的同时也就覆盖了颜色的标准,也就是说口味这个标准包含颜色标准。
在实际的项目里选择覆盖标准可能要更复杂一些,一般需要考虑三个方面的因素:
- 第一:是处理测试需求的难易程度;
- 第二:是生成测试的难易程度;
- 第三:是用测试发现缺陷的能力;
白盒测试技术
白盒测试是利用程序内部的逻辑结构,对程序的所有逻辑路径进行测试,那么为什么要做白盒测试呢?
先来看一个例子:
假设有一台面包机,从上面倒入面粉和水,开动机器之后下面出来的就是烤好的面包,这个机器的功能比较单一,接口也很清晰,输入的是面粉和水,输出的是面包。假设这个机器很多年没有使用,现在需要检查一下机器的情况,一种办法是直接把水和面粉加进去看它是否可以正常工作,这就是黑盒方法。但是这种情况下我们没有办法了解机器内部的情况,比如说零件是否生锈、是不是残存了很多脏东西等。还有一种比较好的办法,是把机器拆开检查和清洗内部的零件,然后再组装起来,把水和面粉加进去试一试,显然拆开检查就是白盒方法,再组装回来就是黑盒方法。
该例子说明单纯使用黑盒测试是不够的,还需要进行白盒测试。白盒测试对代码中逻辑关系的覆盖更为全面,做得好可以极大地增强产品的健壮性。在这一门课中主要介绍基于控制流的白盒测试技术。
基于控制流的白盒测试方法
控制流图
控制流图(CFG, Control Flow Graph)是一个过程或程序的抽象表示。
- 控制流图的节点是语句或语句的一部分,它的边表示语句的控制流;
- 控制流图是程序流程图的一个简化,它更突出了控制流的结构。
- 在控制流图里面,我们把所有顺序执行的语句看成是一个节点,对于判断分支来说判断条件是一个节点,这个节点根据判断的真和假产生两个不同的分支,在判断分支执行结束之后还会有一个合并的节点。
举例说明如何画出程序的控制流图。
这个程序的功能是从文件中读取成绩,然后再求出所有成绩的平均值,并打印输出。
程序的前三行都是顺序执行语句,我们把它看出是一个节点,后面 while 循坏产生一个判断分支,在循环的里面有一个 if 的判断分支,if 为真时,执行两个顺序语句,if 语句后面是一个读文件的语句,整个 while 循环结束后,还有一个判断分支,为真执行两个顺序语句,为假执行一个语句,这里标记的1、4、5、7和8都是可执行语句,也称为基本块:
根据上述对程序的分析,画出下图所示的控制流图:
图中,三个红色的菱形是三个分支节点,其余都是基本块组成的基本计算节点。
测试步骤
基于控制流的白盒测试方法主要包括以下步骤:
首先根据程序单元,画出程序的流程图,再用流程图得到控制流图,然后根据路径选择标准选择合适的路径,对于所选择的路径生成相应的测试数据,如果选择的路径可行那么就得到测试的输入数据,否则继续选择其他路径。
代码覆盖标准
白盒测试方法使用代码覆盖率作为测试的衡量指标。代码覆盖率描述的是代码被测试的比例和程度,如果我们只是编写测试用例进行测试,而不检查究竟哪些代码被测试过,哪些还没有测试过,就很容易形成测试的漏洞。通过代码覆盖率我们可以知道哪些代码没有被覆盖,然后再进一步地补充更多的测试用例。
6种类型
代码覆盖率通常包括下面6种类型:
下面以一个简单程序例子来讲解一下这六种覆盖类型:
左边是程序,右边是该程序的流程图,它一共有 5 个节点,其中有 2 个判断节点。
1、语句覆盖
它要求程序中每一个可执行语句至少要被执行一次。
这个例子中,如果我们选择 a 等于 2,b 等于 1,c 等于 6 这样一个测试用例,三个可执行语句,标记序号中的 2、4 和 5 都执行了一次,那么可以满足语句覆盖的要求。
但是如果判断条件有问题,比如说第一个条件判断中「与」被错误地写成「或」,显然这个测试用例是无法检查出来的。由于这种方法仅仅针对程序中显示存在的可执行语句进行测试,对于隐藏条件和可能达到的隐式逻辑分支是无法覆盖的,可以说语句覆盖是最弱的逻辑覆盖准则。
2、判定覆盖
也称分支覆盖,它要求程序中每一个判断的取真和取假分支至少经历一次,也就是判断的真、假值都要被覆盖。
这个例子中,输入 a 等于 2,b 等于 1,c 等于 6,这样一个测试用例何以同时覆盖判断 1 和判断 2 两个取真的分支。输入 a 等于 -2,b 等于 -1,c 等于 -3,这个测试用例同时覆盖了判断 1 和判断 2 取假的分支。
判定覆盖比语句覆盖要多几乎一倍的测试路径,当然也具有更强的测试能力,但是如果第二个条件判断中 a 等于 1 写成 a 小于 1,那么还是无法检测出来。
大部分判定语句是由多个逻辑条件组合而成的,如果仅仅是判断整个判断的最终结果,而忽略每一个条件的取值情况,那公就会遗漏部分的测试路径,显然判定覆盖具有比语句覆盖更强的测试能力,但任然是比较弱的逻辑覆盖。
3、条件覆盖
它要求每个判断中每一个条件的可能取值至少要满足一次。
这个例子中,输入 a=2, b=-1, c=-2,这个测试用例覆盖了a>0, b≤0, a>1, c≤1四个条件。输入a=-1, b=2, c=3,这个测试用例覆盖了a≤0, b>0, a≤1, c>1四个条件。这两个测试用例覆盖了两个判断的所有条件的可能取值,需要说明的是条件覆盖不一定包含判定覆盖,比如上面的测试用例就没有覆盖判断 1 的取真分支和判断 2 的取假分支。
显然条件覆盖只能保证每一个条件至少有一次为真,而没有考虑到整个判定的结果。
4、判定条件覆盖
它要求判断中所有条件的可能取值至少执行一次,而且所有判断的可能结果也要至少执行一次。
这个例子中,输入a=2, b=1, c=6,这个测试用例是覆盖了a>0, b>0, a>1, c>1四个条件,同时也覆盖了两个判断都为真的结果。输入a=-1, b=-2, c=-3,这个测试用例覆盖了a≤0, b≤0, a≤1, c≤1四个条件,同时覆盖了两个判断为假的结果。显然判定条件覆盖能够同时满足判定和条件两种覆盖的标准,但是它还是有不足的,它没有考虑到条件的各种组合的情况。
5、条件组合覆盖
它是在判定条件覆盖的基础上,增加了每一个条件的所有可能取值的组合情况。
对于这个例子中程序的判断 1 来说,下面框选处为4种条件的组合情况:
而下面框选处对应判断 1 的结果取值:
最后是判断 1 满足判定条件组合的4种情况:
同样的,也可以得到判断2的结果。根据上述分析,选择下图所示的4个测试用例:
它们可以满足每个条件所有可能的组合,而且覆盖了每一个判断的所有结果。但是再进一步看,这些测试用例所覆盖的路径,显然1-2-5这个路径是没有被覆盖的。
6、路径覆盖
它要求覆盖程序中所有可能的执行路径。
我们把前面条件组合覆盖的第二个测试用例进行了修改,得到了下面的这样的结果:
这个测试用例的结果覆盖了所有的执行路径,但再进一步看,它没有覆盖每一个条件的所有组合情况。
为了再覆盖所有执行路径的同时还能够覆盖所有条件组合的情况,我们又补了一个测试用例,最终得到了下图显示的5个测试用例结果:
如何看待测试覆盖率
- 测试覆盖率是对测试完全程度的一种评价,但是达到百分之百只是一个理想的情况,即使达到了百分之百,也很难说明测试是完全彻底的;
- 测试覆盖率只能代表测试过哪些代码,并不能代表测试的好坏;
- 测试覆盖率过低,说明测试做得不够,反过来并不成立;
- 测试人员不能盲目追求代码覆盖率,而应该想办法设计更好的测试用例;
- 测试覆盖率应该达到多少,要结合软件的整体要求和测试成本来决定;
基本路径测试
基本路径测试也是一种白盒方法,它是在控制流图的基础上,通过分析控制构造的环路复杂性,导出一个基本的可执行路径集合,然后设计出测试用例。
它有4个步骤:
- 第一步:绘制程序的控制流图;
- 第二步:计算环路复杂度,这是对程序逻辑复杂性的一种度量,代表了程序中基本独立路径的数量;
- 第三步:确定基本路径;
- 第四步:根据基本路径集合中每一条路径要被执行一次的原则,设计出测试用例;
以上文「例1:计算成绩平均值」为例,来介绍基本路径方法:
首先画出程序的控制流图,上文已提及,然后是计算环路的复杂度,可以用三种方法中的任意一种进行计算:
- 方法一:计算由边和节点圈定的区域数,本例中我们可以找到区域 1、区域 2、区域 3以及图形外的开放区域——区域 4,一共4个区域;
- 方法二:通过「边数-节点数+2」来计算,图中有11条边和 9 个节点,最后得到4;
- 方法三:通过「判断节点数+1」来计算,图中共有3个判断节点,分别是2、3、6,最后计算得出4;
由此,我们得到环路复杂度为4。
接下来要找出基本独立路径集。独立路径具有这样的性质:一条独立路径和其他独立路径相比至少要包含一条新的边,环路复杂度正好是等于程序的独立路径数。
我们先来找第一条路径,从头1、2、3、4、5然后再回到2。第二条路径至少要加入一条新的边,这里选择节点 3 的另一个分支,得到1、2、3、5然后再回到2。第三条路径我们选择节点 2 的另一个分支,得到1、2、6、7、9。第四条路径选择节点 6 的另一个分支,得到1、2、6、8、9。这样我们就得到了该程序的4条基本路径集:
最后我们选择合适的输入数据形成测试用例集。
这里 P3 这条路径是无法达到的,因为在文件没有数据的时候不可能满足乘积个数大于 0 这个条件。另外有一些独立路径往往不是孤立的,而是正常控制流的一部分,比如 P3 就是 P1 和 P2 的一部分,为了更好地测试您也可以再补充一些测试用例,想本例子中,可以补充文件数据全是 0 和负数的情况,它的输出信息和上面的 4 是一样的。
循环测试
循环也是测试的重点,目的是检查循环结构的有效性,循环可以分成:简单循环、嵌套循环、串接循环和非结构循环。
简单循环
对于简单循环来说,根据循环次数 n 可以选择:
- 次数的最小边界,0、1、2;
- 中间任意值 m ;
- 最大边界 n-1、n 和 n+1 次;
嵌套循环
- 先从最内层的循环开始,把其他层循环次数设为最小值;
- 最内层的循环按照简单循环进行测试;
- 然后从内向外紧进行下一层循环,重复这个过程直到测试完所有循环
串接循环
- 独立循环:如果循环之间是相互独立的,就按照简单循环处理;
- 依赖性循环:如果存在依赖性,则采用嵌套循环的方法进行测试;
Z 路径覆盖下的循环测试
Z 路径覆盖下的循环测试是把循环简化为只考虑执行循环体 1 次和 0 次的情况: