第第第七七七章章章软软软件件件测测测试试试编码完成之后,就是对源程序进行测试。软件测试是一项“劳民伤财”的工作,统计表明,开发大规模的软件,有40%以上的精力是耗费在软件测试上(40-20-40规则,Myers认为软件测试占大约50%的项目时间和超过50%总成本)。为了保证软件的正确可靠、为了防患于未然,无论怎样强调软件测试的重要性,都不过分。关于软件测试,曾有种种似是而非的说法,众多的术语和测试技术,也常使我们眼花缭乱。在这里我试尝给大家勾画出一个清晰的逻辑轮廓。7.1基本概念7.1.1软件测试的目的(与地位)说测试不能不提到G.J.Myers的经典著作《软件测试技巧》,他在书中说道:“测试是为了发现错误而执行程序的过程。”E.W.Dijkstra则说:“程序测试能证明错误的存在,但不能证明错误不存在。”在这里,他们明确指出:测试的目的是发现程序中的错误,是为了证明程序有错,而不是证明程序无错。(其实你也证明不了)在软件开发过程中,分析、设计、编码等工作都是建设性的,唯独测试带有“破坏性”,因为它抱着“吹毛求疵”的目的,明确宣布要在程序中“找岔子”。他们认为这种吹毛求疵的态度是至关重要的(态度决定一切!)。如果你是为了证明程序无错而去进行测试,错误就可能在你的眼皮底下漏过,反之,只要你抱着证明程序有错的目的去测试,就会尽心尽力去找程序中的错误。根据Myers的说法,测试又是一个“(在计算机上)执行程序的过程”。分析和设计阶段都要对文档进行技术审查和管理复审,源程序完成后,也要进行代码复审(codereview)。这些审查对减少软件错误有重要作用,但都不能代替在计算机上进行的测试,R.S.Pressman认为,测试可视为分析、设计、编码3个阶段的“最终复审(ultimatereview)”,可见测试在软件质量保证中的重要地位。现在我们干脆把Myers的:“程序测试是为了发现错误而执行程序的过程。”作为软件测试的定义。另一个与测试密切相关的活动叫纠错(debugging),我们也常常说起“纠错和调试”。[纠错和调试]测试的目的是发现错误,纠错则是为了确定错误的性质,并且加以纠正。因此,软件测试其实是这样一个过程:测试——纠错——测试——纠错——…………,这种边测试边纠错的活动,常常借助于一种称为调试程序(debuggingroutine)的专用工具,所以也有人把纠错称为调试。7.1.2软件测试的方法和技术广义地说,软件测试不仅指在计算机上进行的测试(机器测试),也应包括用人工方式进行的代码复审(人工测试),下面我们列出这两类测试所采用的方法和技术。[注](1)机器测试和人工测试程序通过编译后,先要经代码复审,然后再进行机器测试。机器测试是用设定的测试数据(testdata)执行被测程序的过程,故又称为动态测试(dynamictesting)。代码复审采用人工的方式进行,目的在于检查程序的静态结构,找出编译不能发现的错误。经验表明,组织良好的代码复审,可以发现程序中30%到70%的编码和逻辑错误,从而加快动态测试的进程,提高整个测试的效率。根据Myers的研究:人工测试和机器测试是互补的。而且,机器测试只能发现错误的症状,人工测试一旦发现了错误,也就同时确定了错误的位置与性质。人工测试并不是可有可无的,或是为了节约计算机机时而采取的权宜之计,它是机器测试的准备,也是测试中不可缺少的环节。(2)白盒测试和黑盒测试动态测试是一个包括:①设计“测试用例”→②执行被测程序→③分析测试结果并发现错误的过程。[测试用例]以发现程序的错误为目的,而精心设计的一组测试输入数据,以及用这组数据执行被测程序时所期望的输出结果。测试用例={输入数据+期望结果}【注】其中{}表示重复在这一过程中,毫无疑问①设计“测试用例”是最关键!这是因为只有合理设计的“测试用例”,才可能最大限度地发现程序中的错误,从而有效地完成测试任务。我们按照在设计“测试用例”时,是否涉及程序的内部结构,把动态测试分为:“白盒测试”和“黑盒测试”。(3)穷举测试和选择测试能不能通过动态测试,发现程序中的所有错误呢?人们自然地想到:应该让被测程序在一切可能的输入情况下执行一遍,这就是所谓的“穷举测试”。那么穷举测试可能吗?请看:[试对一个“C++编译器”进行黑盒穷举测试]一方面要编写出所有能够想象出来的合法的C++程序让它编译,另一方面又要编写出一切不合法的C++程序,看它能否指出程序的错误。显而易见,合法与不合法的C++程序的数量都是无穷的,因此,用黑盒测试方法进行穷举测试是不可能的。[试对下图所示的程序进行白盒穷举测试][注]51+52+53+……+520≈1014=106亿=102万亿=100万亿这意味着若能每秒完成一次测试,也得用漫长的320万年才能完成这次测试任务。由此可见,穷举测试是不实现的,这就是我们所说的测试不能保证程序无错的原因。在实际测试中,我们只能选择一些有代表性的、典型的测试用例,对程序进行有限的测试,通常称这种测试为选择测试。7.1.3软件测试的步骤按照软件工程的观点。软件测试依次由以下四个层次的测试组成:(1)单元测试:在编码阶段完成;以模块为单位,包括代码复审、动态测试;确定测试用例时,可综合运用白盒和黑盒两类测试技术;(2)综合测试:以软件的设计信息为依据,采纳一定的“测试策略”进行测试;主要用黑盒测试技术确定测试用例;(3)确认测试:以软件的需求信息为依据,采纳一定的“测试策略”进行测试;主要用黑盒测试技术确定测试用例;(4)系统测试:指整个计算机系统(包括软件与硬件)的测试,可与系统的安装和验收结合进行。[注]1)各级测试均须事先制订测试计划,事后写出测试报告;2)测试应由独立的测试小组进行,并挑选有经验的优秀程序员来担任;3)图:软件测试的步骤。7.2代码复审代码复审在程序通过编译之后,动态测试开始之前进行。决不能以为程序通过编译就问题不大,其实编译只能发现极小部分错误,特别对大型软件更是如此。7.2.1代码会审代码会审以小组会的方式进行,会审小组一般由3到4人组成,包括组长一人、程序作者一人。会前要先把源程序清单分发给与会者,还应把复审的要点编成“错误检验表”,供与会者参考。***程序错误检验表数据引用错误例如使用未赋过值或未初始化的变量数据说明错误例如变量类型与初始化的值不符,变量未说明数据计算错误例如混合类型运算,用零作除数数据比较错误例如在不同类型的变量间作比较控制流程错误例如多做或少做了循环,子程序等最后未终止接口错误例如实参和形参类型、顺序或数量不符输入输出错误例如忘记打开或关闭文件,I/O出错处理不对……………………开会时,程序作者逐句朗读和讲解程序,其它人则集中精力,捕捉程序在结构、功能、与编码风格等方面的可能错误。要注意的是:大家都要把精力集中于发现错误,而把改正错误的工作放到会后去做。如果错误较多,或有的错误要作重大改正,应在改正后重新安排代码会审。7.2.2走查走查与代码会审相似,所不同的是:走查要求与会者扮演“计算机”的角色,用人工的方式来运行被审程序。因此,在会前至少要指定一人提出“测试用例”,开会时把这些测试数据“输入”被测程序,并在纸上跟踪监视程序的执行情况,让人代替机器沿着程序的逻辑“走”一遍,并从中“查”出错误,这就是“走查”这一名称的由来。走查实质上是以走查为方式,随着走查的进程不断向程序作者提出有关询问,并从中发现程序的错误。7.2.3办公桌检查办公桌检查可以看成是由一个人参加的代码会审,其内容可以是按照“错误检查表”来检查被审的程序,也可以仿照“走查”对程序进行运行。只适合规模较小的软件。7.3测试用例的设计测试用例是以发现程序的错误为目的,而精心设计的一组测试输入数据,以及用这组数据执行被测程序时所期望的输出结果。测试用例={输入数据+期望结果}其中{}表示重复。这个式子表明,每一个完整的测试用例不仅含有被测程序的输入数据,而且还包括用这组数据执行被测程序后期望的输出结果,如果实测的结果与期望的结果不相符,就表明程序可能存在错误。下图列出了常用的测试用例设计方法。7.3.1黑盒测试方法前面已经提到,黑盒测试方法仅以程序的外部功能为依据来设计测试用例,一方面要检查程序能否完成一切应做的事情,另一方面要检查程序能否拒绝一切不应该做的事情。(1)等价分类法这种方法就是把被测程序的输入域(就是整个键盘)进行分类——划分为若干个“等价类”。[注]1)集合X上的等价关系R所构成的等价类形成一个集合X的划分,此划分叫做X关于R的商集,记作X/R。X/R={[a]R|a∈X}2)集合X上的等价关系与集合X的划分是一一对应的。从逻辑上来说,就是按测试结果“等价”把被测程序的输入域划分为若干个等价类,每一个等价类都选择一例“测试用例”,它代表了一类与它等价的其它测试。这样,测试人员就有可能使用少量“有代表性”的测试用例,对程序进行“有限的测试”。我们再次强调:黑盒测试方法一方面要检查程序能否完成一切应做的事情,另一方面要检查程序能否拒绝一切不应该做的事情。与“应做的事情”相对应的是“有效等价类”,而与“不应该做的事情”相对应的称之为“无效等价类”。设计等价类的测试用例分为两步:①划分等价类并给出定义;②选择测试用例,其原则是:有效等价类的测试用例尽量公用;无效等价类必须每类一例。[例1]某城市的电话号码由3部分组成,这3部分的名称和内容分别是:地区码:空白或3位数字;前缀:非“0”或“1”开头的3位数字;后缀:4位数字。假定被测程序能接受一切符合上述规定的电话号码,拒绝所有不符合上述规定的电话号码,请用等价分类法来设计它的测试用例。(2)边界值分析法(边值法)在等价分类法中,代表一个等价类的测试用例可以在这个等价类的允许值范围内任意选择。但如果把测试用例选在等价类的边值上,往往会有更好的效果,这就是边界值分析法的主要思想。[例]税款计算程序。(“收入”≤3500作为判定条件,可用800、3600两个测试数据分别代表免税和征税两个等价类,但……)大多数情况下,边界值及其邻近的数据都属于敏感区,容易暴露程序的错误。边界值分析法也适用于检查程序的输出值边界。[例]当月实发工资计算程序(如果该工资计算程序规定:当职工的扣款金额超过当月应发工资的一半时,其超出部分应留待下月扣除,如果按边界值分析法选择此时的测试用例,就应有意识地让扣款达到或超过应发工资额的半数,分别观察被测程序计算当月实发工资有何变化)(3)错误猜测法(猜错法)所谓猜错,就是猜测被测程序中哪些地方容易出错,并据此设计测试用例。这种方法更多地依赖于测试人员的直觉与经验,所以仅是一种辅助手段,用来补充一些测试用例。注:等价分类法的一个缺陷是未对输入条件的组合进行分析。(4)因果图法因果图法是借助图形来设计测试用例的一种方法。所谓的因果图是一种简化了的逻辑图,能直观地表明输入条件(因)和输出结果(果)之间的相互关系。该方法适用于被测程序具有多种输入条件,程序的输出又依赖于输入条件的各种组合的情况,往往要借助专用软件来设计测试用例。7.3.2白盒测试方法白盒测试方法是以程序的内部逻辑结构为依据来设计测试用例。我们知道穷举测试是不现实的,合理的白盒测试,就是要选取足够的测试用例,以实现对源程序比较充分的覆盖,以便尽可能多地发现程序中的错误。(1)逻辑覆盖法逻辑覆盖其实是对程序(逻辑)覆盖(程度)要求的一组总称,按照对程序逻辑覆盖程度的由低到高顺序,它们是:1)语句覆盖:要求被测程序的每条语句至少执行一次;2)判定覆盖:不仅要求被测程序的每条语句至少执行一次,而且要求每一分支至少执行一次;3)条件覆盖:不仅要求被测程序的每条语句至少执行一次,而且要求判定中的每个条件均按“真”、“假”两种结果至少执行一次;4)条件组合覆盖:不仅要求被测程序的每条语句至少执行一次,而且要求判定中的每个条件的所有可能组合都至少执行一次。[例2]如图显示了某程序的逻辑结构,试为它设计足够的测试用例,分别实现对程序的①判定覆盖;②条件覆盖;和③条件组合覆盖。S1S2[例3]设计下列伪代码