第3章软件测试方法 按测试过程是否在实际应用环境中运行分类,可以将传统的测试技术分为静态测试和动态测试。静态测试技术是单元测试中的重要手段之一,测试对象可以是需求文件、设计文件或源程序等,适用于新开发的和重用的代码,通常在代码完成并无错误地通过编译或汇编后进行。动态测试是指通过运行程序发现错误,通过观察代码运行过程获取系统的各方面信息,并从中发现缺陷。所有的黑盒测试和绝大多数的白盒测试都可以算作动态测试。 本章要点  静态测试的内容及方法  动态测试的内容及方法  主动测试与被动测试  白盒测试的内容及方法  黑盒测试的内容及方法  不同黑盒测试方法的优缺点和应用场合  白盒测试与黑盒测试的对比 3.1静态测试 静态测试不执行被分析的程序,而是通过对模块源代码进行研读,找出其中的错误或可疑之处,收集一些度量数据。静态测试包括对软件产品的需求和设计规格说明书的评审、对程序代码的复审等。静态测试的查错和分析功能是其他方法所不能替代的,可以采用人工或计算机辅助静态测试手段进行检测。 人工检测指的是完全靠人工审查或评审软件,偏重于编码风格、质量的检验。除了审查编码,还要对各阶段的软件产品进行检验。这种方法可以有效地发现逻辑设计和编码错误,发现计算机辅助静态测试不易发现的问题。 计算机辅助静态测试是利用静态测试工具对被测程序进行特性分析,从程序中提取一些信息,以便检查程序逻辑的各种缺陷和可疑的程序构造,如用错的局部变量和全局变量、不匹配的参数、潜在的死循环等。静态测试中还可以用符号代替数值求得程序结果,以便对程序进行运算规律的检验。 3.1.1代码检查 代码检查包括桌面检查、代码审查和走查等。它主要检查代码和设计的一致性、代码对标准的遵循、可读性、代码逻辑表达正确性、代码结构合理性等方面; 发现程序中不安全、不明确和模糊部分,找出程序中不可移植部分; 发现违背程序编写风格问题。代码检查包括变量检查、命名和类型审查、程序逻辑审查、程序语法检查和程序结构检查等内容。 代码检查应该在编译和动态测试之前进行。在检查前,应准备好需求描述文档、程序设计文档、程序的源代码清单、代码编写标准和代码错误检查表等。 在实际使用中,代码检查法能快速找到缺陷,发现30%~70%的逻辑设计和编码缺陷,而且代码检查法看到的是问题本身而非征兆。但是,代码检查法非常耗费时间,并且需要经验和知识的积累。 代码检查法可以使用测试软件进行自动化测试,以提高测试效率,降低劳动强度; 或者使用人工进行测试,以充分发挥人力的逻辑思维能力。 1. 桌面检查 桌面检查是一种传统的检查方法,由程序员自己检查编写的程序。程序员在程序通过编译之后,对源程序代码进行分析、检验并补充相关文档,目的是发现程序中的错误。 由于程序员熟悉自己的程序和程序设计风格, 程序员自己进行桌面检查可以节省很多检查时间,但应避免主观片面性,因为人们一般不能有效地测试自己编写的程序。桌面检查需要首先运行拼写检查器、语法检查器、句法检查器等进行字面检查,现在大多数集成开发环境集成了这些相应的工具,帮助程序员在编写代码的同时就注意这些可能存在的缺陷。然后,程序员就可以慢慢地复审文档,寻找文档中的不一致、不完全和漏掉信息的地方。在这个过程中所检测到的问题,应该由程序员自己直接修改,这当中可能会需要项目管理者或项目中其他专家提供建议。一旦所有的改正工作完成,就应该重新运行前面所说的桌面检查,发现并修改所有由于修改内容而引起的新的拼写、语法、标点错误。 图31桌面检查案例 图31 所示为一段未经桌面检查的源代码,由集成开发环境进行了初步的检查,并指出了基本的拼写、语法、标点错误。 (1) 第28行: 返回数据类型应该为int,写成了Int。 (2) 第33行: 缺少标点符号“;”。 (3) 第37行: 返回的关键字“return”拼写错误,写成了“returm”。 (4) 第41行: 关键字“this”写成了“that”。 在利用集成开发环境进行编码测试时,大多数工具都会提供桌面检查的工具,若使用文本编辑器进行代码编辑,那么就需要开发人员仔细检查自己编写的代码。 2. 代码审查 代码审查是由若干程序员和测试人员组成一个审查小组,通过阅读、讨论和争议,对程序进行静态分析的过程。代码审查分为以下两步。 (1) 小组负责人提前把设计规格说明书、控制流程图、程序文本以及有关要求、规范等分发给小组成员,作为审查的依据。 (2) 小组成员在充分阅读这些材料后,召开程序审查会,在会上首先由程序员逐行讲解程序逻辑,在此过程中,程序员或其他小组成员可以提出问题展开讨论,审查错误是否存在。 实践表明,程序员在讲解过程中能够发现许多自己以前没有发现的错误,而讨论和争议则促进问题暴露。 在会前应该给每个小组成员准备一份常见错误清单,把以往发现的常见错误罗列出来,供与会者对照检查,以提高审查效率。这份常见错误清单也称为检查表,它把程序中可能出现的各种错误进行分类,对每类列举出尽可能多的典型错误,然后把它们列成表格,供再审查时使用。 表31 所示为一张常规的Java代码审查检查表,测试小组人员根据该表格中的激活项逐一审查被测程序,并可根据级别对被测代码进行评审。 表31Java代码审查检查表 重要性激活结果检查项 总计 命名 重要命名规则是否与所采用的规范保持一致 是否遵循了最小长度最多信息原则 重要has/can/is前缀的函数是否返回布尔型 注释 重要注释是否较清晰且必要 重要Y复杂的分支流程是否已经被注释 距离较远的}是否已经被注释 非通用变量是否全部被注释 重要Y函数是否已经有文档注释(功能、输入、返回及其他可选) 特殊用法是否被注释 声明、空白、缩进 每行是否只声明了一个变量(特别是那些可能出错的类型) 重要变量是否已经在定义的同时初始化 重要类属性是否都执行了初始化 代码段落是否被合适地以空行分隔 Y是否合理地使用了空格使程序更清晰 代码行长度是否在要求之内 折行是否恰当 语句/功能的分布/规模 包含复合语句的{}是否成对出现并符合规范 是否给单个的循环、条件语句也加了{} 续表 重要性激活结果检查项 if/ifelse/ifelse ifelse/dowhile/switchcase语句的格式是否符合规范 单个变量是否只作单个用途 重要单行是否只有单个功能(不要使用; 进行多行合并) 重要单个函数是否执行了单个功能并与其命名相符 Y++和--操作符的应用是否复合规范 规模 重要单个函数不超过规定行数 重要缩进层数是否不超过规定 重要是否已经消除了所有警告 重要Y常数变量是否声明为final 重要对象使用前是否进行了检查 重要局部对象变量使用后是否被复位为NULL 重要对数组的访问是不是安全的(合法的index取值为[0,MAX_SIZE1]) 重要是否确认没有同名变量局部重复定义问题 程序中是否只使用了简单的表达式 重要Y是否已经用()使操作符优先级明确化 重要Y所有判断是否都使用了(常量==变量)的形式 是否消除了流程悬挂 重要是否每个ifelse ifelse语句都有最后一个else以确保处理了全集 重要是否每个switchcase语句都有最后一个default以确保处理了全集 for循环是否都使用了包含下限不包含上限的形式(k=0; k1)and(B=0)和(A=2)or(X>1)都取真值,因此使用上述一组测试数据就够了。但是,如果程序中把第1个判定表达式中的逻辑运算符and错写成or,或把第2个判定表达式中的条件 X>1错写成X<1,使用上面的测试数据并不能查出这些错误。 综上所述,可以看出语句覆盖是很弱的逻辑覆盖标准。为了更充分地测试程序,可以采用下面的覆盖标准。 2. 判定覆盖 判定覆盖又称为判断覆盖、分支覆盖,是比语句覆盖稍强的覆盖标准。判定覆盖指的是设计足够的测试用例,使每个判断获得每种可能的结果至少一次,即对被测试模块中的每个判断要分别取真和假各一次进行测试。判定覆盖是单元测试中很常用的一类覆盖,利用判定覆盖可以检查测试用例的设计是否完整,但是判定覆盖准则依然不够严格。 对于上面的例子,能够分别覆盖路径sacbed和sabd的两组测试数据,或者可以分别覆盖路径 sacbd和sabed的两组测试数据,都满足判定覆盖标准。例如,用下面两组测试数据就可做到完全的判定覆盖。  A=3,B=0,X=3(覆盖sacbd)  A=2,B=1,X=1(覆盖sabed) 判定覆盖比语句覆盖强,但是对程序逻辑的覆盖程度仍然不高,如上述测试数据只覆盖了程序全部路径的一半。 3. 条件覆盖 条件覆盖是指程序中每个判断中的每个条件的所有可能取值至少要都执行一次,条件覆盖独立度量每个子表达式,并对控制流更敏感。但是完全的条件覆盖并不能满足完全地判定覆盖。 考虑下面的程序。 通过以下两个测试用例可以得到100%的条件覆盖率。  a=true,b=false  a=false,b=true 但上述测试用例条件都不会使if的逻辑运算式成立,因此不符合判定覆盖的条件。 4. 条件判定覆盖 既然判定覆盖不一定包含条件覆盖,条件覆盖也不一定包含判定覆盖,自然会提出一种能同时满足这两种覆盖准则的逻辑覆盖,这就是条件判定覆盖。条件判定覆盖是判定覆盖和条件覆盖的组合,指的是设计足够的测试用例,使判定中每个条件的所有可能取值至少出现一次,并且每个判定取到的各种可能的结果也至少出现一次。条件判定覆盖具有两者的简单性并且没有两者的缺点,但是其没有考虑单个判定对整体结果的影响。 下面以一段代码为例,说明条件判定覆盖的测试用例的设计过程。 对其设计测试用例的第一步就是绘制出它的程序流程图,如 图37所示。 图37程序流程图 由于条件判定覆盖是条件覆盖与判定覆盖的组合,所以其测试用例取条件覆盖的用例和判定覆盖的用例的并集即可。 条件覆盖的思想是使每个判断的所有逻辑条件的每种可能取值至少执行一次。 对于判断语句x>0 && y>0 : 条件x>0取真为T1,取假为T1; 条件y>0取真为T2,取假为T2。 对于判断语句x>1 || z>1: 条件x>1取真为T3,取假为T3; 条件z>1取真为T4,取假为T4。 设计测试用例如表32 所示。 表32条件覆盖的测试用例 输入通 过 路 径条 件 取 值 x=7,y=1,z=3abdT1,T2,T3,T4 x=-1,y=-3,z=0aceT1,T2,T3,T4 判定覆盖的思想是使每个判断的取真分支和取假分支至少执行一次。 对于判断语句x>0 && y>0: 取真为M; 取假为M。 对于判断语句x>1 || z>1: 取真为N; 取假为N。 设计测试用例,如 表33所示。 综合 表32 和表33,条件判定覆盖的测试用例如 表34 所示。 表33判定覆盖的测试用例 输入通过路径判定取值 x=7,y=1,z=3abdM,N x=-1,y=-3,z=0aceM,N 表34条件判定覆盖的测试用例 输入通过路径 x=7,y=1,z=3abd x=-1,y=-3,z=0ace 5. 条件组合覆盖 条件组合覆盖又称为多条件覆盖,指的是设计足够的测试用例,使判定条件中每个条件的可能组合至少出现一次。多条件覆盖需要的测试用例是用一个条件的逻辑操作符的真值表确定的。显然,满足多条件覆盖的测试用例一定满足判定覆盖、条件覆盖和条件判定覆盖。多条件覆盖是一个彻底的测试,但是它依然存在以下一些缺点。 (1) 它可能是非常冗长乏味的决定一个需要的测试用例的最小设置,特别是对于一些非常复杂的布尔表达式。 (2) 对于相似的复杂性的条件却需要非常大的变化。 (3) 可能会存在路径遗漏。 以上一段代码为例,给出其测试用例的设计过程。 对各判断语句的逻辑条件的取值组合标记如下。 (1) x>0,y>0,记作T1、T2,条件组合取值M。 (2) x>0,y<=0,记作T1、T2,条件组合取值M。 (3) x<=0,y>0,记作T1、T2,条件组合取值M。 (4) x<=0,y<=0,记作T1、T2,条件组合取值M。 (5) x>1,z>1,记作T3、T4,条件组合取值N。 (6) x>1,z<=1, 记作T3、T4,条件组合取值N。 (7) x<=1,z>1,记作T3、T4,条件组合取值N。 (8) x<=1,z<=1, 记作T3、T4,条件组合取值N。 设计测试用例如 表35 所示。 表35多条件覆盖的测试用例 输入通 过 路 径条 件 取 值覆盖组合号 x=1,y=3,z=2abdT1,T2,T3,T41,7 x=2,y=0,z=8acdT1,T2,T3,T42,5 x=1,y=1,z=1aceT1,T2,T3,T43,8 x=2,y=3,z=0aceT1,T2,T3,T44,8 x=5,y=9,z=0abdT1,T2,T3,T41,6 6. 路径覆盖 路径覆盖是指测试用例中执行到的路径数量占被测试模块所有可能的执行路径的比例。在路径覆盖中,我们只需要考虑所有可能的执行路径,对于不可能执行的路径,是不需要考虑的。而且对于一些大型程序,其包含的路径总量是非常庞大的,如果要把所有路径都找出来去覆盖也是不现实的。因此,我们可以借助以下方法寻找程序中的路径。 1) 单个判断语句的路径计算 单个判断语句中,路径只有两条,一条是判断条件为真,另一条是判断条件为假。在不考虑判断分支中的路径分支时,路径数与判断分支数相等。 2) 单个循环语句中的路径计算 在循环语句中,通常循环的每次迭代都可以看作一条路径,但这样计算出的路径的测试工作量太大,所以通过以下方式简化循环中的路径计算。 (1) 当循环中条件一定会满足,循环内语句一定会执行时,循环语句中的可能执行的路径可看作一条。 例如下面的循环语句: 循环中的条件一定满足,循环语句sum+=i一定会被执行,直到i=101时循环结束。在这种情况下,整个循环语句可能的执行路径可看作只有一条。 (2) 当循环条件不一定满足,循环内的语句有可能被执行到,也可能不被执行时,循环语句中可能的执行路径可看作两条: 一条路径循环被执行,另一条路径循环不被执行。 例如下面的循环语句: 上面的循环中,当x≥100时,循环不执行,因此可能的执行路径有两条。 (3) 当循环过程中有可能出现中断的情况。 例如下面的循环语句: 上面这段程序,由于参数maxnum的不同,在循环的每次迭代都有两种可能,一种是满足判断条件,一种是不满足判断条件,所以可以认为路径的数量是51条,其中50条是for循环中if判断条件都满足的情况,另外一条是for循环中if判断条件没有被满足,break语句没有被执行的情况。实际上,程序中可能执行的路径总数不多于输入参数的等价类的所有组合数量,上 述代码中GetSum()函数的输入参数实际上有51个等价类,可能的执行路径数量也是51条。 3) 有嵌套判断或循环时的路径计算 下面是有嵌套判断语句的例子。 对上述程序的路径统计,先计算出第1个if中的路径为两条,再计算出else中的路径为两条,因此相乘得到总路径为4条,对于有嵌套判断或循环的程序,一般先从内层开始计算路径,然后通过相乘得到总的路径数。 3.3.3基本路径法 基本路径法是在程序控制流图的基础上,通过分析控制构造的环路复杂性,导出基本可执行的路径集合,从而设计测试用例的方法。在基本路径测试中,设计出的测试用例要保证在测试中程序的每条可执行语句至少执行一次。在基本路径法中,需要使用程序的控制流图进行可视化表达。 程序的控制流图是描述程序控制流的一种图示方法。其中,圆圈称为控制流图的一个节点,表示一个或多个无分支的语句或源程序语句; 箭头称为边或连接,代表控制流。在将程序流程图简化成控制流图时,应注意: (1) 在选择或多分支结构中,分支的汇聚处应有一个汇聚节点; (2) 边和节点圈定的区域叫作区域,当对区域计数时,图形外的区域也应记为一个区域。 控制流图如图38所示。 图38控制流图表示 环路复杂度是一种为程序逻辑复杂性提供定量测度的软件度量,将该度量用于计算程序的基本的独立路径数目,为确保所有语句至少执行一次的测试数量的上界。独立路径必须包含一条在定义之前不曾用到的边。以下3种方法可用于计算环路复杂度。 (1) 流图中区域的数量对应于环路的复杂度。 (2) 给定流图G的环路复杂度V(G),定义为V(G)=E-N+2,其中,E为流图中边的数量,N为流图中节点的数量。 (3) 给定流图G的环路复杂度V(G),定义为V(G)=P+1,其中,P为流图G中判定节点的数量。 基本路径测试法适用于模块的详细设计及源程序,其步骤如下。 (1) 以详细设计或源代码为基础,导出程序的控制流图。 (2) 计算控制流图G的环路复杂度V(G)。 (3) 确定线性无关的路径的基本集。 (4) 生成测试用例,确保基本路径集中每条路径的执行。 每个测试用例执行后与预期结果进行比较,如果所有测试用例都执行完毕,则可以确信程序中所有可执行语句至少被执行了一次。但是必须注意,一些独立路径往往不是完全孤立的,有时它是程序正常控制流的一部分,这时对这些路径的测试可以是另一条测试路径的一部分。 下面将以一个具体实例为出发点,讲解使用基本路径法测试的细节。 对于下面的程序,假设输入的取值范围为1000100。 (2) 如果输入条件规定了输入值的集合或是规定了“必须如何”的条件,则可以确定一个有效等价类和一个无效等价类。例如 ,输入值是日期类型的数据,那么有效等价类是日期类型的数据; 无效等价类是非日期类型的数据。 (3) 如果输入是布尔表达式,可以分为一个有效等价类和一个无效等价类。例如,要求密码非空,则有效等价类为非空密码; 无效等价类为空密码。 (4) 如果输入条件是一组值,且程序对不同的值有不同的处理方式,则每个允许的输入值对应一个有效等价类,所有不允许的输入值的集合为一个无效等价类。例如,输入条件“职称”的值是初级、中级或高级,那么有效等价类应该有3个 : 初级、中级和高级; 无效等价类有一个: 其他任何职称。 (5) 如果规定了输入数据必须遵循的规则,可以划分出一个有效等价类(符合规则)和若干个无效等价类(从不同的角度违反规则)。 划分好等价类后,就可以设计测试用例了。设计测试用例的步骤可以归结为3步。 (1) 对每个输入和外部条件进行等价类划分,画出等价类表,并为每个等价类进行编号。 (2) 设计一个测试用例,使其尽可能多地覆盖有效等价类,重复这一步,直到所有的有效等价类被覆盖。 (3) 为每个无效等价类设计一个测试用例。 下面将以一个测试NextDate函数的具体实例为出发点,讲解使用等价类划分法设计测试用例的过程。输入3个变量(年、月、日),函数返回输入日期后面一天的日期: 1≤月≤12,1≤日≤31,1812≤年≤2012。给出等价类划分表并设计测试用例。 划分等价类,得到等价类划分表,如 表36所示。 表36等价类划分表 输入及外部条件有效等价类等价类编号无效等价类等价类编号 日期的类型数字字符1非数字字符8 年1812~20122 <18129 >201210 月1~123 <111 >1212 非闰年的2月日为1~284 <113 >2814 闰年的2月日为1~295 <115 >2916 月份为1月、3月、5月、7月、8月、10月、12月日为1~316 <117 >3118 月份为4月、6月、9月、11月日为1~307 <119 >3020 为有效等价类设计测试用例,如 表37所示。 表37有效等价类的测试用例 序号 输入数据预期输出 年月日年月日覆盖范围 (等价类编号) 1200331520033161,2,3,6 2200421320042141,2,3,5 31999231999241,2,3,4 4197092919709301,2,3,7 为无效等价类设计测试用例,如 表38所示。 表38无效等价类的测试用例 序号 输入数据 年月日预期结果覆盖范围 (等价类编号) 1xy59输入无效8 2170048输入无效9 32300111输入无效10 42005011输入无效11 520091425输入无效12 619892-1输入无效13 71977230输入无效14 820002-2输入无效15 92008234输入无效16 101956100输入无效17 111974878输入无效18 1220079-3输入无效19 1318661235输入无效20 通过案例可以了解,等价类划分法可以作为一种有效的黑盒测试方法,设计测试用例能够覆盖程序功能,而又不存在冗余的测试用例。但是需要对程序规格说明书进行深入了解并合理地划分等价类。有些时候,规格说明书中可能没有定义对无效输入的预期输出应该是什么样子,因此测试人员需要花费大量时间来定义这些测试用例的预期输出。这也是等价类划分法存在的一个缺陷。 2. 边界值分析法 人们从长期的测试工作经验中得知,大量的错误往往发生在输入和输出范围的边界上,而不是范围的内部。因此,针对边界情况设计测试用例,能够更有效地发现错误。 边界值分析法是一种补充等价类划分法的黑盒测试方法,它不是选择等价类中的任意元素,而是选择等价类边界的测试用例。实践证明,这些测试用例往往能取得很好的测试效果。边界值分析法不仅重视输入范围边界,也从输出范围中导出测试用例。 通常情况下,软件测试所包含的边界条件有以下几种类型: 数字、字符、位置、质量、大小、速度、方位、尺寸、空间等; 边界值应该为最大/最小、首位/末位、上/下、最快/最慢、最高/最低、最短/最长、空/满等情况。 用边界值分析法设计测试用例时应当遵守以下几条原则。 (1) 如果输入条件规定了取值范围,应以该范围的边界内及刚刚超范围的边界外的值作为测试用例。如以a和b作为输入条件,测试用例应当包括a和b,以及略大于a和略小于b的值。 (2) 若规定了值的个数,应分别以最大、最小个数和稍小于最小个数和稍大于最大个数作为测试用例。 (3) 针对每个输出条件,也使用上面两条原则。 (4) 如果程序规格说明书中提到的输入或输出范围是有序的集合,如顺序文件、表格等,应注意选取有序集的第1个和最后一个元素作为测试用例。 (5) 分析规格说明,找出其他的可能边界条件。 边界值分析法利用输入变量的最小值、略大于最小值、输入范围内任意值、略小于最大值、最大值设计测试用例。如 图310 所示,对于n个变量,使除一个以外的所有变量都取正常值,使剩余的变量取上述5个值,对每个变量都重复进行。一个n变量函数的边界值有4n+1个测试用例。 健壮性测试是边界值分析的一种简单扩展,除了使用5个边界值分析取值,还要采用一个略小于最小值和一个略大于最大值的取值。健壮性测试更关注例外情况如何处理。如 图311 所示,一个n变量函数的健壮性边界值有6n+1个测试用例。 如果对于每个变量,首先取边界值的5个取值作为集合,然后对这些集合进行笛卡尔积运算生成测试用例,则称为最坏情况测试。一个n变量函数的最坏情况测试有5n个测试用例。同理,可以进行健壮最坏情况测试。一个n变量函数的最坏情况测试有7n个测试用例。可以看到,测试的完全度与测试复杂度是成正比的,在实际测试中选择哪种边界值分析法,要根据项目具体需要确定。 普通边界条件是很容易找到的,它们在规格说明书中有定义,或者在使用软件过程中确定。有些边界在软件内部,最终用户几乎看不到,但是软件测试仍有必要检查,这样的边界条件成为次边界条件。寻找这样的边界条件就需要测试人员了解软件大概的工作方式。边 图310边界值分析测试用例示意图 图311健壮性边界值测试用例示意图 界条件的确定有时也需要一定的领域知识。 以上面提到的NextDate函数为例,除了已经用等价类划分法设计出的测试用例外,还应该用边界值分析法再补充 如表39所示的测试用例。 表39边界值分析法设计的测试用例 序号边界值 输入数据预期输出 年月日年月日 1使年刚好等于最小值18123151812316 2使年刚好等于最大值20123152012316 3使年刚刚小于最小值1811315输入无效 4使年刚刚大于最大值2013315输入无效 5使月刚好等于最小值20001152000116 6使月刚好等于最大值2000121520001216 7使月刚刚小于最小值2000015输入无效 8使月刚刚大于最大值20001315输入无效 9使闰年的2月的日刚好等于最小值200021200022 10使闰年的2月的日刚好等于最大值2000229200031 11使闰年的2月的日刚刚小于最小值200020输入无效 12使闰年的2月的日刚刚大于最大值2000230输入无效 13使非闰年的2月的日刚好等于最小值200121200122 14使非闰年的2月的日刚好等于最大值2001228200131 15使非闰年的2月的日刚刚小于最小值200120输入无效 16使非闰年的2月的日刚刚大于最大值2001229输入无效 17使1月、3月、5月、7月、8月、10月、12月的日刚好等于最小值20011012001102 18使1月、3月、5月、7月、8月、10月、12月的日刚好等于最大值200110312001111 续表 序号边界值 输入数据预期输出 年月日年月日 19使1月、3月、5月、7月、8月、10月、12月的日刚刚小于最小值2001100输入无效 20使1月、3月、5月、7月、8月、10月、12月的日刚刚大于最大值20011032输入无效 21使4月、6月、9月、11月的日刚好等于最小值200161200162 22使4月、6月、9月、11月的日刚好等于最大值2001630200171 23使4月、6月、9月、11月的日刚刚小于最小值200160输入无效 24使4月、6月、9月、11月的日刚刚大于最大值2001631输入无效 3. 因果图法 等价类划分法和边界值分析法都主要考虑的是输入条件,而没有考虑输入条件的各种组合以及各个输入条件之间的相互制约关系。然而,如果在测试时考虑到输入条件的所有组合方式,可能其本身非常大甚至是个天文数字。因此,必须考虑描述多种条件的组合,相应地产生多个动作的形式,设计测试用例。这就需要利用因果图法。 因果图法是一种黑盒测试方法,它从自然语言书写的程序规格说明书中寻找因果关系,即输入条件与输出和程序状态的改变,通过因果图产生判定表。它能够帮助人们按照一定的步骤高效地选择测试用例,同时还能指出程序规格说明书中存在的问题。 在因果图中,用C表示原因,E表示结果,各节点表示状态,取值为0表示某状态不出现,取值为1表示某状态出现。因果图有4种关系符号,如 图312 所示。 图312因果图基本符号 (1) 恒等: 若原因出现,则结果出现; 若原因不出现,则结果不出现。 (2) 非(~): 若原因出现,则结果不出现; 若原因不出现,则结果反而出现。 (3) 或(∨): 若几个原因中有一个出现,则结果出现; 若几个原因都不出现,则结果不出现。 (4) 与(∧): 若几个原因都出现,结果才出现; 若其中一个原因不出现,则结果不出现。 为了表示原因与原因之间、结果与结果之间可能存在的约束关系,在因果图中可以附加一些表示约束条件的符号,如 图313 所示。从输入考虑,有以下4种约束。 (1) E约束(互斥): 表示a和b两个原因不会同时成立,最多有一个可以成立。 (2) I约束(包含): 表示a和b两个原因至少有一个必须成立。 (3) O约束(唯一): 表示a和b两个原因必须有且仅有一个成立。 (4) R约束(要求): 表示a出现时,b也必须出现。 从输出考虑,有一种约束。 M约束(强制): 表示a为1时,b必须为0。 图313因果图约束符号 因果图法设计测试用例的步骤如下。 (1) 分析程序规格说明书的描述中,哪些是原因,哪些是结果。原因常常是输入条件或输入条件的等价类,而结果常常是输出条件。 (2) 分析程序规格说明书中描述的语义内容,并将其表示成连接各个原因与各个结果的因果图。 (3) 由于语法或环境的限制,有些原因和结果的组合情况是不可能出现的,为表明这些特定的情况,在因果图上使用若干特殊的符号标明约束条件。 (4) 把因果图转化为决策表。 (5) 为决策表中每列表示的情况设计测试用例。 后面两个步骤中提到的决策表,将在后续进行详细介绍。如果项目在设计阶段已存在决策表,则可以直接使用而不必再画因果图。 下面以一个自动饮料售货机软件为例,展示因果图分析方法。该自动饮料售货机软件的规格说明如下。 有一个处理单价为1元5角的盒装饮料的自动售货机软件。若投入1元5角硬币,按下 “可乐”“雪碧”或“红茶”按钮,相应的饮料就送出来; 若投入2元硬币,在送出饮料的同时退还5角硬币。 首先从软件规格说明中分析原因、结果以及中间状态。分析结果如 表310所示。 表310自动饮料售货机软件分析结果 原因C1: 投入1元5角硬币 C2: 投入2元硬币 C3: 按“可乐”按钮 C4: 按“雪碧”按钮 C5: 按“红茶”按钮 中间状态11: 已投币 12: 已按钮 结果E1: 退还5角硬币 E2: 送出可乐 E3: 送出雪碧 E4: 送出红茶 根据 表310中的原因与结果,结合软件规格说明,连接成如 图314 所示的因果图。 图314自动饮料售货机软件因果图 4. 决策表法 在一些数据处理问题中,某些操作是否实施依赖于多个逻辑条件的取值。在这些逻辑条件取值的组合所构成的多种情况下,分别执行不同的操作。处理这类问题的一个非常有力的工具就是决策表。 图315决策表组成 决策表是分析和表达多逻辑条件下执行不同操作的情况的工具,可以把复杂逻辑关系和多种条件组合的情况表达得比较明确。决策表通常由4部分组成,如 图315 所示。 (1) 条件桩: 列出问题的所有条件。 (2) 条件项: 列出所列条件下的取值在所有可能情况下的真假值。 (3) 动作桩: 列出问题规定可能采取的动作。 (4) 动作项: 列出在条件项的各种取值情况下应采取的动作。 规则规定了任何一个条件组合的特定取值及其相应要执行的操作。在决策表中贯穿条件项和动作项的一列就是一条规则。有两条或多条规则具有相同的动作,并且其条件项之间存在着极为相似的关系的规则可以进行规则合并。 决策表的建立应当根据软件规格说明书,分为以下几个步骤。 (1) 确定规则个数。 (2) 列出所有条件桩和动作桩。 (3) 填入条件项。 (4) 填入动作项,制订初始决策表。 (5) 简化,合并相似规则或者相同动作。 在简化并得到最终决策表后,只要选择适当的输入,使决策表每列的输入条件得到满足即可生成测试用例。 将上面得到的自动饮料售货机软件因果图转换为决策表,如 表311 所示。 表311自动饮料售货机软件决策表 1234567891011 条件 C1: 投入1元5角硬币11110000000 C2: 投入2元硬币00001111000 C3: 按“可乐”按钮10001000100 C4: 按“雪碧”按钮01000100010 C5: 按“红茶”按钮00100010001 中间状态 11: 已投币11111111000 12: 已按钮11101110111 动作 E1: 退还5角硬币00001110000 E2: 送出可乐10001000000 E3: 送出雪碧01000100000 E4: 送出红茶00100010000 可以根据上述决策表设计测试用例,从而验证适当的输入组合能否得到正确的输出。特别是在本案例中,利用因果图和决策表法能够很清晰地验证自动饮料售货机软件的功能完备性。 5. 正交试验法 在将因果图转换成决策表生成测试用例时,若要进行全面测试,其得到的测试用例数目大得惊人。例如,对于有n个原因导致一个结果的因果图,如果每个原因的取值有两种: 存在或不存在,则进行全面测试需要为此设计2n种测试用例,再考虑到其他因果图,最后得出的测试用例数量无法想象,这给软件测试带来了沉重的负担。为了有效、合理地减少测试的工时与费用,可利用正交试验法进行测试用例的设计。 正交试验法是从大量的实验数据中挑选适量的、有代表性的点,合理安排测试的设计方法。 日本著名统计学家田口玄一将正交试验选择的水平组合列成表格,称为正交表。例如,做一个3因素3水平的试验,按全面试验要求,须进行33=27种组合的试验,且尚未考虑每种组合的重复数。若按L9(33)正交表安排试验,只需 9次试验,按L18(37)正交表也只需18次试验,显然大大减少了工作量。因而正交试验设计在很多领域的研究中已经得到广泛应用。 正交表的形式为L行数(水平数因素数)。 其中,行数表示正交表中的行的个数,即试验的次数,也是我们通过正交试验法设计的测试用例的个数; 因素数是正交表中列的个数,即要测试的功能点; 水平数是任何单个因素能够取得的值的最大个数。正交表中包含的值为从0~(水平数-1)或从1~水平数,即要测试功能点的输入条件。 正交表具有以下两项性质。 (1) 每列中,不同的数字出现的次数相等。例如,在2水平正交表中,任何一列都有数码1和2,且任何一列中它们出现的次数是相等的; 在3水平正交表中,任何一列都有数码1、2和3,且在任何一列的出现数均相等。 (2) 任意两列中数字的排列方式齐全而且均衡。例如,在2水平正交表中,任何两列(同一行内)有序对共有4种: (1,1)、(1,2)、(2,1)、(2,2),每对出现次数相等; 在3水平情况下,任何两列(同一行内)有序对共有9种: (1,1)、(1,2)、(1,3)、(2,1)、(2,2)、(2,3)、(3,1)、(3,2)、(3,3),且每对出现次数也相等。 以上两点充分地体现了正交表的两大优越性,即均匀分散性和整齐可比性。通俗地说,每个因素的每个水平与另一个因素的每个水平各碰一次,这就是正交性。 下面以一个用户注册功能为例,展示正交试验法设计测试用例的方法。该用户注册页面有7个输入框,分别是用户名、密码、确认密码、真实姓名、地址、手机号、电子邮箱。假设每个输入框只有填与不填两种状态,则可以设计L8(27) 正交表,如 表312 所示。其中因素C1~C7分别表示上述7个输入框。表312中1表示 填该输入框,0表示不填。 根据 表312 可以得到8个测试用例,读者可以根据各因素代表的输入框含义自己生成测试用例。 表312L8(27)正交表 因素 行号C1C2C3C4C5C6C7 11111111 21110000 31001100 41000011 50101010 60100101 70011001 80010110 6. 场景法 现在软件很多都是用事件触发控制流程,事件触发时的情形便形成场景,而同一事件不同的触发顺序和处理结果就形成了事件流。这种在软件设计中的思想也可以应用到软件测试中,可生动地描绘出事件触发时的情形,有利于测试者执行测试用例,同时测试用例也更容易理解和执行。 用例场景是通过描述流经用例的路径确定的过程,这个流经过程要从用例开始到结束遍历其中所有的基本流和备选流。 (1) 基本流: 采用黑直线表示,是经过用例的最简单路径,表示无任何差错,程序从开始执行到结束。 (2) 备选流: 采用不同颜色表示,一个备选流可以从基本流开始,在某个特定条件下执行,然后重新加入基本流,也可以起源于另一个备选流,或终止用例,不再加入基本流。 应用场景法进行黑盒测试的步骤如下。 (1) 根据规格说明,描述出程序的基本流和各个备选流。 (2) 根据基本流和各个备选流生成不同的场景。 (3) 对每个场景生成相应的测试用例。 (4) 对生成的所有测试用例进行复审,去掉多余的测试用例,对每个测试用例确定测试数据。 下面以一个经典的ATM机为例,介绍使用场景法设计测试用例的过程。ATM机取款流程的场景分析如 图316 所示,其中灰色框构成的流程为基本流。 图316ATM取款流程场景法分析图 该程序用例场景如 表313所示。 表313用例场景 序号场景基本流备选流 场景1成功提款基本流 场景2无效卡基本流备选流1 场景3密码错误3次以内基本流备选流2 场景4密码错误超过3次基本流备选流3 场景5ATM无现金基本流备选流4 续表 序号场景基本流备选流 场景6ATM现金不足基本流备选流5 场景7账户余额不足基本流备选流6 场景8超出提款上限基本流备选流7 接下来设计用例覆盖每个用例场景,如表314所示。 表314场景法测试用例 用例号场景账户密码操作预 期 结 果 1场景1621226XXXXXXXXX3481123456插卡,取500元成功取款500元 2场景2——插入一张无效卡系统退卡,显示该卡无效 3场景3621226XXXXXXXXX3481123456插卡,输入密码111111系统提示密码错误,请求重新输入 4场景4621226XXXXXXXXX3481123456插卡,输入密码111111超过3次系统提示密码输入错误超过3次,卡被吞掉 5场景5621226XXXXXXXXX3481123456插卡,选择取款系统提示ATM无现金,退卡 6场景6621226XXXXXXXXX3481123456插卡,取款2000元系统提示现金不足,返回输入金额界面 7场景7621226XXXXXXXXX3481123456插卡,取款3000元系统提示账户余额不足,返回输入金额界面 8场景8621226XXXXXXXXX3481123456插卡,取款3500元系统提示超出取款上限(3000元),返回输入金额界面 3.4.2黑盒测试方法选择 此外,黑盒测试还有错误推测等方法,本书不再展开叙述。黑盒测试的每种测试方法都有各自的优缺点,需要测试人员根据实际项目特点和需要选择合适的方法设计测试用例。以下是选择黑盒测试方法的几条经验。 (1) 在任何情况下都必须选择边界值分析方法。经验表明用这种方法设计出的测试用例发现程序错误的能力最强。 (2) 必要时用等价类划分法补充一些测试用例。 (3) 用错误推测法再追加一些测试用例。 (4) 如果程序的功能说明中含有输入条件的组合情况,则可选用因果图法和决策表法。 选择合适的测试方法能够极大地提高黑盒测试的效率和效果。除了上述几条经验,还需要测试人员积累实际的测试经验,做出合适的选择。 3.4.3白盒测试与黑盒测试的比较 白盒测试和黑盒测试是两类软件测试方法,传统的软件测试活动基本上都可以划分到这两类测试方法中。 表315给出了两种方法的基本比较。 表315黑盒测试和白盒测试比较 黑 盒 测 试白 盒 测 试 不涉及程序结构考查程序逻辑结构 用软件规格说明书生成测试用例用程序结构信息生成测试用例 适用于从单元测试到系统验收测试主要适用于单元测试和集成测试 某些代码段得不到测试对所有逻辑路径进行测试 白盒测试和黑盒测试各有侧重点,不能相互取代,在实际测试活动中,这两种测试方法不是截然分开的。通常在白盒测试中交叉着黑盒测试,黑盒测试中交叉着白盒测试。相对来说,白盒测试比黑盒测试成本要高得多,它需要测试在可以被计划前产生源代码,并且在确定合适数据和决定软件是否正确方面需要花费更多的工作量。 在实际测试活动中,应当尽可能使用可获得的软件规格从黑盒测试方法开始测试计划,白盒测试计划应当在黑盒测试计划已成功通过之后再开始,使用已经产生的流程图和路径判定。路径应当根据黑盒测试计划进行检查并且决定和使用额外需要的测试。 灰盒测试结合了白盒测试和黑盒测试的要素,考虑了用户端、特定的系统知识和操作环境。它在系统组件的协同性环境中评价应用软件的设计。可以认为集成测试就是一类灰盒测试。关于灰盒测试,本书不再展开叙述。 3.5本章小结 本章主要介绍了静态测试、动态测试的定义与内容,以及静态测试、动态测试的分类及方法。 白盒测试关注软件产品的内部细节和逻辑结构,可以分为静态测试和动态测试。静态测试不通过执行程序进行测试,其关键是检查软件的表示与描述是否一致,是否存在冲突或歧义; 动态测试需要执行程序,当程序在模拟的或真实的环境中执行之前、之中和之后,对程序行为分析,主要验证一个程序在检查状态下是否正确。本章介绍了白盒测试常用的方法,并着重介绍了程序插桩技术、逻辑覆盖法以及基本路径法,并对各个技术方法附以相关实例进行详细说明。 黑盒测试主要关注被测软件功能的实现,而不是其内部逻辑。本章重点介绍了常用的几种黑盒测试方法,并给出了相应的案例说明。 拓展练习 习题3 (1) 代码检查法主要包括哪些主要内容?可以发现哪些问题? (2) 试比较代码审查与走查的异同。 (3) 静态结构分析有哪几种形式? (4) 动态测试可分为哪些方法?分类依据分别是什么? (5) 什么是白盒测试?包括哪些技术? (6) 利用基本路径测试技术为下面一段程序设计测试用例。 while(a >0) { a = a - 1; if(b <0 || c >=1) { c = c - b; } else c = c + b; } a = b + c; (7) 什么是黑盒测试?有哪些主要方法? (8) 给出白盒测试与黑盒测试的不同。 (9) 某程序功能说明书指出,该程序的输入数据为每个学生的学号。其中,学号由以下3部分构成:  入学年份: 4位数字(1900~2999);  专业编码: 0或1开头的4位数字;  序号: 两位数字。 试用等价类划分法设计测试用例。 (10) 对于一个需要输入姓名、身份证号码、手机号码的系统,按每个输入有两个状态(填与不填)设计一个最小行数的正交表。