软件测试技术研究

发表于:2007-04-22来源:作者:点击数: 标签:软件测试研究技术
一、 软件测试 1、 软件测试 的目的 软件测试 是在软件投入运行前,对软件需求分析、设计规格说明和编码的最终复审,它是 软件质量 保证的关键步骤。测试要求以较少的 用例 、时间和人力找出软件中潜在的各种错误和 缺陷 ,以确保系统的质量。 软件测试 的目

   
一、软件测试
1、软件测试的目的
软件测试是在软件投入运行前,对软件需求分析、设计规格说明和编码的最终复审,它是软件质量保证的关键步骤。测试要求以较少的用例、时间和人力找出软件中潜在的各种错误和缺陷,以确保系统的质量。
    软件测试的目的决定了如何去组织测试。如果测试的目的是为了尽可能多地找出错误,那么测试就应该直接针对软件比较复杂的部分或是以前出错比较多的位置。如果测试目的是为了给最终用户提供具有一定可信度的质量评价,那么测试就应该直接针对在实际应用中会经常用到的商业假设。这里引用Grenford J. Myers在《The Art of Software Testing》一书中的观点:
  ①、软件测试是为了发现错误而执行程序的过程;
  ②、测试是为了证明程序有错,而不是证明程序无错误。
  ③、一个好的测试用例是在于它能发现至今未发现的错误;
  ④、一个成功的测试是发现了至今未发现的错误的测试。
软件测试是一项细致并需要高度技巧的工作,稍有不慎就会顾此失彼,发生不应有的疏漏。软件工程的总目标是充分利用有限的人力和物力资源,高效率、高质量地完成测试。为了降低测试成本,选择测试用例时应注意遵守"经济性"的原则。
 
2、软件测试的分类
从测试的类型来看,测试分为2种:黑盒测试和白盒测试。
黑盒测试又称为功能测试或数据驱动测试,把系统看成一个黑盒子,不考虑程序的内在逻辑,只根据需求规格说明书的要求来检查程序的功能是否符合它的功能说明。
白盒测试又称为结构测试和逻辑驱动测试,允许测试人员对程序内部逻辑结构及有关信息来设计和选择测试用例,对程序的逻辑路径进行测试。
   从测试实际的前后过程来看,软件测试上是由一系列的不同测试所组成,这些软件测试的步骤分为:单元测试、组装测试(集成测试)、确认测试和系统测试
单元测试针对每个模块进行的测试,通常在编码阶段进行,必要的时候要制作驱动模块和桩模块。集成测试在单元测试的基础上,将所有模块按照设计要求组装成为系统,必须精心计划,应提交集成测试计划、集成测试规格说明和集成测试分析报告。确认测试验证软件的功能和性能及其它特性是否与用户的要求一致,系统测试是在实际运行环境下进行一系列的测试。软件开发的过程是自顶向下的,测试则正好相反,以上这些过程就是自底向上,逐步集成的
 
3、软件测试工作的流程:

测试并不仅仅是为了要找出错误。通过分析错误产生的原因和错误的分布特征,可以帮助项目管理者发现当前所采用的软件过程的缺陷,以便改进。同时,这种分析也能帮助我们设计出有针对性地检测方法,改善测试的有效性。

没有发现错误的测试也是有价值的,完整的测试是评定测试质量的一种方法。详细而严谨的可靠性增长模型可以证明这一点。由此可见,软件测试工作应该贯穿于整个软件开发阶段。
 
下面是软件测试工作的总体流程图和每个主要阶段的流程图:
 
 
二、单元测试
 
1、 什么是单元测试
单元测试是在软件开发过程中要进行的最低级别的测试活动,测试的对象是软件设计的最小单位——模块。单元测试的依据是详细设描述,单元测试应对模块内所有重要的控制路径设计测试用例,以便发现模块内部的错误。在单元测试活动中,软件的独立单元将在与程序的其他部分相隔离的情况下进行测试。单元测试多采用白盒测试技术,系统内多个模块可以并行地进行测试。
  在一种传统的结构化编程语言中,比如C,要进行测试的单元一般是函数或子过程。在象C++这样的面向对象的语言中,要进行测试的基本单元是类。对Ada语言来说,开发人员可以选择是在独立的过程和函数,还是在Ada包的级别上进行单元测试。单元测试的原则同样被扩展到第四代语言(4GL)的开发中,在这里基本单元被典型地划分为一个菜单或显示界面。
  单元测试不仅仅是作为无错编码一种辅助手段在一次性的开发过程中使用,单元测试必须是可重复的,无论是在软件修改,或是移植到新的运行环境的过程中。因此,所有的测试都必须在整个软件系统的生命周期中进行维护。
  经常与单元测试联系起来的另外一些开发活动包括代码走读(Code review),静态分析(Static analysis)和动态分析(Dynamic analysis)。静态分析就是对软件的源代码进行研读,查找错误或收集一些度量数据,并不需要对代码进行编译和执行。动态分析就是通过观察软件运行时的动作,来提供执行跟踪,时间分析,以及测试覆盖度方面的信息。
 
2、单元测试任务 
 单元测试任务包括:1 模块接口测试;2 模块局部数据结构测试;3 模块边界条件测试;4 模块中所有独立执行通路测试;5 模块的各条错误处理通路测试。
  模块接口测试是单元测试的基础。只有在数据能正确流入、流出模块的前提下,其他测试才有意义。测试接口正确与否应该考虑下列因素:
  1 输入的实际参数与形式参数的个数是否相同;
  2 输入的实际参数与形式参数的属性是否匹配;
  3 输入的实际参数与形式参数的量纲是否一致;
  4 调用其他模块时所给实际参数的个数是否与被调模块的形参个数相同;
  5 调用其他模块时所给实际参数的属性是否与被调模块的形参属性匹配;
  6调用其他模块时所给实际参数的量纲是否与被调模块的形参量纲一致;
  7 调用预定义函数时所用参数的个数、属性和次序是否正确;
  8 是否存在与当前入口点无关的参数引用;
  9 是否修改了只读型参数;
  10 对全程变量的定义各模块是否一致;
  11是否把某些约束作为参数传递。
如果模块内包括外部输入输出,还应该考虑下列因素:
  1 文件属性是否正确;
  2 OPEN/CLOSE语句是否正确;
  3 格式说明与输入输出语句是否匹配;
  4缓冲区大小与记录长度是否匹配;
  5文件使用前是否已经打开;
  6是否处理了文件尾;
  7是否处理了输入/输出错误;
  8输出信息中是否有文字性错误;
 
检查局部数据结构是为了保证临时存储在模块内的数据在程序执行过程中完整、正确。局部数据结构往往是错误的根源,应仔细设计测试用例,力求发现下面几类错误:
  1 不合适或不相容的类型说明;
  2变量无初值;
  3变量初始化或省缺值有错;
  4不正确的变量名(拼错或不正确地截断);
  5出现上溢、下溢和地址异常。
  除了局部数据结构外,如果可能,单元测试时还应该查清全局数据(例如FORTRAN的公用区)对模块的影响。
  在模块中应对每一条独立执行路径进行测试,单元测试的基本任务是保证模块中每条语句至少执行一次。此时设计测试用例是为了发现因错误计算、不正确的比较和不适当的控制流造成的错误。此时基本路径测试和循环测试是最常用且最有效的测试技术。计算中常见的错误包括:
  1 误解或用错了算符优先级;
  2混合类型运算;
  3变量初值错;
  4精度不够;
  5表达式符号错。
  比较判断与控制流常常紧密相关,测试用例还应致力于发现下列错误:
  1不同数据类型的对象之间进行比较;
  2错误地使用逻辑运算符或优先级;
  3因计算机表示的局限性,期望理论上相等而实际上不相等的两个量相等;
  4比较运算或变量出错;
  5循环终止条件或不可能出现;
  6迭代发散时不能退出;
  7错误地修改了循环变量。
  一个好的设计应能预见各种出错条件,并预设各种出错处理通路,出错处理通路同样需要认真测试,测试应着重检查下列问题:
  1输出的出错信息难以理解;
  2记录的错误与实际遇到的错误不相符;
  3在程序自定义的出错处理段运行之前,系统已介入;
  4异常处理不当;
  5错误陈述中未能提供足够的定位出错信息。
边界条件测试是单元测试中最后,也是最重要的一项任务。众的周知,软件经常在边界上失效,采用边界值分析技术,针对边界值及其左、右设计测试用例,很有可能发现新的错误。
 
3、单元测试的过程与环境
  一般认为单元测试应紧接在编码之后,当源程序编制完成并通过复审和编译检查,便可开始单元测试。测试用例的设计应与复审工作相结合,根据设计信息选取测试数据,将增大发现上述各类错误的可能性。在确定测试用例的同时,应给出期望结果。
  应为测试模块开发一个驱动模块(driver)和(或)若干个桩模块(stub),下图显示了一般单元测试的环境。驱动模块在大多数场合称为"主程序",它接收测试数据并将这些数据传递到被测试模块,被测试模块被调用后,"主程序"打印"进入-退出"消息。
  驱动模块和桩模块是测试使用的软件,而不是软件产品的组成部分,但它需要一定的开发费用。若驱动和桩模块比较简单,实际开销相对低些。遗憾的是,仅用简单的驱动模块和桩模块不能完成某些模块的测试任务,这些模块的单元测试只能采用下面讨论的综合测试方法
  提高模块的内聚度可简化单元测试,如果每个模块只能完成一个,所需测试用例数目将显著减少,模块中的错误也更容易发现。
 

 
 
三、集成测试的基本方法
 
   时常有这样的情况发生,每个模块都能单独工作,但这些模块集成在一起之后却不能正常工作。主要原因是,模块相互调用时接口会引入许多新问题。例如,数据经过接口可能丢失;一个模块对另一模块可能造成不应有的影响;几个子功能组合起来不能实现主功能;误差不断积累达到不可接受的程度;全局数据结构出现错误,等等。综合测试是组装软件的系统测试技术,按设计要求把通过单元测试的各个模块组装在一起之后,进行综合测试以便发现与接口有关的各种错误。 
 某设计人员习惯于把所有模块按设计要求一次全部组装起来,然后进行整体测试,这称为非增量式集成。这种方法容易出现混乱。因为测试时可能发现一大堆错误,为每个错误定位和纠正非常困难,并且在改正一个错误的同时又可能引入新的错误,新旧错误混杂,更难断定出错的原因和位置。与之相反的是增量式集成方法,程序一段一段地扩展,测试的范围一步一步地增大,错误易于定位和纠正,界面的测试亦可做到完全彻底。
下面讨论两种增量式集成方法。
1、自顶向下集成  
自顶向下集成是构造程序结构的一种增量式方式,它从主控模块开始,按照软件的控制层次结构,以深度优先或广度优先的策略,逐步把各个模块集成在一起。深度优先策略首先是把主控制路径上的模块集成在一起,至于选择哪一条路径作为主控制路径,这多少带有随意性,一般根据问题的特性确定。以下图为例,若选择了最左一条路径,首先将模块M1,M2,M5和M8集成在一起,再将M6集成起来,然后考虑中间和右边的路径。广度优先策略则不然,它沿控制层次结构水平地向下移动。仍以下图为例,它首先把M2、M3和M4与主控模块集成在一起,再将M5和M6 和其他模块集资集成起来。  
自顶向下综合测试的具体步骤为:  
1 以主控模块作为测试驱动模块,把对主控模块进行单元测试时引入的所有桩模块用实际模块替代;  2 依据所选的集成策略(深度优先或广度优先),每次只替代一个桩模块;  3 每集成一个模块立即测试一遍;  4 只有每组测试完成后,才着手替换下一个桩模块;  5 为避免引入新错误,须不断地进行回归测试(即全部或部分地重复已做过的测试)。   从第二步开始,循环执行上述步骤,直至整个程序结构构造完毕。下图中,实线表示已部分完成的结构,若采用深度优先策略,下一步将用模块M7替换桩模块S7,当然M7本身可能又带有桩模块,随后将被对应的实际模块一一替代。 
 自顶向下集成的优点在于能尽早地对程序的主要控制和决策机制进行检验,因此较早地发现错误。缺点是在测试较高层模块时,低层处理采用桩模块替代,不能反映真实情况,重要数据不能及时回送到上层模块,因此测试并不充分。解决这个问题有几种办法,第一种是把某些测试推迟到用真实模块替代桩模块之后进行,第二种是开发能模拟真实模块的桩模块;第三种是自底向上集成模块。第一种方法又回退为非增量式的集成方法,使错误难于定位和纠正,并且失去了在组装模块时进行一些特定测试的可能性;第二种方法无疑要大大增加开销;第三种方法比较切实可行,下面专门讨论。
 
2、自底向上集成  
自底向上测试是从"原子"模块(即软件结构最低层的模块)开始组装测试,因测试到较高层模块时,所需的下层模块功能均已具备,所以不再需要桩模块。  
自底向上综合测试的步骤分为:  
1 把低层模块组织成实现某个子功能的模块群(cluster);  2 开发一个测试驱动模块,控制测试数据的输入和测试结果的输出;  3 对每个模块群进行测试;  4 删除测试使用的驱动模块,用较高层模块把模块群组织成为完成更大功能的新模块群。  从第一步开始循环执行上述各步骤,直至整个程序构造完毕。  下图说明了上述过程。首先"原子"模块被分为三个模块群,每个模块群引入一个驱动模块进行测试。因模块群1、模块群2中的模块均隶属于模块Ma,因此在驱动模块D1、D2去掉后,模块群1与模块群2直接与Ma接口,这时可对MaD3被去掉后,M3与模块群3直接接口,可对Mb进行集成测试,最后Ma、Mb和 Mc全部集成在一起进行测试。
     
自底向上集成方法不用桩模块,测试用例的设计亦相对简单,但缺点是程序最后一个模块加入时才具有整体形象。它与自顶向综合测试方法优缺点正好相反。因此,在测试软件系统时,应根据软件的特点和工程的进度,选用适当的测试策略,有时混和使用两种策略更为有效,上层模块用自顶向下的方法,下层模块用自底向上的方法。
  此外,在综合测试中尤其要注意关键模块,所谓关键模块一般都具有下述一或多个特征:①对应几条需求;②具有高层控制功能;③复杂、易出错;④有特殊的性能要求。关键模块应尽早测试,并反复进行回归测试。
 
四、确认测试的基本方法
 

通过集成测试之后,软件已完全组装起来,接口方面的错误也已排除,确认测试即可开始。确认测试应检查软件能否按合同要求进行工作,即是否满足软件需求说明书中的确认标准。

1. 确认测试标准  实现软件确认要通过一系列墨盒测试。确认测试同样需要制订测试计划和过程,测试计划应规定测试的种类和测试进度,测试过程则定义一些特殊的测试用例,旨在说明软件与需求是否一致。无是计划还是过程,都应该着重考虑软件是否满足合同规定的所有功能和性能,文档资料是否完整、准确人机界面和其他方面(例如,可移植性、兼容性、错误恢复能力和可维护性等)是否令用户满意。  确认测试的结果有两种可能,一种是功能和性能指标满足软件需求说明的要求,用户可以接受;另一种是软件不满足软件需求说明的要求,用户无法接受。项目进行到这个阶段才发现严重错误和偏差一般很难在预定的工期内改正,因此必须与用户协商,寻求一个妥善解决问题的方法。
2. 配置复审  确认测试的另一个重要环节是配置复审。复审的目的在于保证软件配置齐全、分类有序,并且包括软件维护所必须的细节。
3. α、β测试  事实上,软件开发人员不可能完全预见用户实际使用程序的情况。例如,用户可能错误的理解命令,或提供一些奇怪的数据组合,亦可能对设计者自认明了的输出信息迷惑不解,等等。因此,软件是否真正满足最终用户的要求,应由用户进行一系列"验收测试"。验收测试既可以是非正式的测试,也可以有计划、有系统的测试。有时,验收测试长达数周甚至数月,不断暴露错误,导致开发延期。一个软件产品,可能拥有众多用户,不可能由每个用户验收,此时多采用称为α、β测试的过程,以期发现那些似乎只有最终用户才能发现的问题。  α测试是指软件开发公司组织内部人员模拟各类用户行对即将面市软件产品(称为α版本)进行测试,试图发现错误并修正。α测试的关键在于尽可能逼真地模拟实际运行环境和用户对软件产品的操作并尽最大努力涵盖所有可能的用户操作方式。经过α测试调整的软件产品称为β版本。紧随其后的β测试是指软件开发公司组织各方面的典型用户在日常工作中实际使用β版本,并要求用户报告异常情况、提出批评意见。然后软件开发公司再对β版本进行改错和完善。
 
五、系统测试的基本方法 
 
计算机软件是基于计算机系统的一个重要组成部分,在系统测试之前,软件工程师应完成下列工作:  
为测试软件系统的输入信息设计出错处理通路; 
设计测试用例,模拟错误数据和软件界面可能发生的错误,记录测试结果,为系统测试提供经验和帮助;  
参与系统测试的规划和设计,保证软件测试的合理性。  
系统测试应该由若干个不同测试组成,目的是充分运行系统,验证系统各部件是否都能政党工作并完成所赋予的任务。
下面简单讨论几类系统测试。
1、恢复测试  恢复测试主要检查系统的容错能力。当系统出错时,能否在指定时间间隔内修正错误并重新启动系统。恢复测试首先要采用各种办法强迫系统失败,然后验证系统是否能尽快恢复。对于自动恢复需验证重新初始化(reinitialization)、检查点(checkpointing mechanisms)、数据恢复(data recovery)和重新启动 (restart)等机制的正确性;对于人工干预的恢复系统,还需估测平均修复时间,确定其是否在可接受的范围内。
2、安全测试  安全测试检查系统对非法侵入的防范能力。安全测试期间,测试人员假扮非法入侵者,采用各种办法试图突破防线。例如,①想方设法截取或破译口令;②专门定做软件破坏系统的保护机制;③故意导致系统失败,企图趁恢复之机非法进入;④试图通过浏览非保密数据,推导所需信息,等等。理论上讲,只要有足够的时间和资源,没有不可进入的系统。因此系统安全设计的准则是,使非法侵入的代价超过被保护信息的价值。此时非法侵入者已无利可图。
3、强度测试  强度测试检查程序对异常情况的抵抗能力。强度测试总是迫使系统在异常的资源配置下运行。例如,①当中断的正常频率为每秒一至两个时,运行每秒产生十个中断的测试用例;②定量地增长数据输入率,检查输入子功能的反映能力;③运行需要最大存储空间(或其他资源)的测试用例;④运行可能导致虚存操作系统崩溃或磁盘数据剧烈抖动的测试用例,等等。
4、 性能测试  对于那些实时和嵌入式系统,软件部分即使满足功能要求,也未必能够满足性能要求,虽然从单元测试起,每一测试步骤都包含性能测试,但只有当系统真正集成之后,在真实环境中才能全面、可靠地测试运行性能系统性能测试是为了完成这一任务。性能测试有时与强度测试相结合,经常需要其他软硬件的配套支持。
 
六、软件测试自动化的一些具体做法
因为软件测试的工作量很大(40% 到60% 的总开发时间),而又有很大部分适于自动化,因此,测试的改进会对整个开发工作的质量、成本和周期带来非常显著的效果。  
  下面举出一些测试自动化的例子:
1.    测试个案(test case ,或称为测试用例)的生成  
 用编程语言或更方便的剧本语言(script language 例如Perl等)写出短小的程序来产生大量的测试输入(包括输入数据与操作指令)。或同时也按一定的逻辑规律产生标准输出。输入与输出的文件名字按规定进行配对,以便控制自动化测试及结果核对的程序易于操作。  这里提到测试个案的命名问题,如果在项目的文档设计中作统一规划的话,软件产品的需求与功能的命名就应该成为后继开发过程的中间产品的命名分类依据。这样,就会为文档管理和配置管理带来很大的方便,使整个产品的开发过程变得更有条理,更符合逻辑。任何新手半途加入到开发工作中也会更容易进入状态。
2.    测试的执行写控制  
单元测试或集成测试可能多用单机运行。但对于系统测试或回归测试,就极有可能需要多台机在网络上同时运行。记住一个这样的原则,在开发过程中的任何时候,如果你需要等候测试的运行结果的话,那就是一个缩短开发时间的机会。对于单个的测试运行,挖潜的机会在测试的设置及开始运行和结果的对比及显示。有时候,需要反复修改程序,重新汇编和重新测试。这样,每一个循环的各种手工键入的设置与指令所花费的时间,加起来就非常可观。如果能利用make或类似的软件工具来帮助,就能节省大量的时间。 对于系统测试或回归测试这类涉及大量测试个案运行的情况,挖潜的的机会除了利用软件工具来实现自动化之外,就是怎样充分利用一切硬件资源。往往,就算是在白天的工作时间内,每台计算机的负荷都没有被充分利用。能够把大量测试个案分配到各台机器上去同时运行,就能节省大量的时间。另外,把大量的系统测试及回归测试安排到夜间及周末运行,更能提高效率。如果不购买商品化的工具的话,应当遵从正规的软件开发要求来开发出好的软件测试自动化工具。在实践中,许多企业自行开发的自动化工具都是利用一些现成的软件工具再加上自己写的程序而组成的。这些自己开发的工具完全是为本企业量身定做的,因此可用性非常强。同时,也能根据需要随时进行改进,而不必受制于人。在设计软件自动测试工具的时候,路径(path)控制是一个非常重要的功能。理想的使用情况是:这个工具可以在任何一个路径位置上运行,可以到任何路径位置去取得测试用例,同时也可以把测试的结果输出放到任何的路径位置上去。这样的设计,可以使不同的测试运行能够使用同一组测试用例而不至于互相干扰,也可以灵活使用硬盘的空间,并且使备份保存工作易于控制。同时,软件自动测试工具必须能够有办法方便地选择测试用例库中的全部或部分来运行,也必须能够自由地选择被测试的产品或中间产品采作为测试对象。
3.    测试结果与标准输出的对比 
  在设计测试用例的时候,必须考虑到怎样才能够易于对此测试结果和标准输出。输出数据量的多少及数据格式对比较的速度有直接影响。而另一方面,也必须考虑到输出数据与测试用例的测试目标的逻辑对应性及易读性,这将会大大有利于分析测试所发现的不吻合,也有利于测试用例的维护。  许多时候,要写一些特殊的软件来执行测试结果与标准输出的对比工作,因为可能有部分的输出内容是不能直接对比的(比如,对运行的日期时间的记录,对运行的路径的记录,以及测试对象的版本数据等),就要用程序进行处理。
4.    不吻合的测试结果的分析、分类、记录和通报  
上一点所谈到的,用于对测试结果与标准输出进行对比的特殊软件,往往也同时担任对不吻合的测试结果进行分析、分类、记录和通报的任务。"分析"是找出不吻合的地方并指出错误的可能起因。"分类"包括各种统计上的分项,例如,对应的源程序的位置,错误的严重级别(提示、警告、非失效性错误、失效性错误;或别的分类方法),新发现的还是已有记录的错误,等等。"记录",是按分类存档。"通报",是主动地对测试的运行者及测试用例的"负责人"通报出错的信息。   这里提到测试用例的"负责人"的概念。是用以指定一个测试用例运行时发现的缺陷,由哪一个开发人员负责分析(有时是另外的开发人员引进的缺陷而导致的错误)及修复。在设立测试用例库时,各用例均应有指定的负责人。  最直接的通报方法是由自动测试软件发出电子邮件给测试运行者及测试用例负责人。邮件内容的详细程度可根据需要灵活决定。
5.    总测试状况的统计,报表的产生  

这些都是自动测试工具所应有的功能。目的是提高过程管理的质量,同时节省用于产生统计数据的时间。产生出来的统计报表,最好是存放到一个约定的路径位置,以便任何有关人员都知道怎样查阅。同时,可按需要用电子邮件向适当的对象(如项目经理,测试经理和质量保证经理)寄出统计报表。

原文转自:http://www.ltesting.net