8.1 软件测试的基本概念
8.2 软件测试方法
8.3 测试用例的设计
8.4 软件测试的步骤
8.5 调试退出第八章 软件测试
8.6 软件可靠性
8.7 测试工具
8.1 软件测试的基本概念
8.1.1 软件测试的定义
8.1.2 软件测试的基本原则退出
8.1.3 软件测试的步骤
8.1.4 软件测试的信息流计关于测试目的,G.J.Myers给出了以下的观点:
测试的定义:为了发现程序中的错误而执行程序的过程 。 具体地说,软件测试是根据软件开发各阶段的规格说明和程序的内部结构而精心设计出一批测试用例,并利用测试用例来运行程序,以发现程序错误的过程 。
8.1.1 软件测试的定义
( 1) 测试是为了发现程序中的错误而执行程序的过程;
( 2)好的测试方案是极可能发现迄今为止尚未发现的错误的测试方案;
( 3)成功的测试是发现了至今为止尚未发现的错误的测试。
8.1.2 软件测试的基本原则
( 1)尽早地、不断地进行软件测试。
( 2)设计测试用例时,要给出测试的预期结果。
( 3)开发小组和测试小组分开。
( 4)要设计非法输入的测试用例。
( 5)在对程序修改之后要进行回归测试。
( 6)程序中尚未发现的错误的数量往往与在该段程序中已发现的错误的数量成正比。
8.1.3 软件测试的步骤
1.单元测试又称模块测试 。 每个程序模块完成一个相对独立的子功能,所以可以对该模块进行单独的测试 。
由于每个模块都有清晰定义的功能,所以通常比较容易设计相应的测试方案,以检验每个模块的正确性 。
2.集成测试在单元测试完成后,要考虑将模块集成为系统的过程中可能出现的问题,例如,模块之间的通信和协调问题,所以在单元测试结束之后还要进行集成测试 。 这个步骤着重测试模块间的接口,子功能的组合是否达到了预期要求的功能,全程数据结构是否有问题等 。
3.有效性测试
4.系统测试系统测试是把通过有效性测试的软件,作为基于计算机系统的一个整体元素,与整个系统的其他元素结合起来,在实际运行环境下,对计算机系统进行一系列的集成测试和有效性测试。
集成测试通过后,应在用户的参与下进行有效性测试。这个时候往往使用实际数据进行测试,从而验证系统是否能满足用户的实际需要。
8.1.4 软件测试信息流可靠性模型可靠性预测错误率数据错误测试结果预测结果测试配置软件配置测试评价调试正确
8,2 软件测试方法
8.2.1 黑盒测试
8.2.2 白盒测试退出
8.2.1 黑盒测试任何产品都可以使用以下两种方法进行测试:
( 1) 如果已知产品的功能,则可以对它的每一个功能进行测试,看是否都达到了预期的要求;
( 2) 如果已知产品的内部工作过程,则可以对它的每种内部操作进行测试,看是否符合设计要求 。
第一种方法是黑盒测试,第二种方法是白盒测试 。
黑盒测试时完全不考虑程序内部的结构和处理过程,
只按照规格说明书的规定来检查程序是否符合它的功能要求 。 黑盒测试是在程序接口进行的测试,又称为功能测试 。
黑盒测试检查的主要方面有:
程序的功能是否正确或完善;
数据的输入能否正确接收,输出是否正确;
是否能保证外部信息(如数据文件)的完整性等。
用黑盒法设计测试用例时,必须用所有可能的输入数据来检查程序是否都能产生正确的输出。
黑盒测试不可能实现穷尽测试:
假设有一个很简单的小程序,输入量只有两个,A和
B,输出量只有一个,C。 如果计算机的字长为 32位,A
和 B的数据类型都只是整数类型 。 利用黑盒法进行测试时,
将 A和 B的可能取值进行排列组合,输入数据的可能性有:
232× 232= 264种 。 假设这个程序执行一次需要 1毫秒,要完成所有的测试,计算机需要连续工作 5亿年 。 显然,这是不能容忍的,而且,设计测试用例时,不仅要有合法的输入,而且还应该有非法的输入,在这个例子中,输入还应该包括实数,字符串等,这样,输入数据的可能性就更多了 。 所以说,穷尽测试是不可能实现的 。
白盒测试时将程序看作是一个透明的盒子,也就是说测试人员完全了解程序的内部结构和处理过程 。 所以测试时按照程序内部的逻辑测试程序,检验程序中的每条通路是否都能按预定的要求正确工作 。 白盒测试又称为结构测试 。
利用白盒测试设计测试用例时,应包括以下三类测试:
( 1) 语句测试:要求程序中的每个语句至少测试一次;
( 2) 分支测试:要求程序中的每个分支至少测试一次;
( 3) 路径测试:要求程序中的每条路径至少测试一次 。
8.2.2 白盒测试白盒测试也不能实现穷尽测试:
左图所示的一个小程序的控制流程,其中每个圆圈代表一段源程序 ( 或语句块 ),图中的曲线代表执行次数不超过 20的循环,循环体中共有 5条通路 。
这样,可能执行的路径有
520条,近似为 1014条可能的路径 。 如果完成一个路径的测试需要 1毫秒,那么整个测试过程需要 3170年 。
显然,这也是不能接受的 。
8.3 测试用例的设计
8.3.1 逻辑覆盖
8.3.2 等价类划分退出
8.3.3 边界值分析
8.3.4 错误推测法逻辑覆盖是以程序的内部逻辑结构为基础的测试用例设计技术,属于白盒测试 。 它要求测试人员十分清楚程序的逻辑结构,考虑的是测试用例对程序内部逻辑覆盖的程度 。
根据覆盖的目标,逻辑覆盖又可以分为:
语句覆盖判定覆盖条件覆盖判定/条件覆盖条件组合覆盖
8.3.1 逻辑覆盖
1、语句覆盖语句覆盖就是设计足够的调试用例,使得程序中的每个语句至少执行一次 。
A >1
A N D B = 0
F
F
入口
A = 2
OR x > 1
返回
x = x / A
T
x = x + 1
T
a
c
b
d
e
左图程序段中共有 4条路径,P1( ace),P2
( abd),P3( abe),P4( acd) 。
语句覆盖不能发现判断中的逻辑运算中的错误 。
第一个判断中的逻辑运算符,&&”若错写成了,||”,
利用上面的输入数据则检查不出这个错误 。
P1正好满足语句覆盖的条件。可以设计如下的输入数据:
A= 2,B= 0,x= 4
2、判定覆盖判定覆盖就是设计足够的测试用例,使得程序中每个判定的取,真,分支和取,假,分支至少都执行一次,判定覆盖又称分支覆盖 。
测试用例如果能够测试路径 P1( ace) 和 P2( abd),就可以满足判定覆盖要求 。 可以设计如下两组输入数据:
A= 2,B= 0,x= 4
A= 1,B= 1,x= 1
也可以让测试用例测试路径 P3( abe) 和 P4( acd) 。 相应的两组输入数据如下:
A= 2,B= 1,x= 1
A= 4,B= 0,x= 4
判定覆盖比语句覆盖强,但是仍不能保证判断条件的正确性 。 例如:第二个判断条件中的 x> 1若错写成了 x< 1,利用上面的输入数据就不能检查出这个错误 。
3、条件覆盖条件覆盖就是设计足够的测试用例,使得程序判定中的每个条件能获得各种可能的结果 。
条件,A> 1,B= 0,A= 2,x> 1。 需要有足够的测试用例使得上述四个条件都能有满足和不满足的情况 。 以下这两组输入数据能满足这些要求:
A= 2,B= 0,x= 4
A= 1,B= 1,x= 1
这两组数据不仅满足条件覆盖的要求,而且也满足判定覆盖的要求 。 但并不是所有的满足条件覆盖要求的数据都满足判定覆盖的要求 。 下面的两组数据满足条件覆盖的要求:
A= 1,B= 0,x= 3
A= 2,B= 1,x= 1
但是这组数据不满足判定覆盖的要求 。 为了解决这个问题,可以采用下面的判定/条件覆盖 。
4、判定/条件覆盖判定/条件覆盖就是设计足够的测试用例,使得判定中的每个条件都取到各种可能的值,而且每个判定表达式也都取到各种可能的结果 。
对于上面的例子,下述两组输入数据能满足这些要求:
A= 2,B= 0,x= 4
A= 1,B= 1,x= 1
判定/条件覆盖仍有缺陷 。 从表面上看,它测试了所有条件的所有可能结果,但事实上并不是这样 。 因为某些条件掩盖了另一些条件 。 例如,在逻辑表达式中,如果,与,表达式中某一条件为,假,,则整个表达式的值为,假,,这个表达式中另外的几个条件就不起作用了 。 同样地,如果在
,或,表达式中,某一条件为,真,,则整个表达式的值为
,真,,其它条件也就不起作用了 。 因此,采用判定/条件覆盖时,逻辑表达式中的错误不一定能测试出来 。
5、条件组合覆盖条件组合覆盖就是设计足够的测试用例,使得每个判定中的条件的各种可能组合都至少出现一次 。
可能的条件组合:
( 1) A> 1,B= 0
( 2) A> 1,B≠0
( 3) A≤1,B= 0
( 4) A≤1,B≠0
( 5) A= 2,x> 1
( 6) A= 2,x≤1
( 7) A≠2,x> 1
( 8) A≠2,x≤1
相应的输入数据:
A= 2,B= 0,x= 4
满足 ( 1) 和 ( 5)
A= 2,B= 1,x= 1
满足 ( 2) 和 ( 6)
A= 1,B= 0,x= 2
满足 ( 3) 和 ( 7)
A= 1,B= 1,x= 1
满足 ( 4) 和 ( 8)
显然,满足条件组合覆盖的测试数据,也一定满足判定覆盖,条件覆盖和判定/条件覆盖标准 。 但是,满足条件组合覆盖标准的测试数据并不一定覆盖了程序中的每条路径,
例如,利用上述四组测试数据就遗漏了路径 P4( acd) 。
8.3.2 等价类划分等价类划分是一种实用的测试技术,属于黑盒测试 。 与逻辑覆盖不同,使用等价类划分设计测试用例时,完全不需要考虑程序的内部逻辑结构,而主要依据程序的功能说明 。
穷尽测试是不可能实现的,实际上也是不必要的,我们可以从所有可能的输入数据中选择一个子集来进行测试 。 如何选择这个子集,使得这个子集具有代表性,能尽可能多地发现程序中的错误,等价类划分就是基于这种考虑的一种实现方法 。 该方法根据输入数据和输出数据的特点,将程序输入域划分成若干个部分,即子集,然后从每个子集中选取具有代表性的数据作为测试用例 。
1、划分等价类等价类的划分在很大程度上依靠的是测试人员的经验,
下面给出几条基本原则:
( 1) 如果输入条件规定了取值范围,则可划分出一个有效的等价类 ( 输入值在此范围内 ) 和两个无效的等价类
( 输入值小于最小值,输入值大于最大值 ) 。
( 2) 如果输入条件规定了输入数据的个数,则可相应地划分出一个有效的等价类 ( 输入数据的个数等于给定的个数要求 ) 和两个无效的等价类 ( 输入数据的个数少于给定的个数要求,输入数据的个数多于给定的个数要求 ) 。
( 3) 如果输入条件规定了输入数据的一组可能的值,
而且程序对这组可能的值做相同的处理,则可将这组可能的值划分为一个有效的等价类,而这些值以外的值划分成无效的等价类 。
( 4)如果输入条件规定了输入数据的一组可能的值,但是程序对不同的输入值做不同的处理,则每个输入值是一个有效的等价类,此外还有一个无效的等价类(所有不允许值的集合)。
( 5)如果输入条件规定了输入数据必须遵循的规则,则可以划分一个有效的等价类(符合规则)
和若干个无效的等价类(从各种角度违反规则)。
2、确定测试用例划分出等价类后,根据以下原则设计测试用例:
( 1) 为每个等价类编号 。
( 2) 设计一个新的测试用例,使它能包含尽可能多的尚未被覆盖的有效等价类 。 重复这一过程,直到所有的有效等价类都被覆盖 。
( 3) 设计一个新的测试用例,使它包含一个尚未被覆盖的无效等价类 。 重复这一过程,直到所有的无效等价类都被覆盖 。
8.3.3 边界值分析人们在长期的测试中发现,程序往往在处理边界值的时候容易出错,比如数组的下标,循环的上下界等 。 针对这种情况设计测试用例的方法就是边界值分析方法 。
使用边界值分析方法设计测试用例时,首先要确定边界情况 。 通常输入等价类和输出等价类的边界,就是应该着重测试的程序边界情况 。 也就是说,
应该选取恰好等于,小于和大于边界的值作为测试数据,而不是选取每个等价类内的典型值或任意值作为测试数据 。
边界值分析也属于黑盒测试,可以看作是对等价类划分的一个补充 。 在设计测试用例时,往往联合等价类划分和边界值分析这两种方法 。
8.3.4 错误推测法错误推测法的基本想法是:列举出程序中所有可能有的错误和容易发生错误的特殊情况,根据它们选择测试用例 。
例如,输入数据为零或输出数据为零的地方往往容易出错;各模块间对公有变量的引用也是容易出错的地方 。
8.4 软件测试的步骤
8.4.1 单元测试
8.4.2 集成测试退出
8.4.3 有效性测试
8.4.4 系统测试单元测试又称模块测试,集中对软件设计的最小单位 —— 模块进行测试,主要是为了发现模块内部可能存在的各种错误和不足 。
进行单元测试时,根据程序的内部结构设计测试用例,主要使用白盒测试法 。 由于各模块间相对独立,因而对多个模块的测试可以并行地进行,以提高测试效率 。
8.4.1 单元测试
1、单元测试的内容
( 1) 模块接口主要进行的测试项目有以下几方面:
所测模块的形式参数和调用该模块的实际输入参数在参数数目,属性和顺序上是否匹配;
是否修改了只做输入用的形式参数;
输出给被调用模块的参数在数目,属性和顺序上是否正确;
全程变量的定义和用法在各个模块中是否一致 。
若模块中有外部的 I/O操作,还应该进行以下的测试项目:
文件属性是否正确;
打开文件语句和关闭语句是否正确;
格式说明书与输入/输出语句是否一致;
缓冲区的大小与记录长度是否匹配;
使用文件之前是否先打开了文件;
文件操作结束后是否关闭了文件;
是否进行了输入/输出错误检查并进行了相应的处理 。
( 2) 局部数据结构模块的局部数据结构是常见的错误来源,测试者应该仔细设计测试用例,以便发现这样一些类型的错误:
错误的变量名(变量名拼写错或被编译程序截短);
错误的或不一致的数据类型说明;
使用尚未赋值或尚未初始化的变量;
错误的初始值或错误的缺省值;
数据类型不相容;
上溢、下溢或地址异常。
如果有可能的话,在单元测试期间除了局部数据结构之外,还应该检查全程数据对模块的影响。
( 3) 重要的执行路径选择适当的测试用例,对模块中的最有代表性、最可能发现错误的执行路径进行测试。
错误的计算主要集中在以下几个方面:
运算的优先次序不对或误解了运算符的优先次序;
混合运算(运算对象的类型彼此不相容);
变量的初始值赋值不正确;
运算的精度不够;
表达式的符号有错误。
错误的比较和控制流主要集中在以下几个方面:
不同数据类型之间的比较;
逻辑运算符不正确或优先次序不正确;
由于精度问题造成的两值比较时不相等;
差,1”错,即循环次数多一次或少一次;
错误的或不可能的循环终止条件;
当遇到发散的迭代时不能终止的循环;
错误地修改循环变量。
( 4) 出错处理由于输入等条件的限制,程序在运行中出错往往是不可避免的。因而好的程序设计应该能预见可能出现的各种出错情况,并且设置相应的出错处理,以便在出现错误时执行相应的操作。
在单元测试时也应该对模块中的出错处理部分进行测试,进行这一部分测试时可能存在的错误主要有:
对错误的描述难于理解,或者是描述过于简单;
显示的错误信息与实际错误不相符;
在对错误进行处理之前,错误条件已经引起系统的干预;
对错误的处理不正确。
( 5) 边界条件我们知道,软件常常在它的边界上失效。例如,处理 n
元数组的第一个元素或最后一个元素时,在 n次循环中的第 n
次重复时,往往会发生错误。因此,使用刚好小于、等于或大于最大值或最小值的数据结构、控制量和数据值的测试方案时,很可能会发现软件中的错误。
2、单元测试的步骤单元测试的对象是模块 。 测试者必须自己动手设计这两类模块:驱动模块和存根模块 。
驱动模块
A
存根模块 B ……,存根模块 N
驱动模块:相当于所测模块的,主程序,。 它接收测试数据,
把这些数据传送给所测模块,然后输出测试结果 。
存根模块:也叫虚拟子程序 。
它的作用是模拟被测模块所调用的子模块 。 存根模块可以做少量的数据操作,一般情况下,不需要把实际子模块的所有功能都带进来 。
8.4.2 集成测试集成测试过程中要考虑的问题:
( 1) 数据穿过模块接口时是否会丢失;
( 2) 模块的功能是否会对其它模块的功能产生不利的影响;
( 3) 把子功能组合起来,能否达到预期的主功能要求;
( 4) 单个模块的误差累积起来是否会放大到不能接受的程度;
( 5) 全局数据结构是否有问题 。
将各个模块组装成系统的方法:非增殖式组装方式和增殖式组装方式 。
采用非增殖式组装方式:先分别对每个模块进行测试,
再把所有模块按设计要求组装在一起进行测试,最终得到所要求的软件。
采用增殖式组装方式:把下一个要测试的模块同已经测试好的那些模块结合起来进行测试,测试完以后再把下一个应该测试的模块结合进来测试,这种方法实际上同时完成单元测试和集成测试。
这两种方法各有优缺点:
( 1) 采用非增殖式组装方式时,可以较早发现模块间的接口错误,而采用增殖式组装方式时,只有在模块加进来时才可能发现,因此接口错误发现较晚 。
( 2) 采用非增殖式组装方式时要对每个模块进行单元测试,需要编写的测试软件较多,工作量大,而采用增殖式组装方式时,利用已测试过的模块部分作为部分测试软件,因而工作量较小 。
( 3) 非增殖式组装方式要求一下子把所有模块组装起来,如果发现错误则较难判断错误的位置,而采用增殖式组装方式时,由于每次只加入一个模块,因而错误往往与刚加入的模块有关,查错则相对容易些 。
( 4) 采用非增殖式组装方式时,各模块的单元测试可以并行地进行,因此可以充分利用人力,加快测试进程,采用增殖式组装方式时却不能如此 。
1、自顶向下结合在使用增殖式组装方式时,常用的有自顶向下和自底向上两种方法 。
采用这种组装方式时,是从主控制模块开始,沿着软件的控制层次向下移动,从而逐渐把各个模块都结合起来 。
M1
M3
S7
M2 S4
M6M5
M8
左图是一个树形结构,主控制模块是
M1,在把主控制模块
M1所属的那些模块都组装起来时可以采取两种方法:深度优先策略或者宽度优先策略 。
采用深度优先的结合方法时,先把软件结构的一条主控制通路上的所有模块一个一个地结合组装起来 。 主控制通路的选择取决于应用的特点 。 对于图 8.5来说,如果选取左通路为主控通路,那么首先结合模块 M1,M2和 M5,然后是 M8。 如果 M2的某个功能需要的话,可结合 M6。 然后结合中间的和右边的控制通路 。
采用宽度优先的结合方法时,逐层结合直接下属的所有模块,即把处于同一个控制层次上的所有模块组装起来 。 对于图 8.5来说,首先结合模块 M2,
M3和 M4( 代替存根模块 S4),接着结合下一个控制层次中的模块 M5,M6和 M7;如此继续进行下去,
直到所有模块都被结合进来 。
不管是采用深度优先策略还是宽度优先策略,其结合过程如下:
( 1)用主控制模块作为测试驱动模块,所有直接下属于主控制模块的模块用存根模块代替,对主模块进行测试;
( 2)根据选定的结合策略(深度优先或宽度优先),
每次用一个实际模块替换一个存根模块,对新结合进来的模块的直接下属模块,用新的存根模块代替;
( 3)对结合进来的模块进行相应的测试;
( 4)为了保证新加入的模块不引入新的错误,可以进行回归测试,即重复以前进行过的部分测试或全部测试。
从第( 2)步开始,不断地重复进行上述过程,直到所有模块都结合进来为止。
采用自顶向下的结合策略的好处:
在测试过程中能够较早地对主要的控制或关键的判断点进行检验 。 因为在一个功能划分合理的软件结构中,
关键的判断点常常出现在较高的层次里,所以能够较早碰到 。 如果主要控制存在问题,及早发现这类问题并尽快想办法解决是十分重要的,这样可以大大减少后面的工作量 。 如果选择的是深度优先结合方法,可以首先实现并验证软件的一个比较完整的功能,这样对增强开发人员和用户双方的信心是很有意义的 。
采用自顶向下的结合策略的不足:
可能会遇到逻辑上的问题。当我们为了充分地测试较高层次的功能时,可能需要较低层次上处理的信息,
但是我们采用自顶向下的方法时,存根模块代替了低层次的模块,若高层模块需要低层模块返回的信息不仅数量大,而且种类也很多时,存根模块有可能很难完全满足这个要求,因而,这种方法有一定的局限性。为了解决这个问题,可以采用以下解决办法:
( 1)把许多测试推迟到用实际模块替换了存根模块以后再进行。采用这种方法也有一定的缺陷:由于我们对一些特定的测试和组装与特定模块间的对应关系失去了某些控制,从而在确定错误原因时会发生困难。
( 2)由层次系统的底部向上组装软件。这种方法就是下面要介绍的自底向上结合方法。
2、自底向上结合自底向上测试是从软件结构最低层的模块开始进行组装和测试 。 它不需要存根模块,但需要驱动模块 。 其结合过程如下:
( 1) 把低层模块组合成实现某个特定软件子功能的模块族;
( 2) 为每一个族编写一个驱动模块,作为测试的控制来协调测试用例的输入和输出;
( 3) 对模块族进行测试;
( 4) 按模块结构图依次向上扩展,用实际模块替换驱动模块,将模块族与新的模块结合,形成新的模块族,再进行测试,直到所有模块都被结合进来 。
图中自底向上的结合过程:首先把模块组合成族 1,族 2和族 3,然后设计相应的驱动模块 D1,D2
和 D3,并对每个子功能族进行测试;族 1
和族 2下属于模块 Ma,
去掉驱动模块 D1和
D2,把这两个族直接与 Ma结合,同样地,
在族 3与模块 Mb结合之前将 D3去掉;最后
Ma和 Mb与 Mc结合起来 。
D2
M c
M a Mb
D1 D3
族 1
族 2
族 3
自顶向下结合的主要优点:不需要设计测试驱动模块,
与存根模块相联系的问题可能在测试的早期发现 。 主要缺点是:需要设计存根模块,并且由于为了使存根模块能够尽量模拟实际模块的功能,必然会增加设计存根模块的复杂度,
从而导致增加一些附加的测试 。
自底向上结合的主要优点:不需要设计存根模块,而设计测试驱动模块一般比建立存根模块要容易,同时比较容易设计测试用例,并且可以实现多个模块的并行测试,从而提高测试效率。主要缺点是:直到最后一个模块结合进来以前,
程序作为一个整体始终不存在。也就是说,对主要的控制直到最后才接触到。
一般来说,我们并不只是使用单一的自顶向下结合或自底向上结合方式,而是根据情况结合这两种方法来进行组装和测试:对软件结构中较上层模块使用自顶向下结合方法,
对软件结构中较下层模块使用自底向上结合方法。
8.4.3 有效性测试有效性测试的任务:进一步验证软件的有效性,
即验证软件的功能和性能是否与用户的要求一致 。
在每个有效性测试用例测试完成以后,可能有两种情况:
( 1) 软件的功能和性能与用户的要求一致,软件可以接受;
( 2) 软件的功能或性能与用户的要求有差距 。
若出现后一种情况,通常与需求分析阶段的差错有关,这时要列出一张软件缺陷表,通过与用户的协商,找出问题所在并解决它 。
8.4.4 系统测试软件仅仅是计算机系统的一个组成部分,在实际运行中,它要和计算机系统的其它元素一起工作,
所以最终要把软件与其它系统元素结合起来,进行一系列的集成测试和有效性测试 。
系统测试的目的在于通过与系统的需求定义作比较,发现软件与系统定义不符合或与之矛盾的地方 。
8.5 调试
8.5.1 调试的步骤
8.5.2 调试的策略退出调试过程由两个部分组成:首先,确定程序中错误的确切性质和位置;然后,对程序代码进行分析,确定问题的原因,并设法改正这个错误 。
具体地说,由以下步骤组成:
( 1) 从错误的外部表现入手,确定程序中出错的位置;
( 2) 分析有关程序代码,找出错误的内在原因;
( 3) 修改程序代码,排除这个错误;
( 4) 重复进行暴露了这个错误的原始测试以及某些回归测试,以确保该错误确实被排除且没有引入新的错误;
( 5) 如果所作的修正无效,则撤消这次改动,重复上述过程,直到找到一个有效的办法为止 。
8.5.1 调试的步骤
8.5.2 调试的策略
1、强行排错这是目前使用较多但效率较低的一种调试方法 。 具体地说,通常有三种措施:
( 1) 输出存储器内容
( 2)打印语句
( 3)自动调试工具
2、回溯法采用回溯法排错时,调试人员首先分析错误征兆,确定最先出现,症状,的位置 。 然后人工沿程序的控制流程往回追踪源程序代码,直到找到错误根源或确定错误产生的范围为止 。
实践证明,回溯法是一种可以成功地用在小程序中的很好的纠错方法 。 通过回溯,我们往往可以把错误范围缩小到程序中的一小段代码,
仔细分析这段代码,不难确定出错的准确位置 。
但是,随着程序规模的扩大,由于回溯的路径数目越来越多,回溯法会变得很困难,以至于完全不可能实现 。
3、归纳法归纳法就是从线索 ( 错误征兆 ) 出发,通过分析这些线索之间的关系而找出故障的一种系统化的思考方法 。 这种方法主要包括下述四个步骤:
( 1) 收集有关的数据
( 2) 组织数据
( 3) 提出假设
( 4) 证明假设
4、演绎法演绎法从一般原理或前提出发,经过排除和精化的过程推导出结论 。 演绎法排错的过程是这样的:测试人员首先列出所有可能出错的原因或假设,然后再用原始测试数据或新的测试,
逐个排除不可能正确的假设,最后,证明剩下的原因确实是错误的根源 。
列举可能的原因排除不适当的原因对保留的假设继续推断有剩余 证明假设能 纠正错误没有剩余收集更多的数据不能
8.6 软件可靠性
8.6.1 软件可靠性的定义
8.6.2 软件正确性证明退出硬件可靠性可以用平均故障间隔时间 ( MTBF) 来测量:
MTBF= MTTF十 MTTR
其中,MTTF和 MTTR分别是平均无故障时间和平均修复时间 。
软件可靠性可以根据与技术系统可靠性接近的平行定义来表示 。 我们可以这样来定义软件可靠性:程序故障的频率和临界值 。 这里,故障是指在许可的运行条件下一个不可接受的结果或行为 。 同硬件一样,软件可靠性可以用错误出现和被纠正的速率来表示 。
8.6.1 软件可靠性的定义程序正确性证明是一项复杂的课题,涉及到许多复杂的领域 。 采用诸如利用数学归纳法或谓词演算的人工的正确性证明,在评价小程序时可能有些价值,但是在证明大型软件的正确性时,不仅工作量太大,而且在证明的过程中也很容易引进一些新的错误,因此并不实用 。
目前,已经开发出了一些自动的计算机软件正确性证明方法 。 自动的正确性证明程序一般涉及到程序逻辑的形式化描述,这种描述可以由宏编译程序来开发 。 宏编译程序产生软件的符号表示,利用以人工智能理论和谓词演算为基础的自动化技术来,证明,程序的正确性 。 目前已经研究出了 PASCAL和 LISP等的正确性证明程序,正在对这些系统进行评价和改进 。 这些系统目前还只能对较小的程序进行评价 。
8.6.2 程序正确性证明
8.7 测试工具
8.7.1 人工测试
8.7.2 自动测试工具退出
( 1) 检查变量的交叉引用
( 2) 检查标号的交叉引用
( 3) 检查子程序,宏,函数
( 4) 常量检查
( 5) 标准检查
( 6) 风格检查
( 7) 比较控制流
( 8) 选择,激活路径
( 9) 对照程序的规格说明,详细阅读源代码
( 10) 补充文档
8.7.1 人工测试
1、桌前检查代码评审一般由三至五人组成小组,成员组成包括:
组长,秘书和测试人员 。
首先,组长提前把设计规格说明书、控制流程图、程序文本及有关要求、规范等分发给小组成员,作为评审的依据。小组成员要先充分阅读这些材料,为评审会做好准备。第二步是召开程序审查会,首先由测试人员进行讲解,
其它成员可以提问并展开讨论。通过讲解和讨论,可以暴露许多程序员以前没有发现的错误。讨论过程中不讨论任何纠错问题,主要是发现错误,否则违背了测试的目的。
在代码会审之后,需要把发现的错误登记造表,并交给程序员;若发现错误较多,或发现重大错误,则在改正之后,
还要再次组织代码会审。
2、代码评审走查与代码评审基本相同,但要求更加严格 。 其过程分为两步:
第一步把有关材料先发给走查小组每个成员,让他们先认真阅读材料,准备好意见 。 第二步是开会,开会的程序与代码会审不同,首先由测试组成员准备好一批有代表性的测试用例,提交给走查小组 。 开会时,集体扮演计算机角色,让测试用例沿程序的逻辑运行一遍,并随时记录程序的踪迹,供分析和讨论用 。 利用测试用例的媒介作用对程序的逻辑和功能提出各种疑问,结合问题开展热烈的讨论和争议,能够发现更多的问题 。
3、走查
( 1) 静态分析程序
( 2) 断言处理程序
( 3) 测试数据生成程序
( 4) 测试覆盖监视程序
( 5) 测试处理程序
( 6) 输出比较程序
8.7.2 自动测试工具