第三章 软件工程基础
双击滚屏  关闭窗口

3. 4 软件测试

  随着计算机软,硬件技术的发展,计算机的应用领域越来越广泛,方方面面的应用对软件的功能要求也几越来越强,而且软件的复杂程度也就越来越高。但是,如何才能确保软件的质量并保证软件的高度可靠性呢?无疑,通过对软件产品进行必要的测试是非常重要的一个环节。软件测试也是在软件投入运行前对软件需求、设计、编码的最后审查。
   软件测试是保证软件质量的重要手段,其主要过程蕴涵了整个软件生命期的过程,包括需求定义阶段的需求测试、编码阶段的单元测试、集成测试以及后期的确认测试、系统测试,验证软件是否合格、能否交付用户使用等。

  3.4.1 软件测试的目的
  3.4.2 软件测试的准则
  3.4.3 软件测试技术与方法综述

  3.4 1 软件测试的目的

  1983 年 IEEE 将软件测试定义为:使用人工或自动手段来运行或测试定某个系统过程,其目的在于检验它是否满足规定的需求或是弄清预期结果与实际结果之间的差别。
   关于软件测试的目的, Grenford j.Myers 在《 The Art of Software Testing 》书中给出了更了解深刻的阐述:
   软件测试是为了发现错误而执行程序的过程;
   一个好的测试用例是指很可能找到迄今为止尚未发现的错误的用例;
   一个成功的测试是发现了至今尚未发现的错误的测试。
   Myers 的观点告诉人们测试要以查找错误为中心,而不是为了演示软件的正确功能。

  3.4.2 软件测试的准则 

  鉴于软件测试的重要性,要做好软件测试,设计出有效的测试方案和好的测试用例,软件测试人员需要充分理解和运用软件测试的一些基本准则:
   •  所有测试都应追溯到需求
   •  严格执行测试计划,排除测试的随意性
   软件测试应当指定明确的测试计划并按照计划执行。测试计划应包括:所测软件的功能 = 输入和输出、测试内容、各项测试的目的和进度安排、测试资料、测试工具、测试用例的选择、资源要求、测试的控制方式和过程等。
   •  充分注意测试中的群集现象
   经验表蜜柑内,程序中存在错误的概率与该程序中已发现的错误数成正比。这一现象说明,为了提高测试效率,测试人员应集中对付那些错误群集程序。
   •  程序员应避免检查自己的程序
   为了达到好的测试效果,应该由独立的第三方来构造测试。因为从心理学角度讲,程序人员或设计方在测试自己的程序时,要采取客观的态度是程度不同地存在障碍的。
   •  穷举测试不可能
   所谓穷举测试是指把程序所有的执行路径都进行检查的测试。但是,即使规模较小的程序,其路径排列数也是相当大的,在实际测试过程中不可能穷举每一种组合。这说明,测试只能证明程序中有错误,不能证明程序中没有错误。
   •  妥善保存测试计划、测试用例、出错统计和最终分析报告,为维护提供方便。

  3.4.3 软件测试技术与方法综述

  软件测试的方法和技术是多种多样的,。对于软件测试方法和技术,可以从不同的角度加以分类。
   若从是否需要执行被测试软件的角度,可以分为静态测试和动态测试方法。若按照功能划分可以分为白盒测试和黑盒测试方法。
   1.静态测试与动态测试
   •  静态测试
    静态测试包括代码检查、静态结构分析、代码质量度量等。静态测试可以由人工进行,充分发挥人的逻辑思维优势,也可以借助软件工具自动进行。经验表明,使用人工测试能够有效地发现 30% 到 70% 的逻辑设计和编码错误。
   代码检查主要检查代码和设计的一致性,包括代码的逻辑表达的正确性,代码结构的合理性等方面。这项工作可以发现违背程序编写标准的问题,程序中不安全、不明确和模糊的部分,找到程序中不可能移植部分,违背程序编程风格的问题,包括变量检查、命名和类型审查、程序逻辑审查、程序语法检查和程序结构检查等内容。代码检查包括审查、代码走查、桌面检查、静态分析等具体方式。
   代码审查:小组集体阅读、讨论检查代码。
   代码走查:小组成员通过用“脑”研究、执行程序来检查代码。
   桌面检查:由程序员自己检查自己编写的程序。程序员在程序通过编译之后,进行单元测试之前,对源代码进行分析、检验、并补充相关文档,目的是发现程序的错误。
   静态分析:对代码的机械性、程序化的特征分析方法,包括控制流分析、数据流分析、接口分析、表达式分析。
   •  动态测试
   静态测试不实际运行软件,主要通过人工进行。动态测试是基于计算机的测试,是为了发现错误而执行程序的过程。或者说,是根据软件开发各阶段的规格说明和程序的内部结构而精心设计一批测试用例(即输入数据及其预期的输出结果),并利用这些测试用例去运行程序,以发现程序错误的过程。
   设计高效、合理的测试用例是动态测试的关键。测试用例( Test Case )是为测试设计的数据。测试用例由测试输入数据和与之对应的预期输出结果两部分组成。测试用例的格式为:
   [ (输入值集),(输出值集) ]
   下面重点讨论动态的白盒测试方法和黑盒测试方法。
   2.白盒测试方法与测试用例设计
   白盒测试方法也称为结构测试或逻辑驱动测试。它是根据软件产品的内部工作过程,检查内部成分,以确认每种内部操作符合设计规格要求。白盒测试把测试对象看作一个打开的盒子,允许测试人员利用程序内部的逻辑结构及有关信息来设计或选择试用例,对程序所有的逻辑路径进行测试。通过在不同点检查程序的状态来了解实际的运行状态是否预期的一致。所以,白盒测试是在程序内部进行,主要用于完成软件内部操作的验证。
   白盒测试的基本原则是:保证所测模块中每一个独立路径至少执行一次;保证所测模块所有判断的每一分支至少执行一次;保证所测模块每一个循环都在边界条件和一般条件下至少各执行一次;验证所有内部数据结构的有效性。
   按照白盒测试的基本原则,“白盒”法是穷举路径测试。在使用这一方案时,测试者必须检查程序的内部结构,从检查程序的逻辑着手,得出测试数据。贯穿程序的独立路径数是天文数字,但即使每条路径都测试了仍然可能有错误。第一,穷举路径测试决不能查出程序是否违反了规范,即程序本身是个错误的程序;第二,穷举路径测试不可能查出程序因遗漏路径而出错;第三,穷举路径测试可能发现不了一些与数据相关的错误。
   白盒测试的主要方法有逻辑覆盖、基本路径测试等。
   (1)逻辑覆盖测试
   逻辑覆盖是泛指一系列以程序内部的逻辑结构为基础的测试用例设计技术。通常所指的程序中的逻辑表示有判断、分支、条件等几个表示方式。
   ① 语句覆盖。选择足够的测试用例,使得程序中每个语句至少都能被执行一次。
   例 3.1 设有程序流程图表示的程序如图 3.24 。
   按照语句覆盖的测试要求,对图 3.24 的程序设计如下测试用例 1 和测试用例 2 。
           
                            图3.24 程序流程图
  语句覆盖是逻辑覆盖中基本的覆盖,尤其对单元测试来说。但是语句覆盖往往没有关注判断中的条件有可能隐含的错误。
  ② 路径覆盖。执行足够的测试用例,使程序中所有可能的路径都至少经历一次。
  例 3.2 设有程序流程图表示的程序如图 3.25 。
              
                             图3.25 程序流程图
   对图 3.25 的程序设计如表 3.2 列出的一组测试用例,就可以覆盖该程序的全部 4 条路径: ace, abd, abe, acd 。
      

                             表3.2 一组测试用例
  ③ 判定覆盖。使设计的测试用例保证程序中每个判断的每个取值分支( T 或 F )至少经历一次。
  根据判定覆盖的要求,对如图 3.26 所示的程序,如果其中包含条件 i ≧ j 的判断为真值(即为“ T ”)和为假值(即为“ F ”)的程序执行路径至少经历一次,仍然可以试用例 3.1 的测试用例 1 和测试用 例 2 。
  程序每个判断中若存在多个联立的条件,仅保证判断的真假值往往会导致某些单个条件的错误不能被发现。例如,某判断是“ x<1 或 y> 5 ” ,其中只要一个条件是否错误,判断的结果都为真。这说明,仅 有判断覆盖还无法保证能查出在判断的每个条件的可 能取值至少执行一次。
  ④ 条件覆盖。设计的测试用例保证程序中每个判断的每个条件的可能取值至少执行一次。
  例 3.3 设有程序流程图表示的程序如图 3.26 。
          
                            图3.26 程序流程图
  按照条件覆盖的测试要求 , 对图 3.26 的程序判断框中的条件 i ≧ j 和条件 j<5 设计如下测试用例 1 和测试用例 2 ,就能保证该条件取真值和取假值的情况至少执行一次。
  条件覆盖深入到判断中的每个条件,但是可能会忽略全面的判断覆盖的要求。有必要考虑判断 - 条件覆盖。
  ⑤ 判断 - 条件覆盖。设计足够的测试用例,是判断中每个条件的所有可能取值至少执行一次,同时每个判断中的三个条件的所有可能取值分支至少执行一次。
    例3.4 设有程序流程图表示的程序如图3.27
          
                            图3.27 程序流程图
   按照判断 - 条件覆盖的测试要求,对图 3.27 程序的两个判断框的每个取值分支至少经历一次,同时两个判断中的三个条件的所有可能取值至少执行一次,设计如下测试用例 1 、测试用例 2 和测试用例 3 ,就能保证满足怕判断 - 条件覆盖。
判断 - 条件覆盖也有缺陷,对质量要求高的软件单元,可根据情况提出多重条件组合覆盖以及其他更高的覆盖要求。
  ( 2 )基本路径测试
  基本路径测试的思想和步骤是,根据软件过程性描述中的控制流程确定程序的环路复杂性度量,用此度量定义基本路径集合,并由此导出一组测试用例对每一条独立执行路径进行测试。
   •  设有程序流程图表示的程序如图 3.28 。
                   

                             图3.28 程序流程图
  对图 3.28 的程序流程图确定程序的环路复杂度,方法是:
  环路复杂度 = 程序流程图中的判断框个数 +1 则环路复杂度的值即为要设计测试用例的基本路径数,图 3.28 的所示的程序环路复杂度为 3 ,设计如表 3.3 列出的一组测试用例,覆盖的基本路径是: abf,acef,acdf 。
            
                             表3.3 一组测试用例
  3. 黑盒测试方法与测试用例设计
  黑盒测试方法也称功能测试或数据驱动测试。黑盒测试是对软件已经实现的功能是否满足需求进行测试和验证。黑盒测试完全不考虑程序内部的逻辑结构和内部特性,只依据程序的需求和功能规格说明,检查程序的功能是否符合它的功能说明。所以,黑盒测试是在软件接口处进行,完成功能验证。黑盒测试只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数据而产 生正确的输出信息,并且保持外部信息(如数据库或文件)的完整性。
  黑盒测试主要诊断功能不对或遗漏、界面错误、数据结构或外部数据库访问错误、初始化和终止条件错。
  黑盒测试方法主要有等价类划分法、边界值分析法、错误推测法、、因果图等,主要用于软件确认测试。
  ( 1 )等价类划分法
  等价类划分法是一种典型的黑盒测试方法。它是将程序的所有可能的输入数据划分为若干部分(及若干等价类),然后从每个等价类中选取数据作为测试用例。对每一个等价类,各个输入数据对发现程序中的错误的机率都是等效的,因此只需从每个等价类中选取一些有代表性的测试用例进行测试而发现错误。
  使用等价类划分法设计测试方案,首先需要划分输入集合的等价类。等价类包括:
  ①有效等价类:合理、有意义的输入数据构成的集合。可以检验程序中符合规定的功能、性能。
  ②无效等价类:不合理、无意义的输入数据构成的集合。可以检验程序中不符合规定的功能、性能。
  为此,需要研究的功能说明,从而确定输入数据的有效等价类和无效等价类。
  第 1 步:划分等价类;
  第 2 步:根据等价类选取相应的测试用例。
  例 3.6 程序实现输入 3 个边长(设为 a,b,c ),判断能否构成三角形。对该程序考虑等价类划分法。
  满足测试三角形构成条件程序的等价类划分如表 3.4 所示
          
                    表3.4  满足测试三角形构成条件程序的等价类划分
  根据表 3.4 划分的等价类,可以设计以下的测试用例:
  对满足输入条件①和②的有效等价类设计的测试用例:
  [ ( a=3,b=4,c=5 ),(符合三角形构成条件) ]
  对满足输入条件①的无效等价类设计的测试用例:
  [ ( a=-3,b=4,c=5 ),(无效输入) ]
  对满足输入条件②的无效等价类设计的测试用例;
  [ ( a=3,b=4,c=8 ),(无效输入) ]
  划分等价类常用的几条原则是:
  若输入条件规定了确切的取值范围,则可以划分出一个有效等价类和两个无效等价类:
  若输入条件规定了输入值的集合(或有“必须如何”的条件),可确定一个有效等价类和一个无效等价类:
  若输入条件是一个布尔量,则可确定一个有效等价类和一个无效等价类:
  若输入数据是一个布尔量,则可确定一个有效等价类和若干个无效等价类:
  若规定了输入数据必须遵守一定规则,则可确定一个有效等价类和一个无效等价类。
  若已划分的等价类中各元素在程序中处理方式不同,须将该等价类进一步划分(更小的等价类)。
  ( 2 )边界值分析法
  边界值分析法是对各种输入、输出范围的边界情况设计测试用例的方法。
  经验表明,程序错误最容易出现在输入或输出范围的边界处,而不是在输入范围的内部。因此针对各种边界情况设计测试用例,可以查出更多的错误。
  使用边界值分析方法的使用要注意以下几点:
  ①如果输入条件规定了取值范围或数据个数,则可选择正好等于边界值、刚刚超越边界值进行测试;
  ②针对规格说明的每个输入条件,使用上述原则;
  ③对于有叙数列,选择第一个和最后一个作为测试数据。
  例如,对例 3.6 中的判断三角形构成的程序,如果在等价类划分法中加入边界值分析的例 3.6 的测试用例可以设计如下:
  对满足输入条件①的无效等价类设计的测试用例:
  [ ( a=0,b=4,c=5 ),(无效输入) ]
  或 [ ( a=3,b=0,c=5 ),(无效输入) ]
  或 [ ( a=3,b=4,c=0 ),(无效输入) ]
  对满足输入条件②的无效等价类设计的测试用例:
  [ ( a=3,b=4,c=7 ),(无效输入) ]
  或 [ ( a=9,b=4,c=5 ),(无效输入) ]
  或 [ ( a=3,b=8,c=5 ),(无效输入) ]
  ⑦ 现象可能是周期出现的。如在软件、硬件结合的嵌入式系统中常常遇到。
  ( 2 )修改设计和代码,以排除错误
  排错是软件开发过程中一项艰苦的工作,这也决定了调试工作是一个具有很强技术性和技巧性的工作。软件工程人员在分析测试结果的时候会发现,软件运行失效或出现问题,往往只是潜在错误的外部表现,而外部表现与内在原因之间常常没有明显的联系。如果要找出真正的原因,排除潜在的错误,不是一件易事。因此可以说,调试是通过现象,找出原因的一个思维分析的过程。
  ( 3 )进行回归测试,防止引进新的错误
  因为修改程序可能带来新的错误,重复进行暴露这个错误的原始测试或某些有关测试,以确认该错误是否被排除、是否引进了新的错误。如果所做的修正无效,则撤消这次改动,重复上述过程,直到找到一个有效的解决办法为止。
  2 .程序调试的原则
  在软件调试方面,许多原则实际上是心理学方面的问题。因为调试活动由对程序中错误的定性、定位和排错两部分组成,因此调试原则也从以下两个方面考虑。
   •  确定错误的性质和位置时的注意事项:
  ① 分析思考与错误征兆有关的信息。
  ② 避开死胡同。如果程序调试人员在调试中陷入困境,最好暂时把问题避开,留到后面适当的时间再去考虑,或者向其他人讲解这个问题,去寻求新的解决思路。
  ③ 只把调试工具当做辅助手段来使用。利用调试工具,可以帮助思考,但不能代替思考。因为调试工具给人提供的是一种无规律的调试方法。
  ④ 避免用试探法,最多只能把它做最后手段。这是一种碰运气的盲目的动作,它的成功机率很小,而且还常把新的错误带到问题中来。
   •  修改错误的原则
  ① 在出现错误的地方,很可能还有别的错误。经验表明,错误有群集现象,当在某一程序段发现有错误时,在该程序段中还存在别的错误的概率也很高。因此,在修改一个错误时,还要观察和检查相关的代码,看是否还有别的错误。
  ② 修改错误的一个常见失误是只修改了这个错误的征兆或这个错误的表现,而没有修改错误本身。如果提出的修改不能解释与这个错误有关的全部现象,那就表明了只修改了错误的一部分。
  ③ 注意修正一个错误的同时有可能会引入新的错误。人们不仅需要注意不正确的修改,而且还要注意看起来是正确的修改可能会带来的副作用,即引进新的错误。因此在修改了错误之后,必须进行回归测试。
  ④ 修改错误的过程将迫使人们暂时回到程序设计阶段。修改错误也是程序设计的一种形式。一般说来,在程序设计阶段所使用的任何方法都可以应用到错误修正的过程中来。
  ⑤ 修改源代码程序,不要改变目标代码。

3 . 5 . 2 软件调试方法

  调试的关键在于推断程序内部的错误位置及原因。从是否跟踪和执行程序的角度,类似于软件测试,软件调试可以分为静态调试和动态调试。软件测试中讨论的静态分析方法同样适用静态调试。静态调试主要指通过人的思维来分析源程序代码和排错,是主要的调试手段,而动态调试是辅助静态调试的。主要的调试方法可以采用:
  1 .强行排错法
  作为传统的调试方法,其过程可概括为,设置断点、程序暂停、观察程序状态、继续运行程序。这是目前使用较多、效率较低的调试方法。涉及的调试技术主要是设置断点和监视表达式。例如:
  ① 通过内存全部打印来排错。
  ② 在程序特定部位设置打印语句——即断点法。输出存储器内容,就是在程序执行到某一行的时候,计算机自动停止运行,并保留这时各变量的状态,方便检查,校对。
  ③ 自动调试工具。可供利用的典型的语言功能有打印出语句执行的追踪信息、追踪子程序调用,以及指定变量的变化情况。自动调试工具的功能是设置断点,当程序执行到某个特定的语句或某个特定的变量值改变时,程序暂停执行。程序员可在终端上观察程序此时的状态。
应用以上任何一种技术之前,都应当对错误的征兆进行全面彻底的分析,得出对出错位置及错误性质的推测,再使用一种适当的排错方法来检验推测的正确性。
  2 .回溯法
  该方法适合于小规模程序的排错。即一旦发现了错误,先分析错误征兆,确定最先发现“症状”的位置。然后,从发现“症状”的地方开始,沿程序的控制流程,逆向跟踪源程序代码,直到找到错误根源或确定错误产生的范围。
  回溯法对于小程序很有效,往往能把错误范围缩小到程序中的一小段代码,仔细分析这段代码不难确定出错的准确位置。但随着源代码行数的增加,潜在的回溯路径数目很多,回溯会变的很困难,而且实现这种回溯的开销大。
  3 .原因排除法
  原因排除法是通过演绎和归纳,以及二分法来实现的。
  演绎法是一种从一般原理或前提出发,经过排除和精化的过程来推导出结论的思考方法。演绎法排错是测试人员首先根据已有的测试用例,设想及枚举出所有可能出错的原因作为假设。然后再用原始测试数据或新的测试,从中逐个排除不可能正确的假设。最后,再用测试数据验证余下的假设确定出错的原因。
  归纳法是一种从特殊推断出一般的系统化思考方法。其基本思想是从一些线索(错误征兆或与错误发生有关的数据)着手,通过分析寻找潜在的原因,从而找出错误。
  二分法实现的基本思想是,如果已知每个变量在程序中若干个关键点的正确值,则可以使用定值语句(如赋值语句、输入语句等)在程序中的某点附近给这些变量赋正确值,然后运行程序并检查程序的输出。如果输出结果是正确的,则错误原因在程序的前半部分;反之,错误原因在程序的后半部分。对错误原因所在的部分重复使用这种方法,直到将出错范围缩小到容易诊断的程度为止。
  上面的每一种方法都可以使用调试工具来辅助完成。例如,可以使用带调试功能的编译器、动态调试器、自动测试用例生成器以及交叉引用工具等。
  需要注意的一个实际问题是,调试的结果是排错,为了修改程序中错误,往往会采用“补丁程序”来实现,而这种做法会引起整个程序质量的下降,但是从目前程序设计发展的状况看,对大规模的程序的修改和质量保证,又不失为一种可行的方法。 

 
                             返回首页                     双击滚屏  关闭窗口
数学与信息科学学院 版权所有