第七章 软件测试 Software Testing 7.1 软件测试的基本概念(Testing Concepts) 7.2 软件测试方法 (Testing Method) 7.3 测试用例的设计(Test Case Design) 7.4 软件测试的步骤 (Steps in Testing) 7.5 调试 (Debugging) 7.6 软件可靠性 (Software Reliability) 7.7 测试工具 (Testing Tools)
Definition of Software Testing Rule of Software Testing 7.1 软件测试的基本概念 Testing Concepts 7.1.1 软件测试的定义 Definition of Software Testing 7.1.2 软件测试的基本原则 Rule of Software Testing 7.1.3 软件测试的步骤 Steps in Testing 7.1.4 软件测试的信息流 Test Information Flow
Definition of Software Testing 7.1.1 软件测试的定义 Definition of Software Testing 关于测试目的,G.J.Myers给出了以下的观点: (1)测试是为了发现程序中的错误而执行程序的过程; (2)好的测试方案是极可能发现迄今为止尚未发现的错误的测试方案; (3)成功的测试是发现了至今为止尚未发现的错误的测试。 测试的定义:为了发现程序中的错误而执行程序的过程。具体地说,软件测试是根据软件开发各阶段的规格说明和程序的内部结构而精心设计出一批测试用例,并利用测试用例来运行程序,以发现程序错误的过程。
Rule of Software Testing 7.1.2 软件测试的基本原则 Rule of Software Testing (1)尽早地、不断地进行软件测试。 (2)设计测试用例时,要给出测试的预期结果。 (3)开发小组和测试小组分开。 (4)要设计非法输入的测试用例。 (5)在对程序修改之后要进行回归测试。 (6)程序中尚未发现的错误的数量往往与在该段程序中已发现的错误的数量成正比。
7.1.3 软件测试的步骤 Steps in Testing 1.单元测试(Unit Testing/Module Testing) 7.1.3 软件测试的步骤 Steps in Testing 1.单元测试(Unit Testing/Module Testing) 又称模块测试。每个程序模块完成一个相对独立的子功能,所以可以对该模块进行单独的测试。由于每个模块都有清晰定义的功能,所以通常比较容易设计相应的测试方案,以检验每个模块的正确性。 2.集成测试(Integration testing /Sub-system Testing) 在单元测试完成后,要考虑将模块集成为系统的过程中可能出现的问题,例如,模块之间的通信和协调问题,所以在单元测试结束之后还要进行集成测试。这个步骤着重测试模块间的接口,子功能的组合是否达到了预期要求的功能,全程数据结构是否有问题等。
3.有效性测试(Validity testing) 集成测试通过后,应在用户的参与下进行有效性测试。这个时候往往使用实际数据进行测试,从而验证系统是否能满足用户的实际需要。 4.系统测试(System Testing) 系统测试是把通过有效性测试的软件,作为基于计算机系统的一个整体元素,与整个系统的其他元素结合起来,在实际运行环境下,对计算机系统进行一系列的集成测试和有效性测试。
Integration testing and Validity testing 软件测试的步骤: 集成测试、组装测试、联合测试; 重点在于测试模块之间的接口; Integration testing and Validity testing 模块测试Module Testing 子系统测试Sub-system Testing 目的:保证每个模块作为一个单元能够正确运行,又称为单元测试 Unit Testing 有用户参加的系统测试; 验证是否满足用户的需要。 System Testing with user 系统测试System Testing 将经过测试的子系统装配成一个完整的系统来测试; 发现设计和编码的错误,验证系统是否满足需求说明所定义的功能及其动态特性; System Testing 验收测试Check and Accept Testing 平行运行Simultaneous Running 同时运行新旧两个系统,并且对处理的结果进行比较,以确定新系统是否满足相关性能指标。 Simultaneous Running of Old System and New System
7.1 4 测试阶段的信息流 Test Information Flow 正确 调试 包括需求说明书、设计说明书、源程序清单等 错误 软件配置 评价 测试结果 测试 可靠性预测 可靠性模型 错误率数据 预期结果 测试配置 包括测试计划和测试方案 测试与软件开发各个阶段的关系 软件开发过程是一个自顶向下,逐步细化的过程 软件计划阶段定义软件作用域 软件需求分析建立软件信息域、功能和性能需求、约束等 软件设计 把设计用某种程序设计语言转换成程序代码 测试过程是依相反顺序安排的自底向上,逐步集成的过程。
测试与软件开发各个阶段的关系 (示意图) 基本概念 注意:确认测试定义为验收测试。
7. 2 软件测试方法 Testing Method 7.2.1 黑盒测试 (Black-Box Testing) 7. 2 软件测试方法 Testing Method 7.2.1 黑盒测试 (Black-Box Testing) 7.2.2 白盒测试 (White-Box Testing)
7.2.1 黑盒测试 Black-Box Testing 任何产品都可以使用以下两种方法进行测试: 7.2.1 黑盒测试 Black-Box Testing 任何产品都可以使用以下两种方法进行测试: (1)如果已知产品的功能,则可以对它的每一个功能进行测试,看是否都达到了预期的要求; (2)如果已知产品的内部工作过程,则可以对它的每种内部操作进行测试,看是否符合设计要求。 第一种方法是黑盒测试,第二种方法是白盒测试。
黑盒测试时完全不考虑程序内部的结构和处理过程,只按照规格说明书的规定来检查程序是否符合它的功能要求。黑盒测试是在程序接口进行的测试,又称为功能测试。 黑盒测试检查的主要方面有: 程序的功能是否正确或完善; 数据的输入能否正确接收,输出是否正确; 是否能保证外部信息(如数据文件)的完整性等。 用黑盒法设计测试用例时,必须用所有可能的输入数据来检查程序是否都能产生正确的输出。
黑盒测试不可能实现穷尽测试: 假设有一个很简单的小程序,输入量只有两个:A和B,输出量只有一个:C。如果计算机的字长为32位,A和B的数据类型都只是整数类型。利用黑盒法进行测试时,将A和B的可能取值进行排列组合,输入数据的可能性有:232×232=264种。假设这个程序执行一次需要1毫秒,要完成所有的测试,计算机需要连续工作5亿年。显然,这是不能容忍的,而且,设计测试用例时,不仅要有合法的输入,而且还应该有非法的输入,在这个例子中,输入还应该包括实数、字符串等,这样,输入数据的可能性就更多了。所以说,穷尽测试是不可能实现的。
7.2.2 白盒测试 White-Box Testing 7.2.2 白盒测试 White-Box Testing 白盒测试时将程序看作是一个透明的盒子,也就是说测试人员完全了解程序的内部结构和处理过程。所以测试时按照程序内部的逻辑测试程序、检验程序中的每条通路是否都能按预定的要求正确工作。白盒测试又称为结构测试。 利用白盒测试设计测试用例时,应包括以下三类测试: (1)语句测试:要求程序中的每个语句至少测试一次; (2)分支测试:要求程序中的每个分支至少测试一次; (3)路径测试:要求程序中的每条路径至少测试一次。
白盒测试也不能实现穷尽测试(Exhaustive testing): 左图所示的一个小程序的控制流程,其中每个圆圈代表一段源程序(或语句块),图中的曲线代表执行次数不超过20的循环,循环体中共有5条通路。这样,可能执行的路径有520条,近似为1014条可能的路径。如果完成一个路径的测试需要1毫秒,那么整个测试过程需要3170年。显然,这也是不能接受的。
7.3 测试用例的设计 Test Case Design 7.3.1 逻辑覆盖(Logic Coverage) 7.3 测试用例的设计 Test Case Design 7.3.1 逻辑覆盖(Logic Coverage) 7.3.2 等价类划分(Equivalence Partitioning) 7.3.3 边界值分析(Boundary Value Analysis) 7.3.4 错误推测法(Error Guess)
7.3.1 逻辑覆盖 Logic Coverage 逻辑覆盖是以程序的内部逻辑结构为基础的测试用例设计技术,属于白盒测试。它要求测试人员十分清楚程序的逻辑结构,考虑的是测试用例对程序内部逻辑覆盖的程度。 根据覆盖的目标,逻辑覆盖又可以分为: 语句覆盖(Statemet Coverage) 判定覆盖(Decision Coverage) 条件覆盖(Condition Coverage) 判定/条件覆盖(Decision /Condition Coverage) 条件组合覆盖(Condition compounding Coverage) 路径覆盖(Path Coverage)
语句覆盖Statemet Coverage PROCEDURE Example(A,B:real; X:real ); Begin IF (A>1) AND (B=0) THEN X:= X / A; IF ( A=2 ) OR (X>1) THEN X:=X+1 END; S 1 a T 4 (A>1) and (B=0) c F X = X / A 2 5 b I. A=2, B= 0, X=4 ---- sacbed (A=2) or (X>1) T 6 e F X=X+1 3 语句覆盖 所有的语句至少执行一次! 7 d
判定覆盖Decision Coverage 每个判定的每种可能都至少执行一次! 即每个判定的每个分支都至少执行一次! S 1 a T 4 (A>1) and (B=0) c F X = X / A 2 I: A=3, B=0,X=3: sacbd 5 b II: A=2, B=1,X=1: sabed (A=2) or (X>1) T 6 e F X=X+1 满足判定覆盖的测试用例一定满足语句覆盖:判定覆盖比语句覆盖强。 3 7 d
条件覆盖 Condition Coverage 每个语句至少执行一次,而且判定表达式中的每个条件都要取得各种可能的结果。 S 1 a T 4 (A>1) (A≤1) (A>1) and (B=0) c (B=0) (B≠0) F X = X / A 2 5 (A=2) (A≠2) b (X>1) (X≤1) (A=2) or (X>1) T 6 e I: A=2, B=0,X=4: sacbed F X=X+1 7 3 II: A=1, B=1,X=1: sabd d I: A=2, B=0,X=1: sacbed 条件覆盖一般情况下比判定覆盖要强,但是也有满足条件覆盖但不能满足判定覆盖的情况。 II: A=1, B=1,X=1: sabed
判定/条件覆盖 Decision /Condition Coverage 判定/条件覆盖就是设计足够的测试用例,使得判定中的每个条件都取到各种可能的值,而且每个判定表达式也都取到各种可能的结果。 S 1 a T (A>1) (A≤1) 4 (A>1) and (B=0) c (B=0) (B≠0) F X = X / A 2 (A=2) (A≠2) 5 b (X>1) (X≤1) (A=2) or (X>1) T 6 e I: A=2, B=0,X=4: sacbed F X=X+1 7 II: A=1, B=1,X=1: sabd 3 d 测试用例I,II既满足判定覆盖也满足条件覆盖的要求。严格来讲,合适的条件覆盖测试用例设计应该作到满足判定/条件覆盖的标准:判定/条件覆盖并不比条件覆盖更强。逻辑表达式中的错误不一定能测试出来。
条件组合覆盖 Condition compounding Coverage 条件组合覆盖要求选取足够多的测试数据,使得每个判定表达式中条件的各种可能组合都至少出现一次。 S 1 a (A>1) (B=0) I. A=2,B=0,X=4 T 4 c (A>1) and (B=0) (A≤1) (B≠0) II. A=2,B=1,X=1 F X = X / A 2 覆盖路径 I: sacbed II: sabed III: sabed IV: sabd 5 (A=2) (X>1) b (A=2) or (X>1) T e (A≠2) (X≤1) 6 F X=X+1 III. A=1,B=0,X=2 7 3 显然,满足条件组合覆盖的测试数据,也一定满足判定覆盖、条件覆盖和判定/条件覆盖标准。但是,不一定覆盖了程序中的每条路径,例如,利用上述四组测试数据就遗漏了路径P4(acd)。 IV. A=1,B=1,X=1 d
点覆盖: 边覆盖: 路径覆盖(Path Coverage) : 如果连通图 G 的子图G´是连通的,而且包含G的所有节点,则称G´是G 的点覆盖。 与语句覆盖标准相同。 边覆盖: 如果连通图 G 的子图G´是连通的,而且包含G的所有边,则称G´是G 的边覆盖。 通常与判定覆盖标准相同。 路径覆盖(Path Coverage) : 选取足够多的测试数据,使程序的每条 可能路径都至少执行一次(如果程序图 中有环,则要求每个环至少经过一次)。 路径覆盖是较强的逻辑覆盖标准。 1 4 2 5 6 3 I: A=1,B=1,X=1 (1-2-3) ; II: A=1,B=1,X=2 (1-2-6-7) III: A=3,B=0,X=1 (1-4-5-3); IV: A=2,B=0,X=4 (1-4-5-6-7) 7
Equivalence Partitioning 7.3.2 等价类划分 Equivalence Partitioning 等价类划分是一种实用的测试技术,属于黑盒测试。与逻辑覆盖不同,使用等价类划分设计测试用例时,完全不需要考虑程序的内部逻辑结构,而主要依据程序的功能说明。 穷尽测试是不可能实现的,实际上也是不必要的,我们可以从所有可能的输入数据中选择一个子集来进行测试。如何选择这个子集,使得这个子集具有代表性,能尽可能多地发现程序中的错误,等价类划分就是基于这种考虑的一种实现方法。该方法根据输入数据和输出数据的特点,将程序输入域划分成若干个部分,即子集,然后从每个子集中选取具有代表性的数据作为测试用例。
设计测试方案—黑盒测试技术 测试人员将程序看成是一个“黑盒”,即不关心程序内部是什么,只要检查程序是符合它的“功能说明”。 设计测试方案—黑盒测试技术 测试人员将程序看成是一个“黑盒”,即不关心程序内部是什么,只要检查程序是符合它的“功能说明”。 N个等价类 每个等价类中的一组具代表性的测试数据 2 等 价 划 分 (1) 等价分类法是将输入数据的可能值分成若干“等价类”,每一类以一个代表性的测试数据进行测试,这个数据就等价于这一类中的其它数据。 该法的关键在于如何将输入数据分类。 例如:输入的数据范围是1~999,我们可以划分三类:x<1,1≦x ≦ 999,x>999
Equivalence classes Partitioning 1、划分等价类 Equivalence classes Partitioning 等价类的划分在很大程度上依靠的是测试人员的经验,下面给出几条基本原则: (1)如果输入条件规定了取值范围,则可划分出一个有效的等价类(输入值在此范围内)和两个无效的等价类(输入值小于最小值、输入值大于最大值)。 (2)如果输入条件规定了输入数据的个数,则可相应地划分出一个有效的等价类(输入数据的个数等于给定的个数要求)和两个无效的等价类(输入数据的个数少于给定的个数要求、输入数据的个数多于给定的个数要求)。 (3)如果输入条件规定了输入数据的一组可能的值,而且程序对这组可能的值做相同的处理,则可将这组可能的值划分为一个有效的等价类,而这些值以外的值划分成无效的等价类。
(4)如果输入条件规定了输入数据的一组可能的值,但是程序对不同的输入值做不同的处理,则每个输入值是一个有效的等价类,此外还有一个无效的等价类(所有不允许值的集合)。 (5)如果输入条件规定了输入数据必须遵循的规则,则可以划分一个有效的等价类(符合规则)和若干个无效的等价类(从各种角度违反规则)。
Test case design for Equivalence Partitioning 2、确定测试用例 Test case design for Equivalence Partitioning 划分出等价类后,根据以下原则设计测试用例: (1)为每个等价类编号。 (2)设计一个新的测试用例,使它能包含尽可能多的尚未被覆盖的有效等价类。重复这一过程,直到所有的有效等价类都被覆盖。 (3)设计一个新的测试用例,使它包含一个尚未被覆盖的无效等价类。重复这一过程,直到所有的无效等价类都被覆盖。
2 等 价 划 分 (3) 等价类说明 测试数据 预期输出 测试结果 备注 1-6个数字的数字串 1 最高位是零的数字串 000001 2 等 价 划 分 (3) 等价类说明 测试数据 预期输出 测试结果 备注 1-6个数字的数字串 1 最高位是零的数字串 000001 最高位数字左邻是负号的数字串 -00001 -1 000000 太小的负整数 -47561 错误—无效输入(负数) 太大的正整数 132767 错误—无效输入(正数) 空字符串----6个空格 错误-没有数字 字符串左边字符既不是空格也不是零 *+kgh1 错误—填充错 最高位数字后面有空格 1 2 错误—无效输入 最高位数字后面有其他字符 1****2 负号和最高位数字之间有空格 - 12 错误—负号位置错
Boundary Value Analysis 7.3.3 边界值分析 Boundary Value Analysis 人们在长期的测试中发现,程序往往在处理边界值的时候容易出错,比如数组的下标,循环的上下界等。针对这种情况设计测试用例的方法就是边界值分析方法。 使用边界值分析方法设计测试用例时,首先要确定边界情况。通常输入等价类和输出等价类的边界,就是应该着重测试的程序边界情况。也就是说,应该选取恰好等于、小于和大于边界的值作为测试数据,而不是选取每个等价类内的典型值或任意值作为测试数据。 边界值分析也属于黑盒测试,可以看作是对等价类划分的一个补充。在设计测试用例时,往往联合等价类划分和边界值分析这两种方法。
经验表明:处理边界情况时程序最容易发生错误; 下标、数据结构、循环等边界。 对等价划分法中的不同等价类的边界情况进行重点测试; 边 界 值 分 析 经验表明:处理边界情况时程序最容易发生错误; 下标、数据结构、循环等边界。 对等价划分法中的不同等价类的边界情况进行重点测试; 等价类说明 测试数据 预期输出 测试结果 备注 使输出刚好等于最小的负整数 -32768 使输出刚好等于最大的正整数 32767 使输出刚刚小于最小的负整数 -32769 错误—无效输入(负数) 使输出刚刚大于最大的正整数 32768 错误—无效输入(正数) 4 错 误 推 测 不同类型不同特点的程序通常有一些特殊的容易出错的情况; 有时测试数据的组合数量也是非常多,难于覆盖所有情况; 经验数据
7.3.4 错误推测法 Error Guess 错误推测法的基本想法是:列举出程序中所有可能有的错误和容易发生错误的特殊情况,根据它们选择测试用例。 例如,输入数据为零或输出数据为零的地方往往容易出错;各模块间对公有变量的引用也是容易出错的地方。
b a c a,b,c Triangle 5. 实用测试策略(1) 例:程序Triangle读入三个整数值,这三个整数代 表同一个三角形三条边的长度,程序根据这三个 值判断三角形属于不等边、等腰或等边三角形中 的那一种。 a c a,b,c 三角形的类型? Triangle 黑盒测试(等价划分) 正常的三角形 (a,b,c) 不等边三角形 (8,10,12); (10,8,12); (10,12,8) 等边三角形 (10,10,10) 等腰三角形 (10,10,17); (10,17,10); (17,10,10) 黑盒测试(边界值分析) 一条边长度为零的情况 (0,10,12);(10,0,12); (10,12,0) 两条边的长度为零的情况 (0,0,17); (0,17,0); (17,0,0) 三条边的长度为零的情况 (0,0,0) 退化的三角形 (a,b,c) 不等边三角形 (10,6,4) 等边三角形 (0,0,0) 等腰三角形 (10,5,5); (5,10,5); (10,5,5) 黑盒测试(错误推测) 输入数据中包含负整数 (-10,-10,-10) …… 输入数据不全(不足三个正整数) (10,-,-)…… 输入数据中包含非整数型的数据 (a,b,c) (1.2,6e-4,7.8) …… 不能构成三角形的非法数据 (a,b,c) a+b<c (10,10,21) b+c<a (21,10,10) c+a<b (10,21,10)
start stop 5. 实用测试策略(2) 程序图 a<b+c? b<a+c? c<a+b? a=b? a=c? F a<b+c? 程序流程图 T F b<a+c? T F c<a+b? T F F a=b? a=c? T T T b=c? F b=c? F T 印出 “不等边三角形” 印出 “不是三角形” 印出 “等边三角形” 印出 “等腰三角形” stop
7.4 软件测试的步骤 Steps in Testing 7.4.1 单元测试(Unit Testing) 7.4 软件测试的步骤 Steps in Testing 7.4.1 单元测试(Unit Testing) 7.4.2 集成测试(Integration testing) 7.4.3 有效性测试(Validity testing) 7.4.4 系统测试(System Testing)
7.4.1 单元测试 Unit Testing 单元测试又称模块测试,集中对软件设计的最小单位——模块进行测试,主要是为了发现模块内部可能存在的各种错误和不足。 进行单元测试时,根据程序的内部结构设计测试用例,主要使用白盒测试法。由于各模块间相对独立,因而对多个模块的测试可以并行地进行,以提高测试效率。
Unit Testing Consideratins 1、单元测试的内容 Unit Testing Consideratins (1)模块接口module interface 主要进行的测试项目有以下几方面: 所测模块的形式参数和调用该模块的实际输入参数是否匹配; 是否修改了只做输入用的形式参数; 输出给被调用模块的参数在数目、属性和顺序上是否正确; 全程变量的定义和用法在各个模块中是否一致。 模块中外部的I/O操作测试。 (2)局部数据结构local data structrre 模块的局部数据结构是常见的错误来源,测试者应该仔细设计测试用例。
(3)重要的执行路径important execution path 选择适当的测试用例,对模块中的最有代表性、最可能发现错误的执行路径进行测试。 错误的表达式计算主要集中在以下几个方面: 运算的优先次序不对或误解了运算符的优先次序; 混合运算(运算对象的类型彼此不相容); 变量的初始值赋值不正确; 运算的精度不够; 表达式的符号有错误。 错误的比较和控制流主要集中在以下几个方面: 不同数据类型之间的比较; 逻辑运算符不正确或优先次序不正确; 由于精度问题造成的两值比较时不相等; 差“1”错,即循环次数多一次或少一次; 错误的或不可能的循环终止条件; 当遇到发散的迭代时不能终止的循环; 错误地修改循环变量。
(4)出错处理Error-handling 由于输入等条件的限制,程序在运行中出错往往是不可避免的。因而好的程序设计应该能预见可能出现的各种出错情况,并且设置相应的出错处理,以便在出现错误时执行相应的操作。 在单元测试时也应该对模块中的出错处理部分进行测试,进行这一部分测试时可能存在的错误主要有: 对错误的描述难于理解,或者是描述过于简单; 显示的错误信息与实际错误不相符; 在对错误进行处理之前,错误条件已经引起系统的干预; 对错误的处理不正确。
(5)边界条件Bondary conditions 我们知道,软件常常在它的边界上失效。例如,处理n元数组的第一个元素或最后一个元素时,在n次循环中的第n次重复时,往往会发生错误。因此,使用刚好小于、等于或大于最大值或最小值的数据结构、控制量和数据值的测试方案时,很可能会发现软件中的错误。
Unit Testing Procedures 2、单元测试的步骤 Unit Testing Procedures 单元测试的对象是模块。测试者必须自己动手设计这两类模块:驱动模块和存根模块。 驱动模块(driver module):相当于所测模块的“主程序”。它接收测试数据,把这些数据传送给所测模块,然后输出测试结果。 存根模块(stub modeule):也叫虚拟子程序。它的作用是模拟被测模块所调用的子模块。存根模块可以做少量的数据操作,一般情况下,不需要把实际子模块的所有功能都带进来。
7.4.2 集成测试 Integration testing 集成测试过程中要考虑的问题: (1)数据穿过模块接口时是否会丢失; 7.4.2 集成测试 Integration testing 集成测试过程中要考虑的问题: (1)数据穿过模块接口时是否会丢失; (2)模块的功能是否会对其它模块的功能产生不利的影响; (3)把子功能组合起来,能否达到预期的主功能要求; (4)单个模块的误差累积起来是否会放大到不能接受的程度; (5)全局数据结构是否有问题。
将各个模块组装成系统的方法:非渐增式组装方式和渐增式组装方式。 采用非渐增式组装方式:先分别对每个模块进行测试,再把所有模块按设计要求组装在一起进行测试,最终得到所要求的软件。 采用渐增式组装方式:把下一个要测试的模块同已经测试好的那些模块结合起来进行测试,测试完以后再把下一个应该测试的模块结合进来测试,这种方法实际上同时完成单元测试和集成测试。
集成测试(续) 1.一次性组装方式 它是一种非渐增式组装方式。也叫做整体拼装。 使用这种方式,首先对每个模块分别进行模块测试,然后再把所有模块组装在一起进行测试,最终得到要求的软件系统。 整体组装 单元测试 系 统结构图
这两种方法各有优缺点: (1)采用非渐增式组装方式时,可以较早发现模块间的接口错误,而采用渐增式组装方式时,只有在模块加进来时才可能发现,因此接口错误发现较晚。 (2)采用非渐增式组装方式时要对每个模块进行单元测试,需要编写的测试软件较多,工作量大,而采用渐增式组装方式时,利用已测试过的模块部分作为部分测试软件,因而工作量较小。 (3)非渐增式组装方式要求一下子把所有模块组装起来,如果发现错误则较难判断错误的位置,而采用渐增式组装方式时,由于每次只加入一个模块,因而错误往往与刚加入的模块有关,查错则相对容易些。 (4)采用非渐增式组装方式时,各模块的单元测试可以并行地进行,因此可以充分利用人力,加快测试进程,采用渐增式组装方式时却不能如此。
1、自顶向下结合Top-Down Intergration 在使用渐增式组装方式时,常用的有自顶向下和自底向上两种方法。 1、自顶向下结合Top-Down Intergration 采用这种组装方式时,是从主控制模块开始,沿着软件的控制层次向下移动,从而逐渐把各个模块都结合起来。 左图是一个树形结构,主控制模块是M1,在把主控制模块M1所属的那些模块都组装起来时可以采取两种方法:深度优先策略或者宽度优先策略(depth-frist or breadtn-frist manner)。
不管是采用深度优先策略还是宽度优先策略,其结合过程如下: (1)用主控制模块作为测试驱动模块,所有直接下属于主控制模块的模块用存根模块代替,对主模块进行测试; (2)根据选定的结合策略(深度优先或宽度优先),每次用一个实际模块替换一个存根模块(stub modeule),对新结合进来的模块的直接下属模块,用新的存根模块代替; (3)对结合进来的模块进行相应的测试; (4)为了保证新加入的模块不引入新的错误,可以进行回归测试,即重复以前进行过的部分测试或全部测试。 从第(2)步开始,不断地重复进行上述过程,直到所有模块都结合进来为止。
采用自顶向下的结合策略的好处: 在测试过程中能够较早地对主要的控制或关键的判断点进行检验。因为在一个功能划分合理的软件结构中,关键的判断点常常出现在较高的层次里,所以能够较早碰到。如果主要控制存在问题,及早发现这类问题并尽快想办法解决是十分重要的,这样可以大大减少后面的工作量。如果选择的是深度优先结合方法,可以首先实现并验证软件的一个比较完整的功能,这样对增强开发人员和用户双方的信心是很有意义的。
采用自顶向下的结合策略的不足: 可能会遇到逻辑上的问题。当我们为了充分地测试较高层次的功能时,可能需要较低层次上处理的信息,但是我们采用自顶向下的方法时,存根模块代替了低层次的模块,若高层模块需要低层模块返回的信息不仅数量大,而且种类也很多时,存根模块有可能很难完全满足这个要求,因而,这种方法有一定的局限性。 由层次系统的底部向上组装软件。这种方法就是下面要介绍的自底向上结合方法。
2、自底向上结合Bottom-Up Intergration 自底向上测试是从软件结构最低层的模块开始进行组装和测试。它不需要存根模块,但需要驱动模块(driver modeule): 。其结合过程如下: (1)把低层模块组合成实现某个特定软件子功能的模块族; (2)为每一个族编写一个驱动模块,作为测试的控制来协调测试用例的输入和输出; (3)对模块族进行测试; (4)按模块结构图依次向上扩展,用实际模块替换驱动模块,将模块族与新的模块结合,形成新的模块族,再进行测试,直到所有模块都被结合进来。
图中自底向上的结合过程:首先把模块组合成族1、族2和族3,然后设计相应的驱动模块D1、D2和D3,并对每个子功能族进行测试;族1和族2下属于模块Ma,去掉驱动模块D1和D2,把这两个族直接与Ma结合,同样地,在族3与模块Mb结合之前将D3去掉;最后Ma和Mb与Mc结合起来。
自顶向下结合的主要优点:不需要设计测试驱动模块,与存根模块相联系的问题可能在测试的早期发现。主要缺点是:需要设计存根模块,并且由于为了使存根模块能够尽量模拟实际模块的功能,必然会增加设计存根模块的复杂度,从而导致增加一些附加的测试。 自底向上结合的主要优点:不需要设计存根模块,而设计测试驱动模块一般比建立存根模块要容易,同时比较容易设计测试用例,并且可以实现多个模块的并行测试,从而提高测试效率。主要缺点是:直到最后一个模块结合进来以前,程序作为一个整体始终不存在。也就是说,对主要的控制直到最后才接触到。 根据情况结合这两种方法来进行组装和测试:对软件结构中较上层模块使用自顶向下结合方法,对软件结构中较下层模块使用自底向上结合方法。
7.4.3 有效性测试 Validity testing 7.4.3 有效性测试 Validity testing 有效性测试的任务:进一步验证软件的有效性,即验证软件的功能和性能是否与用户的要求一致。 在每个有效性测试用例测试完成以后,可能有两种情况: (1)软件的功能和性能与用户的要求一致,软件可以接受; (2)软件的功能或性能与用户的要求有差距。 若出现后一种情况,通常与需求分析阶段的差错有关,这时要列出一张软件缺陷表,通过与用户的协商,找出问题所在并解决它。
7.4.4 系统测试 System Testing 软件仅仅是计算机系统的一个组成部分,在实际运行中,它要和计算机系统的其它元素一起工作,所以最终要把软件与其它系统元素结合起来,进行一系列的集成测试和有效性测试。 系统测试的目的在于通过与系统的需求定义作比较,发现软件与系统定义不符合或与之矛盾的地方。
7.5 调试 Debugging 7.5.1 调试的步骤Debugging Procedures 7.5 调试 Debugging 7.5.1 调试的步骤Debugging Procedures 7.5.2 调试的策略 Debugging Strategy
7.5.1 调试的步骤 Debugging Procedures 7.5.1 调试的步骤 Debugging Procedures 调试过程由两个部分组成:首先,确定程序中错误的确切性质和位置;然后,对程序代码进行分析,确定问题的原因,并设法改正这个错误。 具体地说,由以下步骤组成: (1)从错误的外部表现入手,确定程序中出错的位置; (2)分析有关程序代码,找出错误的内在原因; (3)修改程序代码,排除这个错误; (4)重复进行暴露了这个错误的原始测试以及某些回归测试,以确保该错误确实被排除且没有引入新的错误; (5)如果所作的修正无效,则撤消这次改动,重复上述过程,直到找到一个有效的办法为止。
1、强行排错Brute force elimination 7.5.2 调试的策略 Debugging Strategy 1、强行排错Brute force elimination 这是目前使用较多但效率较低的一种调试方法。具体地说,通常有三种措施: (1)输出存储器内容 (2)打印语句 (3)自动调试工具
2、回溯法Backtracking 采用回溯法排错时,调试人员首先分析错误征兆,确定最先出现“症状”的位置。然后人工沿程序的控制流程往回追踪源程序代码,直到找到错误根源或确定错误产生的范围为止。 实践证明,回溯法是一种可以成功地用在小程序中的很好的纠错方法。通过回溯,我们往往可以把错误范围缩小到程序中的一小段代码,仔细分析这段代码,不难确定出错的准确位置。但是,随着程序规模的扩大,由于回溯的路径数目越来越多,回溯法会变得很困难,以至于完全不可能实现。
3、归纳法 Induction method 归纳法就是从线索(错误征兆)出发,通过分析这些线索之间的关系而找出故障的一种系统化的思考方法。这种方法主要包括下述四个步骤: (1)收集有关的数据 (2)组织数据 (3)提出假设 (4)证明假设
4、演绎法 Deductive method 演绎法从一般原理或前提出发,经过排除和精化的过程推导出结论。演绎法排错的过程是这样的:测试人员首先列出所有可能出错的原因或假设,然后再用原始测试数据或新的测试,逐个排除不可能正确的假设,最后,证明剩下的原因确实是错误的根源。
Definition of Software Reliability 7.6 软件可靠性 Software Reliability 7.6.1 软件可靠性的定义 Definition of Software Reliability 7.6.2 软件正确性证明 Proof of Correctness
Definition of Software Reliability 7.6.1 软件可靠性的定义 Definition of Software Reliability 软件可靠性是软件可靠性是程序在给定的时间间隔内,按照规格说明书的规定成功地运行的概率。 术语“错误”的含义是由开发人员造成的软件差错(bug),而术语“故障”的含义是由错误引起的软件的不正确行为。 软件可用性是程序在给定的时间点,按照规格说明书的规定,成功地运行的概率。 平均无故障时间MTTF是系统按规格说明书规定成功地运行的平均时间。
如果在一段时间内,软件系统故障停机时间分别为td1,td2,…,正常运行时间分别为tu1,tu2,…,则系统的稳态可用性为: 如果在一段时间内,软件系统故障停机时间分别为td1,td2,…,正常运行时间分别为tu1,tu2,…,则系统的稳态可用性为: Ass=Tup/(Tup+Tdown) (7.1) 其中Tup=∑tui,Tdown=∑tdi 如果引入系统平均无故障时间MTTF和平均维修时间MTTR的概念,则(7.1)式可以变成 Ass=MTTF/(MTTF+MTTR) (7.2) 平均无故障时间MTTF它主要取决于系统中潜伏的错误的数目,因此和测试的关系十分密切。
1.估算平均无故障时间的方法 软件的平均无故障时间MTTF是一个重要的质量指标,往往作为对软件的一项要求,由用户提出来。为了估算MTTF,首先引入一些有关的量。 1. 符号 在估算MTTF的过程中使用下述符号表示有关的数量: ET——测试之前程序中错误总数; IT——程序长度(机器指令总数); τ——测试(包括调试)时间; Ed(τ)——在0至τ期间发现的错误数; Ec(τ)——在0至τ期间改正的错误数。
2. 基本假定 根据经验数据,可以作出下述假定。 (1) 在类似的程序中,单位长度里的错误数ET/IT近似为常数。美国的一些统计数字表明,通常0.5×10-2≤ET/IT≤2×10-2也就是说,在测试之前每1000条指令中大约有5~20个错误。 (2) 失效率正比于软件中剩余的(潜藏的)错误数,而平均无故障时间MTTF与剩余的错误数成反比。
εr(τ)=ET/Ir-Ec(τ)/IT (7.4) (3) 此外,为了简化讨论,假设发现的每一个错误都立即正确地改正了(即,调试过程没有引入新的错误)。因此 Ec(τ)=Ed(τ) 剩余的错误数为 Er(τ)=ET-Ec(τ) (7.3) 单位长度程序中剩余的错误数为 εr(τ)=ET/Ir-Ec(τ)/IT (7.4)
经验表明,平均无故障时间与单位长度程序中剩余的错误数成反比,即 3. 估算平均无故障时间 经验表明,平均无故障时间与单位长度程序中剩余的错误数成反比,即 MTTF=1/[K(ET/IT-Ec(τ)/IT)] (7.5) 其中K为常数,它的值应该根据经验选取。美国的一些统计数字表明,K的典型值是200。 估算平均无故障时间的公式,可以评价软件测试的进展情况。此外,由(7.5)式可得 Ec=ET-IT/(K×MTTF) (7.6) 因此也可以根据对软件平均无故障时间的要求,估计需要改正多少个错误之后,测试工作才能结束。
4. 估计错误总数的方法 程序中潜藏的错误的数目是一个十分重要的量,它既直接标志软件的可靠程度,又是计算软件平均无故障时间的重要参数。 程序中的错误总数ET与程序规模、类型、开发环境、开发方法论、开发人员的技术水平和管理水平等都有密切关系。下面介绍估计ET的两个方法。 (1) 植入错误法 使用这种估计方法,在测试之前由专人在程序中随机地植入一些错误,测试之后,根据测试小组发现的错误中原有的和植入的两种错误的比例,来估计程序中原有错误的总数ET。
假设人为地植入的错误数为Ns,经过一段时间的测试之后发现ns个植入的错误,此外还发现了n个原有的错误。如果可以认为测试方案发现植入错误和发现原有错误的能力相同,则能够估计出程序中原有错误的总数为 N^=n/ns×Ns (7.7) 其中N^即是错误总数ET的估计值。
(2) 分别测试法 植入错误法的基本假定是所用的测试方案发现植入错误和发现原有错误的概率相同。但是,人为地植入的错误和程序中原有的错误可能性质很不相同,发现它们的难易程度自然也不相同,因此,上述基本假定可能有时和事实不完全一致。 如果有办法随机地把程序中一部分原有的错误加上标记,然后根据测试过程中发现的有标记错误和无标记错误的比例,估计程序中的错误总数,则这样得出的结果比用植入错误法得到的结果更可信一些。
为了随机地给一部分错误加标记,分别测试法使用两个测试员(或测试小组),彼此独立地测试同一个程序的两个副本,把其中一个测试员发现的错误作为有标记的错误。具体做法是,在测试过程的早期阶段,由测试员甲和测试员乙分别测试同一个程序的两个副本,由另一名分析员分析他们的测试结果。用τ表示测试时间,假设 τ=0时错误总数为B0; τ=τ1时测试员甲发现的错误数为B1; τ=τ1时测试员乙发现的错误数为B2; τ=τ1时两个测试员发现的相同错误数为bc。
即程序中有标记的错误总数为B1,则测试员乙发现的B2个错误中有bc个是有标记的。假定测试员乙发现有标记错误和发现无标记错误的概率相同,则可以估计出测试前程序中的错误总数为 B0^ =B2/bcB1 (7.8)
7.6.2 程序正确性证明 Proof of Correctness 7.6.2 程序正确性证明 Proof of Correctness 程序正确性证明是一项复杂的课题,涉及到许多复杂的领域。采用诸如利用数学归纳法或谓词演算的人工的正确性证明,在评价小程序时可能有些价值,但是在证明大型软件的正确性时,不仅工作量太大,而且在证明的过程中也很容易引进一些新的错误,因此并不实用。 目前,已经开发出了一些自动的计算机软件正确性证明方法。自动的正确性证明程序一般涉及到程序逻辑的形式化描述,这种描述可以由宏编译程序来开发。宏编译程序产生软件的符号表示,利用以人工智能理论和谓词演算为基础的自动化技术来“证明”程序的正确性。目前已经研究出了PASCAL和LISP等的正确性证明程序,正在对这些系统进行评价和改进。这些系统目前还只能对较小的程序进行评价。
Automated testing tools 7.7 测试工具 Testing Tools 7.7.1 人工测试 Artificial testing 7.7.2 自动测试工具 Automated testing tools
7.7.1 人工测试Artificial testing 1、桌前检查 (1)检查变量的交叉引用 (2)检查标号的交叉引用 (3)检查子程序、宏、函数 (4)常量检查 (5)标准检查 (6)风格检查 (7)比较控制流 (8)选择、激活路径 (9)对照程序的规格说明,详细阅读源代码 (10)补充文档
2、代码评审 代码评审一般由三至五人组成小组,成员组成包括:组长,秘书和测试人员。 首先,组长提前把设计规格说明书、控制流程图、程序文本及有关要求、规范等分发给小组成员,作为评审的依据。小组成员要先充分阅读这些材料,为评审会做好准备。第二步是召开程序审查会,首先由测试人员进行讲解,其它成员可以提问并展开讨论。通过讲解和讨论,可以暴露许多程序员以前没有发现的错误。讨论过程中不讨论任何纠错问题,主要是发现错误,否则违背了测试的目的。在代码会审之后,需要把发现的错误登记造表,并交给程序员;若发现错误较多,或发现重大错误,则在改正之后,还要再次组织代码会审。
7.7.2 自动测试工具 (1)静态分析程序Static analyzers (2)动态分析程序Dynamic analyzers 7.7.2 自动测试工具 (1)静态分析程序Static analyzers (2)动态分析程序Dynamic analyzers (3)测试数据生成程序Test data generators (4)测试覆盖监视程序Test data Coverage monitor (5)输出比较程序Output comparators
设计下列伪码程序的语句覆盖和路径覆盖测试用例: START INPUT (A,B,C) IF A>5 THEN X=10 ELSE X=1 END IF IF B>10 THEN Y=20 ELSE Y=2 IF C>15 THEN Z=30 ELSE Z=3 PRINT (X,Y,Z) STOP