软件测试概述
软件测试概述
信息技术的飞速发展,使软件产品应用到社会的各个领域,软件产品的质量自然成为人们共同关注的焦点。不论软件的生产者还是软件的使用者,均生存在竞争的环境中,软件开发商为了占有市场,必须把产品质量作为企业的重要目标之一,以免在激烈的竞争中被淘汰出局。用户为了保证自己业务的顺利完成,当然希望选用优质的软件。质量不佳的软件产品不仅会使开发商的维护费用和用户的使用成本大幅增加,还可能产生其他的责任风险,造成公司信誉下降,继而冲击股票市场。在一些关键应用(如民航订票系统、银行结算系统、证券交易系统、自动飞行控制软件、军事防御和核电站安全控制系统等) 中使用质量有问题的软件,还可能造成灾难性的后果。
软件危机曾经是软件界甚至整个计算机界最热门的话题。为了解决这场危机,软件从业人员、专家和学者做出了大量的努力。现在人们已经逐步认识到所谓的软件危机实际上仅是一种状况,那就是软件中有错误,正是这些错误导致了软件开发在成本、进度和质量上的失控。有错是软件的属性,而且是无法改变的,因为软件是由人来完成的,所有由人做的工作都不会是完美无缺的。问题在于我们如何去避免错误的产生和消除已经产生的错误,使程序中的错误密度达到尽可能低的程度。
给软件带来错误的原因很多,具体地说,主要有如下几点:
①、交流不够、交流上有误解或者根本不进行交流
在应用应该做什么或不应该做什么的细节(应用的需求)不清晰的情况下进行开发。
②、软件复杂性
图形用户界面(GUI),客户/服务器结构,分布式应用,数据通信,超大型关系型数据库以及庞大的系统规模,使得软件及系统的复杂性呈指数增长,没有现代软件开发经验的人很难理解它。
③、程序设计错误
象所有的人一样,程序员也会出错。
④、需求变化
需求变化的影响是多方面的,客户可能不了解需求变化带来的影响,也可能知道但又不得不那么做。需求变化的后果可能是造成系统的重新设计,设计人员的日程的重新安排,已经完成的工作可能要重做或者完全抛弃,对其他项目产生影响,硬件需求可能要因此改变,等等。如果有许多小的改变或者一次大的变化,项目各部分之间已知或未知的依赖性可能会相互影响而导致更多问题的出现,需求改变带来的复杂性可能导致错误,还可能影响工程参与者的积极性。⑤、时间压力
软件项目的日程表很难做到准确,很多时候需要预计和猜测。当最终期限迫近和关键时刻到来之际,错误也就跟着来了。
⑥、自负人更喜欢说:
'没问题'
'这事情很容易'
'几个小时我就能拿出来'
太多不切实际的‘没问题’,结果只能是引入错误。
⑦、代码文档贫乏
贫乏或者差劲的文档使得代码维护和修改变的异常艰辛,其结果是带来许多错误。事实上,在许多机构并不鼓励其程序员为代码编写文档,也不鼓励程序员将代码写得清晰和容易理解,相反他们认为少写文档可以更快的进行编码,无法理解的代码更易于工作的保密(“写得艰难必定读的痛苦”)。
⑧、软件开发工具
可视化工具,类库,编译器,脚本工具,等等,它们常常会将自身的错误带到应用软件中。就象我们所知道的,没有良好的工程化作为基础,使用面向对象的技术只会使项目变得更复杂。为了更好地解决这些问题,软件界做出了各种各样的努力。
人们曾经认为更好的程序语言可以使我们摆脱这些困扰,这推动了程序设计语言的发展,更多的语言开始流行,为了使程序更易于理解开发了结构化程序设计语言,如PL/1,PASCAL等;为了解决实时多任务需求开发了结构化多任务程序设计语言,如Modula,Ada 等;为了提高重用性开发了面向对象的程序设计语言,如Simlasa 等;为了避免产生不正确的需求理解,开发形式化描述语言,如HAL/S等,这使得建立基于自然语言的描述成为可能,人们以形式化语言来描述需求;为了支持大型数据库应用,开发了可视化工具,如Visual Studio、Power Builder 等。程序语言对提高软件生产效率起到了一定的积极作用,但它对整个软件质量尤其是可靠性的影响,与其他因素相比作用较小。
可能是因为程序语言基于严格的语法和语义规则,人们企图用形式化证明方法来证明程序的正确性。将程序当作数学对象来看待,从数学意义上证明程序是正确的是可能的。数学家对形式化证明方法最有兴趣,在论文上谈起来非常吸引人,但实际价值却非常有限,因为形式化证明方法只有在代码写出来之后才能使用,这显然太迟了,而且对于大的程序证明起来非常困难。受到其他行业项目工程化的启发,软件工程学出现了,软件开发被视为一项工程,以工程化的方法来进行规划和管理软件的开发。
在可以借助许多新的技术和工具进行软件开发的今天,软件开发过程的成熟性问题开始引起人们的重视。这种产品一致性问题的主要症结在于管理,因此人们将目标转向了管理的改善,一些以改进软件开发过程为目标的活动已经展示出积极的结果。
不论采用什么技术和什么方法,软件中仍然会有错。采用新的语言、先进的开发方式、完善的开发过程,可以减少错误的引入,但是不可能完全杜绝软件中的错误,这些引入的错误需要测试来找出,软件中的错误密度也需要测试来进行估计。测试是所有工程学科的基本组成单元,是软件开发的重要部分。自有程序设计的那天起测试就一直伴随着。统计表明,在典型的软件开发项目中,软件测试工作量往往占软件开发总工作量的40%以上。而在软件开发的总成本中,用在测试上的开销要占30%到50%。如果把维护阶段也考虑在内,讨论整个软件生存期时,测试的成本比例也许会有所降低,但实际上维护工作相当于二次开发,乃至多次开发,其中必定还包含有许多测试工作。因此,测试对于软件生产来说是必需的,问题是我们应该思考“采用什么方法、如何安排测试?”
第一节测试的基本概念
一、目的与任务
G.J.Myers 在他的名著《软件测试技巧》一书中,给出了测试的定义:“程序测试是为了发现错误而执行程序的过程”。根据这一定义,测试(Testing )的目的与任务可以描述为:
目的:发现程序的错误;
任务:通过在计算机上执行程序,暴露程序中潜在的错误。
另一个与测试有关的术语叫纠错(Debugging )。它的目的与任务可以规定为:目的:定位和纠正错误;
任务:消除软件故障,保证程序的可靠运行。
测试与纠错的关系,可以用图7.1的数据流图来说明。图中表明,每一次测试都要准备好若干必要的测试数据,与被测试程序一道送入计算机执行。通常把程序执行需要的测试数据,称为一个“测试用例(Testing Case )”。每一个测试用例产生一个相应的“测试结果”。如果它与“期望结果”不相符合,便说明程序中存在错误,需要用纠错来改正。测试数据期望结果
程序测试测试结果评价错误信息纠错改正信息
图7.1
对于长度仅有数百行的小程序,测试与纠错一般由编码者一人完成;但对于大型的程序,测试与纠错必须分开进行。为了保证大程序的测试不受干扰,通常都把它交给独立的小组进行,等发现了程序有错误,再退回编码者进行纠错。有人把小程序得测试和纠错合称为“调试”。一旦编好了一个小程序,先拿到机器上测试,发现错误便进行纠错,修改后再重复测试。这一交替进行的“测试-纠错-再测试-再纠错”的过程,常被通俗的称为“调试程序”。其实“调试”在英语中并无相当的词,但纠错时有一种常用的工具叫“Debugger ”,通常被称为调试程序,而不译为纠错程序。
二、测试的特性
与分析、设计、编码等工作相比,程序测试具有若干特殊的性质。了解这些性质,将有助于我们正确处理和做好测试工作。
1、挑剔性
测试时对质量的监督与保证,所以“挑剔”和“揭短”很自然的成为测试人员奉行的信条。测试是为了证明程序有错,而不是证明程序无错。因此,对于被测程序就是要“吹毛求疵”,就是要在“鸡蛋里面挑骨头”。只有抱着为证明程序有错的目的去测试,才能把程序中潜在的大部分错误找出来。
2、复杂性
人们常常以为开发一个程序是困难的,测试一个程序则是比较容易的,这其实是误解。设计测试用例是一项需要细致和高度技巧的工作,稍有不慎就会顾此失彼,发生不应有的疏漏。
举一个极简单的例子。假如一个程序的功能是输入3个数作为三角形的3
条边,然后鉴别这一三角形的类别。输入3个”5”时程序回答“等边三角形”,但若输入3个“0”程序也回答“等边三角形”,就真假不分了。又如,三条便分别为2、3、4时应判断是“不等边三角形”,但如果对2、3、5或2、3、6也判断为“不等边三角形”,也会闹笑话。可见在设计测试用例时,需要考虑到各种可能出现的情况,对被测程序进行多方面的考核。切忌挂一漏万,把原本复杂的问题想的过于简单。小程序尚且如此,大型程序就可想而知了。所以有人认为,搞好一个大型程序的测试,其复杂性不亚于对这个程序的开发,主张挑选最有才华的程序员来参加测试工作。
3、不彻底性
“程序测试只能证明错误的存在,但不能证明错误不存在”。这句话揭示了测试所固有的一个重要性质――不彻底性。
可能有人认为,测一遍不彻底可以测十遍,十遍不彻底可以测百遍。需要多少测试用例就用多少测试用例,难道还怕达不到彻底吗?
为了回答这个问题,举一个例子。假如有人开发了一个C 语言的编译程序,要对它进行彻底的测试,需要设计多少个测试用例呢?从正面说,该编译程序应能把一切符合语法的C 程序正确的翻译为目标程序;从反面说,它应能对一切有语法错误的C 程序指出程序的语法。显然,这两个“一切都是无穷量”,十无法实现的。所谓彻底测试就是让被测程序在一切可能的输入情况下全部执行一遍。通常称这种测试为“穷举测试(Exhaustive Testing )”。实际测试中,穷举测试要不是像上例那样根本无法实现,就是工作量太大(例如一个小程序要连续测试许多年),在实践上行不通。这就注定了一切实际测试都是不彻底的,当然也就不能保证测试后的程序不存在遗留的错误。
4、经济性
既然穷举测试行不通,所以在程序测试中,总是选择一些典型的、有代表性的测试用例,进行有限的测试。通常把这种测试称为“选择测试(Selective Testing )”,为了降低测试成本(一般占整个开发成本的1/3左右),选择测试用例时应注意遵守“经济性”的原则。所以,第一,要根据程序的重要性和一旦发生故障将造成的损失开确定它的可靠性等级,不要随意提高等级,从而增加测试的成本;第二,要认真研究测试策略,以便能使用尽可能少的测试用例,发现尽可能多的程序错误。
三、测试的种类
静态分析器分析
(自动方式)静态分析
程序
测试
动态测试
代码评审走查办公桌检查(人工方式)黑盒测试(测试程序功能)白盒测试(测试程序结构)
图7.2
测试是一个执行程序的过程,即要求被测程序在机器上运行。其实,不执行程序也可以发现程序的错误。为便于区分,一般把前者称为“动态测试”,后者称为“静态分析(Static Analysis )”。广义的说,它们都属于程序测试,详见图
7.2
顾名思义,静态分析就是通过对被测程序的静态审查,发现代码中潜在的错误。它一般用人工方式脱机完成,故亦称为人工测试或代码评审(Code Review );也可借助于静态分析器在机器上以自动方式进行检查,但不要求程序本身在机器上运行。按照评审的不同组织形式,代码评审又可区分为代码会审、走查盒办公桌检查3种。对某个具体的程序,通常只使用一种评审方式。代码会审是由一组人通过阅读、讨论和争议对程序进行静态分析的过程。会审小组由组长,2~3名程序设计和测试人员及程序员组成。会审小组在充分阅读待审程序文本、控制流程图及有关要求、规范等文件基础上,召开代码会审会,程序员逐句讲解程序的逻辑,并展开热烈的讨论甚至争议,以揭示错误的关键所在。实践表明,程序员在讲解过程中能发现许多自己原来没有发现的错误,而讨论和争议则进一步促使了问题的暴露。例如,对某个局部性小问题修改方法的讨论,可能发现与之有牵连的甚至能涉及到模块的功说明、模块间接口和系统总结构的大问题,导致对需求定义的重定义、重设计验证,大大改善了软件的质量。
动态测试也可区分为两种。一类把被测程序看成一个黑盒,根据程序的功能来设计测试用例,称为黑盒测试(Black Box Testing );另一类则根据被测程序的内部结构设计测试用例,测试者需事先了解被测程序的结构,故称为白盒测试(White Box Testing )。
四、测试的文档
为了保证测试质量,软件测试必须完成规定的文档。按照软件工程的要求,测试文档主要应包括测试计划和测试报告两个方面的内容。
测试计划的主体是“测试内容说明”。它包括测试项目名称,实测结果与期望结果的比较,发现的问题,以及测试达到的效果等。
测试报告的主体是“测试结果”,它包括测试项目名称,实测结果与期望结果的比较,发现的问题,以及测试达到的效果等。
一个程序所需的测试用例可以定义为:
测试用例={测试数据+期望结果}
式中的{}表示重复。它表明,测试一个程序要使用多个测试用例,而每一个测试用例都应包括一组测试数据和一个相应的期望结果。如果在测试用例后面再加“实际结果”,就成为“测试结果”,即
测试结果={测试数据+期望结果+实际结果}
由此可见,测试用例不仅是连接测试计划与执行的桥梁,也是软件测试的中心内容。有效的设计测试用例,是搞好软件测试的关键。
第二节黑盒测试
黑盒测试就是根据被测程序功能来进行测试,所以也称为功能测试。用黑盒法设计测试用例,有4种常用技术,本节将逐一进行介绍。
一、等价分类法
所谓等价分类(Equivalence Partitioning ),就是把输入数据的可能值划分为若干等价类,使每类种的任何一个测试用例,都能代表同一等价类种的其它测试用例。换句话说,如果从某一等价类种任意选出一个测试用例未能发现程序的错误,就可以合理的认为在该类中的其它测试用例也不会发现程序的错误。这样,就把漫无边际的随机测试变成有针对性的等价类测试,有可能用少量有代表性的例子来代替大量内容相似的测试,借以实现测试的经济性。
采用这一技术要注意一下两点:一是划分等价类不仅要考虑代表“有效”输入值的有效等价类,还须考虑代表“无效”输入值的无效等价类;二是每一无效等价类至少要用一个测试用例,不然就可能漏掉某一类错误,但允许若干有效等价类合用同一个测试用例,以便进一步减少测试的次数。下面请看一个例子。
例7.1某工厂招工,规定报名者年龄应在16周岁到35周岁之间(到2002年3月30日止)。即出生年月不在上述范围内,将拒绝接受,并显示“年龄不合格”等出错信息。试用等价类法设计对这一程序功能的测试用例。
解第一步:划分等价类。假定已知出生年月由6位数字字符表示,前4位代表年,后2位代表月,则可以划分为3个有效等价类,7个无效等价类,如表7.2所示。
输入数据有效等价类无效等价类
出生年月(1)6位数字字符(2)有非数字字符
(3)少于6个数字符
(4)多于6个数字符
对于数值(5)在196702~198603(6)
之间(7)>198603
月份对应数值(8)在1~12之间(9)等于“0”
(10)>12
第二步:设计有效等价类需要的测试用例。如表7.2中的(1)(5)(8)等3个有效等价类,可以公用一个测试用例,例如:
测试数据
197011期望结果输入有效测试范围(1)(5)(8)
第三步:为每一个无效等价类至少设计一个测试用例。本例具有7个无效等价类,需要不少于7个测试用例。例如:
测试数据
MAY ,[***********][***********]22期望结果输入无效输入无效输入无效年龄不合格年龄不合格输入无效输入无效测试范围(2)(3)(4)(6)(7)(9)(10)
让几个有效等价类公用一个测试用例,可以减少测试次数,有利而无弊;但若几个无效等价类合用一个测试用例,就可能使错误漏检。就上例而言,假定把“195512”(对应无效等价类(6))和“196200”(对应无效等价类(9))合并为一个测试用例,例如“195500”及使在测试过程中程序显示出“年龄不合格”的信息,仍不能证明程序对月份为“00”的输入数据也具有识别和拒绝接受的功能。再进一步讲,其实在第一步“划分等价类”时,就应防止有意或无意的将几个独立的无效等价类写成一个无效等价类。例如,若在上例中把(9)(10)两个无效等价类合并写成“末两位的对应值为0或>12”则与之相应的测试用例也将从原来的2个减为1个,对程序的测试就不够完全了。
二、边界值分析法
实践表明,程序员在处理边界情况时,很容易因疏忽或考虑不周发生编码错误。例如,在数组容量、循环次数以及输入数据与输出数据的边界值附件程序出错的概率往往较大。采用边界值分析法(Boundary Value Analysis ),就是要这样来选择测试用例,使得被测试程序能在边界值及其附件运行,从而更有效的暴露程序中潜藏的错误。我们仍以例7.1为例说明。
例7.2程序同例7.1。试用边界值分析法设计其测试用例。
解用等价类法设计测试用例时,测试数据可以在等价类值域内任意选取。就拿例7.1来说,为了只接受年龄合格的报名者,程序中可能设有语句:
if (196702
但如果编码时把以上语句中的“
从例7.1可知,本例有3个输入等价类,即(1)出生年月;(2)对于数值;
(3)月份对应数值。采用边界值分析法,可为这3个输入等价类选取14个边界值测试用例,(其中有两个相重,实有13个),其内容如下:
表7.3
说明:表中第6、第七两个测试用例数据相同,可合用一个测试用例。将例7.1和例7.2进行比较,可见:
(1)等价类分类法的测试数据是在各个等价类允许的值域内任意选取的,而边
界值分析的数据必须在边界值附件选取;
(2)例7.1用了8个测试用例,而例7.2用了13个。一般的说,用边界值分析
法设计的测试用例比等价分类法代表性更广,发现错误的能力也更强。但是对于边界的分析与确定比较复杂,要求测试人员具有更多的经验和创造性。
还需要指出,例7.2包含的边界值情况比较简单,只需要分析输入等价类。在有些情况下,除了考察输入值边界外,还需要考虑输出值和其它可能存在的边界。例如,假定被测程序是一个计算X 的函数Sin (X ),其输出具有3个边界值-1,0和1。则在选择测试用例时,应使X 的值分别产生上述的3种输出边界值,即选取-∏/2、0、∏/2作为X 的测试数据。
三、错误猜测法
所谓猜错(Error Guessing ),就是猜测被测程序在哪些地方容易出错,然后针对可能的薄弱环节来设计测试用例。显然,它比前两种方法更多的依靠测试人员的直觉与经验。所以,一般都先用前两种方法设计测试用例,然后用猜错法补充一些例子作为辅助的手段。
仍以上述的报名程序为例,在已经用等价类法和边界值法设计过测试用例的基础上,还可以用猜错法补充一些测试用例,例如:
(1)出生年月为“0”
(2)漏送“出生年月”;
(3)年月次序颠倒,例如将“197012”误输为“121970”等。
四、因果图法
1) 因果图的适用范围
前面介绍的等价类划分方法和边界值分析方法,都是着重考虑输入条件,但未考虑输入条件之间的联系。如果在测试时考虑输入条件的各种组合,可能又会产生一些新情况。
所有输入条件之间的组合情况往往相当多。必须考虑使用一种适合于描述对于多种条件的组合,相应产生多个动作的形式来考虑设计测试用例,这就需要利用因果图。因果图方法最终生成的是判定表。它适合于检查程序输入条件的各种组合情况。
2)用因果图生成测试用例的基本步骤
(1)分析软件规格说明描述中,哪些是原因(即输入条件或输入条件的等价类),哪些是结果(即输出条件),并给每个原因和结果赋予一个标识。
(2)分析软件规格说明描述中的语义,找出原因与结果之间、原因与原因之间对应的是什么关系?根据这些关系,画出因果图。
(3)由于语法或环境限制,有些原因与原因之间、原因与结果之间的组合情况不可能出现。为表明这些特殊情况,在因果图上用一些记号标明约束或限制条件。
(4)把因果图转换为判定表
(5)把判定表的每一列拿出来作为依据,设计测试用例。
3)在因果图中出现的基本符号(a)恒等C1E1(b)非C1○○E1(c)或C1○
○E1
C2○(d)与C1○
∧E1
C2○
通常在因果图中用Ci 表示原因,用Ei 表示结果,其基本符号如图7所示。主要的原因和结果之间的关系有:
(a)恒等:表示原因与结果之间一对一的对应关系。若原因出现,则结果也出现。若原因不出现,则结果也不出现。
(b)非:表示原因与结果之间的一种否定关系。若原因出现,则结果不出现。若原因不出现,反而结果出现。
(c)或:表示若几个原因中有一个出现,则结果出现,只有当这几个原因都不出现时,结果才不出现。
(d)与:表示若几个原因都出现,结果才出现。若几个原因中有一个不出现,结果就不出现。
4)表示约束条件的符号
为了表示原因与原因之间、结果与结果之间可能存在的约束条件,在因果图中可以附加一些表示约束条件的符号。若从输入(原因)考虑,有以下四种约束:
输入(1)E (互斥):表示a ,b 两个原因不会同时成立,两个中最多有一个可能成立。
(2)I (包含):表示a ,b ,c 三个原因中至少有一个必须成立。
(3)O (唯一):表示a 和b 当中必须有一个,且仅有一个成立。
(4)R (要求):表示当a 出现时,b 必须也出现。不可能a 出现,b
不出现。
输出(5)M (屏蔽):表示当a 是1时,b 必须是0。而当a 为0时,b
的值不定。
5)利用因果图设计测试用例的实例
【例】有一个处理单价为5角钱的饮料的自动售货机软件测试用例的设计。规格说明为:“若投入5角钱或1元钱的硬币,押下【橙汁】或【啤酒】的按钮,则相应的饮料就送出来。若售货机没有零钱找,则一个显示【零钱找完】的红灯亮,这时在投入1元钱硬币并押下按钮后,饮料不送出来而且1元硬币也退出来;若有零钱找,则显示【零钱找完】的红灯灭,在送出饮料的同时退还5角硬币。”
(1)分析这一段说明,列出原因和结果
原因4、押下橙汁按钮
5、押下啤酒按钮结23、退还5硬币果24、送出橙汁饮料(2)画出因果图,如下7.3所示。所有原因结点列在左边,所有结果结点列在右边。建立两个中间结点,表示处理的中间状态。
(3)由于2与3,4与5不能同时发生,分别加上约束条件E
(4)把因果图转换成判定表
在因果图中,阴影部分表示因违反约束条件的不可能出现的情况,删去。第16列与第32列因什么动作也没做,也删去。最后可根据剩下的16列作为确定测试用例的依据。
序号
条件中间结果结
①②③④⑤(11)(12)(13)(14)(21)(22)(23)(24)(25)
果测试用例
第三节白盒测试
白盒测试以程序的结构为依据,所有又称为结构测试。早期的白盒测试把注意力放在流程图的各个判定框,使用不同的逻辑覆盖标准来表达对程序进行测试的详尽程度。随着测试技术的发展,人们越来越重视对程序执行路径的考察,并且用程序图代替流程图来设计测试用例。为了区分这两种白盒测试技术,以下把前者称为逻辑覆盖测试,后者称为路径测试。
一、逻辑覆盖测试
逻辑覆盖测试法(Logic Coverage Testing )考察的重点是图中的判定框(菱形框)。因为这些判定若不是与选择结构有关,就是与循环结构有关,是决定程序结构的关键成分。
按照对被测程序所作测试的有效程度,逻辑覆盖测试可由弱到强区分为5种覆盖标准:发现错误的能力
语句覆盖判定覆盖条件覆盖判定/条件覆盖条件组合覆盖强
每条语句至少执行一次
每一判定的每个分支至少执行一次
每一判定中的每个条件,分别按“真”、“假”至少各执行一次
同时满足判定覆盖和条件覆盖的要求
求出判定中所有条件的各种可能组合值,每一可能的条件组合至少执行一次
弱
举例说明:
有一个程序段如下:
a
b
(A>1)
(B=0)
T c
x=x/a
d (A=2)(x>1)
e
x=x+1
(a)语句覆盖:设计若干个测试用例,运行被测程序,使得每一个可执行语句至
少执行一次。例如在上图所给出的例子中,正好所有的可执行语句都在路径L1(a ->c->e)上,所以选择路径L1设计测试用例,就可以覆盖所有的可执行语句。
L1(a ->c->e)
={(A>1)and (B=0)}and {(A=2)or (x/A>1)}
=(A=2)and (B=0)or {(A>1)and (B=0)and (x/A>1)}测试用例可以设计为:【(2,0,4),(2,0,3)】覆盖ace 【L1】
从程序中每个可执行语句都得到执行这一点来看,语句覆盖的方法似乎能够比较全面的检验每一个可执行语句。但与后面介绍的其它覆盖相比,语句覆盖是最弱的逻辑覆盖准则。
(b)判定覆盖
所谓判定覆盖就是设计若干个测试用例,运行被测试程序,使得程序中每个判断的取真分支和取假分支至少经历一次。判定覆盖又称为分支覆盖。如上例如果选择路径L1(a ->c->e)和L2(a ->b->d),可得满足要求得测试用例:
L2(a ->b->d)
={(A
=(A
如果选取路径L3(a ->b->e)和L4(a ->c->d)L3(a ->b->e)
={(A1)}
={(A1)}or {(B ≠0) and (A=2)}or {(B ≠0) and (x>1)}L4(a ->c->d)
={(A>1)and (B=0)}and {(A≠2) and (x/A
所有测试用例得取法不唯一。注意有例外情况,例如,如果把上例中第二个判定中的条件x>1错写成x
(c)条件覆盖
所谓条件覆盖就是设计若干个测试用例,运行被测程序,使得程序中每个判断的每个条件的可能取值至少执行一次。上例中,我们事先可对所有条件得取值加以标记。例如:
对于第一个判断:条件A>1取真值为T1,取假值为T1
条件B=1取真值为T2,取假值为对于第二个判断:条件A=2取真值为T3,取假值为T3
条件x>1取真值为T4,取假值为T4
则可选取测试用例如下:
测试用例
【(2,0,4) ,(2,0,3) 】【(1,0,1) ,(1,0,1) 】【(2,1,1) ,(2,1,2) 】
通过路径ace(L1)abd(L2)abe(L3)通过路径abe(L3)abe(L3)
条件取值T1T2T3T4T1T3T1T2T3T4条件取值T1T2T3T4T1T2T3T4
覆盖分支c,e b,d b,e 覆盖分支c,e b,d
或
测试用例
【(1,0,3) ,(1,0,4) 】【(2,1,1) ,(2,1,2) 】
注意,前一组测试用例不但覆盖了所有判断得取真分支和取假分支,而且覆盖了判断中所有条件得可能取值。但是后一组测试用例虽满足了条件覆盖,但只覆盖了第一个判断得取假分支和第二个判断得取真分支,不满足判定覆盖得要求。为了解决这一矛盾,需要对条件和分支兼顾,有必要考虑以下得判定-条件覆盖。
(d)判定-条件覆盖
所谓判定-条件覆盖就是设计足够的测试用例,使得判断中每个条件得所有可能取值至少执行一次,同时每个判断本身的所有可能判断结果至少执行一次。上例中可以设计如下测试用例:
测试用例
【(2,0,4) ,(2,0,3) 】【(1,1,1) ,(1,1,1) 】
通过路径ace(L1)abd(L2)
条件取值T1T2T3T4T1T2T3T4
覆盖分支c,e b,d
判定-条件覆盖也有缺陷。从表面上来看,它测试了所有条件得取值,但是事实并非如此。因为往往某些条件掩盖了另一些条件。对于条件表达式(A>1)and (B=0)来说,若(A>1)的测试结果为真,则还有测试(B=0),才能决定表达式得值;而若(A>1)的测试结果为假,可以立刻确定表达式得结果为假。这时往往就不再测试(B=0)的取值了。因此,条件(B=0)就没有检查。
同样,对于条件表达式(A=2)or (X>1)来说,若(A=2)得测试结果为真,就可以立即确定表达式的结果为真。这时,条件(X>1)就没有检查。因此,采用判定-条件覆盖,逻辑表达式中的错误不一定能够查得出来。
(e)条件组合覆盖
所谓条件组合覆盖就是设计足够得测试用例,运行被测程序,使得每个判断得所有可能得条件取值组合至少执行一次。上例中,先对各个判断得条件取值组合加以标记。如下:记
1A>1,B=0作T1属第一个判断得取真分支;2A>1,B≠0作T1T2, 属第一个判断得取假分支;3A ≯1,B=0作属第一个判断得取假分支;4A ≯1,B ≠0作T1T2, 属第一个判断得取假分支;5A=2,x>1作T3T4, 属第二个判断得取真分支;6A=2,x≯1作T4, 属第二个判断得取真分支;7A ≠2,x>1作T3T4, 属第二个判断得取真分支;8A ≠2,x ≯1作T3T4, 属第二个判断得取假分支;对于每个判断,要求所有可能得条件取值得组合都必须取到。测试用例如下:
测试用例
【(2,0,4) ,(2,0,3) 】【(2,1,1) ,(2,1,2) 】【(1,0,3) ,(1,0,4) 】【(1,1,1) ,(1,1,1) 】
通过路径ace(L1)abe(L3)abe(L3)abd(L2)
条件取值T1T2T3T4T2T4T1T2T3T4T1T2T3T4
覆盖分支①, ⑤②, ⑥③, ⑦④, ⑧
这组测试用例覆盖了所有条件得可能取值的组合,覆盖了所有判断得可取分支,但路径漏掉了L4。测试还不完全。【练习】
写出“直接插入排序”的算法,画出流程图,并用逻辑覆盖法,写出测试用例。
【解】已知直接插入排序算法得基本步骤如下:(1)从一组数中取出第一个数
(2)取下一个数,如数已取完,则排序结束;
(3)如果所取数大于等于其前邻数,则重复(2)步(4)如果所取数小于其前邻数,则与其前邻数交换位置
(5)重复第(4)步,直到所取已无前邻数(即已交换到当前数列得第一位
置),或大于等于其前邻数为止(6)返回第(2)步。
流程图如下:
排序开始
I
I>K
I
排序结束
A[I]>=A[I-1]
J
J
A[J]>=A[j-1]
J
A[J]A[j-1]
图7.5排序部分的流程图
程序代码如下(PASCAL )
PROGRAM bubblesort(input,output)LABEL 99;
CONST n=100VAR
A:ARRAY[1..n]of integer; I,j,k,temp:INTEGER;BEGIN
READLN(k);
FOR I:=1to k DO read(a[i]);FOR I:=2to k DO BEGIN
IF a[i]>=a[i-1]then goto 99; For j:=Idownto 2do
99:END
For I:=1to k do write(a[i])End.
【设计测试用例】
由以上分析可知,排序程序具有双重嵌套循环结构。其内外循环体各包含一条选择语句,用于在条件满足时提前推出循环。程序中得4个判断是测试时考察得重点。以下分别列出按不同覆盖标准设计得测试用例:
(1)语句覆盖。稍作分析便不难看出,只要送入先大后小得两个数,程序
执行时就可以遍历流程图中的所有框。因此,仅需选用一组测试数据如{A={8,4},K =2},就能实现语句覆盖。这类覆盖发现错误得能力不强,例如若将程序中得两个“>=”均误写为“=”,用上述得测试数据就不能发现。
(2)判定覆盖。选用上述得测试数据,内、外层循环都是从正常得循环出
口退出得。要实现判定覆盖,还需在语句覆盖得基础上,增加两个能使程序从非正常出口退出的测试数据。例如,用以下两组数据:{A={8,4,8},K =3}和{A={8,4,4},K =3}或
{A={8,4,8,4},K =4}
则程序将在满足A[I]=A[I-1]或A[J]=A[J-1]的条件下通过非正常出口,也能实现判定覆盖。但又可能出现另一种偏向,掩盖把“>=”误写为“=”的错误,造成更加严重得测试漏洞。
(3)条件覆盖。从以上分析很容易想到,必须选取足够得测试,使复合条
件占的每个条件分别按“真”、“假”出现一次,才能克服前述的缺点,进一步提高发现错误的能力。测试用例:{A={8,4,9,6},K =4}{A={8,4,8,4},K =4}
就能对程序实现条件覆盖。此时A[I](或A[J])大于、等于或小于A[I-1](或A[J-1])的3中情况将分别至少出现一次,无论把“>=”误写为“>”或“=”,都可用这两组数据检查出来。
(4)其它覆盖。本例中得两个复合条件,其组成条件都不是互相独立的。
如果其中有一个条件(例如A[I]=A[I-1])为真,则另一个条件(例如A[J]=A[J-1])必然为假。所有就本例来说,判定条件覆盖及条件组合覆盖都没有实际意义,可以不必讨论。
由此可见,本例宜选择条件覆盖,以便得到较强得查错能力。测试数据可选择
{A={8,4,9,6},K =4}{A={8,4,8,4},K =4}或合成一组:
{A={8,4,8,4,9,6},K =6}
Begin
Temp:=a[j];A[j]:=a[j-1];A[j-1]:=temp;End
二、路径测试
逻辑覆盖测试引导人们把注意力集中在程序得各个判定部分,抓住了结构测试的重点。但是另一方面,它却忽略了另一个对测试也有重要影响的方面-程序的执行路径。随着程序结构复杂性的增长和测试技术的发展,人们逐渐认识到这种忽略所带来的缺陷。于是,着眼于程序执行路径得测试方法便应运而生。这就是路径测试(Path Testing )。
路径测试离不开程序图。下面先对程序图作一简单介绍。1、程序图(Program Graph )
程序图实际上是一种简化了的流程图。在路径测试中,它是用来考察测试路径得有用工具。图7.6显示了分别用流程图和程序图来表示的基本控制结构,流程图中各种不同形状的框,在程序图中都被简化为用圆圈表示得一个个结点。由于程序图保留了控制流得全部轨迹,舍弃了各框得细节,因而画面简洁,路径清楚,用它来验证各种测试数据对程序执行路径的覆盖情况,比流程图更加方便。还有两点需要说明:
(1)顺序执行的多个结点,在程序图中可以合并画成一个结点,
如图7.7所示。
(2)含有复合条件的判定框,在程序图中常常先分解成几个简
单条件判定框,然后再画,如图7.8所显示。
(a )流程图
(b )程序图
图7.6程序图与流程图得对照图形
A ≥B
A>B
A>B
图7.7合并结点得程序图图7.8分解为简单条件结点后得程序图
2、路径测试的特征
所谓路径测试,就是对程序图中每一条可能的程序执行路径至少测试一次。如果程序中含有循环(在程序图中表现为环),则每个循环至少执行一次。
路径测试具有以下特征:(1)满足结构测试的最低要求。有些作者主张,语句覆盖加判定
覆盖应是对白盒测试的最低要求。他们把同时满足这两种标准的覆盖取名为“完全覆盖(Complete Coverage )”从上述对路径测试的要求可见,它本身就包含了语句覆盖和判定覆盖(在程序图中分别称为点覆盖和边覆盖)。换句话说,只要满足了路径覆盖,就必然满足完全覆盖,也即满足了白盒测试的最低要求。
(2)有利于安排循环测试。循环测试是路径测试的一个重要部分。
从程序的执行路径而不是单纯从判定的角度来测试程序,有利于对程序中包含的循环结构进行更有效的测试。一般的说,对单循环结构的路径测试可包括:
1. 零次循环,即不执行循环体,直接从循环入口跳到出口2. 一次循环,循环体仅执行一次,注意检查在循环初始化中
肯存在的错误3. 典型次数的循环
4. 最大值次循环,如果循环次数存在最大值,应按次最大值
进行循环,需要时还可增加比最大值次数少一次或多一次的循环测试。
3、选择测试路径的原则
(1)选择具有功能含义的路径(2)尽量用短路经代替长路径
(3)从上一条测试路径到下一条测试路径,应尽量减少变动的部分(包
括变动的边和结点)
(4)由简入繁,如果可能,应先考虑不含循环的测试路径,然后补充对
循环的测试(5)除非不得已(如为了覆盖某条边),不要选取没有明显功能含义的复
杂路径。
4、路径测试举例
【举例】仍以“直接插入排序”的算法为例,设计测试用例【解】(1)将图7.5的流程图转换成程序图,如图7.9所示,在将其逆时针旋转90度,在把所有结点和边记上标号,如图7.10所示
9
7
a
1
b
2
c
8
3
d
4
e
5
f
6
n
AE
I j
图7.10
图7.9根据流程图导出的程序图
(2)按照上述选择路径的原则选择足够多的路径,使之覆盖所有的结点和边,如下表所示。
测试数据A={4,8},k=2A={4,4},k=2A={4,8,8},k=3A={8,4},k=2A={8,4,4},k=3A={8,4,6},k=3
覆盖的结点
①②③④⑤⑥⑦⑧⑨
覆盖的边
f g h I j k
a b c d e l m n q
√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√
√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√
√√√√
√√√
以上6次测试已达到最低覆盖要求——完全覆盖。
(3)补充几项测试,见下表循环测试内容测试数据外循环零次外循环最大次,内循环零次A ={1,2…,99,100},K=100外循环最大次,那循环最大次A ={100,99,…2,1},K=100
第四节测试用例设计
以上讨论了软件测试的黑盒与白盒技术。本小节以两个简单程序为例,说明怎样从被测程序的实际出发,来确定测试策略和选择测试用例。需要指出的是,黑盒法与白盒法均包括多种技术,各有所长。但若单独采用其中的一种技术,都不能产生被测程序所需要的全部测试用例。因此在实际的程序测试中,总是将各种技术结合起来,取长补短,形成综合的测试策略。
一、黑盒测试用例设计
【例1】假设现有以下的三角形分类程序。该程序的功能是,读入代表三角形边长的3个整数,判定它们能否组成三角形。如果能够,则输出三角形是等边、等腰或任意三角形的分类信息。图9.11显示了该程序的流程图和程序图。为以上的三角形分类程序设计一组测试用例。
开始
S
A
B
123
C
A=B
F
A=C
F
12
4
8
10
F
B=C
T
B=C
5
9
印出“等腰三角形“
F
67E
11
印出“不是三角形“印出“等边三角形“印出“任意三角形“
终止
图9.11三角形程序的流程图和程序图
【解】第一步:确定测试策略。在本例中,对被测程序的功能有明确的要求,即:(1)判断能否组成三角形;(2)识别等边三角形;(3)识别等腰三角形;(4)识别任意三角形。因此可首先用黑盒法设计测试用例,然后用白盒法验证其完整
性,必要时再进行补充。
第二步:根据本例的实际情况,在黑盒法中首先可用等价分类法划分输入的等价类,然后用边界值分析法和猜错法作补充。
等价分类法:
有效等价类
输入3个正整数:
(1)3数相等(2)3数中有2个数相等,比如AB 相等(3)3数中有2个数相等,比如BC 相等(4)3数中有2个数相等,比如AC 相等(5)3数均不相等(6)2数之和不大于第3数,比如最大数是A (7)2数之和不大于第3数,比如最大数是B (8)2数之和不大于第3数,比如最大数是C
无效等价类:
(9)含有零数据(10)含有负整数(11)少于3个整数(12)含有非整数(13)含有非数字符
边界值法:
(14)2数之和等于第3数
猜错法:
(15)输入3个零(16)输入3个负数
第四步:用白盒法验证第三步产生的测试用例的充分性。结果表明,上表中的前8个测试用例,已能满足对被测程序图的完全覆盖,不需要再补充其他的测试用例。
二、白盒测试用例设计
【例2】学生成绩查询程序,最多可以存放100个学生的成绩,按学号排序。如果输入的学号在这批学生内,就输出该学生的成绩,否则便回答“学号??没有找到”。程序采用折半查找,另外,当输入学号为“0”时,即结束查找。为该程序设计测试用例。
【解】
第一步:确定测试策略。折半查找时实现查找的一种算法。就本例而言,主要应测试程序在各种典型情况下能否有效的实现查找,并选择正确的执行路径。因此,路径覆盖法显然是比较合理的测试策略。
第二步:令成绩表的长度为1项或小于100项的任意项(例如5项)。在成绩表的不同位置上每次查+/-1个学生,考察其执行情况:1成绩表只有1项,查0个学生2成绩表只有1项,查1学生(是成绩表中的学生)3成绩表只有1项,查2学生(都不是成绩表中的学生)4成绩表只有5项,查1学生(正好是表的中间项)5成绩表只有5项,查1学生(正好是表的上半部分)6成绩表只有5项,查1学生(正好是表的下半部分)7成绩表只有5项,查1学生(不是表中的学生)
测试数据下表所示,由表可知,以上测试数据已达到对程序图的“完全覆盖”。
说明:测试数据中略去score 数组的数据。本例中它们可以取任意值,不会影响测试结果。
第三步:成绩表长度为最大值(100项)。连续查找学号为最小值、比最小值小1、最大值、比最大值大1、中间值以及上半、下半表中典型值的7个学生。具体测试数据可以是:
K =100,studentno ={101,102,…,199,200}Num={100,101,200,201,150,130,170,0}
从以上3步可见:1虽然是路径测试,同时也测试了程序的功能,例如:
●只查一个学生,连续查多个学生,或一个学生不查;
●成绩表长度为1项,为100项,或为任意项(例如5项)2虽然是按白盒法设计的测试用例,也运用了黑盒法中的等价类分类与取边缘值等思想。
从前面的两个例子可知,设计测试用例的过程,实际上也是对黑盒测试技术与白盒测试技术综合运用的过程。我们不可能期望只靠几条简单的规则就能解决各种实际问题,恰恰相反,有些做法“可以意会,不能言传”,真正需要的倒是举一反三,灵活的运用在黑盒和白盒测试中所蕴涵的原则和思想。
开始
读入成绩表
读入num
Num0
A First,last,middle
赋初值
查找结束?
B
C 在上半部分?
调整last 调整fist
D E
F 重算middle
找到所查学号?
打印分数印出”未找到”
读入下一个num
结束a 1b 2q 103d 4J 5i 6G h 7n 8L m 9
A :first=1,last=k,middle=int((first+last)/2)B :num=studentno[middle]or last=firstC :num
K=1
studentno={100}
num={0}
={100,0}
={90,110,0}
k=5
studentno
={2,4,6,8,10}
num={6,0}
={2,0}
={8,0}
={9,0}√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√√覆盖结点①②③④⑤⑥⑦⑧⑨⑩覆盖的边a b c d e f g h I j k l m n q
第五节软件的纠错
当测试发现程序有错后,就要进行纠错。譬如治病,发现有病不是目的,要紧的是找出病因,加以排除。人们在长期的纠错实践中,积累了丰富的经验,提出了多种行之有效的纠错策略与技术。现分别介绍如下:
一、纠错的策略
试凑法
正向跟踪
纠错策略跟踪法
归纳推理
推理法
演绎推理
1、试凑法
这是一种不甚严格的纠错策略。根据测试中暴露达到错误征兆,首先可设定一个可疑区。然后采用一些简单的纠错手段(例如在程序中插入打印语句),进一步获取与可疑区有关的信息,借以肯定或修改原来的设想。如果可疑区没有找准,便再来一遍,重置新的可疑区。这种“边试边瞧”的做法,纠错的效率一般较低(这也是试凑(trial and error )一名的由来),故仅适用于结构比较简单的小程序。
2、跟踪法
跟踪执行可疑的程序段,是对小型程序纠错又一种常用的策略。它的要求是,就是让带错的程序“分步执行”,即每执行完一条语句(或指令),就暂时停下检查执行的结果,确认正常后再继续进行。许多高级语言都设有专用于跟踪纠错的语句或命令。他们都具有让程序分步执行的功能。
对于较小的程序,用跟踪执行来确定错误部位是相当有效的。例如:倘若再某一PASCAL 程序中把语句
FOR I :=1to 10中的10误写为-10,则程序执行到语句就会直接跳过循环体中的语句,转到for 循环之后的语句去执行。
但有些时候,发现程序出错的语句其本身并无错误,问题倒出在位于它前面的一些语句上。例如,当某FORTRAN 程序执行到语句
ix =iy/(k/n)时发生了运行出错,屏幕上显示“整数上溢”。顺着这条语句返回寻找,在它的前数行找到了给k ,n 赋值的语句:
k =5n =6
则不难想到,由于k/n整除所得结果为0,才导致iy 被0除后发生上溢。像这样从出错处逐句返回探寻错误的根由,一般称为回溯(back tracking )或反向跟踪。而通常高级语言所设置的跟踪命令都只提供正向跟踪,反向跟踪只能人工进行。
当程序较长,包含的分支与嵌套又相当多时,跟踪尤其是反向跟踪的路径变得多而且复杂。这是使用跟踪法纠错不仅效率不高,而且会变得难于执行,最好改用推理法进行纠错。适合大、小程序适合小程序
3、推理法
推理法来源于定理证明,又可区分为归纳法和演绎法两大类。
归纳法是从个别到整体的推理过程。它从收集个别的故障症状开始,分析各种症状的相互关系后,就有可能将它们归纳为某一些假想的错误。如果这一些假想能被证实,就找到了真实的病根。
演绎法是从一般到特殊的推理过程。根据测试获得的错误症状,可以先列出一批可能的病因。接着在这一大范围的设想中,逐一的排除根据不足或与其他测试结果有明显矛盾的病因,然后对余下的一种或数种病因作详细的鉴别,确定真正的病因。由于真正的病因是从大量可能病因中筛选得来的,所以此法有时也称为“病因排除法(Cause Elimination )”
二、常用的纠错技术
纠错先要查错。查错的工作量通常占整个纠错的十分之九以上。所谓纠错的技术,主要是指查明程序错误时可能采用的工具和手段。这些手段如果运用得当,就能明显的提高查错的效率。
1、插入打印语句
在程序中插入暂时性的打印语句,是一种十分常见的查错技术。这类打印语句的作用主要是显示程序的中间结果或有关变量的内容。插入打印适用于任何高级语言书写的程序。但其输出与程序的原输出夹杂在一起,需要注意分辩。此外,纠错结束后必须记住将它们删除。
2、设置断点
查错的基本技术之一,就是在程序的可疑区设置断点。每当程序执行到设置的断点时,就会暂停执行,以便纠错者观察变量内容和分析程序的运行状况。
3、掩蔽部分程序
对可疑程序进行检查时,常常要让程序反复执行。如果整个程序较长,可疑区仅占其中的一小部分,则每次运行整个程序,必将浪费许多时间和精力。在这种情况下,明智的作法是把不需要检查的程序掩蔽起来,只让可疑的部分程序反复运行。
掩蔽无关程序可使用下述方法:
(1)在要掩蔽的语句行加上注释符,使解释或编译程序把它们当作注释行,不予处理。
(2)把要掩蔽的程序段置入一个“常假”的选择结构中,使它总没有机会执行。
(3)用GOTO 语句跳越要掩蔽的程序段
无论使用哪一种掩蔽方法,纠错结束后都应撤销掩蔽,使程序复原。
4、蛮力纠错技术(Dubugging by Brute Force )
某些系统或调试程序能提供一种“转储”命令(DUMP ),用来打印出内存可疑区或输出文件的全部内容,供纠错者分析使用。这种作法的优点是信息齐全,只要有耐心,总可以找出问题。但输出的数据量大,从中寻找错误的迹象好比大海捞针,效率很低。如果说前3种技术都重视分析与错误有关的信息,DUMP 命令却不论数据与错误有无关联,一律拿出来“曝光”。所以有些文献称之为蛮力纠错,仅在程序很小或其他纠错手段未能奏效时才使用这种方法。
三、纠错举例:
仍以学生成绩查询程序为例, 程序如下, 试对它进行纠错:
读入成绩表,读入num
while num0do begin first=1;last=k;middle=int((first+last)/2);A while num=studentno[middle]do begin if num
解:(1)首先设成绩表中有5个学生,输入初始值:
k =5,studentno ={2,4,6,8,10}score ={60,70,80,90,100}
键入num ={6,2,8,0},查询学号为6,2,8的3个学生的分数,屏幕显示:no =6score =80no =2score =60no =8score =90
符合预期结果。
(2)成绩表同第一步。键入:
num ={9,0}
因成绩表中无此学生,预期屏幕应该显示:
student number 9not found
但实际测试时,程序却运行很长时间没有结果,估计发生了死循环。
(3)为了弄清查找过程是否存在问题,在内层do 循环内外即A 、B 处分别插入一条内容相同的打印语句:
write first ,last ,middle
重新运行以上1、2步的测试数据。屏幕将显示:
对num ={6,2,8,0}对于num ={9,0}
153153no =6score =[**************]44no =2score =[1**********]。。。no =8score =90
(4)归纳以上结果可以假定:对成绩表中存在的学生(如学号2,6,8),程序能正常查找和打印,但若所查学号不在表中(如学号9),程序将陷入死循环,并且出现last
(5)为了验证上述的假定,再补充一下测试:
成绩表:k =100
studentno ={101,102,…,199,200}score={80,其余99个值任意}
查询学号:num ={101,100,0}
屏幕将显示:
[***********]16153121no =101score =[***********][1**********]00
当查询的学号不在表中,再一次出现了last
(6)现在,可以提出假想的错误了。分析对first 、last 赋值的语句后,发现问题出在last =middle -1这一语句上。从表面上看,它比“last =middle ”多舍弃一项,可以提高查找效率,但是这个“减一”,可能导致出现last
(7)为了证实这一假想,将语句“last =middle -1”修改为:last =middle 重新作第3步的测试,屏幕将显示:
对num ={6,2,8,0}对于num ={9,0}
153153no =6score =[**************]55no =2score =[1**********]。。。no =8score =90
可见当查询的学号不在表中时,程序仍将陷入死循环,但last
(8)重做第5步的试验,当查询学号为100时,屏幕将显示:
[***********][1**********]111
。。。
这一结果与第6步的结果是一致的。即:仍有死循环,但未出现last
(9)继续改错。从(6)至(8)的结果可知,如果把first =last 也当作推出循环的一种条件,死循环就可以避免。这只要把循环的条件while num=studentno[middle]do 改为:while num=studentno[middle]or last=firstdo
(10)将经过上述(7)、(9)两步修改后的程序重新测试,便可发现不论查询的学号是不是在表内,程序均能正常运行。例如,当重复第(7)步num ={9,0}的测试时,屏幕将显示:
153454555student number 9not found
(11)删去插入的两条打印语句,纠错结束。
第六节多模块程序的测试策略
实际的应用程序大都是多模块程序。像前面所列举的例子可能仅相当于大程序的一个模块。所以在软件工程的术语中,软件测试主要系指多模块程序的测试。
前几节讨论的测试与纠错技术,对大、小程序是普遍适用的。但是,就测试的内容与实施步骤来说,多模块程序要比单模块小程序复杂的多。这种复杂性主要表现是测试的层次性。
一、测试的层次性
按照软件工程的观点,多模块程序的测试共包括4个层次,如下表:
单元测试(Unit Testing )
综合测试(Integration Testing )
确认测试(Validation Testing )
高级测试
系统测试(System Testing )
第一层为单元测试,应该在编码阶段完成。单元一般以模块或子程序为单位,故又称为模块测试(Module Testing )
测试阶段应完成集成测试与确认测试两个层次的测试。这一阶段的任务,是把通过了单元测试的模块逐步组装起来,通过测试与纠错,最终得到了一个满足需求的目标软件。其中,集成测试是一个逐步组装的过程。它从一个单元开始,逐一的添加新的单元,边添边测边纠错,直至最终将所有单元集成为一个系统,所以也称为集成测试。确认测试是对整个程序的测试,用于确认组装完毕的程序确能满足用户的全部需求。
系统测试在验收阶段进行。目的是检查当被测程序安装到系统上以后,与系统的其他软、硬件能否协调工作,并完成说明书规定的任务。
确认测试与系统测试都是对整个程序的测试,二者都属于高级测试(Higher order Testing )。1、单元测试
单元测试是层次测试的第一步,也是整个测试的基础。据估计,单元测试发现的错误,约占程序总错误数的65%,接近2/3。
对于多模块程序的测试从单元开始,至少有一下好处:(1)减少测试的复杂性(2)易于确定错误的位置(不超过一个模块) (3)多个单元可以并行测试(4)缩短测试周期
2、集成测试
通过了单元测试的模块,要按照一定的策略组装为完整的程序。在组装过程中进行的测试,就称为集成测试或组装测试。
为什么所有的模块都经过了单元测试,组装中还会出现问题呢?因为:
(1)多模块程序各模块之间,可能有比较复杂的接口,稍有疏忽就易
出错。例如有的数据在穿过接口时会不慎丢失,有些全局性数据
在引用中可能出问题等。
(2)有些在单个模块可以允许的误差,组装后的积累误差可能达到不
能容忍的地步,或者模块的分功能似乎正常,组装后也可能产生
了预期的综合功能。由此可见,在软件的层析测试中,集成测试
不仅必要,而且占有重要的地位。测试的层次
集成测试的策略:
集成测试的策略,可以分自顶向下、由底向上和从两头逼近的混合方式3种。
(1)自顶向下测试
从顶层模块开始,沿被测程序的结构图逐步向下测试。按照移动路线的差异,又可区分为两种不同的实施步骤.
先广后深实施步骤。这时模块的组装顺序是:
M1-M2-M3-M4-M5-M6-M7-M8
先深后广实施步骤。这时模块的组装顺序是:
M1-M2-M5-M8-M6-M3-M4-M7
(2)由底向上测试
它与自顶向下一样,每次也只添一个新模块,但组装顺序恰好相反,采取由下向上的路线。其典型步骤可以描述为:
●从下层模块中找出一个没有下级模块的模块,由下向上的逐步添加新模
块,组成程序中的一个子系统或模块
●从另一个子系统中选出另一个无下级模块的模块,仿照前一步组成又一
个子系统;
●重复上一步,直至得出所有的子系统,把它们组装为完整的程序。这时模块的组装顺序是:
M8-M5-M6-M2M7-M4-M3-M1
(3)混合方式测试
它是上述两种测试方式的结合,其具体步骤为:
●对上层模块采取自顶向下测试
●对关键模块或子系统采取由底向上测试
3、确认测试
(1)有效性测试(黑盒测试)和配置复审
确认测试继集成测试之后进行,其目的在于确认组装完毕的程序是否满足软件需求规格说明书(SRS )的要求。典型的确认测试通常包括有效性测试(黑盒测试)和配置复审(Configuration Review )等内容。在SRS 中一般都有标题为“有效性标准”的一节,其中的内容就是确认测试的依据。配置复审则用于查明程序的文档是否配齐,以及文档内容的一致性。
(2)验收测试
如果软件是给一个客户开发的,需要进行一系列的验收测试来保证满足客户所有的需求。验收测试主要由用户而不是开发者来进行,可以进行几个星期或者几个月,因而可发现随时间的积累而产生的错误。
(3)Alpha 与Beta 测试
如果一个软件是给很多客户使用的(例如Office 软件),让每一个用户都进行正式的验收测试显然是不切实际的。这时可使用Alpha 与Beta 测试,来发现那些通常只有最终用户才能发现的错误。
Alpha 测试是在一个受控的环境下,由用户在开发者的“指导”下进行的测试,由开发者负责纪录错误和使用中出现的问题。
Beta 测试则不同与Alpha 测试,是由最终用户在自己的场所进行的,开发者通常不会在场,也不能控制应用的环境。所有Beta 测试中遇到的问题均由
用户纪录,并定期把它们报告给开发者,开发者在接收到Beta 测试的问题报告之后,对系统进行最后的修改,然后就开始准备向所有的用户发布最终的软件产品。
4、系统测试
系统测试是在更大的范围进行的测试。除被测程序外,系统还可能包括硬件和原来就有的其他软件。测试的目的是检查把确认测试合格的软件安装到系统中以后,能否与系统的其余部分协调运行,并且完成SRS 对它的要求。系统测试是验收工作的一部分,应由用户单位组织实施。软件开发单位应付为系统测试创造良好的条件,负责回答和解决测试中可能发现的一切质量问题。
二、中止测试的标准
黑盒测试和白盒测试都是选择测试,不可能彻底发现程序的所有错误。既然如此,测试该进行到什么程度才算完成任务呢?显然,测试过少,程序的遗留错误较多,将降低其可靠性;但过量的测试,也会不必要的增大软件成本。
1、规定测试策略和应达目标
白盒测试时一般可规定以完全覆盖为标准,即语句覆盖和判定覆盖必须分别达到100%。满足了这些条件可中止测试。黑盒测试时,可结合程序的实际情况选择一或数种方法来设计测试用例。当把所有的测试用例全部用完后测试便可中止。
2、规定至少要查出的错误数量
如果已经积累了较丰富的测试经验,可以把查出预定数量的错误,作为某类应用程序的测试终止标准。例如,假定被测程序时一个约有10000行的管理信息系统,根据以往的经验,这么多行的程序约有300个设计错误和200个代码和结构错误。若预定的目标是消除95%的设计错误和98%的编码与结构错误,则可以规定,查出285个设计错误和196个编码与结构错误就可以终止测试了。