测试免费装扮空间

免费装扮空间  时间:2021-02-23  阅读:()

TheArtofSoftwareTestingSecondEditionGlenfordJ.
MyersRevisedandUpdatedbyTomBadgettandToddM.
ThomaswithCoreySandlerCopyright2004byWordAssociation,Inc.
Allrightsreserved.
PublishedbyJohnWiley&Sons,Inc.
,Hoboken,NewJersey.
PublishedsimultaneouslyinCanada.
整理:丁兆杰,黄墀晖,黄米青,林嘉,马晓靖,王博,吴航,余华兵,俞培源v.
1.
27/1/2008未经同意,严禁以任何形式拷贝ii前言1979年GlenfordJ.
Myers出版了一本现在仍被证明为经典的著作,这就是本书的第1版.
本书经受住了时间的考验,25年来一直被列在出版商可供书目的清单中.
这个事实本身就是对本书稳定、基础和珍贵品质的佐证.
在同一时期,本书第2版的几位合著者共出版了120余本著作,大多数都是关于计算机软件的.
其中有一些很畅销,再版了多次(例如CoreySandler的《FixYourOwnPC》自付印以来已出版到第7版,TomBadgett关于微软PowerPoint及其他Office组件的著作已经出版到第4版以上).
然而,这些作者的著作中没有哪一本书能够像本书一样持续数年之后仍畅销不衰.
区别究竟在哪里呢这些新书只涵盖了短期性的主题:操作系统、应用软件、安全性、通信技术及硬件配置.
20世纪80年代和90年代以来的计算机硬件与软件技术的飞速发展,必然使得这些主题频繁地变功和更新.
在此期间出版的有关软件测试的书籍已数以十计、甚至数以百计.
这些书也对软件测试的主题进行了简要的探讨.
然而,本书为计算机界一个最为重要的主题提供了长期、基本的指南:如何确保所开发的所有软件做了其应该做的,并且同样重要的是,未做其不应该做的本书第2版中保留了同样的基本思想.
我们更新了其中的例子以包含更为现代的编程语言.
我们还研究了在Myers编著本书第1版时尚无人了解的主题:Web编程、电子商务及极限编程与测试.
但是,我们不会忘记,新的版本必须遵从其原著.
因此,新版本依然向读者展示GlenfordMyers全部的软件测试思想:这个思想体系以及过程将适用于当今乃至未来的软件和硬件平台.
我们也希望本书能够顺应时代,适用于当今的软件设计人员和开发人员掌握最新的软件测试思想及技术.
iii引言在本书1979年第1版出版的时侯,有一条著名的经验,即在一个典型的编程项目中,软件测试或系统测试大约占用50%的项目时间和超过50%的总成本.
25年后的今天,同样的经验仍然成立.
现在出现了新的开发系统、具有内置工具的语言以及习惯于快速开发大量软件的程序员.
但是,在任何软件开发项目中,测试依然扮演着重要角色.
在这些事实面前,读者可能会以为软件测试发展到现在不断完善,已经成为一门精确的学科.
然而实际情况并非如此.
事实上,与软件开发的任何其他方面相比,人们对软件测试仍然知之甚少.
而且,软件测试并非热门课题,本书首次出版时是这样.
遗憾的是,今天仍然如此.
现在有很多关于软件测试的书籍和论文,这意味着,至少与本书首次出版时相比,人们对软件测试这个主题有了更多的了解.
但是,测试依然是软件开发中的"黑色艺术".
这就有了更充足的理由来修订这本关于软件测试艺术的书,同时我们还有其他一些动机.
在不同的时期,我们都听到一些教授和助教说;"我们的学生毕业后进入了计算机界,却丝毫不了解软件测试的基本知识,而且在课堂上向学生介绍如何测试或调试其程序时,我们也很少有建议可提供.
"因此,本书再版的目的与1979年时一样:填充专业程序员和计算机科学学生的知识空缺.
正如书名所蕴涵的,本书是对测试主题的实践探讨,而不是理论研究,连同了对新的语言和过程的探讨.
尽管可以根据理论的脉络来讨论软件测试,但本书旨在成为实用且"脚踏实地"的手册.
因此很多与软件测试有关的主题,如程序正确性的数学证明都被有意地排除在外了.
本书第l章介绍了一个供自我评价的测试,每位读者在继续阅读之前都须进行测试.
它揭示出我们必须了解的有关软件测试的最为重要的实用信息,即一系列心理和经济学问题,这些问题在第2章中进行了详细讨论.
第3章探讨的是不依赖计算机的代码走查或代码检查的重要概念.
不同于大多数研究都将注意力集中在概念的过程和管理方面,第3章则是从技术上"如何发现错误"的集度来进行探讨.
聪明的读者都会意识到,在软件测试人员的技巧中最为重要的部分是掌握如何编写有未经同意,严禁以任何形式拷贝iv效测试用例的知识.
这正是第4章的主题.
本书第5章和第6章分别探讨了如何测试单个模块或子程序及测试更夫的对象,而第7章则介绍了一些程序调试的实用建议,第8章讨论了极限编程和极限测试的概念,第9章介绍了如何将本书其他章节中详细讨论的软件测试的知识运用到web编程,包括电子商务系统中去.
本书面向三类主要的读者.
尽管我们希望本书中的内容对于专业程序员而言不完全是新的知识,但它应增强专业人员对测试技术的了解.
如果这些材料能使软件人员在某个程序中多发现一个错误,那么本书创造的价值将远远超过书价本身.
第二类读者是项目经理,因为本书中包含了测试过程管理的最新的、实用的知识.
第三类读者是计算机科学的学生,我们的目的在于向学生们展示程序测试的问题,并提供一系列有效的技术.
我们建议将本书作为程序设计课程的补充教材,让学生在学习阶段的早期就接触到软件测试的内容.
GlenfordJ.
MyersTomBadgettToddM.
ThomasCoreySandler目录第1章一个自我评价测试.
1第2章软件测试的心理学和经济学42.
1软件测试的心理学42.
2软件测试的经济学72.
3软件测试的原则112.
4小结15第3章代码检查、走查与评审163.
1检查与走查(InspectionsAndWalkthroughs)173.
2代码检查(CodeInspections)183.
3用于代码检查的错误列表203.
4代码走查(Walkthroughs)293.
5桌面检查(DeskChecking)303.
6同行评分(PeerRatings)313.
7小结32第4章测试用例的设计334.
1白盒测试(White-BoxTesting)344.
2错误猜测(ErrorGuessing)684.
3测试策略70第5章模块(单元)测试715.
1测试用例设计715.
2增量测试805.
3自顶向下测试与自底向上测试845.
4执行测试91第6章更高级别的测试936.
1功能测试(FunctionTesting)986.
2系统测试(SystemTesting)996.
3验收测试(AcceptanceTesting)1096.
4安装测试(InstallationTesting)109未经同意,严禁以任何形式拷贝66.
5测试的计划与控制1106.
6测试结束准则1126.
7独立的测试机构117第7章调试(DEBUGGING)1187.
1暴力法调试(DebuggingbyBruteForce)1197.
2归纳法调试(DebuggingbyInduction)1207.
3演绎法调试(DebuggingbyDeduction)1237.
4回溯法调试(DebuggingbyBacktracking)1267.
5测试法调试(DebuggingbyTesting)1267.
6调试的原则127第8章极限测试1318.
1极限编程基础1318.
2极限测试:概念1358.
3极限测试的应用1378.
4小结141词汇表142软件测试的艺术未经同意,严禁以任何形式拷贝1第1章一个自我评价测试自本书25年前首次出版以来,软件测试变得比以前容易得多,也困难得多.
软件测试何以变得更困难原因在于大量编程语言、操作系统以及硬件平台的出现.
在20世纪70年代只有相当少的人使用计算机,而今天在商业界和教育界,如果不使用计算机,几乎没有人能完成日常工作.
况且,计算机本身的功能也比以前增强了数百倍.
因此,我们现在编写的软件会潜在地影响到数以百万计的人,使他们更高效地完成工作,反之也会给他们带来数不清的麻烦,导致工作或事业的损失.
这并不是说今天的软件比本书第一版发行时更重要,但可以肯定地说,今天的计算机——以及驱动它的软件——无疑已影响到了更多的人、更多的行业.
就某些方面而言,软件测试变得更容易了,因为大量的软件和操作系统比以往更加复杂,内部提供了很多已充分测试过的例程供应用程序集成,无须程序员从头进行设计.
例如,图形用户界面(GUI)可以从开发语言的类库中建立起来,同时,由于它们是经过充分调试和测试的可编程对象,将其作为用户应用程序的组成部分进行测试的要求就减少了许多.
所谓软件测试,就是一个过程或一系列过程.
用来确认计算机代码完成了其应该完成的功能不执行其不该有的操作.
软件应当是可预测且稳定的,不会给用户带来意外惊奇.
在本书中,我们将讨论多种方法来达到这个目标.
好了,在开始阅读本书之前,我们想让读者做一个小测验.
我们要求设计一组测试用例(特定的数据集合),适当地测试一个相当简单的程序.
为此要为该程序建立一组测试数据,程序须对数据进行正确处理以证明自身的成功.
下面是对程序的描述:这个程序从一个输入对话框中读取三个整数值.
这三个整数值代表了三角形三边的长度.
程序显示提示信息,指出该三角形究竟是不规则三角第1章一个自我评价测试未经同意,严禁以任何形式拷贝2形、等腰三角形还是等边三角形.
注意,所谓不规则三角形是指三角形中任意两条边不相等,等腰三角形是指有两条边相等,而等边三角形则是指三条边相等.
另外,等腰三角形等边的对角也相等(即任意三角形等边的对角也相等),等边三角形的所有内角都相等.
用你的测试用例集回答下列问题,借以对其进行评价.
对每个回答"是"的答案,可以得l分:1.
是否有这样的测试用例,代表了二个有效的不规则三角形(注意,如1,2,3和2,5,10这样的测试用例并不能确保"是"的答案,因为具备这样边长的三角形不存在.
)2.
是否有这样的测试用例,代表一个有效的等边三角形3.
是否有这样的测试用例,代表一个有效的等腰三角形(注意如2,2,4的测试用例无效,因为这不是一个有效的三角形.
)4.
是否能少有三个这样的测试用例,代表有效的等腰三角形,从而可以测试到两等边的所有三种可能情况(如3,3,4;3,4,3;4,3,3)5.
是否有这样的测试用例,某边的长度等于06.
是否有这样的测试用例,某边的长度为负数7.
是否有这样的测试用例,三个整数皆大于0,其中两个整数之和等于第三个(也就是说,如果程序判断l,2,3表示一个不规则二角形,它可能就包含一个缺陷.
)8.
是否至少有三个第7类的测试用例,列举了一边等于另外两边之和的全部可能情况(如1,2,3;1,3,2;3,1,2)9.
是否有这样的测试用例,三个整数皆大于0,其中两个整数之和小于第三个整数(如1,2,4;12,15,30)10.
是否至少有三个第9类的测试用例,列举了一边大于另外两边之和的全部可能情况(如1,2,4;1,4,2;4,1,2)11.
是否有这样的测试用例,三边长度皆为0(0,0,0)12.
是否至少有一个这样的测试用例,输入的边长为非整数值(如2.
5,3.
5,5.
5)13.
是否至少有一个这样的测试用例,输入的边长个数不对(如仅输入了两软件测试的艺术未经同意,严禁以任何形式拷贝3个而不是三个整数)14.
对于每一个测试用例,除了定义输入值之外,是否定义了程序针对该输入值的预期输出值当然,测试用例集即使满足了上述条件,也不能确保能查找出所有可能的错误.
但是,由于问题1至问题13代表了该程序不同版本中已经实际出现的错误,对该程序进行的充分测试至少应该能够暴露这些错误.
开始关注自己的得分之前,请考虑以下情况:以我们的经验来看,高水平的专业程序员平均得分仅7.
8(满分14).
如果读者的得分更高,那么祝贺你.
如果没有那么高,我们将尽力帮助你.
这个测验说明,即使测试这样一个小的程序,也不是件容易的事.
如果确实是这样,那么想象一下测试一个十万行代码的空中交通管制系统、一个编译器,甚至一个普通的工资管理程序的难度.
随着面向对象编程语言(如Java、C++)的出现,测试也变得更加困难.
举例来说,为测试这些语言开发出来的应用程序,测试用例必须要找出与对象实例或内存管理有关的错误.
从上面这个例子来看,完全地测试一个复杂的、实际运行的程序似乎是不太可能的.
情况并非如此!
尽管充分测试的难度令人望而生畏,但这是软件开发中一项非常必需的任务,也是可以实现的一部分工作,通过本书我们可以认识到这一点.

第2章软件测试的心理学和经济学未经同意,严禁以任何形式拷贝4第2章软件测试的心理学和经济学软件测试是一项技术性工作,但同时也涉及经济学和人类心理学的一些重要因素.
在理想情况下,我们会测试程序的所有可能执行情况.
然而,在大多数情况下,这几乎是不可能的,即使一个看起来非常简单的程序,其可能的输入与输出组合可达到数百种甚至数千种,对所有的可能情况都设计测试用例是不切合实际的.
对一个复杂的应用程序进行完全的测试,将耗费大最的时间和人力资源,以至于在经济上是不可行的.
另外,要成功地测试一个软件应用程序,测试人员也需要有正确的态度(也许用"愿景(vision)"这个词会更好一些).
在某些情况下,测试人员的态度可能比实际的测试过程本身还要重要.
因此,在深入探讨软件测试更加技术化的本质之前,我们先探讨一下软件测试的心理学和经济学问题.
2.
1软件测试的心理学测试执行得差,其中一个主要原因在于大多数的程序员一开始就把"测试"这个术语的定义搞错了,他们可能会认为:"软件测试就是证明软件不存在错误的过程.
""软件测试的目的在于证明软件能够正确完成其预定的功能.
""软件测试就是建立一个'软件做了其应该做的'信心的过程.
"这些定义都是本末倒置的.
每当测试一个程序时,总是想为程序增加一些价值.
通过测试来增加程序的价值,是指测试提高了程序的可靠性或质量.
提高了程序的可靠性,是指找出并最终修改了程序的错误.
因此不要只是为了证明程序能够正确运行而去测试程序;相反,应该一开始就假设程序中隐藏着错误(这种假设对于几乎所有的程序都成立),然后测试程序,发现尽可能多的错误.
那么,对于测试,更为合适的定义应该是:软件测试的艺术未经同意,严禁以任何形式拷贝5"测试是为发现错误而执行程序的过程".
虽然这看起来像是个微妙的文字游戏,但确实有重要的区别.
理解软件测试的真正定义,会对成功地进行软件测试有很大的影响.
人类行为总是倾向于具有高度目标性,确立一个正确的目标有着重要的心理学影响.
如果我们的目的是证明程序中不存在错误,那就会在潜意识中倾向于实现这个目标,也就是说,我们会倾向于选择可能较少导致程序失效的测试数据.
另一方面,如果我们的目标在于证明程序中存在错误,我们设计的测试数据就有可能更多地发现间题.
与前一种方法相比,后一种方法会更多地增加程序的价值.
这种对软件测试的定义,包含着无穷的内蕴,其中的很多都蕴涵在本书各处.

举例来说,它暗示了软件测试是一个破坏性的过程,甚至是一个"施虐"的过程,达就说明为什么人多数人都觉得它困难.
这种定义可能是违反我们愿望的,所幸的是,我们大多数人总是对生活充满建设性而不是破坏性的愿景.
大多数人都本能地倾向于创造事物,而不是将事物破坏.
这个定义还暗示了对于一个特定的程序:应该如何设计测试用例(测试数据)、哪些人应该而哪些人又不应该执行测试.
为增进对软件测试正确定义的理解,另一条途径是分析一下对"成功的"和"不成功的"这两个词的使用,当项目经理在归纳测试用例的结果时,尤其会用到这两个词.
大多数的项日经理将没发现错误的测试用例称为一次"成功的测试",而将发现了某个新错误的测试称为"不成功的测试".
这又是一次本末倒置.
"不成功的"表示事情不遂人意或令人失望.
我们认为,如果在测试某段程序时发现了错误,而且这些错误是可以修复的,就将这次合理设计并得到有效执行的测试称作是"成功的".
如果本次测试可以最终确定再无其他可查出的错误,同样也被称作是"成功的".
所谓"不成功的"测试,仅指未能适当地对程序进行检查,在大多数情况下,未能找出错误的测试被认为是"不成功的",这是因为认为软件中不包含错误的观点基本上是不切实际的.
能发现新错误的测试用例不太可能被认为是"不成功的";相反,能发现错误就证明它是值得设计的.
一个"不成功的"测试用例.
会使程序输出正确的结果,但不能发现任何错误.
第2章软件测试的心理学和经济学未经同意,严禁以任何形式拷贝6我们可以类比一下病人看医生的情况,病人因为身体不舒服而去看医生.
如果医生对病人进行了某些实验检测,却没有诊断出任何病因,我们就不会认为这此实验检测是"成功的".
之所以是"不成功的"检侧,是因为病人支付了昂贵的实验检测费用,而病状却依然如故.
病人会因此而质疑医生的诊断能力.
但是,如果实验检测诊断出病人是胃溃疡,那么这次检测就是"成功的",医生可以开始进行适当的治疗.
因此.
医疗行业会使用"成功的"或"不成功的"来表达适当的意思.

我们当然可以类推到软件测试中来,当我们开始测试某个程序时,它就好似我们的病人.
"软件测试就是证明软件不存在错误的过程",这个定义会带来第二个问题.
对于几乎所有的程序而言,甚至是非常小的程序,这个目标实际上也是无法达到的.
另外,心理学研究表明,当人们开始一项工作时,如果已经知道它是不可行的或无法实现时,人的表现就会相当糟糕.
举例来说,如果要求人们在15分钟之内完成星期日《纽约时报》里的纵横填字游戏,那么我们会观察到l0分钟之后的进展非常小,因为大多数人都会却步于这个现实,即这个任务似乎是不可能完成的.
但是如果要求在四个小时之内完成填字游戏,我们很可能有理由期望在最初10分钟之内的进展会比前一种情况下的大.
将软件测试定义为发现程序错误的过程,使得测试是个可以完成的任务,从而克服了这个心理障碍.
诸如"软件测试就是证明'软件做了其应该做的'的过程"此类的定义所带来的第三个问题是,程序即使能够完成预定的功能,也仍然可能隐藏错误.
也就是说,当程序没有实现预期功能时,错误是清晰地显现出来的;如果程序做了其不应该做的,这同样是一个错误;考虑一下第l章中的三角形测试程序.
即使我们证明了程序能够正确识别出不规则三角形、等腰三角形和等边三角形,但是在完成了不应执行的任务后(例如将1,2,3说成是一个不规则三角形或将0,0,0说成是一个等边三角形),程序仍然是错的.
如果我们将软件测试视作发现错误的过程,而不是将其视为证明"软件做了其应该做的"的过程,我们发现后一类错误的可能性会大很多.
总结一下,软件测试更适合被视为试图发现程序中错误(假设其存在)的破坏性的过程.
一个成功的测试用例,通过诱发程序发生错误,可以在这个方向上促进软件测试的艺术未经同意,严禁以任何形式拷贝7软件质量的改进.
当然,最终我们还是要通过软件测试来建立某种程度的信心:软件做了其应该做的,未做其不应该做的.
但是通过对错误的不断研究是实现这个目的的最佳途径.
有人可能会声称"本人的程序完美无缺"(不存在错误),针对这种情况建立起信心的最好办法就是尽量反驳他,即努力发现不完美之处,而不只是确认程序在某些输入情况下能够正确地工作.
2.
2软件测试的经济学给出了软件测试的适当定义之后,下一步就是确定软件测试是否能够发现"所有"的错误.
我们将证明答案是否定的,即使是规模很小的程序.
一般说来,要发现程序中的所有错误也是不切实际的,常常也是不可能的.
这个基本的问题反过来暗示出软件测试的经济学问题、测试人员对被测软件的期望,以及测试用例的设计方式.
为了应对测试经济学的挑战,应该在开始测试之前建立某些策略.
黑盒测试和白盒测试是两种最普遍的策略,我们将在下面两节中讨论.
2.
2.
1黑盒测试黑盒测试是一种重要的测试策略,又称为数据驱动的测试或输入/输出驱动的测试.
使用这种测试方法时,将程序视为一个黑盒子.
测试目标与程序的内部机制和结构完全无关,而是将重点集中放在发现程序不按其规范正确运行的环境条件.

在这种方法中,测试数据完全来源于软件规范(换句话说,不需要去了解程序的内部结构).
如果想用这种方法来发现程序的所有错误,判定的标准就是"穷举输入测试",将所有可能的输入条件都作为测试用例.
为什么这样做比如说在三角形测试的程序中,试过了三个等边三角形的测试用例.
这不能确保正确地判断出所有的等边三角形.
程序中可能包含对边长为3842,3842,3842的特殊检查,并指出此三角形为不规则三角形.
由于程序是个黑盒子,因此能够确定此条语句存在的惟一方法,就是试验所有的输入情况.
要穷举测试这个三角形程序,可能需要为所有有效的三角形创建测试用例,只第2章软件测试的心理学和经济学未经同意,严禁以任何形式拷贝8要三角形边长在开发语言允许的最大整数值范围内.
这些测试用例本身就是天文数字,但这还决不是所谓穷尽的,当程序指出-3,4,5是一个不规则三角形或2,A,2是一个等腰三角形时,问题就暴露出来了,为了确保能够发现所有这样的错误,不仅得用所有有效的输入,而且还得用所有可能的输入进行测试.
因此,为了穷举测试三角形程序,实际上需要创建无限的测试用例:这当然是不可能的.
如果测试这个三角形程序都这么难的话,那么要穷举测试一个稍大些的程序的难度就更大了,设想一下,如果要对一个C++编译器进行黑盒穷举测试,不仅要创建代表所有有效C++程序的测试用例(实际上,这又是个无穷数),还需要创建代表所有无效C++程序的测试用例(无穷数),以确保编译器能够检测出它们是无效的.
也就是说,编译器必须进行测试,确保其不会执行不应执行的操作——如顺利地编译成功一个语法上不正确的程序.
如果程序使用到数据存储,如操作系统或数据库应用程序,这个问题会变得尤为严重.
举例来说,在航班预定系统这样的数据库应用程序中,诸如数据库查询、航班预约这样的事务处理需要随上次事务的执行情况而定,因此,不仅要测试所有有效的和无效的事务处理,还要测试所有可能的事务处理顺序.
上述讨论说明,穷举输入测试是无法实现的,这有两方面的含义,一是我们无法测试一个程序以确保它是无错的,二是软件测试中需要考虑的一个基本问题是软件测试的经济学.
也就是说,由于穷举测试是不可能的,测试投人的目标在于通过有限的测试用例,最大限度地提高发现的问题的数量,以取得最好的测试效果.
除了其他因素之外,要实现这个目标,还需要能够窥见软件的内部,对程序作些合理但非无懈可击的假设(例如,如果三角形程序将2,2,2视为等边三角形,那就有理由认为程序对3,3,3也作同样判断).
这种思路将形成本书第4章中测试用例设计策略的部分方法.
2.
2.
2白盒测试另一种测试策略称为白盒测试或称逻辑驱动的测试,允许我们检查程序的内部结构.
这种测试策略对程序的逻辑结构迸行检查,从中获取测试数据(遗憾的是,常常忽略了程序的规范).
软件测试的艺术未经同意,严禁以任何形式拷贝9在这里我们的目标是针对达种测试策略,建立起与黑盒测试中穷举输入测试相似的测试方法.
也许有一个解决的办法,即将程序中的每条语句至少执行一次.
但是我们不难证明,这还是远远不够的.
这种方法通常称为穷举路径测试,在本书第4章中将进一步进行深入探讨,在这里就不多加叙述.
所谓穷举路径测试,即如果使用测试用例执行了程序中所有可能的控制流路径,那么程序有可能得到了完全测试.
然而,这个论断存在两个问题.
首先,程序中不同逻辑路径的数最可能达到天文数字.
图2-1所示的小程序显示了这一点.
该图是一个控制流图,每一个结点或圆圈都代表一个按顺序执行的语句段,通常以一个分支语句结束.
每一条边或弧线表示语句段之间的控制(分支)的转换.
图2-1描述的是一个有着10~20行语句的程序,包含一个迭代20次的DO循环.
在DO循环体中,包含一系列嵌套的IF语句.
要确定不同逻辑路径的数量,也相当于要确定从点a~点b之间所有不同路径的数量(假定程序中所有的判断语句都是相互独立的).
这个数量大约是1014,即100万亿,是从520+519…十51计算而来,5是循环体内的路径数量.
由于大多数的人难以对这个数字有一个直观的概念,不妨设想一下:如果在每五分钟内可以编写、执行和确认一个测试用例,那么需要大约10亿年才能测试完所有的路径.
假如可以快上300倍,每秒就完成一次测试,也得用漫长的320万年才能完成这项工作.
第2章软件测试的心理学和经济学未经同意,严禁以任何形式拷贝10图2-1一个小型程序的控制流图当然,在实际程序中,判断并非都是彼此独立的,这意味着可能实际执行的路径数量要稍微少一些.
但是,从另一方面来讲,实际应用的程序要比图2-1所描述的简单程序复杂得多.
因此,穷举路径测试就如同穷举输入测试,非但不可能,也是不切实际的.
"穷举路径测试即完全的测试"论断存在的第二个问题是,虽然我们可以测试到程序中的所有路径,但是程序可能仍然存在着错误.
这有三个原因.
第一,即使是穷举路径测试也决不能保证程序符合其设计规范.
举例来说,如果要编写一个升序排序程序,但却错误地编成了一个降序排序程序,那么穷举路径测试就没多大价值了;程序仍然存在着一个缺陷:它是个错误的程序因为不符合设计的规范.
第二,程序可能会因为缺少某些路径而存在问题.
穷举路径测试当然不能发现缺少了哪些必需路径.
软件测试的艺术未经同意,严禁以任何形式拷贝11第三,穷举路径测试可能不会暴露数据敏感错误.
这样的例子有很多,举一个简单的例子就能说明问题.
假设在某个程序中要比较两个数值是否收敛,也就是检查两个数值之间的差异是否小于某个既定的值.
比如,我们可能会这样编一条Java语言的IF语句:if(a–bx||y也是不正确的,正确的应该是(i>x)||(i>y).
如果要比较三个数字是否相等,表达式if(a==b==c)的实际意思却大相径庭.
如果需要验证数学关系x>y>z,正确的表达式应该是(x>y)&&(y>z).
6.
在二进制的计算机上,是否有用二进制表示的小数或浮点数的比较运算由于四舍五入,以及用二进制表示十进制数的近似度,这往往是错误的根源.

7.
对于那些包含一个以上布尔运算符的表达式,赋值顺序以及运算符的优先顺序是否正确也就是说,如果碰到如同(if((a==2)&&(b==2)||(c==3))的表达式,程序能否正确理解是"与"运算在先还是"或"运算在先8.
编译器计算布尔表达式的方式是否会对程序产生影响例如,语句if((x==0&&(x/y)>z)对于有的编译器来说是可接受的,因为其认为一旦"与"运算符的一侧为FALSE时,另一侧就不用计算;但是对于其他编译器来说,却可能引起一个被0除的错误.
3.
3.
5控制流程错误1.
如果程序包含多条分支路径,比如有计算GOTO语句,索引变量的值是否会大于可能的分支数量例如,在语句GOTO(200,300,400),i中,i的取值是否总是1、2或32.
是否所有的循环最终都终止了应设计一个非正式的证据或论据来证明每一个循环都会终止.
3.
程序、模块或子程序是否最终都终止了4.
由于实际情况没有满足循环的入口条件,循环体是否有可能从未执行过如软件测试的艺术未经同意,严禁以任何形式拷贝25果确实发生这种情况,这里是否是一处疏漏例如,如果循环以下面的语句作为开头.
for{i==x;i1&&b==0){x=x/a;}if(a==2||x>1){x=x+1;}}通过编写单个的测试用例遍历程序路径ace,可以执行到每一条语句.
也就是说,通过在点a处设置A=2,B=0,X=3,每条语句将被执行一次(实际上,X可被赋任何值).
遗憾的是,这个准则相当不足.
举例来说,也许第一个判断应是"或",而不是"与".
如果这样,这个错误就会发现不到.
另外,可能第二个判断应该写成"X>0",这个错误也不会被发现.
还有,程序中存在一条X未发生改变的路径(路径abd),如果这是个错误,它也不会被发现.
换句话说,语句覆盖这条准则有很大的不足,以至于它通常没有什么用处.
判定覆盖或分支覆盖是较强一些的逻辑覆盖准则.
该准则要求必须编写足够的测试用例,使得每一个判断都至少有一个为"真"和为"假"的输出结果.
换句话说,也就是每条分支路径都必须至少遍历一次.
分支或判定语句的例子包括switch,do-while和if-else语句.
在一些程序语言如FORTRAN中,多重选择GOTO语句也是合法的.
判定覆盖通常可以满足语句覆盖.
由于每条语句都是在要么从分支语句开始,要么从程序入口点开始的某条子路径上,如果每条分支路径都被执行到了,那么每条语句也应该被执行到了.
但是,仍然还有至少三种例外情况:程序中不存在判断.
程序或子程序/方法有着多重入口点.
只有从程序的特定入口点进入时,某条特定的语句才能执行到.
在ON单元(ON-unit)里的语句.
遍历每条分支路径并不一定能确保所有的ON单元都能执行到.
由于我们将语句覆盖视为一个必要条件,那么,第4章测试用例的设计未经同意,严禁以任何形式拷贝36作为似乎更佳准则的判定覆盖的定义理应涵盖语句覆盖.
因此,判定覆盖要求每个判断都必须有"是"和"否"的结果,并且每条语句都至少被执行一次.
换一种更简单的表达方式,即每个判断都必须有"是"和"否"的结果,而且每个入口点(包括ON单元)都必须至少被调用一次.
我们的探讨仅针对有两个选择的判断或分支,当程序中包含有多重选择的判断时,判定/分支覆盖准则的定义就必须有所改变.
典型的例子有包含select(case)语句的Java程序,包含算术(三重选择)IF语句、计算或算术GOTO语句的FORTRAN程序,以及包含可选GOTO语句或GO-TO-DEFENDING-ON语句的COBOL程序.
对于这些程序,判定/分支覆盖准则将所有判断的每个可能结果都至少执行一次,以及将程序或子程序的每个入口点都至少执行一次.
在图4-1中,两个涵盖了路径ace和abd,或涵盖了路径acd和abe的测试用例就可以满足判定覆盖的要求.
如果我们选择了后一种情况,两个测试用例的输入是A=3,B=0,X=3和A=2,B=1,X=1.
判定覆盖是一种比语句覆盖更强的准则,但仍然相当不足.
举例来说,我们仅有50%的可能性遍历到那条X未发生变化的路径(也即,仅当我们选择前一种情况).
如果第二个判断存在错误(例如把X>1写成了X50(达到循环的最后一次迭代)以及J+K=QUEST的情况设计测试用例.
图4-1有四个条件:A>1、B=0、A=2以及X>1.
因此需要足够的测试用例,使得在点a处出现A=2、A1及X1X=X/AA=2X=X+1HJdYNYNB=0X>1YINKYN图4-2图4-1中程序的机器码如图4-2所示,其中的原因是"与"和"或"表达式中某些条件的结果可能会屏蔽掉或阻碍其他条件,的判断.
举例来说,如果"与"表达式中有个条件为"假",那么就无须计算该表达式中的后续条件.
同样,如果"或"表达式中有个条件为"真",那么后续条件也无须计算.
因此,条件覆盖或判定/条件履盖谁则不一定会发现逻辑表达式中的错误.
所谓的多重条件覆盖准则能够部分解决这个问题.
该准则要求编写足够多的测软件测试的艺术未经同意,严禁以任何形式拷贝39试用例,将每个判定中的所有可能的条件结果的组合,以及所有的入口点都至少执行一次.
举例来说,考虑下面的伪代码程序;NOTFOUND=TRUE;DOI=1TOTABSIZEWHILE(NOTFOUND);/*SEARCHTABLE*/……searchinglogic……;END要测试四种情况:1.
ITABSIZE,并且NOTFOUND为真(查询了整个表格,未找到指定条目);4.
I>TABSIZE,并且NOTFOUND为假(指定条目位于表格的最后位置).
很容易发现,满足多重条件覆盖准则的测试用例集,同样满足判定覆盖准则、条件覆盖准则以及判定/条件覆盖淮则.
再次回到图4-l中,测试用例必须覆盖以下8种组合:1.
A>1,B=02.
A>1,B03.
A04.
A=2,X>15.
A=2,X2,X>17.
A2,X999).
2.
如果输入条件规定了取值的个数(例如,"汽车可登记一至六名车主"),那么就应确定出一个有效等价类和两个无效等价类(没有车主,或车主多于六个).
3.
如果输入条件规定了一个输入值的集合,而且有理由认为程序会对每个值进行不同处理(例如,"交通工具的类型必须是公共汽车、卡车、出租车、火车或摩托车"),那么就应为每个输入值确定一个有效等价类和一个无效等价类(例如,"拖车").
4.
如果存在输入条件规定了"必须是"的情况,例如"标识符的第一个字符必须是字母",那么就应确定一个有效等价类(首字符是字母)和一个无效等价类(首字符不是字母).
如果有任何理由可以认为程序并未等同地处理等价类中的元素,那么应该将这个等价类再划分为小一些的等价类,稍后我们将给出这个过程的例子.
2.
生成测试用例第二步是使用等价类来生成测试用例,其过程如下:1.
为每个等价类设置一个不同的编号.
2.
编写新的测试用例,尽可能多地覆盖那些尚未被涵盖的有效等价类,直到所有的有效等价类都被测试用例所覆盖(包含进去).
3.
编写新的用例,覆盖一个且仅一个尚未被覆盖的无效等价类,直到所有的无效等价类都被测试用例所覆盖.
用单个测试用例覆盖无效等价类,是因为某些特定的输入错误检查可能会屏蔽或取代其他输入错误检查.
举例来说,如果规格说明规定了"请输入书籍类型(硬皮、软皮或活页)及数量(l~999)",代表两个错误输入(书籍类型错误,数量软件测试的艺术未经同意,严禁以任何形式拷贝43错误)的测试用例"XYZ0",很可能不会执行对数量的检查,因为程序也许会提示"XYZ是未知的书籍类型",就不检查输入的其余部分了.
4.
1.
3一个范例作为一个例子,假设我们正在为FORTRAN语言的一个子集开发编译器,我们希望对DIMENSION语句的语法检查进行测试.
该语句的规格说明如下所示(这不是FORTRAN语言中的完整DIMENSION语句,我们对其进行了适当的剪裁,使其适合作为教科书的样例.
不要被其误导,以为测试实际的程序就像测试本书中的样例一样容易).
在规格说明中,斜体字中的项是在实际语句中必须被特定实体取代的语法单元,使用括弧代表可选项,省略号代表前面的项可能会连续重复出现多次.

DIMENSION语句用来定义数组的大小DIMENSION语句的格式如下:DIMENSIONad[,ad]…其中ad是数组描述符,其格式如下:n(d[,d]…)其中n是数组的符号名,d是数组的维说明符.
符号名可以由1~6个字母或数字组成,其中首字符必须是字母.
一个数组最少有1个维,最多有7个维.
维说明符的格式如下:[lb:]ub其中lb与ub分别是维的下边界和上边界.
边界可以是-65534~65535之间的一个常数,或是一个整型变变量名(但不能走数组元素名).
如果未指定lb,则其默认值为1.
ub的值必须大于或等于lb.
如果指定了lb,则其值可为负数、零或正数.
就全部语句而言,DIMENSION语句可写成连续多行(规格说明结束).
第一步应该是确定输入条件,然后为输入条件确定等价类.
这些步骤都以表格形式记录在表4-l中.
括号中的数字代表不同等价类的标识符.
表4-1等价类InputConditionValidEquivalenceClassesInvalidEquivalenceClassesNumberofarraydescriptorsone(1),>one(2)none(3)Sizeofarrayname1–6(4)0(5),>6(6)Arraynamehasletters(7),hasdigits(8)hassomethingelse(9)Arraynamestartswithletteryes(10)no(11)第4章测试用例的设计未经同意,严禁以任何形式拷贝44Numberofdimensions1–7(12)0(13),>7(14)Upperboundisconstant(15),integervariable(16)arrayelementname(17),somethingelse(18)Integervariablenamehasletter(19),digits(20)hashassomethingelse(21)Integervariablestartswithletteryes(22)no(23)Constant-65534–65535(24)≤65534(25),>65535(26)Lowerboundspecifiedyes(27),no(28)Upperboundtolowerboundgreaterthan(29),equal(30)lessthan(31)Specifiedlowerboundnegative(32),zero(33),>0(34)Lowerboundisconstant(35),integervariable(36),arrayelementname(37)somethingelse(38)Multiplelinesyes(39),no(40)下一个步骤应该是编写一个测试用例以覆盖一个或多个有效等价类.
举例来说,测试用例DIMENSIONA(2)覆盖了第1,4,7,10,12,15,24,28,29,40等价类.
再下一个步骤应该是设计一个或更多的测试用例以覆盖剩余的有效等价类,如以下形式的测试用例DIMENSIONA12345(1,9,J4XXXX,65535,1,KLM,X100),BBB[-65534:100,0:1000,10:10,I:65535]覆盖了剩余的等价类.
而无效输入等价类及其测试用例如下所示:(3):DIMENSION(5):DIMENSION(10)(6):DIMENSIONA234567(2)(9):DIMENSIONA.
1(2)(11):DIMENSION1A(10)(13):DIMENSIONB(14):DIMENSIONB(4,4,4,4,4,4,4,4)(17):DIMENSIONB(4,A(2))(18):DIMENSIONB(4,7)(21):DIMENSIONC(1,10)(23):DIMENSIONC(10,1J)(25):DIMENSIONC(-65535:1)(26):DIMENSIONC(65536)(31):DIMENSIOND(4:3)(37):DIMENSIOND(A(2):4)软件测试的艺术未经同意,严禁以任何形式拷贝45(38):D(.
:4)因此,所有的等价类都被18个测试用例全部所覆盖了.
读者可以考虑一下,如何将这些测试用例与用特殊方法生成的测试用例集进行比较.
尽管等价划分方法要比随机选取测试用例优越得多,但它仍然存在不足.
例如,这种方法忽略了某些特定类型的高效测试用例,下面介绍的两种方法(边界值分析与因果图)可以弥补其中的很多不足.
4.
1.
4边界值分析(Boundary-ValueAnalysis)经验证明,考虑了边界条件的测试用例与其他没有考虑边界条件的测试用例相比,具有更高的测试回报率.
所谓边界条件,是指输入和输出等价类中那些恰好处于边界、或超过边界、或在边界以下的状态.
边界值分析方法与等价划分方法存在两方面的不同:1.
与从等价类中挑选出任意一个元素作为代表不同,边界值分析需要选择一个或多个元素,以便等价类的每个边界都经过一次测试.
2.
与仅仅关注输入条件(输入空间)不同,还需要考虑从结果空间(输出等价类)设计测试用例.
很难提供一份如何进行边界值分析的"详细说明',因为这种方法需要一定程度的创造性,以及对问题采取一定程度的特殊处理办法(因此,就像测试的许多其他方面一样,这更多的是项智力工作,并非其他的什么).
然而,我们还是给读者提供一些通用指南:1.
如果输入条件规定了一个输入值范围,那么应针对范围的边界设计测试用例,针对刚刚越界的情况设计无效输入测试用例.
举例来说,如果输入值的有效范围是-1.
0至+l.
0,那么应针对-1.
0、1.
0、-1.
001和1.
001的情况设计测试用例.
2.
如果输入条件规定了输入值的数量,那么应针对最小数量输入值、最大数量输入值,以及比最小数量少一个、比最大数量多一个的情况设计测试用例.
举例来说,如果某个输入文件可容纳l~255条记录,那么应根据0、l、255和256条记录的情况设计测试用例.
第4章测试用例的设计未经同意,严禁以任何形式拷贝463.
对每个输出条件应用指南1.
举例来说,如果某个程序按月计算FICA1的扣除额,且最小金额是$0.
00,最大金额为$1165.
25,那么应该设计测试用例来测试扣除$0.
00和$1165.
25的情况.
此外,还应观察是否可能设计出导致扣除金额为负数或超过$1165.
25的测试用例.
注意,检查结果空间的边界很重要,因为输入范围的边界并不总是能代表输出范围的边界情况(例如,三角正弦函数sin的情况就如此).
同样,总是产生超过输出范围的结果也是不大可能的,但无论如何,应该考虑这种可能性.
4.
对每个输出条件应用指南2.
如果某个信息检索系统根据输入请求显示关联程度最高的信息摘要,而摘要的数量从未超过4条,则应编写测试用例,使程序显示0条、l条和4条摘要,还应设计测试用例,导致程序错误地显示5条摘要.
5.
如果程序的输入或输出是一个有序序列(例如顺序的文件、线性列表或表格),则应特别注意该序列的第一个和最后一个元素.
6.
此外,发挥聪明才智找出其他的边界条件.
本书第1章中的三角形分析程序可以说明边界值分析的必要性.
作为代表三角形的输入值,它们必须是大于0的整数,而且其中任意两个之和应大于第三个.
如果定义了等价划分,可能会确定一个满足此条件的等价类,以及另一个两个输入之和不大于第三个的等价类.
因此,3-4-5和1-2-4两个都是可能的测试用例.
然而,我们遗漏了一个可能的错误,即如果程序中表达式写成了A+B>=C,而不是A+B>C,那么程序就会错误地告诉我们l-2-3表示的是一个有效的不规则三角形.
因此,边界值分析方法和等价划分之间的重要区别是,边界值分析考察正处于等价划分边界或在边界附近的状态.
作为边界值分析的一个例子,考虑下面的程序规格说明:MTEST是一个多项选择考试的评分程序.
程序的输入是一个名为OCR的数据文件,包含多个长度为80个字符的记录.
按照文件的格式要求.
第一个记录的内容是标题,作为每份输出报告的标题.
后面的一组记录描述了试题的标准答案,这些记录的最后一个字符是"2".
在这组记录的首条记录中,第l~第3列存储的是试题的数量(一个l~999的数),第10~第59存储的是第l~第1美国联邦社会保险捐款法.
纳税人应依据此项法律交纳一定金额.
-译者注软件测试的艺术未经同意,严禁以任何形式拷贝4750道试题的标准答案(任何字符都为有效答案),后续记录的第10~第59列存储的是第51~第100道试题、第101~第150道试题的标准答案等等.
第三组记录描述的是每个学生的答案,这些记录的最后一个字符皆为"3".
对于每个学生来说,第一条记录的第1~第9列存储的是学生的名字或编号(任意字符),第10~第59列存储的是该学生对第l~第50道试题的答案.
如果本次考试试题超过50个,该学生的后续记录的第10~第59列存储的是第51~第100、第101~第150道试题的答案等等.
学生的人数最多是200.
输入数据如图4-4所示.
四个输出报告分别是:1.
按学生的编号排序的报告,显示每名学生的成绩(正确答案的百分比)和名次.
2.
按成绩排序的报告.
3.
显示成绩的平均值、中间值和标准偏差的报告.
4.
按问题的编号排序的报告,显示正确回答每个问题的学生比例.
(规格说明结束)第4章测试用例的设计未经同意,严禁以任何形式拷贝48标题180第1~50试题的答案180试题数量234910596079第51~100试题的答案1802910596079第1~50试题的答案180学生标识符3910596079第51~100试题的答案1803910596079第1~50试题的答案180学生标识符3910596079图4-4MTEST程序的输入我们从仔细阅读规格说明开始,寻找输入条件.
第一个边界输入条件是一个空输入文件.
第二个输入条件是该标题记录,边界条件是标题记录不存在、可能的最短标题和最长标题.
后面的输入条件是存储标准答案的记录,以及第一个标准答案记录里的"试题数量"域是否存在.
试题数量的等价类不应是l~999,因为在每个50的倍数处会出现某些特殊情况(例如,需要多个记录).
这种输入等价类的一个合理划分是1~50、51~999.
因此,我们需要针对试题数量为0、l、50、51和999的情况设计测试用例.
这样就覆盖了标准答案记录数量的大多数边界条件.
然而,三个最令人感兴趣的输入条件是标准答案记录不存在、记录多了一个以及记录少了一个(例如,试题数量是60个,然而在某个情况下有三个标准答案记录,而在另一种情况下只有一个).
到目前为止,我们生成的测试用例有:1.
输入文件为空.
2.
没有标题记录.
软件测试的艺术未经同意,严禁以任何形式拷贝493.
标题只有1个字符.
4.
标题有80个字符.
5.
考试试题数量为1.
6.
考试试题数量为50.
7.
考试试题数量为51.
8.
考试试题数量为999.
9.
考试试题数量为0.
10.
试题数量域的值为非数字类型.
11.
标题记录后无标准答案记录.
12.
标准答案记录数量多一个.
13.
标准答案记录数量少一个.
下面的输入条件是有关学生的答案的,其边界值测试用例可以是:14.
学生人数为0.
15.
学生人数为1.
16.
学生人数为200.
17.
学生人数为201.
18.
某个学生只有一条答案记录,但却存在两条标准答案记录.
19.
上面那个学生是文件中第一个学生.
20.
上面那个学生是文件中的最后一个学生.
21.
某个学生有两条答案记录,但只有一条标准答案记录.
22.
上面那个学生是文件中第一个学生.
23.
上面那个学生是文件中最后一个学生.
尽管有些输出边界(例如第一份输出报告为空)已被已有的测试用例覆盖到,但我们仍然可以通过检查输出边界而得到有用的测试用例集.
第一份输出报告与第二份输出报告的边界条件是:学生人数为0(同第14号测试样例).
学生人数为1(同第15号测试样例).
学生人数为200(同第16号测试样例).
24.
所有学生的成绩相同.
第4章测试用例的设计未经同意,严禁以任何形式拷贝5025.
所有学生的成绩都不相同.
26.
部分、但不是全部学生的成绩相间(检查名次的计算是否正确).
27.
某个学生的成绩为0.
28.
某个学生的成绩为100.
29.
某个学生的标识符值为可能的最低值(检查排序).
30.
某个学生的标识符值为可能的最高值.
31.
学生的数量恰好够一份报告占满一页(检查是否打印出多余页).
32.
学生的数量除够一份报告占满一页外,还多一个.
第三份输出报告(平均值、中间值和标准偏差)的边界条件是;33.
平均值为其最大值(全部学生都得满分).
34.
平均值为0(全部学生都得0分).
35.
标准偏差为其最大值(一个学生成绩为0分,其他都为100分).
36.
标准偏差为0(全部学生成绩相同).
第33和第34号测试用例同时也覆盖了中间值的边界条件.
另外一个有用的测试用例是学生人数为0的情况(检查程序在计算平均值时是否有被0除的情况),只是这种情况与第14号测试用例相同.
对第四份输出报告的检查可以生成下列边界值测试用例:37.
全部学生都回答正确第一道试题.
38.
全部学生都回答错误第一道试题.
39.
全部学生都回答正确最后一道试题.
40.
全部学生都回答错误最后一道试题.
41.
试题的数量恰好够一份报告占满一页.
42.
试题的数量除够一份报告占满一页外,还多一道.
有经验的程序员很可能会认同这一点,即42个测试用例的大部分代表了在开发该程序的过程中可能会犯的共性错误,但如果我们采用的是随机生成或特殊的测试用例设计方法,这些错误中的大多数都不会被检查出来.
如果使用得正确,边界值分析是最为有效的测试用例设计方法之一.
然而,这种方法常常使用得不好,因为表面上它听起来比较简单.
我们应该认识到,边界条件可能非常微妙,因此把它们确定下来需要煞费一番脑筋.
软件测试的艺术未经同意,严禁以任何形式拷贝514.
1.
5因果图(Cause-EffectGraphing)边界值分析和等价划分的一个弱点是未对输入条件的组合进行分析.
举例来说,上一节中介绍的MTEST程序由于试题数量与学生数量的乘积超过某个阈值时可能会发生失效(例如,程序耗尽了内存).
边界值测试不一定能检查出此类错误.
对输入组合进行测试并不是简单的事情,因为即使对输入条件进行了等价划分,这些组合的数量也是个天文数字.
如果在选择输入条件的子集时没有采用一个系统的方法,很可能选择出一个任意的输入条件子集,这样会使测试没有什么成效.
因果图有助于用一个系统的方法选择出高效的测试用例集.
它还有一个额外的好处,就是可以指出规格说明的不完整性和不明确之处.
因果图是一种形式语言,用自然语言描述的规格说明可以转换为因果图.
因果图实际上是一种数字逻辑电路(一个组合的逻辑网络)、但没有使用标准的电子学符号,而是使用了稍微简单点的符号.
除了了解布尔逻辑(了解逻辑运算符"与"、"或"、"非")之外,读者不必掌握电子学方面的知识.
生成测试用例时采用的过程如下:1.
将规格说明分解为可执行的片段.
这是必须的步骤,因为因果图不善于处理较大的规格说明.
举例来说,当测试一个电子商务系统时,"可执行的片段"可能是指对挑选和确认购物车中的单件商品的规格说明.
在测试一个Web页面设计时,我们可能会测试一个单独的菜单树,甚至是一个不太复杂的导航序列.
2.
确定规格说明中的因果关系.
所谓"因",是指一个明确的输入条件或输入条件的等价类.
所谓"果",是指一个输出条件或系统转换(输入对程序或系统状态的延续影响).
举例来说,如果某个事务引起文件或数据库记录被修改,那么这种改变就是一个系统转换,而系统反馈的确认信息就是一个输出条件.
通过逐字逐句地阅读规格说明,同时标识出描述"因"和"果"的文字或句子,就可以将"因"和"果"确定出来.
因果关系一旦确定下来,每个"因"和"果"都被赋予一个惟一的编号.
3.
分析规格说明的语义内容,并将其转换为连接因果关系的布尔图.
这就是第4章测试用例的设计未经同意,严禁以任何形式拷贝52所谓的因果图.
4.
给图加上注解符号,说明由于语法或环境的限制而不能联系起来的"因"和"果".
5.
通过仔细地跟踪图中的状态变化情况,将因果图转换成一个有限项的判定表.
表中的每一列代表一个测试用例.
6.
将判定表中的列转换成测试用例.
因果图中的基本符号如图4-5所示.
设想一下,每个结点的值为0或为1,0代表"不存在"状态,1代表"存在"状态.
identity函数表示如果a等于1,则b也为1,否则b为0.
not函数表示如果a等于1,则b为0,否则b为1.
or函数表示如果a或b或c等于1,则d为1,否则d为0.
and函数表示如果a和b都等于1,则c为1,否则c为0.
后两个函数(or和and)允许存在任意数量的输入.
图4-5基本的因果图符号为描述一个小的因果图,考虑下面的规格说明:第一列中的字符必须是"A"或"B",第二列中的字符必须是一个数字.
在这种情况下,对文件进行更新.
如果第一个字符不正确,产生提示信息X12.
如果第二个字符不是数字,产生提示信息X13.
"因"如下:软件测试的艺术未经同意,严禁以任何形式拷贝531——第一列的字符是"A"2——第一列的字符是"B"3——第二列的字符是一个数字"果"如下:70——对文件做了更新71——产生提示信息X1272——产生提示信息X13因果图如图4-6所示.
请注意生成的中间结点11.
通过设置"因"的全部可能状态,观察"果"得到了正确的值,就可以确认该图代表了规格说明.
对于熟悉逻辑图的读者来说,图4-7是一个等价的逻辑电路.
图4-6因果图范例图4-7与图4-6等价的逻辑图第4章测试用例的设计未经同意,严禁以任何形式拷贝54尽管图4-6所示的因果图代表了规格说明,但图中包含了一个不可能的原因组合,即原因1和原因2不可能同时设置为1.
在大多数程序中,由于语法或环境的原因,某些原因的组合是不可能存在的(一个字符不能同时为"A"和"B").
为了对此做出解释,我们采用图4-8所示的符号.
约束E表示其必须总为真,而a和b最多只有一个为1(a与b不能同时为1).
约束I表示其为真时,a、b、c中至少有一个应为1(a、b、c不能同时为0).
约束O表示a、b中有且仅有一个必须为1,约束R表示如果a为1,b也必须为1(例如,a为1而b为0的情况是不可能的).
图4-8约束符号在结果之间通常需要建立约束关系.
图4-9中的约束M表示,如果结果a为0,则b强制为0.
软件测试的艺术未经同意,严禁以任何形式拷贝55图4-9屏蔽约束的符号再回到前面那个简单例子中来,我们看到,原因1和原因2实际上是不可能同时成立的,而两者都不成立却是可能的.
因此,它们之间应该用约束E来连接,如图4-10所示.
图4-10带有"排斥性"约束条件的因果图范例为了说明如何从因果图中导出测试用例,需要使用下面介绍的规格说明.
该规格说明用于某个交互系统的一条调试命令.
DISPLAY命令用于从一个终端窗口中观察内存空间的内容.
该命令的语法见图4-11.
括弧表示可替换的可选操作时象.
大写字母表示操作对象的关健字;小写字母表示操作时象的值(即要被取代的实际值).
带下划线的操作对象代表默认值(即操作对象默认时所使用的值).
第一个操作对象(hexloc1)规定了待显示的内容的首字节地址.
该地址可以是1~6位长度的十六进制(0~9,A~F)数.
如果地址没有指定,默认地址为0.
地址必须在机器实际内存地址的范围之内.
第二个操作对象规定了要显示的内存的数量.
如果规定了hexloc2的值,也就确定了要显示的内存空间范围内的末字节地址.
该地址可以是1~6位长度的十六进制数,且必须大于或等于起始地址(hexloc1).
同时,hexloc2也必须在机器实际内存地址的范围之内.
如果定义了END,那么从起始位置(hexloc1)直到机器内存中最后字节的内容都将显示出来.
如果规定了bytecount的值,也就规定了要显示的内存字节数量(从hexloc1指定的位置开始计算),该操作对象是第4章测试用例的设计未经同意,严禁以任何形式拷贝56一个十六进制整数(长度为l~6位).
bytecount与hexloc1之和不能超过实际的内存容量加1,而bytecount的值至少为1.
当显示内存内容时,在屏幕上按如下格式分一行或多行输出:xxxxxx=word1word2word3word4xxxxxx是以十六进制表示的word1的地址.
无论hexloc1为何值,或者要显示的内存容量是多大,总是显示整数个字(一个字由4个字节排列组成,字中首字节的地址是4的倍数).
每一输出行总是包含4个字(16个字节).
被显示范围的首字节包含在第一个字中.
(规格说明结束)可能产生的错误信息如下:M1无效的命令语法.
M2所需内存超出了实际的内存范围.
M3所需内存为0或为负数.
图4-11DISPLAY命令的语法例如:DISPLAY显示内存的前四个字(默认的起始位置为0,默认的字节数为1).
DISPLAY77F显示首字节地址为77F的字以及后续的3个字.
DISPLAY77F-407A显示从字节77F-字节407A间的字.
软件测试的艺术未经同意,严禁以任何形式拷贝57DISPLAY77F.
6显示从字节77F起六个字节的字.
DISPLAY50FF-END显示从字节50FF开始直到内存结束的字.
第一步骤是认真地分析规格说明以确定出"因"和"果".
"因"如下:1.
存在第一个操作对象.
2.
hexloc1操作对象仅包含十六进制数字.
3.
hexlocl操作对象包含1-6个字符.
4.
hexloc1操作对象在机器实际的内存范围之内.
5.
第二个操作对象为END.
6.
第二个操作对象为hexloc2.
7.
第二个操作对象为bytecount.
8.
第二个操作对象默认.
9.
hexloc2操作对象仅包含十六进制数字.
10.
hexloc2操作对象包含l-6个字符.
11.
hexloc2操作对象在机器实际的内存范围之内.
12.
hexloc2操作对象大于或等于hexloc1操作对象.
13.
bytecount操作对象仅包含十六进制数字.
14.
bytecount操作对象包含1-6个字符.
15.
bytecount+hexloc1=1.
17.
定义的内存范围大到足够要求显示多行输出.
18.
显示内存的起始位置不在字单元的边界位置.
每个"因"都按不同的数字进行了编号.
注意,其中四个"因"(第5~第8)是第二个操作对象所必需的,因为第二个操作对象可能是:(1)END;(2)hexloc2;(3)bytecount;(4)不存在;(5)以上情况都不是.
"果"如下:91.
显示了信息M1.
92.
显示了信息M2.
第4章测试用例的设计未经同意,严禁以任何形式拷贝5893.
显示了信息M3.
94.
内存内容显示在一行上.
95.
内存内容显示在多行上.
96.
显示范围的首字节正好在字单元的边界位置.
97.
显示范围的首字节不在字单元的边界位置.
下一个步骤是建立因果图.
"因"结点垂直排列在纸的左边,"果"结点垂直排列在纸的右边.
应仔细分析规格说明所表达的意思,以便建立起"因"与"果"的连接关系(即说明在何种情况下产生何种结果).
图4-12显示了因果图的最初形式.
中间结点32表示一个语法上有效的第一个操作对象;结点35表示一个语法上有效的第二个操作对象;结点36表示一条语法上有效的命令.
如果结点36为1,结果91(错误信息)就不会显示出来,反之就会显示.
软件测试的艺术未经同意,严禁以任何形式拷贝59图4-12DISPLAY命令的初样图完整的因果图如图4-13所示.
应当仔细地研究该图,以确认它如实地反映了规格说明的内容.
第4章测试用例的设计未经同意,严禁以任何形式拷贝60图4-13不带约束条件的完整因果图如果我们利用图4-13来生成测试用例,就会生成很多不可能实现的测试用例出来.
原因是由于语法的制约,有些特定的原因组合是不可能的.
举例来说,除非出现了原因l,原因2和原因3就不可能出现.
而原因4只有当原因2和原因3都出现时才成立.
图4-14显示的就是带有约束条件的完整的因果图.
请注意,原因5、6、7、8中最多仅能出现一个.
其余所有的原因约束条件都是"要求"关系(require,即图4-8中的约束R.
--译者注).
注意原因17(多行输出)要求原因8(第二个操作对象默认)不成立;仅当原因8不出现时,原因17方才出现.
再一次提醒,应软件测试的艺术未经同意,严禁以任何形式拷贝61该仔细地检查这些约束条件.
图4-14DISPLAY命令的完整因果图下一步骤是建立有限项的判定表.
读者如果熟悉判定表的话,就会知道"因"是条件,而"果"是动作.
采用的过程如下:1.
选择一个"果"作为当前状态(1).
2.
对因果图进行回溯,查找导致该"果"为1(根据约束条件)的所有"因"的组合.
3.
在判定表中为每个"因"的组合生成一列.
4.
对于每种"因"的组合,判断所有其它"果"的状态,并放置在每一列中.
在执行第2步时,需要做以下考虑:1.
当回溯经过一个结果应为1的or结点时,不要同时将该or结点的一个以上的输入设置为1.
这就是所谓的路径敏感性(pathsensitizing),其目的是第4章测试用例的设计未经同意,严禁以任何形式拷贝62避免由于原因之间的屏蔽而漏掉某些错误.
2.
当回溯经过一个结果应为0的and结点时,显然应列举出导致结果为0的所有的输入组合情况.
然而,如果碰到的情况是一个输入为0,其它的输入中有一个或更多为1,那么就无须罗列出其它输入可能为1的所有情况.
3.
当回溯经过一个结果应为0的and结点时,仅有一种所有输入皆为0的情况需要列举出来(如果这个and结点位于因果图的中部,其输入来自于其他中间结点,那么所有输入都为的情况就会非常多).
这些复杂的思路在图4-15中总结出来,图4-16是一个范例.
如果x取值为l,则无须考虑当a=b=l时的情况(第l种考虑)如果x取值为0,则列举当a=b=0时的所有情况如果x取值为l,则无须考虑当a=b=c=l时的所有情况如果x取值为0,仅需考虑当a=b=c=0时的情况(第3种考虑).
对于abc为001、010、100、011、101和110时,每种仅需考虑一种情况(第2种考虑)情况图4-15追溯因果图的思路假设我们需要找出所有导致输出状态为0的输入条件.
根据上述第3条思路,我们只需列出一种情况,即结点5和结点6都是0的情况.
根据第2条思路,对于结点5为1而结点6为0的情况,我们只需列出结点5为l的这一种情况,而不必罗列出结点5可能为l的所有情况.
同样地,对于结点5为0而结点6为l的情况,我们也只需列出结点6为1的这一种情况(尽管在本例中只有一种).
根据第1条思路,当结点5应被设置为l时,我们不应将结点1和结点2同时设置为1.
因此,举例来说,我们可以处于从结点1到结点4间的5种状态,而并非是从结点1到结点4间导致输出为0的13种可能的状态,其值如下:软件测试的艺术未经同意,严禁以任何形式拷贝630000(5=0,6=0)1000(5=l,6=0)1001(5=1,6=0)1010(5=1,6=0)0011(5=0,6=1)图4-16描述追溯思路的因果图范例这些思路也许看起来反复无常,但都有一个重要的目标:即减少因果图中的组合关系.
它们排除了会导致生成低效测试用例的状态.
如果不能排除低效测试用例,那么一个因果关系复杂的大因果图会生成天文数字的测试用例.
如果测试用例的数量大得不切合实际,就只得从中挑出一些子集来,而这又不能保证低效的测试用例会被排除在外.
因此,最好在分析因果图的阶段就将其排除掉.
现在,可将图4-14所示的因果图转化为判定表.
最先应选择结果91.
当结点36为0时,才会出现结果91.
当结点32和35为0,0;0,l或l,0时,结点36才会为0,此处可以应用第2条和第3条思路.
通过对原因的回溯以及对原因间约束关系的考虑,可以找出导致结果91出现的原因组合,尽管这样做是个颇费力气的过程.
针对结果91出现的情况,其判定表如图4-17所示(第1列~第11列).
第l列~第3列(也是1~3号测试用例)代表结点32为0而结点35为l的情况,而第4列~第10列代表结点32为1而结点35为0的情况.
根据第3条思路,在结点32和35皆为0的全部21种情况中只需确定一种(第11列).
表格中的空白处表示第4章测试用例的设计未经同意,严禁以任何形式拷贝64"无关紧要"的情况(即与该原因的状态并不相关),或指出由于其他相依赖原因的关系,该原因的状态是显而易见的(例如在第l列中,我们知道由于与原因6存在"至多一个"的关系,原因5、7和8必定为0).
图4-17生成的判定表的前半部分第12列~第15列代表结果92出现了的情况.
第16列~第17列代表结果93出现了的情况.
图4-18描述了判定表的剩余部分.
软件测试的艺术未经同意,严禁以任何形式拷贝65图4-18生成的判定表的后半部分最后一个步骤,是将判定表转化为38个测试用例.
下面列举了一组38个测试用例.
每个测试用例右边的数字指出期望出现的结果.
我们假定所用计算机内存的最末地址是7FFF.
1DISPLAY234AF74-123(91)2DISPLAY2ZX4-3000(91)3DISPLAYHHHHHHHH-2000(91)4DISPLAY200200(91)5DISPLAY0-22222222(91)6DISPLAY1-2X(91)7DISPLAY2-ABCDEFGHI(91)8DISPLAY3.
1111111(91)9DISPLAY44.
$42(91)10DISPLAY100.
91)11DISPLAY10000000-M(91)12DISPLAYFF-8000(92)第4章测试用例的设计未经同意,严禁以任何形式拷贝6613DISPLAYFFF.
7001(92)14DISPLAY8000-END92)15DISPLAY8000-8001(92)16DISPLAYAA-A9(93)17DISPLAY7000.
0(93)18DISPLAY7FF9-END(94,97)19DISPLAY1(94,97)20DISPLAY21-29(94,97)21DISPLAY4021.
A(94,97)22DISPLAY–END(94,96)23DISPLAY(94,96)24DISPLAY–F(94,96)25DISPLAY.
E(94,96)26DISPLAY7FF8-END(94,96)27DISPLAY6000(94,96)28DISPLAYA0-A4(94,96)29DISPLAY20.
8(94,96)30DISPLAY7001-END(95,97)31DISPLAY5-15(95,97)32DISPLAY4FF.
100(95,97)33DISPLAY–END(95,96)34DISPLAY–20(95,96)35DISPLAY11(95,96)36DISPLAY7000-END(95,96)37DISPLAY4-14(95,96)38DISPLAY500.
11(95,96)注意,在大多数情况下,如果对同样一组原因执行两个或更多的测试用例,为改进测试用例的效率,这些原因应取不同的值.
还应注意到,由于受到实际存储空间的限制,第22号测试用例是不能实现的(就如同第33号测试用例,其产生的结果是95而不是94).
因此,最终确定了37个测试用例.
评语因果图方法是一个根据条件的组合而生成测试用例的系统性的方法.
可以替代这种方法的是特殊选取的条件组合,但在这个过程中,很可能会遗漏很多可由因果图方法确定的"令人感兴趣的"测试用例.
由于因果图方法需要将规格说明转换为一个布尔逻辑网络,因此它使我们从不同的视角,以更多的洞察力来审视规格说明.
事实上,建立因果图是一个暴露规格软件测试的艺术未经同意,严禁以任何形式拷贝67说明中模糊和不完整之处的好方法.
举例来说,聪明的读者也许已经注意到,上文讨论的过程已经发现了DISPLAY命令规格说明中的一个问题.
该规格说明规定,所有的输出行都包含4个字.
然而,这并不是对所有的情况都成立;在测试用例18和26中就不会发生,因为其起始地址距内存最末位置不足16个字节.
尽管因果图方法确实能产生一组有效的测试用例,但通常它不能生成全部应该被确定的有效测试用例.
举例来说,在上面的例子中,我们并未提到验证显示出来的内存值是否与内存中的实际值一致,也未提到对程序能否显示出内存空间中任何可能的值进行判断.
另外,因果图方法没有充分考虑边界条件.
当然,在此过程中我们可以尝试覆盖边界状态.
例如,不将hexloc2≥hexloc1确定成一个"因",而将其确定成两个"因":hexloc2=hexloc1hexloc2>hexloc1然而,这样做所带来的问题是使因果图急剧复杂化,导致生成的测试用例的数量非常庞大.
鉴于此,最好是单独考虑边界值分析.
举例来说,可以从DISPLAY命令规格说明中确定出下面的边界条件:1.
hexloc1为1位数字.
2.
hexloc1为六位数字.
3.
hexloc1为七位数字.
4.
hexloc1=0.
5.
hexloc1=7FFF.
6.
hexloc1=8000.
7.
hexloc2为一位数字.
8.
hexloc2为六位数字.
9.
hexloc2为七位数字.
10.
hexloc2=0.
11.
hexloc2=7FFF.
12.
hexloc2=8000.
第4章测试用例的设计未经同意,严禁以任何形式拷贝6813.
hexloc2=hexloc1.
14.
hexloc2=hexloc1+1.
15.
hexloc2=hexloc11.
16.
bytecount为一位数字.
17.
bytecount为六位数字.
18.
bytecount为七位数字.
19.
bytecount=1.
20.
hexloc1+bytecount=8000.
21.
hexloc1+bytecount=8001.
22.
显示16个字节(一行).
23.
显示17个字节(两行).
注意,这并不意味着需要编写出60(37+23)个测试用例来.
由于因果图方法给我们提供了选择操作对象具体值的灵活性,在由因果图生成测试用例时,可以将边界条件分析一并考虑进去.
在上面的例子中,通过对最初37个测试用例的一部分进行重新编写,可以覆盖所有的23种边界条件,而且不必增加任何测试用例.
因此,我们得到了一组虽然不多但却很有效的测试用例,并满足了两方面的目标.

注意,因果图方法是与本书第2章中的几个测试原则相一致的.
确定每个测试用例的预期输出是因果图方法的固有部分(判定表中的每一列指明了预期的结果).
同时还应注意到,此方法鼓励我们查找未预料到的结果.
举例来说,第1列(也是第1号测试用例)指出预期应出现结果91,而不应出现结果92至结果97.
此方法中最具难度的部分是将因果图转化为判定表.
这个过程是有算法的,即意味着我们可以编写程序来自动完成这个过程.
已经有些商业软件可以帮我们完成这一转化.
4.
2错误猜测(ErrorGuessing)常常可以看到这种情况,有些人似乎天生就是测试的能手.
这些人没有用到任何特殊的方法(比如对因果图进行边界值分析),却似乎有着发现错误的诀窍.
对此的一个解释是这些人更多是在下意识中,实践着一种称为错误猜测的测试软件测试的艺术未经同意,严禁以任何形式拷贝69用例设计技术.
接到具体的程序之后,他们利用直觉和经验猜测出错的可能类型,然后编写测试用例来暴露这些错误.
由于错误猜测主要是一项依赖于直觉的非正规的过程,因此很难描述出这种方法的规程.
其基本思想是列举出可能犯的错误或错误易发情况的清单,然后依据清单来编写测试用例.
例如,程序的输入中出现0这个值就是一种错误易发情况.
因此,可以编写测试用例,检查特定的输入值中有0,或特定的输出值被强制为0的情况.
同样,在出现输入或输出的数量不定的地方(如某个被搜索列表的条目数量).
数量为"没有"和"一个"(例如空列表,仅包含一个条目的列表)也是错误易发情况.
另一个思想是,在阅读规格说明时联系程序员可能做的假设来确定测试用例(即规格说明中的一些内容会被忽略,要么是由于偶然因素,要么是程序员认为其显而易见).
由于无法给出一个规程来,次优的选择是讨论错误猜测的实质,最好的做法是举出实例.
假设在测试一个排序程序,要探讨的情况如下:输入列表为空.
输入列表仅包含一个条目.
输入列表所有条目的值都相同.
输入列表已经排过序.
换言之,上面列举出的这些特殊情况可能在程序设计时被忽略.
如果要测试的是一个二进制搜索程序,需要检查的情况包括:(l)被搜索的表中只有一个条目;(2)表的大小是2的幂(如16);(3)表的大小是2的幂差1和2的幂多l(如15和17).
想一想"边界值分析"一节中的MTEST程序.
当使用错误猜测方法之后,我们会想到以下增加的测试:程序是否接受"空白"作为答案一个第2类型的记录(标准答案)出现在第3类型的记录集中(学生答案).
除了首条记录(标题)外,存在最后一列中没有"2"或"3"的记录.
两位学生名字或编号相同.
由于中间值的计算根据数据项的数量是奇数还是偶数而有所不同,因此针第4章测试用例的设计未经同意,严禁以任何形式拷贝70对学生数量为奇数和偶数的情况分别对程序进行测试.
"问题数量"域的值为负数.
对前一节中的DISPLAY命令,所想到的错误猜测测试如下:DISPLAY100-(第二个操作对象不全)DISPLAY100.
(第二个操作对象不全)DISPLAY100-10A42(多余的操作对象)DISPLAY000-0000FF(以0打头)4.
3测试策略本章讨论的测试用例设计方法可以组合为一个整体的策略.
之所以组合,原因现在已经很清楚了,每一种方法都可以提供一组具体的有用的测试用例,但是都不能单独提供一个完整的测试用例集.
一组合理的策略如下:1.
如果规格说明中包含输入条件组合的情况,应首先使用因果图分析方法.

2.
在任何情况下都应使用边界值分析方法.
应记住,这是对输入和输出边界进行的分析.
边界值分析可以产生一系列补充的测试条件,但是,也正如"因果图分析"一节所述,多数甚至全部条件都可以被整合到因果图分析中.
3.
应为输入和输出确定有效和无效等价类,在必要情况下对上面确认的测试用例进行补充.
4.
使用错误猜测技术增加更多的测试用例.
5.
针对上述测试用例集检查程序的逻辑结构.
应使用判定覆盖、条件覆盖、判定/条件覆盖或多重条件覆盖准则(最后的一个最为完整).
如果覆盖准则未能被前四个步骤中确定的测试用例所满足,并且满足准则也并非不可能(由于程序的性质限制,某些条件的组合也许是不可能实现的),那么增加足够数量的测试用例,以使覆盖准则得到满足.
再一次声明,使用上述策略并不能保证可以发现所有的错误,但实践证明这是一个合理的折中方案.
同时,它也代表了客观的艰巨工作量,虽然没人说软件测试是一件容易的事.
软件测试的艺术未经同意,严禁以任何形式拷贝71第5章模块(单元)测试到目前为止,我们在很大程度上忽视了软件测试的机制及被测程序的规模.
然而,大型的软件程序(即超过500条语句块的程序)需要特别的测试对策.
在本章中我们将探讨构建大型程序测试的第一个步骤:模块测试,而剩余的步骤将在本书第6章中介绍.
模块测试(或单元测试)是对程序中的单个子程序、子程序或过程进行测试的过程,也就是说,一开始并不是对整个程序进行测试,而是首先将注意力集中在对构成程序的较小模块的测试上面.
这样做的动机有三个.
首先,由于模块测试的注意力一开始集中在程序的较小单元上,因此它是一种管理组合的测试元素的手段.

其次,模块测试减轻了调试(准确定位并纠正某个已知错误的过程)的难度,这是因为一旦某个错误被发现出来,我们就知道它在哪个具体的模块中.
第三,模块测试通过为我们提供同时测试多个模块的可能,将并行工程引入软件测试中.

模块测试的目的是将模块的功能与定义模块的功能规格说明或接口规格说明进行比较.
为了再次强调所有测试过程的目的,这里的测试目标不是为了说明模块符合其规格说明,而是为了揭示出模块与其规格说明存在着矛盾.
在本章中,我们从以下三个方面来探讨模块测试:1.
测试用例的设计方式.
2.
模块测试及集成的顺序.
3.
对执行模块测试的建议.
5.
1测试用例设计在为模块测试设计测试用例时,需要使用两种类型的信息:模块的规格说明和模块的源代码.
规格说明一般都规定了模块的输入和输出参数以及模块的功能.

模块测试总体上是面向白盒测试的.
其中一个原因是如果对大一点的软件进行测试,例如一个完整的程序(其实是后续的测试过程所针对的对象),白盒测试不容易展开.
第二个原因是,后续的测试过程着眼于发现其他类型的错误(举例来说,第5章模块(单元)测试未经同意,严禁以任何形式拷贝72这些错误不一定与程序的逻辑结构有关,比如程序未能满足其用户需求).
因此,模块测试的测试用例的设计过程如下:使用一种或多种白盒测试方法分析模块的逻辑结构,然后使用黑盒测试方法对照模块的规格说明以补充测试用例.
由于所需的测试用例设计方法已经在本书第4章中讨论过,我们在这里通过一个例子来描述这些方法在模块测试中的应用.
假设我们要测试一个名为BONUS的模块.
其功能是为销售额最高的部门的雇员的薪水增加$2,000,但是如果某个符合条件的雇员的当前工资己经达到或超过了$150,000,则薪水只增加$1000.
2对模块的输入情况如图5-1中的表格所示.
如果模块正确完成了其功能,返回错误代码0.
如果雇员或部门表中不存在任何条目,模块将返回错误代码l.
如果在某个符合条件的部门中未发现任何雇员,模块将返回错误代码2.
图5-1BONUS模块的输入表模块的源程序如图5-2所示.
输入参数ESIZE和DSIZE分别代表雇员表和部门表内条目的数量.
该模块是用PL/l语言编写的,但下面的讨论整体上是与编程语言无关的;这些技术可以用在其他语言编写的程序中.
同时,由于本模块中的PL/1逻辑结构非常简单,实际上任何读者,甚至不熟悉PL/l的人也应该能够读懂程序.
2这三个数据与图5-2源程序中的数据不一致,图是$200,$100.
——译者注.
软件测试的艺术未经同意,严禁以任何形式拷贝73BONUS:PROCEDURE(EMPTAB,DEPTTAB,ESIZE,DSIZE,ERRCODE);DECLARE1EMPTAB(*),2NAMECHAR(6),2CODECHAR(1),2DEPTCHAR(3),2SALARYFIXEDDECIMAL(7,2);DECLARE1DEPTTAB(*),2DEPTCHAR(3),2SALESFIXEDDECIMAL(8,2);DECLARE(ESIZE,DSIZE)FIXEDBINARY;DECLAREERRCODEFIXEDDECIMAL(1);DECLAREMAXSALESFIXEDDECIMAL(8,2)INIT(0);/*MAX.
SALESINDEPTTAB*/DECLARE(I,J,K)FIXEDBINARY;/*COUNTERS*/DECLAREFOUNDBIT(1);/*TRUEIFELIGIBLEDEPT.
HASEMPLOYEES*/DECLARESINCFIXEDDECIMAL(7,2)INIT(200.
00);/*STANDARDINCREMENT*/DECLARELINCFIXEDDECIMAL(7,2)INIT(100.
00);/*LOWERINCREMENT*/DECLARELSALARYFIXEDDECIMAL(7,2)INIT(15000.
00);/*SALARYBOUNDARY*/DECLAREMGRCHAR(1)INIT('M');1ERRCODE=0;2IF(ESIZE=MAXSALES)THENMAXSALES=SALES(I);7END;8DOJ=1TODSIZE;9IF(SALES(J)=MAXSALES)/*ELIGIBLEDEPARTMENT*/10THENDO;11FOUND='0'B;12DOK=1TOESIZE;13IF(EMPTAB.
DEPT(K)=DEPTTAB.
DEPT(J))14THENDO;15FOUND='1'B;16IF(SALARY(K)>=LSALARY)|CODE(K)=MGR)17THENSALARY(K)=SALARY(K)+LINC;18ELSESALARY(K)=SALARY(K)+SINC;19END;20END;21IF(-FOUND)THENERRCODE=2;22END;23END;24END;25END;图5-2BONUS模块无论采用哪种逻辑覆盖方法,第一步都是要列举出程序中所有的条件判断.
该程序中需要列出的对象是所有的IF和DO语句.
通过对程序进行检查,我们可以看出所有的DO语句都是此简单的迭代,每一个迭代的上限都等于或大于初始值(意味着每个循环体总是会至少执行一次),而且退出循环的惟一方法是通过DO语句.
第5章模块(单元)测试未经同意,严禁以任何形式拷贝74因此,该程序中的DO语句无须特别关注,因为任何导致DO语句执行的测试用例最终都会使其进入两个方向的分支路径(即进入循环体和退出循环体).
因此,必须要进行分析的语句有:2IF(ESIZE=MAXSALES)9IF(SALES(J)=MAXSALES)13IF(EMPTAB.
DEPT(K)=DEPTTAB.
DEPT(J))16IF(SALARY(K)>=LSALARY)|(CODE(K)=MGR)21IF(-FOUND)THENERRCODE=2得到了数量较少的判断后,我们可能会选择多重条件覆盖,但应该检查所有的逻辑覆盖准则(语句覆盖准则除外,其限制太多以至于不易于使用),看看它们的效果如何.
为了满足判定覆盖准则,我们需要设计充足的测试用例,来触发上述6个判断中每一个的全部输出结果.
触发所有判定输出所需的输入状态列举在表5-1中.
由于有两个输出结果总会发生,故需要测试用例触发的状态只有10个.
注意,要建立表5-l,必须沿程序逻辑结构对判定输出的状态进行回溯,以判别相应的正确输入状态.
例如,判断16不会被任何符合条件的雇员触发.
雇员必须处在满足条件的部门之内.
表5-1与判定输出对应的状态DecisionTrueOutcomeFalseOutcome2ESIZEorDSIZE≤0.
ESIZEandDSIZE>0.
6Willalwaysoccuratleastonce.
OrderDEPTTABsothatadepartmentwithlowersalesoccursafteradepartmentwithhighersales.
9WillalwaysoccuratleastonceAlldepartmentsdonothavethesamesales.
13Thereisanemployeeinaneligibledepartment.
Thereisanemployeewhoisnotinaneligibledepartment.
16AneligibleemployeeiseitheramanagerorearnsLSALARYormore.
AneligibleemployeeisnotamanagerandearnslessthanLSALARY.
21Alleligibledepartmentscontainnoemployees.
Aneligibledepartmentcontainsatleastoneemployee.
表5-l中关注的10种情况可以被图5-3中所示的两个测试用例触发.
注意,每个测试用例都包含了对预期输出的定义,这是符合本书第2章讨论的测试原则的.
软件测试的艺术未经同意,严禁以任何形式拷贝75测试用例输入预期的输出1ESIZE=0AllotherinputsareirrelevantERRCODE=1ESIZE,DSIZE,EMPTAB,andDEPTTABareunchanged2ESIZE=DSIZE=3EMPTABJONESED4221,000.
00SMITHED3214,000.
00LORINED4210,000.
00DEPTTABD4210,000.
00D328,000.
00D9510,000.
00ERRCODE=2ESIZE,DSIZE,andDEPTTABareunchangedEMPTABJONESED4221,100.
00SMITHED3214,000.
00LORINED4210,200.
00图5-3满足判定覆盖准则的测试用例尽管这两个测试用例都满足判定覆盖准则,但很明显,该模块中仍可能存在着很多类型的错误不能通过这两个用例来发现.
举例来说,这两个用例没有对错误代码为0、某名雇员是管理人员或部门表为空(DSIZE02DSIZE≤0DSIZE≤0DSIZE>06SALES(I)≥MAXSALESWillalwaysoccuratleastonce.
OrderDEPTTABsothatadepartmentWithlowersalesoccursafteradepartmentwithhighersales.
9SALES(J)=MAXSALESWillalwaysoccuratleastAlldepartmentsdonothavethesame软件测试的艺术未经同意,严禁以任何形式拷贝77once.
sales.
13EMPTAB.
DEPT(K)=DEPTTAB.
DEPT(J)ThereisanemployeeThereisanemployeewhoinaneligibleisnotinandepartment.
Eligibledepartment.
16SALARY(K)≥LSALARYAneligibleemployeeearnsLSALARYormore.
AneligibleemployeeearnslessthanLSALARY.
16CODE(K)=MGRAneligibleemployeeisamanager.
Aneligibleemployeeisnotamanager.
21-FOUNDAneligibledepartmentcontainsnoemployees.
Aneligibledepartmentcontainsatleastoneemployee.
测试用例输入预期的输出1ESIZE=0DSIZE=0AllotherinputsareirrelevantERRCODE=1ESIZE,DSIZE,EMPTAB,andDEPTTABareunchanged2ESIZE=0DSIZE>0AllotherinputsareirrelevantSameasabove3ESIZE>0DSIZE=0AllotherinputsareirrelevantSameasabove4ESIZE=5DSIZE=4EMPTABJONESMD4221,000.
00WARNSMD9512,000.
00LORINED4210,000.
00TOYED9516,000.
00SMITHED3214,000.
00DEPTTABD4210,000.
00D328,000.
00D9510,000.
00D4410,000.
00ERRCODE=2ESIZE,DSIZE,andDEPTTABareunchangedEMPTABJONESMD4221,100.
00WARNSMD9512,100.
00LORINED4210,200.
00TOYED9516,100.
00SMITHED3214,000.
00图5-5满足多重条件覆盖准则的测试用例BONUS模块可能存在着大量的错误,即使是满足多重条件覆盖准则的测试用例也检查不出来,认识到这一点很重要.
举例来说,没有测试用例会触发ERRORCODE返回值为0的情况,因此,如果语句1漏掉了,该错误就发现不到.
如果LSALARY被错误地初始化为$150,000.
01,这个错误也将无法发现.
如果语句16中写的是SALARY(K)>LSALARY而不是SALARY(K)>=LSALARY,这个错误第5章模块(单元)测试未经同意,严禁以任何形式拷贝78也将无法发现.
同样,各种off-by-one错误(例如没有正确地处理DEPTTAB或EMPTAB表中最末的条目)能否检查出来,在很大程度上全凭运气.
现在有两个观点清晰了:多重条件覆盖准则优先于其它准则;任何逻辑覆盖准则尚不足以胜任作为生成模块测试用例的惟一手段.
因此,下一个步骤就是用一组黑盒测试用例来补充图5-5中的测试用例.
要做到这一点,我们将BONUS模块的接口规格说明列举在下面:PL/1模块BONUS接收5个参数,分布是EMPTAB,DEPTTAB、ESIZE、DSIZE和ERRORCODE.
这些参数的属性如下:DECLARE1EMPTAB(*),/*INPUTANDOUTPUT*/2NAMECHARACTER(6),2CODECHARACTER(1),2DEPTCHARACTER(3),2SALARYFIXEDDECIMAL(7,2);DECLARE1DEPTTAB(*),/*INPUT*/2DEPTCHARACTER(3),2SALESFIXEDDECIMAL(8,2);DECLARE(ESIZE,DSIZE)FIXEDBINARY;/*INPUT*/DECLAREERRCODEFIXEDDECIMAL(1);/*OUTPUT*/模块假设传递的参数具有以上属性.
ESIZE,DSIZE分别表示EMPTAB、DEPTTAB表中的条目数量,而EPTAB、DEPTTAB表中的条目顺序未作任何假设.
该模块的功能是为销售额(DEPTTAB、SALES)最高的一个或多个部门的雇员增加薪水(EMPTAB,SALARY).
如果某个符合条件的雇员当前工资已经达到或超过了$150,000,或者该雇员为管理人员(EMPTAB.
CODE='M'),则薪水只增加$1,000,否则增加$2,000.
模块假设增加的薪水放入EMPTAB.
SALARY中.
如果ESIZE、DSIZE不大于0,则将ERRCODE设置为1,并且不进行任何操作.
在所有其他情况下,完整地执行模块的功能.
然而,如果某个具有最大销售额的部门没有雇员,程序继续执行,但将ERRCODE设置为2;否则设置为0.
上述规格说明并不适合于因果图分析方法(没有一组应检查其组合情况的能力分辨出来的输入条件),因此采用边界值分析方法.
确定的输入边界如下:1.
EMPTAB中条目数量为1.
2.
EMPTAB中条目数量为最大值(65535).
3.
EMPTAB中条目数量为0.
4.
DMPTAB中条目数量为1.
软件测试的艺术未经同意,严禁以任何形式拷贝795.
DMPTAB中条目数量为最大值(65535).
6.
DMPTAB中条目数量为0.
7.
某个销售额最高的部门仅有1名雇员.
8.
某个销售额最高的部门有65535名雇员.
9.
某个销售额最高的部门没有雇员.
10.
DEPTTAB中所有部门的销售额相同.
11.
销售额最高的部门为DEPTTAB的第一条目.
12.
销售额最高的部门为DEPTTAB的最末条目.
13.
某个符合条件的雇员为EMPTTAB的第一条目.
14.
某个符合条件的雇员为EMPTTAB的最末条目.
15.
某个符合条件的雇员为管理人员.
16.
某个符合条件的雇员不是管理人员.
17.
某个不为管理人员、且符合条件的雇员的薪水为$149999.
99.
18.
某个不为管理人员、且符合条件的雇员的薪水为$150000.
19.
某个不为管理人员、且符合条件的雇员的薪水为$150000.
01.
输出边界如下:20.
ERRCODE=0.
21.
ERRCODE=1.
22.
ERRCODE=2.
23.
某个符合条件雇员增加后的薪水为$299999.
99.
使用错误猜测技术进一步确定的测试条件如下:24.
在DEPTTAB中,某个没有雇员的销售额最大的部门其后跟着另一个有雇员的销售额最大的部门.
这用来判断当遇到ERRCODE=2的情况时,模块是否错误地终止对输入的处理.
评价一下这24种条件,其中条件2、5和8看起来像是不切实际的测试用例.
由于它们所代表的条件不可能发生(在测试中作这种假设通常是很危险的,但在此处似乎比较安全),因此可以将它们排除掉.
下一步是将剩下的21种条件与当前的测试用例集(图5-5)进行比较,判断哪些边界条件尚未被覆盖到.
通过比较,我第5章模块(单元)测试未经同意,严禁以任何形式拷贝80们发现需要为条件l、4、7、10、14、17、18、19、20、23和24设计图5-5中没有的测试用例.
再下一步是设计额外的测试用例以覆盖上述11种边界条件.
一种方法是将这些条件合并到现有的测试用例中去(即对图5-5中的第4个用例进行修改),但我们不推荐这样做,因为这样做会打乱现有测试用例完整的多重条件覆盖.
因此,最保险的方法是增加图5-5之外的测试用例.
在这样做的过程中,我们的目标是使设计覆盖边界条件所需的最小数量的测试用例.
图5-6中的三个测试用例实现了这一点.
测试用例5覆盖了条件7、10、14、17、18、19和20,测试用例6覆盖了条件l、4和23,而测试用例7则覆盖了条件24.
在这里我们有理由相信,逻辑覆盖准则或白盒测试用例、图5-6所示的测试用例已实现对模块BONUS适度的模块测试.
测试用例输入预期的输出5ESIZE=3DSIZE=2EMPTABALLYED3614,999.
99BESTED3315,000.
00CELTOED3315,000.
01DEPTTABD3355,400.
01D3655,400.
01ERRCODE=0ESIZE,DSIZE,andDEPTTABareunchangedEMPTABALLYED3615,199.
99BESTED3315,100.
00CELTOED3315,100.
014ESIZE=1DSIZE=1EMPTABCHIEFMD9998,899.
99DEPTTABD9999,000.
00ERRCODE=0ESIZE,DSIZE,andDEPTTABareunchangedEMPTABCHIEFMD9999,899.
994ESIZE=2DSIZE=2EMPTABDOLEED6710,000.
00WARNSED2233,333.
33DEPTTABD6620,000.
00D6720,000.
00ERRCODE=2ESIZE,DSIZE,andDEPTTABareunchangedEMPTABDOLEED6710,000.
00WARNSED2233,333.
33图5-6BONUS模块补充的边界值分析测试用例5.
2增量测试在执行模块测试过程中,我们主要有两点考虑:第一,如何设计一个有效的测试用例集,这在上一节已经讨论过.
第二,将模块组装成工作程序的方式.
第二点考软件测试的艺术未经同意,严禁以任何形式拷贝81虑很重要,因为它涉及模块测试用例编写的形式、可能用到的测试工具类型、模块编码和测试的顺序、生成测试用例的成本以及调试(定位并修复检查出的错误)的成本等.
简而言之,它具有实际重要性.
在这一节中,我们将讨论两类方法,增量测试和非增量测试.
在下一节中,我们将探讨两种增量方法:自顶向下的和自底向上的开发或测试过程.
这里需要考虑的问题是:软件测试是否应先独立地测试每个模块,然后再将这些模块组装成完整的程序还是先将下一步要测试的模块组装到测试完成的模块集合中,然后再进行测试第一种方法称为非增量测试或"崩溃(big-bang)"测试,而第二种方法称为增量测试或集成.
图5-7所示的程序可作为一个例子.
矩形框代表程序的6个模块(子程序或过程),连接模块间的线条代表程序的控制层次,也就是说,模块A调用模块B、C和D,模块B调用模块E等等.
作为传统方法的作增量测试是按如下方式进行的:首先,对6个模块中的每一个模块进行单独的模块测试,将每个模块视为一个独立实体.
根据环境(例如,是人机交互式的,还是使用批处理计算工具)和参与人数,这些模块可以同时或按次序进行测试.
最后,将这些模块组装或集成(例如"连接编辑")为完整的程序.
图5-7包含6个模块的程序范例测试单独的模块需要一个特殊的驱动模块(drivemodule)和一个或多个桩模块(stubmodule).
举例来说,测试模块B,首先要设计测试用例,然后将测试用例作第5章模块(单元)测试未经同意,严禁以任何形式拷贝82为输入参数由驱动模块传递给模块B.
驱动模块是人们编写的一个小模块,用来将测试用例驱动或传输到被测模块中(也可以用测试工具替代).
驱动模块还必须向测试人员显示模块B的结果.
此外,由于模块B调用了模块E,所以还必须使用一个额外的组件,该组件在模块B调用模块E时接受模块B的控制指令.
这就由桩模块来完成它是一个被命名为"E"的特殊模块,用来模拟模块E的功能.
当所有6个模块的模块测试都完成之后,就将这些模块组装成完整的程序.
另一种可选择的方法是增量测试.
不同于独立地测试每个模块,增量测试首先将下一个要测试的模块组装到前面已经测试过的模块集合中去.
现在要给出对图5-7所示的程序进行增量测试的步骤还为时太早,因为还有大量可能的增量方法.
一个关键问题是我们究竟是从程序的顶部开始、还是从底部开始进行测试.
由于这个问题将在下一节中讨论,我们暂且假设从底部开始测试.
第二步先测试模块E、C和F,可以并行测试(由三个人进行),也可串行进行.
请注意,我们必须要为每个模块准备一个驱动模块,但不是桩模块.
下一步是测试模块B和D,但不是单独地测试它们,而是分别将其与模块E和F组装在一起.
换言之,要测试模块B,应编写驱动模块和集成测试用例,将模块B和E组合起来测试.
将下一个要测试的模块组装到前面已经测试过的模块集合或子集中去,这个增长的过程会一直进行到测试完最后一个模块(本例中是模块A)为止,请注意,这个过程也可以自顶向下进行.
下面是几个显而易见的结论:1.
非增量测试所需的工作量要多一些.
对于图5-7所示的程序,需要准备5个驱动模块和5个桩模块(假设顶部的模块不需要驱动模块).
自底向上的增量测试需要5个驱动模块,但不需要桩模块.
自顶向下的增量测试需要5个桩模块,但不需要驱动模块.
增量测试所需的工作量要少一些,因为使用了前面测试过的模块来取代非增量测试中所需要的驱动模块(如果从顶部开始测试)或桩模块(如果从底部开始测试).
2.
如果使用了增量测试,可以较早地发现模块中与不匹配接口、不正确假设相关的编程错误.
这是由于尽早地对模块组合进行了集成测试.
然而,如果采用非增量测试,只有到了测试过程的最后阶段,模块之间才能"互相看到".
3.
因此如果使用了增量测试,调试会进行得容易一些,我们假定存在着与模软件测试的艺术未经同意,严禁以任何形式拷贝83块间接口或假设相关的编程错误(根据经验而来的合理假设),那么,如果使用非增量测试,直到整个程序组装之后,这些错误才会浮现出来.
到了这个时候,我们就难以定位错误.
因为它可能存在于程序内部的任何位置,相反,如果使用增量测试,这种类型的错误就很容易发现,因为该错误很可能与最近添加的模块有关.
4.
增量测试会将测试进行得更彻底.
如果当前正在测试模块B,要么是模块E,要么是模块A(取决于测试是从底部还是从顶部开始的)被当作结果而执行.
虽然模块E或模块A先前已经进行了完全的测试,但将其作为B的模块测试结果而执行,则会诱发出一个新的情况,可能会暴露出先前测试过的模块E或模块A中存在的一个新缺陷.
另一方面,如果使用的是非增量测试,对模块B的测试仅影响到其本身.
换言之,增量测试使用先前测试过的模块,取代了非增量测试中使用的桩模块或驱动模块.
因此,到最后一个模块测试完成时,实际的模块经受到了更多的检验.
5.
非增量测试所占用的机器时间显得少一些.
如果使用自底向上的方法测试图5-7中的模块A,在执行A的过程中,模块B、C、D、E和F也会执行到.
而在对模块A的非增量测试中,仅会执行模块B、C和E的桩模块.
自顶向下的增量测试的情况也是如此.
如果测试的是模块F,那么在执行模块F时还会执行模块A、B、C、D和E;而在对模块F的非增量测试中,仅有模块F的驱动模块与其一起执行.
因此,完成一次增量测试所需执行的机器指令,显然多于采用非增量测试方法所需的指令.
但此消彼长的是,非增量测试要比增量测试需要更多的驱动模块和桩模块,开发这些驱动模块和桩模块是要占用机器时间的.
6.
模块测试阶段开始时,如果使用的是非增量测试,就会有更多的机会进行并行操作(也就是说,所有的模块可以同时测试).
对于大型的软件项目〔模块和人员都很多),这可能十分重要,因为在模块测试开始之时,项目的人员数量常常处于最高峰.
总的来说,第1条~第4条结论是增量测试的优点,而第5、6条结论是其不利之处.
考虑到计算机行业当前的趋势(硬件成本已经降低而且势必会持续下去,硬件的功能不断增加,而人力劳动成本和软件错误的代价在不断增长),再考虑到第5章模块(单元)测试未经同意,严禁以任何形式拷贝84错误发现得越早,改正它的成本也越低,我们会看到第1条至第4条结论的重要性日益突出,而第5条结论越来越显得不那么重要.
如果有一个缺点的话,第6条结论似乎确是一个薄弱的缺点.
从而我们可以得出结论,增量测试要更好一些.

5.
3自顶向下测试与自底向上测试在上一节结论的基础上,即增量测试要优于非增量测试,本节将讨论两种增量测试策略:自顶向下的测试和自底向上的测试.
然而在讨论它们之前,先要澄清几个误解.
首先,"自顶向下的测试"、"自顶向下的开发"和"自顶向下的设计"常用作近义词.
"自顶向下的测试"和"自顶向下的开发"确实是同义词(表示安排模块的编码和测试顺序的策略),但"自顶向下的设计"则完全不同并且是独立的概念,按自顶向下模式设计的程序既可使用自顶向下的方式,也可使用自底向上的方式进行增量测试.
其次,自底向上的测试(或自底向上的开发)常被错误地当作非增量测试.
原因在于自底向上的测试的开展方式与非增量测试是相同的(即对底层或终端模块进行测试),但是就如我们从上一节看到的那样,自底向上的测试是一种增量测试.
最后,由于两种策略都属于增量测试,因此增量测试的优点在这里就不再赘述,仅讨论自顶向下测试与自底向上测试的差异.
5.
3.
1自顶向下的测试自顶向下的测试是从程序的顶部或初始模块开始.
测试开始之后,挑选哪一个后续模块进行增量测试没有惟一正确的方法:惟一的原则是:要成为合乎条件的下一个模块,至少一个该模块的从属模块(调用它的模块)事先经过了测试.

我们用图5-8来说明这种测试策略.
A至L代表程序的12个模块.
假定模块J包含程序的I/O读操作,而模块I包含I/O写操作.
第一步是测试模块A,测试要求必须编写出代表B、C和D的桩模块.
遗憾的是,我们经常会错误理解桩模块的生成.
作为佐证,我们可能经常会听到这样的说软件测试的艺术未经同意,严禁以任何形式拷贝85法,"一个桩模块仅需要写一条'我们进行到了这一步'的信息"、"在很多情况下,模拟的桩模块仅仅只是存在而不起任何作用".
在大多数情况下,这些说法都是错误的.
由于模块A调用模块B,模块A就需要模块B执行一些操作,这些操作很可能就是返回给模块A的结果(输出参数).
如果桩模块仅仅只是返回了控制,或显示一条出错信息却没有返回一个有意义的结果,模块A就会发生失效,这并不是由于模块A存在错误,而是因为桩模块未能模拟出相应的模块.
此外,桩模块仅仅返回一个"已经连通(wired-in)"的结论是不够的.
举例来说,让我们考虑编写一个桩模块,代表一个平方根程序、一个数据库表搜索程序,一个"获取相关主文件记录"程序或诸如此类的程序等.
如果这个桩模块仅仅返回一条固定的"已经连通"输出,却没有返回调用模块此次调用所希望的特定值,那么调用模块将会发生失效或是产生一个混乱的结果.
因此,编写桩模块是很关键的.
图5-8包含12个模块的程序范例另一个需要考虑的地方是采取了什么样的形式将测试用例提交给程序,这是一个非常重要的问题,大多数对自顶向下测试的研究都没有提到这一点.
在我们给出的例子中,存在这样的问题:如何向模块A提交测试用例由于在典型的程序中,顶部模块既不接收输入参数,也不执行输入/输出操作,因此问题的答案不是显而易见的.
答案是:测试数据是通过其一个或多个桩模块提交给模块(此处为模块A)的.
为了说明这一点,假设模块B、C和D的功能如下:第5章模块(单元)测试未经同意,严禁以任何形式拷贝86B―获取事务文件的概要.
C―判断每周的状态是否满足限额.
D―生成每周总结报告.
那么自桩模块B返回的一个事务概要就是模块A的一个测试用例.
桩模块D可能包含将其输入数据写到打印机的语句,这样就可以检查每一个测试的结果.

在本程序中还存在另一个间题.
由于假定模块A仅调用模块B一次,问题是如何将多个测试用例提交给模块A.
一个解决方法是编写出桩模块B的多个版本,每一个版本都将一个各不相同的有效测试数据集返回给模块A.
为了执行这些测试用例,程序需要执行多次,每次都使用桩模块B的不同版本.
另一种可选择的方法是将测试数据放置在外部文件中,由桩模块B读取并返回给模块A.
根据前面的讨论,对于任何一种情况,开发桩模块通常要比实际理解的更为困难.
而且,由于程序的特点所致,通过被测模块之下的多个桩模块来传送测试数据常常是必需的(即被测模块通过调用多个桩模块来获得要处理的测试数据).
模块A测试完成之后,就用一个实际的模块代替其中的一个桩模块,而该模块需要的桩模块也被添加进来.
举例来说,图5-9就显示了该程序的下一个版本.
图5-9自顶向下测试的第二个步骤测试完顶部模块之后,接下来可能的测试序列有很多.
举例来说,如果我们要执行所有的测试序列,大量可能的模块序列中的四个序列如下:1.
ABCDEFGHIJKL2.
ABEFJCGKDHLI软件测试的艺术未经同意,严禁以任何形式拷贝873.
ADHIKLCGBFJE4.
ABFJDIECGKHL如果可以进行并行测试,可能还有其他的选择.
举例来说,模块A测试结束之后,一位程序员可能会选取模块A,测试模块A-B的组合,另一位程序员可能会测试模块A-C的组合,而第三位程序员可能会测试模块A-D的组合.
总的来说,不存在最佳的模块序列,但却有下面可供考虑的两项指南:1.
如果程序中存在关键部分(例如模块G),那么在设计模块序列时就应将这些关键模块尽可能早地添加进去.
所谓"关键部分"可能是某个复杂的模块、某个采用新算法的模块或某个被怀疑容易发生错误的模块.
2.
在设计模块序列时,应将I/O模块尽可能早地添加进来.
第一项指南的动机非常清楚,但第二项指南的动机则需要进一步的讨论.
回想一下,桩模块的问题就是一部分桩模块须包含测试用例,而另一部分桩模块则须将其输入写到打印机中或显示出来.
然而,接收程序输入的模块一旦被添加进来,测试用例的描述就相当简单了,其采用的形式就与最终程序接收的输入一样(例如,通过事务文件或终端).
相似地,一旦执行程序输出功能的模块被添加进来,桩模块中就可能无需再放置输出测试用例结果的代码.
因此,如果模块J和模块I是I/O模块,而模块G执行某些关键操作,那么增长序列可能是:ABFJDICGEKHL而第6个增量3之后,程序可能是如图5-10所示的形式.
一旦到达了如图5-10所示的中间阶段,测试用例的描述以及测试结果的检查就简单化了.
由于有一个程序实际运行的框架版本,也就是执行实际的输入和输出操作,就带来了另一个好处.
然而,桩模块依然模拟着部分"内幕".
这个早期的程序框架版本有以下优点:可以使我们发现人为因素的错误和问题.
可以将程序演示给最终用户看.
证明程序的整体设计是合理的.
3即加入I.
——译者注第5章模块(单元)测试未经同意,严禁以任何形式拷贝88起到精神上的鼓舞作用.
然而另一方而,自顶向下策略还有一些严重缺陷.
假定我们当前的测试状态如图5-10所示,下一步是用模块H取代桩模块H.
这时(或更早一些)我们所要做的是使用本章前面所述的方法,为H设计一个测试用例集.
但是请注意,这些测试用例采用的是向模块J的实际程序输入的形式.
这带来了一些问题.
首先,由于在模块J和模块H之间存在中间摸块(即模块F、B、A和D),我们会发现无法将测试过模块H中所有预先确定的情况的测试用例提交到模块J中去.
举例来说,如果H是如图5-2所示的BONUS模块,由于中间模块D的存在,就无法生成图5-5和图5-6中的7个测试用例中的部分用例.
其次,由于H和程序中测试数据引入点之间存在着"距离",即使存在着测试全部状态的可能性,要决定往模块J中输入什么样的数据来测试到H中的所有状态,通常也是一项困难的脑力劳动.
最后,由于一个测试显示出来的输出可能来自于一个与被测模块相距甚远的模块,要将显示出来的输出与此模块的实际执行情况联系起来非常困难,甚至是不可能的.
想象一下将模块E添加到图5-10中,每个测试用例的结果都取决于检查模块I的输出,但是由于存在着中间模块,要推演出模块E的实际输出(即返回给模块B的数据)可能是很困难的.
ABStubCDFStubHIStubEJ图5-10自顶向下测试的中间状态软件测试的艺术未经同意,严禁以任何形式拷贝89自顶向下的测试策略取决于其使用的方法,可能还存在两个更深层次的问题.

人们会偶尔感觉到它可能与程序的设计阶段重叠.
举例来说,如果我们正在设计如图5-8所示的程序,可能会觉得在最先的两个层次设计完成之后,在下面层次的设计进行的同时就可以对模块A至模块D进行编码和测试了.
正如我们在其他地方所强调的那样,这往往不是明智之举.
程序设计是一个迭代的过程,这意味着当我们在设计程序结构的较低层次时,可能会对较高层次进行合理的变更或改进.
如果程序的较高层次已经完成了编码和测试,那么这些理想的改进就会被摒弃,最终成为一个不明智的决策.
实践中时常会发生的一个终极问题是,在进行到下一个模块前未能穷举测试此模块.
这来自于两个原因:一是由于将测试数据嵌入桩模块中存在困难,二是由于程序的较高层次通常会为较低层次提供资源.
在图5-8中,我们看到,对模块A的测试需要用到针对模块B的多个版本的桩模块.
在实践中,我们会倾向于说"由于这需要投人很多工作,我现在就不执行模块A的所有测试用例,一直等到将模块J添加到程序中,此时引入测试用例就容易多了,我会记得在那时完成对模块A的测试".
当然,这里的问题是到了那个较晚的时间点,我们可能会忘记模块A中剩下的测试.
另外,因为较高的层次常常会提供资源给较低层次(例如打开文件)使用,有时除非到了使用资源的低层次模块测试完成之后,我们很难判断这些资源提供得是否正确(例如,文件是否以正确的属性打开).
5.
3.
2自底向上的测试下面讨论自底向上的增量测试策略.
在大多数情况下,自底向上的策略与自顶向下的策略是相对立的.
自顶向下测试的优点成为自底向上测试的缺点,而自顶向下测试的缺点又成为自底向上测试的优点.
正因为这一点,我们对自底向上测试的介绍就简短一些.
自底向上的策略开始于程序中的终端模块(此类模块不再调用其他任何模块).
测试完这些模块之后,同样没有最佳的方法来挑选要进行增量测试的下一个模块.

惟一正确的原则是,要成为合乎条件的下一个模块,该模块所有的从属模块(它调用的模块)都已经事先经过了测试.
回到图5-8,第一步是测试模块E、J、G、K、L和I中的部分或全部模块,既可以串行进行,也可以并行进行.
要做到这一点,第5章模块(单元)测试未经同意,严禁以任何形式拷贝90每一模块都需要一个特殊的驱动模块:即包含着有效的测试输入、调用被测模块且将输出显示出来(或将实际输出与预期输出作比较)的模块.
有别于使用桩模块的情况,由于驱动模块可以交迭地调用被测模块,因此不需要为驱动模块提供多个版本.
在大多数情况下,开发驱动模块要比开发桩模块更容易些.
如同前面的例子一样,影响测试序列的因素是模块的关键程度.
如果我们觉得模块D和模块F最为关键,那么应该自底向上增量测试的某个中间状态可能如图5-11所示.
接下来的步骤可能是测试模块E,然后再测试模块B,将模块B与先前测试过的模块E、F和J组装起来进行测试.
DriveHDILJDriveFJ图5-11自底向上测试的中间状态自底向上策略的一个不足是,它没有早期程序框架的概念.
事实上,直到最后一个模块(模块A)被添加进来,才形成了可工作的程序,也就是完整的程序.
尽管I/O功能可以在整个程序集成之前进行测试(I/O模块在图5-11中用到),早期程序框架的优点在这里体现不出来.
自顶向下方法中无法建立所有测试环境的问题,在这里都不复存在.
如果将驱动模块看作是一个测试探针的话,那么该探针是直接放入被测模块中去的,不会受到中间模块的困扰.
检查一下与自顶向下方法相关的其他问题,我们再也不会做出让设计和测试重叠的不明智决定,因为自底向上的测试要直到程序底层设计完成之后方才开始.
同样,在没有测试完一个模块之前就开始另一个模块测试的问题也不软件测试的艺术未经同意,严禁以任何形式拷贝91会存在,这是因为使用自底向上的测试不再有如何将测试数据绑定到桩模块中去的烦恼.
5.
3.
3比较如果自顶向下的方法和自底向上的方法,就象增量测试和非增量测试一样区别分明,那么比较起来很容易,但遗憾的是,情况并非如此.
表5-3概括了它们之间相对的优点和不足(前面讨论过的两者皆有的优点除外,也就是增量测试的优点).
每种方法的第一个优点似乎是决定性的因素,但是也没有证据表明主要的缺陷会更容易发生在典型程序的顶部或底层.
最保险的判断方法是,根据特定的被测程序,对表5-3中所示的各因素进行权衡.
由于这里缺乏一个规程,自顶向下测试第四个缺点的严重后果,以及有可用的测试工具减少了对驱动模块而不是桩模块的需求,这样似乎给自底向上的策略带来了优势.
除此之外,自顶向下的方法和自底向上的方法很显然都不是惟一可能的增量测试策路.
表5-3自顶向下测试与自底向上测试的比较自顶向下测试优点缺点1.
如果主要的缺陷发生程序的顶层将非常有利2.
一旦引人I/O功能,提交测试用例会更容易3.
早期的程序框架可以进行演示,并可激发积极性1.
必须开发桩模块2.
桩模块要比最初表现的更复杂3.
在引入I/O功能之前,向桩模块中引入测试用例比较用难4.
创建测试环境可能很难,甚至无法实现5.
观察测试输出很困难6.
使人误解设计和测试可以交迭进行7.
会导致特定模块测试的完成延后自底向上优点优点缺点1.
如果主要的缺陷发生在程序的底层将非常有利2.
测试环境比较容易建立3.
观察测试输出比较容易1.
必须开发驱动模块2.
直到最后一个模块添加进去,程序才形成一个整体5.
4执行测试按下来介绍模块测试的其他部分如何实际进行测试.
这里我们给出了一系列操作的提示和指南.
第5章模块(单元)测试未经同意,严禁以任何形式拷贝92当测试用例造成模块输出的实际结果与预期结果不匹配的情况时,存在两个可能的解释:要么该模块存在错误,要么预期的结果不正确(测试用例不正确).
为了将这种混乱降低到最小程度,应在测试执行之前对测试用例集进行审核或检查(也就是说,应对测试用例进行测试).
使用自动化测试工具可以使测试过程中的枯燥劳动减至最小.
举例来说,现在已有测试工具可以降低我们对驱动模块的需求.
流程分析工具可以列举出程序中的路径、找出从未被执行的语句("不可达"代码),以及找出变量在赋值前被使用的实例.
在准备模块测试时,重温一下本书第2章中讨论的心理学和经济学原则会有所裨益.
如同本章前面所做的那样,记住对预期输出进行定义是测试用例必不可少的部分.
在执行测试时,应该查找程序的副作用(即模块执行了某些不该执行操作的情况).
一般情况下,这些情况都是很难发现的,但如果在测试用例执行完之后,检查那些不应有变动的模块输入,可能会发现一些错误实例.
举例来说,图5-7中的测试用例7声明ESIZE、DSIZE和DEPTTAB作为预期结果的一部分,不应发生变更.
在执行此测试用例时,不仅要检查输出结果是否正确,还要检查ESIZE、DSIZE和DEPTTAB,判断它们是否被错误地修改了.
因个人试图测试自己编写的程序所带来的心理学问题,也适用于模块测试.
程序员不应测试自己编写的模块,而应交换模块进行测试.
编写调用模块的程序员始终是测试被调用模块的最佳候选人.
注意,这仅仅适用于测试,对模块的调试一般应当由编程人员本人进行.
应避免随意丢弃测试用例,应将它们按某种格式记录下来,以便将来可以重新使用它们.
回想一下图2-2中那个有悖于直观的现象.
如果发现某一部分模块存在大量错误,那么很有可能这些模块甚至包含着更多的错误,只是尚未检查出来而已.
这样的模块应该进行更进一步的测试,可能还需要进行额外的代码走查或检查.
最后,记住模块测试的目的不是证明模块能够正确地运行,而是证明模块中存在着错误.
软件测试的艺术未经同意,严禁以任何形式拷贝93第6章更高级别的测试完成了对程序的模块测试之后,整个测试过程才刚刚开始,对于大型或复杂的软件来说尤为如此.
考虑下面这个重要概念:当程序无法实现其最终用户要求的合理功能时,就发生了一个软件错误.

根据这个定义,即使完成了一次非常完美的模块测试,仍然不能保证已经找出了程序中的所有错误.
因此,要结束整个测试任务,还必须进行其他形式的更深入的测试.
我们将这些新形式的测试称为"更高级别的"测试.
软件开发过程在很大程度上是沟通有关最终程序的信息、并将信息从一种形式转换到另一种形式.
由于这个原因,绝大部分软件错误都可以归因为信息沟通和转换时发生的故障、差错和干扰.
第6章更高级别的测试未经同意,严禁以任何形式拷贝94图6-1软件开发过程图6-1描述了软件开发的这个观点,它表示了一个软件产品开发周期的模型.
过程的流程可归结为以下7个步骤:1.
将软件最终用户的要求转换为一系列书面的需求.
这些需求就是该软件产品要实现的目标.
2.
通过评估可行性与成本、消除相抵触的用户需求、建立优先级和平衡关系,将用户需求转换为具体的目标.
3.
将上述目标转换为一个准确的产品规格说明,将产品视为一个黑盒,仅考虑其接口以及与最终用户的交互.
该规格说明被称为"外部规格说明".
4.
如果该产品是一个系统,如操作系统、飞行控制系统、数据库管理系统或雇员人事系统等,而不仅是一个程序(编译器、工资程序、字处理程序等),那么下软件测试的艺术未经同意,严禁以任何形式拷贝95一步骤就是系统设计.
该步骤将系统分割为单独的程序、部件或子系统,并定义它们的接口.
5.
通过定义每个模块的功能、模块的层次结构以及模块间的接口,来设计程序或程序集合的结构.
6.
设计一份准确的规格说明,定义每个模块的接口与功能.
7.
经过一个或更多的子步骤,将模块接口规格说明转换为每个模块的源代码算法.
以下是从其他角度来审视上述文档的形式:需求规格说明定义了为什么要开发程序.
目标定义了程序要做什么,以及应做得怎样.
外部规格说明定义了程序对用户的准确表现.
与后续阶段相关的文档越来越详细地规定了程序是如何建立起来的.
假定软件开发周期的七个阶段包括了信息的沟通、理解和转换,以及大多数的软件错误都来源于信息处理中的故障,那么现在有三个补充的方法来预防或识别这些错误.
首先,我们可以使软件开发过程更加精密,以防其中出现很多错误;其次,在每个阶段结束时可以引入一个独立的验证过程,在进入下一个阶段之前尽可能多地发现问题.
这种方法如图6-2所示.
举例来说,对外部规格说明的验证可以通过与前一个阶段的输出(对目标的叙述)进行比较,然后将任何发现的错误反馈到外部规格说明定义过程中去.
在第七阶段结束时,使用本书第3章讨论的代码检查和走查方法进行验证.
第6章更高级别的测试未经同意,严禁以任何形式拷贝96最终用户需求目标外部规格说明系统设计程序结构设计模块接口规格说明代码验证验证验证验证验证验证验证图6-2包含中间验证步骤的开发过程第三个方法是对不同的开发阶段采用不同的测试方法.
也就是说,将每一个测试过程都重点针对一个特定的转换步骤,从而也针对一类具体的错误.
这种方法如图6-3所示.
测试周期是模仿软件开发周期建立起来的,换言之,我们应该能够在开发过程和测试过程之间建立起一对一的联系.
举例来说:模块测试的目的是发现程序模块与其接口规格说明之间的不一致.
功能测试的目的是为了证明程序未能符合其外部规格说明.
系统测试的目的是为了证明软件产品与其初始目标不一致.
这种结构的好处是避免了没有效果的多余测试,并使我们不会遗漏掉大量的错误类型.
举例来说,不能仅将系统测试定义为"对整个系统的测试"并且可能仅重软件测试的艺术未经同意,严禁以任何形式拷贝97复先前的测试,而是针对一种特定类型的错误(在将目标转换为外部规格说明时所犯的错误),并就开发过程中的特定类型的文档进行度量.
图6-3所示的更高级别的测试方法最适用于软件产品(作为合同的结果或面向广泛应用而编写的程序,与做试验用的或仅供作者本人使用的程序有所不同).
不作为产品而编写的程序常常没有正规的需求和目标.
对于这些程序,功能测试可能就是惟一的更高级别的测试.
同时,对更高级别测试的需求是与程序的规模一同增长的.
这是由于在大型程序中,设计错误(在早期开发阶段所犯的错误)与编码错误之间的比率要比在小程序中的比率高很多.
最终用户需求目标外部规格说明系统设计程序结构设计模块接口规格说明代码验证验证验证验证验证验证验证验收测试系统测试功能测试集成测试模块测试安装测试图6-3开发过程与测试过程的对应关系第6章更高级别的测试未经同意,严禁以任何形式拷贝98注意,图6-3所示的测试过程顺序并不定意味着严格的时间顺序.
举例来说,由于系统测试并非定义为"功能测试之后进行的测试类型",而是定义为一种特定类型的测试,关注于具体类型的错误,因此它很有可能与其他测试过程在时间上发生部分重叠.
在本章中,我们将讨论功能测试、系统测试、验收测试和安装测试的过程.
在这里忽略了集成测试,因为集成测试往往并不作为一个独立的测试步骤,而且在进行增量模块测试时,它是模块测试的隐含部分.
我们将简要讨论这些测试过程,并且大多不提供范例,因为这些更高级别的测试所使用的特定测试技术是与具体的被测程序高度相关的.
举例来说,对操作系统进行的系统测试的特点(测试用例的类型、测试用例设计的方式、使用的测试工具)与对编译器、核反应堆控制程序或数据库应用程序所进行的系统测试的特点有很大不同.
本章的最后几节将会计论测试计划和测试组织等话题,以及决定何时终止测试这一重要问题.
6.
1功能测试(FunctionTesting)如图6-3所示,功能测试是一个试图发现程序与其外部规格说明之间存在不一致的过程.
外部规格说明是一份从最终用户的角度对程序行为的精确描述.

除了在小程序中的使用情况之外,功能测试通常是一项黑盒操作.
也就是说,要依赖早期的模块测试的过程来实现理想的白盒逻辑覆盖准则.
在进行功能测试时,需要对规格说明进行分析以获取测试用例集.
本书第4章所讨论的等价类划分方法、边界值分析方法、因果图分析方法和错误猜测方法尤其适合于功能测试.
实际上,第4章中的例子就是功能测试的范例.
对FORTRAN语言的DIMENSION语句、考试评分程序以及DISPLAY命令的描述实际上就是外部规格说明的例子(但是,请注意它们并不是完全现实的例子:例如真正的评分程序的外部规格说明应该包括对报告格式的准确描述).
因此,在本节中不再列举有关功能测试的例子.
软件测试的艺术未经同意,严禁以任何形式拷贝99本书第2章中的很多原则也特别适合于功能测试.
跟踪哪些功能暴露出的错误数量最多,这个信息作常重要,因为它告诉我们这些功能很可能还包含着大多数尚未发现的错误.
应记住对无效和未预想到的输入条件给予足够的重视.
回想一下,对预期结果的定义是测试用例的重要部分.
最后,应始终牢记功能测试的目的是为了暴露程序的错误以及与规格说明不一致之处,而不是为了证明程序符合其外部规格说明.
6.
2系统测试(SystemTesting)系统测试最容易被错误理解,也是最困难的测试过程.
系统测试并非是测试整个系统或程序功能的过程,因为有了功能测试,这样会显得多余.
如图6-3所示,系统测试有着特定的目的:将系统或程序与其初始目标进行比较.
给定这个目标之后,隐含两方面的含义:1.
系统测试并不局限于系统.
如果产品是一个程序,那么系统测试就是一个试图说明程序作为一个整体是如何不满足其目标的过程.
2.
根据定义,如果产品没有一组书面的、可度量的目标,系统测试也就无法进行.
在寻找程序与其目标之间的不一致的过程中,应重点注意那些在设计外部规格说明的过程中所犯的转换错误.
系统测试因而成为一种关键的测试类型,因为就软件产品本身、所犯错误的数量及其严重性而言,开发周期的这个阶段是最易出错的.
这也暗示与功能测试的情况不同,外部规格说明不能作为获得系统测试用例的基础,否则就破坏了系统测试的目标.
然而另一方面,也不能利用目标文档本身来表示测试用例,因为根据定义,这些文档并不包含对程序外部接口的准确描述.
克服这一两难局面的方法是利用程序的用户文档或书面材料.
通过分析目标文档来设计系统测试,分析用户文档来阐明测试用例.
该方法能够产生两方面的作用,一是将程序与其目标和用户文档相比较,二是同时也将用户文档与程序目标相比较,如图6-4所示.
第6章更高级别的测试未经同意,严禁以任何形式拷贝100图6-4系统测试图6-4说明为什么系统测试是最困难的测试过程.
图中最左边的箭头表示将程序与其目标进行比较,是系统测试的核心目的,但是没有说明使用什么样的测试用例设计方法.
因为目标文档阐述了程序应该做什么、做到什么程度,却没有说明程序功能如何表现.
举例来说,本书第4章中定义的DISPLAY命令的目标如下:该命令用来从终端查看主存储空间中的内容,其语法应与所有其他系统命令的语法相一致.
用户可以通过一个地址范围或者一地址加上一数值来定义空间范围.
该命令操作符应具有合理的默认值.
命令的输出可以分多行显示多个字(以十六进制形式),字与字之间以空格相隔.
每一行须包含该行第一个字的地址.
该命令是条"不太重要的"指令,意味着其在合理的系统负载下,应在两秒之内开始显示输出,输出各行之间不应有可觉察的延时.
命令处理器中发生的编程错误在最坏情况下可能导致该命令失效;而系统以及用户交互则不应受到影响,系统投入使用之后,命令处理器中包含的用户发现的错误不应超过一个.
软件测试的艺术未经同意,严禁以任何形式拷贝101目标虽已阐明,但并没有确认生成测试用例集的方法,仅有一些含糊却有用的指南来指导如何编写测试用例,以试图证明程序与目标文档中每一条语句都存在着不一致性.
因此,系统测试采取了一种不同的测试用例设计方法,不是描述一项技术,我们讨论的是不同类型的系统测试用例.
由于没有一个方法,系统测试需要大量的创造性.
事实上,设计好的系统测试用例比设计系统或程序需要更多的创造性、智慧和经验.
这里我们将讨论15种测试用例,我们并不认为所有的15种类型都适用于任何程序,但是为了避免有所遗漏,设计测试用例时应考虑全部的15种类型.
6.
2.
1能力测试(FacilityTesting)最明显的系统测试类型是判断目标文档提及的每一项能力(或功能,为了避免与功能测试发生混淆而不使用"功能"一词)是否都确实已经实现.
能力测试的过程是逐条语句地检查目标文档,当某条语句定义了一个"要做什么"(例如,"语法应该一致……"、"用户应当可以指定一个空间范围……"等),就判断程序是否满足.
此种类型的测试常常可以在不使用计算机的情况下进行;有时人工对目标和用户文档进行比较就足够了.
尽管如此,利用问题检查单将有助于在下一次进行测试时,确保人工检查的目标是相同的.
6.
2.
2容量测试(VolumeTesting)第二类系统测试是使程序经受大容量数据的检验.
举例来说,编译器可能要编译规模非常庞大的源程序,连接编辑器可能需要处理一个包含上千模块的程序,电子电路模拟器可能要输入一个包含上千部件的电路,而操作系统的作业队列可能已经达到饱和的容量.
如果程序需要处理跨越不同卷的文件,则应产生足够的数据使程序从一个卷转换到另一个中.
换言之,容量测试的目的是为了证明程序不能处理目标文档中规定的数据容量.
由于容量测试显然需要大量的资源,鉴于对机器和工时的考虑,不可进行过多的容量测试.
当然,每个程序应该至少进行几次容量测试.
第6章更高级别的测试未经同意,严禁以任何形式拷贝1026.
2.
3强度测试(StressTesting)强度测试使程序承受高负载或强度的检验.
这不应和容量测试发生混淆;所谓高强度是指在很短的时间间隔内达到的数据或操作的数量峰值.
类似的情况是测试一名打字员.
容量测试是判断打字员能否处理大篇幅的稿子,而强度测试则是判断打字员能否达到每分钟50个单词的速度.
由于强度测试涉及时间因素,因此,它不适用于很多程序,如编译器或批处理工资程序.
然而,强度测试适用于在可变负载下运行的程序,以及交互式程序、实时程序和过程控制程序.
假如某个空中交通控制系统要求在其区域内最多可跟踪200架飞机,则可以通过模拟200架飞机存在的情况来对其进行强度测试.
由于在客观上无法避免第201架飞机进入该区域,因此需要进一步的强度测试,以考察系统对这个不速之客的反应.
附加的强度测试则会模拟大量飞机同时进入该区域的情况.
如果操作系统要求支持最多15个多道程序的作业,则可尝试同时运行15个作业对其进行强度测试.
可以让学员强行打左舵、后拉节流阀、放下襟翼、抬起机头、放下起落架、打开着陆灯并向左转弯等所有这些操作同时进行,观察系统如何反应,从而对飞行员训练模拟器进行强度测试(这个测试用例可能需要一个长着四只手的飞行员,或者现实一点,需要飞行座舱里有两个测试专家).
可以通过让所有被监视的过程同时产生信号,来对过程控制系统进行强度测试.
当对电话交换系统进行强度测试时,可以让大量电话同时打入该系统.
基于web的应用程序是最常按受强度测试的软件之一.
在这里,我们需要确信的是应用程序及硬件能够处理一定容量的并发用户.
读者可能会争辩说,也许有数百万人在同一时刻访问站点,但这是不现实的.
我们需要弄清用户群,然后设计一个强度测试,体现出可能访问站点的最大人群的情况.
本书第9章将提供关于测试基于Web应用程序的更多信息.
虽然有很多强度测试体现的是程序在运行过程中可能会遇到的情况,然而也有另一些强度测试确实体现了"不可能发生"的情况,但这并不意味这些测试是无用的.
如果在这些不可能发生的情况中检查出了错误,那么这项测试就是有价值的,软件测试的艺术未经同意,严禁以任何形式拷贝103因为同样的错误也可能发生在现实的、强度稍低的环境中.
6.
2.
4易用性测试(UsabilityTesting)系统测试的另一个重要类型是试图发现人为因素或易用性的问题.
当本书的第一版出版时,计算机行业并不太注意研究和定义编程系统中良好的人为因素问题.

今天的软件系统,尤其是那些为广大商业市场而设计的软件,通常都进行了广泛的人为因素的研究,而现在的软件自然也受益于过去的成千上万的程序和系统.
然而,对人为因素的分析依然是一项极为主观的事情.
在以下的清单中,我们列举了需要测试的一些问题:1.
每个用户界面是否都根据最终用户的智力、教育背景和环境要求而进行了调整2.
程序的输出是否有意义、不模糊且没有计算机的杂乱信息3.
错误诊断(如错误信息)是否直接,用户是否需要有计算机学科的博士学位才能理解它门举例来说,程序是否产生了诸如"IEK022AOPENERRORONFILE'SYSIN'ABENDCODE=102"此类的信息象这样的信息在二十世纪七、八十年代的软件系统中并不鲜见.
今天的面向大众销售的系统在这方面有了改进,但我们仍然会遇到诸如"出现一个未知错误"或"程序遇到了一个错误,必须重新启动"这样无用的信息.
自己编写的程序是由自己控制的,不应加入这些无用的信息.
即使我们并不开发软件,如果在测试小组中工作,那么可以推动人机界面这个领域的改进.
4.
整体的用户界面是否在语法、惯例、语义、格式、风格和缩写方面展现出了相当程度的概念完整性,基本的一致性和统一性5.
在准确性极为重要的环境里,如网上银行系统,输入中是否有足够的冗余信息举例来说,该系统可能会要求输入账号、用户名和PIN(个人识别号)来验证访问账户信息的是合法用户.
6.
系统是否包含过多或不太可能用到的选项现代软件的一个趋势是,仅向用户提供那些基于软件测试和设计考虑而确定出的最有可能使用的菜单选项.
一个设计良好的软件可以向用户学习,并开始向不同的用户展示其经常访问的菜单项.
即使已经有了这样智能化的菜单系统,仍需要设计成功第6章更高级别的测试未经同意,严禁以任何形式拷贝104的软件,使得对不同选项的访问合乎逻辑、符合直觉.
7.
对于所有的输入,系统是否返回了某些类型的即时确认信息举例来说,在点击鼠标进行输入的环境里,被选项可以变换颜色,或者某个按钮对象可以显示凹进或凸起的状态.
如果要让用户从列表中选择,那么当用户做出选择后被选序号应显示在屏幕上.
还有,如果被选的操作需要一些运行时间(如果软件正在访问一个远程的系统,情况常会如此),那么应显示一条信息通知用户当前正在做什么.
8.
程序是否易于使用举例来说,如果输入是区分大小字符的,这一点对用户来说是否清楚此外,如果程序要求浏览一系列的菜单或操作,那么返回到主菜单的方法是否清楚用户是否可以很容易浏览到上一层或下一层6.
2.
5安全性测试(SecurityTesting)由于社会对个人隐私的日益关注,许多软件都有特别的安全性目标.
安全性测试是设计测试用例来突破程序安全检查的过程.
举例来说,我们可以设计测试用例来规避操作系统的内存保护机制,破坏数据库管理系统的数据安全机制.
设计此种测试用例的方法之一是研究类似系统中已知的安全问题,然后生成测试用例,尽量暴露被测系统存在相似问题.
例如,在杂志,聊天室和新闻组中发布的资料,经常包含有操作系统或其他软件系统的已知错误.
通过在与被测软件提供相似服务的现有系统中搜寻安全漏洞,可以设计测试用例来判断软件是否受到类似问题的困扰.

基于web的应用程序常常比绝大多数程序所需的安全测试级别更高.
对于电子商务网站尤其如此.
尽管已经有了足够多的技术(例如密码学)允许客户在因特网上安全地完成交易,但不能单纯依赖技术的应用来确保安全.
除此之外,我们必须向客户群证明软件是安全的,否则就会有失去客户的风险.
另外,本书第9章提供了更多的有关基于因特网的应用程序的安全性测试的资料.
6.
2.
6性能测试(PerformanceTesting)很多软件都有特定的性能或效率目标,这终特性描述为在特定负载和配置环境下程序的响应时间和吞吐率.
再一次强调,由于系统测试的目的是为了证明程序不软件测试的艺术未经同意,严禁以任何形式拷贝105能实现其目标,因此应设计测试用例来说明程序不能满足其性能目标.
6.
2.
7存储测试(StorageTesting)类似地,软件偶尔会有存储目标,举例来说,可能描述了程序使用的内存和辅存的容量,以及临时文件或溢出文件的大小.
应设计测试用例来证明这些存储目标没有得到满足.
6.
2.
8配置测试(ConfigurationTesting)诸如操作系统,数据库管理系统和信息交换系统等软件都支持多种硬件配置,包括不同类型和数量的I/O设备和通信线路,或不同的存储容量.
通常可能的配置数量非常之大,以至于测试无法面面俱到,但是至少应该使用每一种类型的设备,以最大和最小的配置来测试程序.
如果软件本身的配置可忽略掉某些程序组件,或可运行在不同的计算机上,那么该软件所有可能的配置都应测试到.
如今的很多软件都设计成可运行在多种操作系统下,因此如果测试此类程序,应该在该程序面向的所有操作系统环境中对其进行测试.
对设计在Web浏览器里运行的程序,需要特别的注意,因为Web浏览器的种类繁多,并不是所有浏览器都按同样方式运行.
除此之外,即使是同一种Web浏览器,在不同的操作系统之下,运行方式也会不同.
6.
2.
9兼容性/配置/转换测试(Compatibility/Configuration/ConversionTesting)大多数开发的软件都并不是全新的,常常是为了替换某些不完善的系统.
这样的软件往往有着特定的自标,涉及与现有系统的兼容以及从现有系统的转换过程.

再次强调,在针对这些目标测试程序时、测试用例的目的是证明兼容性目标未被满足,转换过程并未生效.
在将数据从一个系统转移到另一个系统时,应尽力发现错误.
升级数据库管理系统就是一个例子.
需要确定现有的数据安置到了新的系统中.
有很多不同的方法测试这个过程;但这些方法都高度依赖于所使用的数据库系统.

第6章更高级别的测试未经同意,严禁以任何形式拷贝1066.
2.
10安装测试(InstallabilityTesting)有些类型的软件系统安装过程非常复杂,测试安装过程是系统测试中的一个重要部分.
对于包含在软件包中的自动安装系统而言,这尤其重要.
安装程序如果出现故障,会影响用户对软件的成功体验.
用户的第一次体验来自于安装软件的过程.
如果这个过程进行得很槽糕,用户或顾客就要么寻找其他的产品,要么对软件的有效性不抱太大信心.
6.
2.
11可靠性测试(ReliabilityTesting)当然,所有类型的测试都是为了提高软件的可靠性,但是如果软件的目标中包含了对可靠性的特别描述,就必须设计专门的可靠性测试.
测试可靠性目标可能很困难.
举例来说,诸如公司广域网(WAN)或因特网服务供应商(ISP)等现代在线系统在整个运行期间,正常运行时间应占99.
97%.
我们现在还不太可能花上数月甚至数年的时间来测试这个目标.
今天的关键软件系统的可靠性标准甚至更高,而现今的硬件可以令人信服地保障这个目标的实现.
但如果软件或系统有更为适中的平均故障间隔时间(MTBF)目标或合理的(以测试而言)功能错误目标,就有可能对其进行测试.
例如,MTBF值不超过20个小时,或者系统目标是程序在投入使用之后暴露的不同错误的数量不得超过12个,那么就可以进行测试,特别是使用了统计的、程序验证的或基于模型的测试方法之后.
这些方法都超出了本书的范围,但有些技术文献(网上或其他方面的)对这个领域提供了充分指导.
举例来说,如果读者对软件测试的这个领域感兴趣,可以研究归纳断言的概念.
这种方法的目的是设计出有关被测软件的一系列定理,作为判断软件中不存在错误的依据.
这种方法首先要对程序的输入条件及其正确结果编写断言.
断言用形式逻辑的符号表示,通常是一阶谓词演算.
然后需要确定程序中的每个循环,对于每一个循环都写一个断言,描述出在循环中任意点都不变的(总为真)条件.
程序现在已经被划分为固定数量的固定长度的路径(在成对的断言中的全部所有路径).
对于每一条路径,取中间程序语句的语义来修改断言,最终到达路径的终点.
此时在路径的终点处存在着两条断言:原先的断言以及从路径的另一个终点处断言引申出软件测试的艺术未经同意,严禁以任何形式拷贝107的断言.
然后写出一条定理,说明原先的断言隐含着引申出的断言,并试图证明这个定理.
如果能够证明该定理,就可以认为该程序不存在错误——只要程序最终能够结束,需要单独的证明来说明程序总会结束.
虽然此种类型的软件证明或预测听起来非常复杂,但可靠性测试及软件可靠性工程(SRE)的概念已经为我们所认识,并且对于那些必须维持非常高的正常运行时间的系统,其重要性日益增加.
为了说明这一点,请查看表6-l中某个系统为支持不同的正常运行时间的需要而每年必须达到的运行小时数.
这些数字能够说明对SRE的需求.
表6-1不同正常运行时间要求的小时数/年正常运行时间的比例需求要求的运行小时/年1008760.
099.
98751.
2988584.
8978497.
2968409.
6958322.
06.
2.
12可恢复性测试(RecoveryTesting)诸如操作系统、数据库管理系统和远程处理系统等软件通常都有可恢复性目标,说明系统如何从程序错误、硬件失效和数据错误中恢复过来.
系统测试的一个目标是证明这些恢复机制不能够正确发挥作用.
我们可以故意将程序错误置入某个系统中,判断系统是否可以从中恢复.
诸如内存校验错误或I/O设备错误等硬件错误也可以进行模拟.
而如通信线路中的嗓音或数据库中的无效指针等数据错误可以故意生成或模拟出来,以分析系统的反应.
这些系统的设计目标之一是使平均恢复时间(MTTR)最小.
系统宕机才通常会减少公司的收入.
我们的一个测试目标是证明系统不能满足MTTR的服务合同.
MTTR往往有上界和下界,所以测试用例应反映出这些界限.
6.
2.
13适用性测试(ServiceabilityTesting)软件还可能有适用性或可维护性的目标.
所有的此类目标都必须测试到.
这些第6章更高级别的测试未经同意,严禁以任何形式拷贝108目标可能定义了系统提供的服务辅助功能,包括存储转存程序或诊断程序、调试明显问题的平均时间、维护过程以及内部业务文档的质量等.
6.
2.
14文档测试(DocumentationTesting)如同我们在图6-4中所描述的那样,系统测试也需要检查用户文档的正确性.
完成此任务的主要方法是根据文档来确定系统测试用例的形式.
也就是说,一旦设计完成某个具体的测试情况,应该使用文档作为编写实际测试用例的指南.
同时,用户文档应成为审查的对象(类似于本书第3章中的代码审查的概念),检查其正确性和清晰性.
在文档中描述的任何范例应编成测试用例,并提交给程序.

6.
2.
15过程测试(ProcedureTesting)最后,很多软件都是较大系统的组成部分,这些系统并不完全是自动化的,包含了很多人员操作过程.
在系统测试中,必须对所有已规定的人工过程,如系统操作员、数据库管理员或最终用户的操作过程进行测试.
举例来说,数据库管理员必须记录备份和恢复数据库系统的操作过程.
在可能的情况下,应由与数据库管理不相关的人来测试这些过程.
然而,公司必须为充分测试这些过程而提供所需的资源,这些资源通常包括硬件和额外的软件许可证.

6.
2.
16系统测试的执行系统测试执行中一个最关键的考虑是决定由谁来进行测试.
我们从反面来回答这个问题:(l)不能由程序员来进行系统测试;(2)在所有的测试阶段之中,这是惟一明确不能由负责该程序开发的机构来执行的测试.
第一点基于的事实是,执行系统测试的人思考问题的方式必须与最终用户相同,这意味着必须充分了解最终用户的态度和应用环境,以及程序的使用方式.
那么显然的是,如果可行的话,一位或多位最终用户是很好的执行测试的候选人.
但是,由于一般的最终用户都不具备执行很多前面所描述的测试类型的能力或专业技术,因此,理想的系统测试小组应由几位专业的系统测试专家(以执行系统测试作为职业)、一位或两位最终用户的代表、一位人类工程学工程师以及该程序主要的软件测试的艺术未经同意,严禁以任何形式拷贝109分析人或设计者所组成.
将原先的设计者包括进来并不违反先前的测试原则,即不提倡测试由自己编写的程序.
因为程序自构思以来已经历经人手,所以原先的设计者不会再受到心理束缚的影响,对程序的测试不会再触及该原则.
第二点基于的事实是,系统测试是一项"随心所欲,百无禁忌"的活动,而软件开发机构会受到心理束缚,有悖于此项活动.
而且大多数的开发机构最为关心的是让系统测试进行得尽可能顺利并按时完成,而不会尽力证明程序不能满足其目标.
系统测试至少应由很少(如果有的话)受开发机构左右的独立人群来执行.
也许最经济的执行系统测试的方式(所谓经济,是指花一定的成本发现最多的错误,或利用更少的费用发现相同数量的错误)是将测试分包给一个独立的公司来完成.

这一点将在本章的后面章节进一步讨论.
6.
3验收测试(AcceptanceTesting)让我们回到图6-3所示的开发过程的完整模型上来,可以看到验收测试是将程序与其最初的需求及最终用户当前的需要进行比较的过程.
这是一种不寻常的测试类型,因为该测试通常是由程序的客户或最终用户来进行,一般不认为是软件开发机构的职责.
对于软件按合同开发的情况,由订购方(用户)来进行验收测试,将程序的实际操作与原始合同进行对照.
如同其他类型的测试一样,验收测试最好的方法是设计测试用例,尽力证明程序没有满足合同要求.
假如这些测试用例都是不成功的,那么就可以接受该程序.
对于软件产品的情况,如计算机制造商的操作系统或编译器,或是软件公司的数据库管理系统,明智的用户首先会进行一次验收测试以判断产品是否满足其要求.
6.
4安装测试(InstallationTesting)图6-3描述的测试过程的剩余部分是安装测试.
安装测试在图6-3中的位置有些不寻常,它与所有其他测试过程不同.
与设计过程中的任何阶段都没有联系.
它的不寻常是由于其目的不是为了发现软件中的错误,而是为了发现在安装过程中出现的错误.
在安装软件系统期间会发生很多事件.
作为示例的简短列表包括了下列事件:第6章更高级别的测试未经同意,严禁以任何形式拷贝1101.
用户必须选择大量的选项.
2.
必须分配并加载文件和库.
3.
必须进行有效的硬件配置.
4.
软件可能要求网络联通,以便与其他软件连接.
安装测试应由生产软件系统的机构来设计,作为软件的一部分来发布,在系统安装完成之后进行.
除此之外,测试用例需要检查以确认已选的选项集合互不冲突,系统的所有部件全部存在,所有的文件已经创建并包含必需内容,硬件配置妥当等.
6.
5测试的计划与控制如果认为测试一个大型软件系统可能需要编写、执行和验证数万个测试用例、处理数千个模块、改正数千个错误、雇佣数百人花费一年甚至更长的时间工作,那么很明显我们在计划、监视和控制测试过程方面遇到了巨大的项目管理挑战.
事实上,这些问题非常繁杂,我们可以将整本书都用来讨论软件测试的管理问题.
本节的初衷是总结其中的一些问题.
正如第2章所提到的,在计划测试过程中最常出现的主要错误是默认为不会发现软件缺陷.
这个错误带来的显然结果是对计划投人的资源(人力、时间表及计算机时间)明显估计不足,这在计算机行业内是个声名狼藉的问题.
造成这个问题的原因是测试阶段处于开发周期的最后阶段,致使调整资源非常困难.
另外,可能是更重要的问题,即对软件测试的定义有误,因为很难看到对测试正确定义(测试的目的是发现错误)的人在假定找不到任何错误的情况下去计划一个测试.

与大多数项目的情况一样,计划是管理测试过程中至关重要的一环.
一个良好的测试计划应包括:1.
目标.
必须定义每个测试阶段的目标.
2.
结束准则.
必须制定准则以规定每个测试阶段何时可以结束,该问题将在下一节中讨论.
3.
进度.
每个阶段都须有时间表.
应指出何时设计、编写和执行测试用例,某些软件技术,如极限编程(在本书第8章中讨论)要求在程序编码开始之前就设计测试用例和单元测试.
软件测试的艺术未经同意,严禁以任何形式拷贝1114.
责任.
对于每一个阶段,应当确定谁来设计、编写和验证测试用例,谁来修改发现的软件错误.
由于在大型项目中讨论特定的测试结果是否代表错误时,有可能出现争端,因此还需要确定一名仲裁者.
5.
测试用例库及标准.
在大型项目中,用于确定、编写以及存储测试用例的系统方法是必须的.
6.
工具.
必须确定需要使用的测试工具,包括计划由谁来开发或采购、如何使用工具以及何时需要使用工具.
7.
计算机时间.
计划每个测试阶段所需的计算机时间,包括用来编译应用程序的服务器(如果需要的话)、用来进行安装测试所需的桌面计算机、用来运行基于web应用程序的web服务器、联网的设备(如果需要的话)等等.
8.
硬件配置.
如果需要特别的硬件配置或设备,则需要一份计划来描述该需求,该如何满足需求以及何时需要满足.
9.
集成.
测试计划的一部分是定义程序如何组装在一起的方法(例如自顶向下的增量测试).
一个系统如果包含大的子系统或程序,可按增量的方式组装在一起,例如可以使用自顶向下或自底向上的方法,但是这些构造块是程序或子系统,而不是模块.
如果是这种情况,就需要一个系统集成计划.

系统集成计划规定了系统集成的顺序、系统每个版本的功能以及编写"脚手架"代码以模拟不存在的部件的职责分工.
10.
跟踪步骤.
必须跟踪测试进行中的方方面面,包括对错误易发模块的定位,以及有关进度、资源和结束准则的进展估计.
11.
调试步骤.
必须制定上报已发现错误、跟踪错误修改进程以及将修改部分加入系统中去的机制.
调试计划中还应包括进度、责任分工、工具以及计算机时间/资源等.
12.
回归测试.
回归测试在对程序作了功能改进或进行了修改之后进行,其目的是判断程序的改动是否引起了程序其他方面的退步.
回归测试通常重新执行测试用例中的某个子集.
回归测试很重要,因为对程序的改动和对错误的纠正要比原来的程序代码更容易出错(与报纸排版错误很相似,这些错误通常由于最后所做的编辑改动而引起的,而不是修改先前版本而引起的).
回归测试计划规定了测试人员、测试方法和测试时间,它也是必须的.
第6章更高级别的测试未经同意,严禁以任何形式拷贝1126.
6测试结束准则在软件测试过程中最难回答的一个问题,是判断何时终止测试,因为我们无法知道刚刚发现的错误是否是最后一个错误.
事实上,除了非常小的软件,期望所有的错误最终都能被发现是不切实际的.
在这种两难情况之下,而且基于经济条件也要求测试必须最终结束的事实,我们可能会产生疑惑,是极其武断地回答此问题呢,还是存在一些有用的终止准则我们在实际中使用的典型的结束准则既无意义,也不能实现目标.
最常见的两个准则是:1.
用完了安排的测试时间后,测试使结束.
2.
当执行完所有测试用例都未发现错误,测试便结束.
也就是说,当所有的测试用例不成功时便结束.
第一条准则没有任何作用,因为我们可以完全什么都不做也可满足它.
它并不能衡量测试的质量.
第二条准则同样也是无用的,因为它也与测试用例的质量无关,而且也不能够实现测试目标,它下意识里鼓励我们编写发现错误可能性较低的测试用例.
正如本书第2章所述,人类是高度受目标驱使的.
如果一旦获悉所有的测试用例都不成功就完成了任务,那么人们就会下意识地朝这个目标编写测试用例,避开了有用的、高效的和具破坏性的测试用例.
有三类较为有用的结束准则.
第一类,但不是最佳的准则,根据的是特定的测试用例设计技术.
举例来说,我们会这样定义模块测试的结束准则:测试用例来源于(l)满足多重条件覆盖准则,以及(2)对模块接口规格说明进行边界值分析,产生的所有测试用例最终都是不成功的.
我们会在满足下列情况时规定功能测试结束:测试用例来源于(l)因果图分析,(2)边界值分析,以及(3)错误猜测,产生的所有测试用例最终都是不成功的.
尽管这种类型的准则要优于前面提到的两条准则,但仍然存在三个问题.
首先,软件测试的艺术未经同意,严禁以任何形式拷贝113对于那些没有特定方法的测试阶段,如系统测试阶段,这类准则不起作用.
第二,它要依赖于主观的度量,因为没有办法保证测试人员适当而又严格地使用特定的方法,如边界值分析方法.
第三,不同于设置一个目标再让测试人员选择最佳的实现方法,它的做法正好相反,指定了测试用例设计的方法,却并不设定目标.
因此,这种类型的准则对于某些测试阶段有时很有效,但是只有在测试人员根据以往的经历,证明自己可以成功地使用测试用例设计方法时,这些准则方可适用.
第二类,也许也是最有价值的准则,是以确切的数量来描述结束测试的条件.

因为测试的目的是发现错误,为什么不将测试结束准则定为发现了某个既定数量的错误呢举例来说,在对某个具体模块进行模块测试时,直到发现了三个错误才可以认为测试结束了.
也许系统测试的结束准则应该规定为发现并修改了70个错误,或测试实际进行了3个月,无论以后发生什么.
应该注意的是,虽然这种准则强化了软件测试的定义,但它也有两个问题,每一个都是可以解决的.
一个问题是判断如何获得要发现的错误数量.
得到这一数字需要进行下面几个预测:1.
预测出程序中错误的总数量.
2.
预测这些错误中有多大比例可能通过测试而发现.
3.
预测这些错误中有多少是由各个设计阶段产生的,以及在什么样的测试阶段能够发现这些问题.
可以通过几种方法来大致预测错误的总数.
一种方法是利用以前程序的经验来预测出数字.
另外,还存在多种预测模型.
有些模块需要测试一段时间,记录下连续发现错误的间隔时间,然后将这些时间输入一个公式的参数中.
有些模块被置入一些已知但未公开的种子错误,测试一段时间后,检查被发现的种子错误与非种子错误的比例.
还有的模型则让两个独立的测试小组分别测试一段时间,然后检查各自找出的错误以及两个组找出的共同问题,再使用这些参数来预测错误的总数.
还有一种获得预计数字的粗略方法是使用行业范围内的平均值.
举例来说,在编码结束时(在进行代码走查或检查之前),一般程序中的错误数量大致是每100行语句中含4-8个错误.
上面列举的第二个预测(可以通过测试发现的错误比例)包含一定程度的随意猜测,考虑了程序的性质以及未发现的错误造成的后果.
第6章更高级别的测试未经同意,严禁以任何形式拷贝114关于错误是如何及何时产生的,我们现在得到的信息还很少,因此第二个预测最为困难.
现有的数据表明,在大型程序中,大约有40%的错误是编码和逻辑设计错误,剩下的错误则产生于早期的设计阶段.
为了使用这个准则,需要根据手头的程序做出自己的预测.
这里有一个简单的例子.
假设我们要着手测试一个10,000行语句的程序,进行代码检查之后剩余的错误数量预计每100行语句5个错误,并且我们估计,作为测试的目标,要检查出98%的编码和逻辑设计错误,以及95%的早期设计错误.
这样,错误总数为500.
在这500个错误之中,我们假设200个错误是编码和逻辑设计错误,300个是设计缺陷.
因此,我们的目标是找出196个编码和逻辑设计错误以及285个设计错误.
表6-2显示了对何时可能发现错误的近似合理的预测.
表6-2对错误发现时期的推测编码和逻辑设计错误设计错误模块测试65%0%功能测试30%60%系统测试3%35%总计98%95%如果我们计划进行4个月的功能测试、3个月的系统测试,可以建立如下3个结束准则:1.
当发现并修改了130个错误之后(估计的200个编码和逻辑设计错误中的65%),模块测试即告结束.
2.
当发现并修改了240个错误之后(200个错误的30%加上300个错误的60%),或功能测试进行了4个月之后,无论后面发生什么,功能测试即告结束.
加上第二条的原因在于,如果我们很快发现了240个错误,那么就很有可能表明我们低估了错误的总数,因此,不应很早就结束功能测试.
3.
当发现并修改了111个错误之后,或系统测试进行了3个月之后,无论以后发生什么,系统测试即告结束.
这类准则的另一个明显问题是过度地预测.
在上述例子中,如果在功能测试开始时剩余的错误数量少于240个会发生什么情况呢根据这条准则,我们可能永远也不能结束功能测试.
软件测试的艺术未经同意,严禁以任何形式拷贝115如果你仔细想一想,这里有一个奇怪的问题.
这个问题是错误的数量不够,程序的质量过高了.
我们可以不将其当成问题,因为这是个很多人都喜欢遇到的"问题".
如果这个问题确实发生了,可以根据常识来解决它.
如果我们在4个月内没有发现240个错误,项目经理可以聘请一个局外人来分析测试用例,判断问题究竟是测试用例不足,还是测试用例很棒却没什么错误可发现.
第三类结束准则表面上似乎很容易,其中却涉及许多判断和直觉.
它需要我们在测试过程中记录每单位时间内发现的错误数量.
通过检查统计曲线的形状,常常可以决定究竟是继续该阶段的测试,还是结束它并开始下一测试阶段.
假设某个程序正在进行功能测试,对每周发现的错误数量都进行了记录.
如果第7周的曲线如图6-5的上部所示,那么即使发现的错误数量已经达到了结束准则,此时结束测试也会显得草率,因为,在第7周里我们似乎仍处于高峰(发现很多错误),此时最明智的决定(记住我们的目标是发现错误)是继续功能测试.
如有必要,设计额外的测试用例.
然而另一方面,如果曲线处于图6-5的下部,错误发现率明显下降,意味着我们已经"啃干净"了功能测试这块骨头,也许最佳的行动是结束功能测试并开始新的测试类型(也许是系统测试).
当然,我们还必须考虑其他因素,比如错误发现率的降低是否是因为缺少计算机时间,或执行完了可用的测试用例.
第6章更高级别的测试未经同意,严禁以任何形式拷贝116图6-5通过记录单位时间内发现的错误来预测测试的结束图6-6显示了如果我们没有记录发现错误的数量,会发生什么情况.
该图显示了个非常大的软件系统的三个测试阶段.
一个显而易见的结论是,该项目在第6时段后不应转到别的阶段.
在第6时段,错误发现率还很高(对于测试人员而言,发现率越高越好).
然而在这个时候转移到下一个阶段,导致了错误发现率的明显下降.
最佳的结束准则可能是上述三种类型的组合.
对于模块测试而言,特别是由于多数项目在此阶段都没有正式跟踪已发现的错误,最佳的结束准则可能是第一类.

我们应该要求使用一系列具体的测试用例设计方法.
而对于功能测试和系统测试而言,结束准则可能是发现了既定数量的错误,或用完了计划的时间,再出现什么都不管,但条件是错误分析与时间图的对比表明测试的效率已很低了.
软件测试的艺术未经同意,严禁以任何形式拷贝117图6-6对某个大型项目测试过程的事后研究6.
7独立的测试机构在本章的前面章节和第2章中,我们强调了软件机构应避免测试自己的软件,其中的原因在于,负责开发程序的机构难以客观地测试同一程序.
就公司的架构而言,测试部门应尽可能远离开发部门.
事实上,最理想的是测试机构不应是同一个公司的一部分,因为如果不是这样,测试机构仍然会受到与开发部门同样的管理压力的影响.
解决这个矛盾的一个方法是雇佣独立的公司进行软件测试.
这是个好主意,不管是系统的设计和使用单位开发的这个软件,还是第三方单位开发的这个软件.
这种做法常被提及的好处是提升了测试过程中的积极性、建立了与开发机构的良性竞争、避免了测试过程处于开发机构的管理控制之下,以及独立的测试机构带来的解决问题的专业知识.
第7章调试(DEBUGGING)未经同意,严禁以任何形式拷贝118第7章调试(DEBUGGING)简单地讲,调试是执行一次成功的测试之后所要进行的工作.
记住,所谓成功的测试,是指它可以证明程序没有实现预期的功能.
调试是一个包含两个步骤的过程,从执行了一个成功的测试用例、发现了一个问题之后开始.
第一步,确定程序中可疑错误的准确性质和位置;第二步,修改错误.
虽然调试对于程序测试来说非常必要、不可或缺,但它似乎是软件开发过程中最不受程序员欢迎的部分之一.
其主要原因可能包括以下几点:个人自尊会从中阻挠.
不管我们是否喜欢,调试都说明了程序员并不完美,要么在软件的设计,要么在程序编码时会犯错.
热情耗尽.
在所有的软件开发活动中,调试是最耗费脑力的苦差事,况且,进行调试往往经受着来自机构或自身的巨大压力,必须尽可能快地改正问题.
可能会迷失方向.
调试是艰苦的脑力工作,因为发现的错误实际上可能会出现在程序的任何语句中.
也就是说,如果不首先检查程序,我们就不能绝对地肯定在一个薪金管理程序出具的支票中出现的数字错误不是由某个子程序引起的,该子程序要求操作员将一个特定的表格传输给打印机.
让我们以诊断一个物理系统为例子作对比,如汽车.
假如汽车在爬坡时熄火了(症状),那么我们可能会迅速而有效地排除掉某些部件——调频/调幅收音机、速度表或汽车门锁——引起该故障的可能.
根据我们对汽车引擎的整体了解,该故障一定是发生在引擎上,我们甚至可以排除掉某些引擎部件,如水箱和滤油器.
必须自力更生.
与其他软件开发活动相比,关于调试过程的研究、资料和正式的指南都比较少.
尽管本书是关于软件测试的,并不讨论调试,但这两个过程显然是相互联系的.
针对调试的两个步骤,即错误定位和错误修改,对错误进行定位可能解决了95%的软件测试的艺术未经同意,严禁以任何形式拷贝119问题.
因此,本章集中讨论错误的定位过程,当然是假定某个成功的测试用例已经发现了一个错误.
7.
1暴力法调试(DebuggingbyBruteForce)调试程序的最为普遍的模式是所谓的"暴力"方法.
这种方法之所以流行,是因为它不需要过多思考,是耗费脑力最少的方法,但同时也效率低下,通常来讲不是很成功.
暴力调试方法可至少被划分为三种类型:1.
利用内存信息输出来调试.
2.
根据一般的"在程序中插入打印语句"建议来调试.
3.
使用自动化的调试工具进行调试.
第一种类型,使用内存信息输出(通常使用十六进制或八进制格式粗略地显示所有的存储区域)是最缺乏效率的暴力调试方法,原因如下:难以在内存区域写源程序中的变量之间建立对应关系.
即使对下复杂程度较低的程序,内存信息输出也会产生数最非常庞大的数据,其中的大多数都是与调试无关的.
内存信息输出显示的是程序的静态快照,仅能显示出在某一个时刻程序的状态;为了发现错误,还需要研究程序的动态状态(随时间的状态变化).
内存信息输出很少可以精确地在错误发生的地方产生,因此无法显示在错误发生时程序的状态.
错误发生到输出内存信息这段时间之内程序执行的活动,可能会掩盖掉发现错误所需的线素.
通过分析输出的内存信息来发现问题的方法并不大多(因此很名程序员都是密切注视,急切地渴望着错误能神奇地从内存信息输出中自行暴露出来).
第二种类型,在失效的程序中插入输出变量值的语句,这种做法也不具有很强的优势.
它可能比内存信息输出要好一些,因为可以显示程序的动态状态,让我们检查的信息可以相对容易地与源程序联系起来.
但是这种方法同样也有很多缺点:它不是鼓励我们去思考程序中的问题,而主要是一种碰运气的方法.
第7章调试(DEBUGGING)未经同意,严禁以任何形式拷贝120它所产生的需要分析的数据量非常庞大.
它要求我们修改程序,这些修改可能会掩盖掉错误、改变关键的时序关系,或者会引入新的错误.
它可能对小型程序有效,但如果应用到大型程序,成本就相当高.
况且对于某些类型的程序,如操作系统或过程控制软件,这种办法甚至无法使用.

第三种类型,自动化调试工具的工作机制类似于在程序中插入打印语句,但是并不修改程序本身.
可以使用编程语言的调试功能,或使用特殊的交互式调试工具来分析程序的动态状态.
可能会用到的典型的语言功能有:产生可打印的语句执行轨迹的机制、子程序调用以及/或者对特定变量的修改等.
调试工具的一个共同的功能是可以设置断点,使程序在执行到某条特定语句或改动了某个特定变量的值时暂停执行,然后程序员就可以检查程序的当前状态.
同样,这种方法也主要是在碰运气,常常会生成数量过于庞大的无关数据.
这些暴力调试方法的主要问题在于:它们都忽略了思考的过程.
我们可以在调试程序和侦破谋杀案之间找出相似点来.
实际上,在几平所有的谋杀悬念小说中,谜案都是通过仔细分析线索,将表面上不重要的细节全联结起来而最终侦破的.
这不是一个使用蛮力的方法,要使用蛮力的是寻觅障碍物或搜寻财宝.
还有些证据表明,无论调试小组成员是富有经验的程序员还是学生,肯动脑筋而不是依赖别人帮助的人能够更快、更准确地发现程序错误.
因此,我们建议仅在下列情况下使用暴力调试方法:(l)其他的方法都失败了:(2)作为我们下面将会讨论的思考过程的补充,而不是替代方法.
7.
2归纳法调试(DebuggingbyInduction)很显然,认真的思考能够发现大部分错误,甚至不需要调试人员使用调试工具.
归纳是一种特殊的思考过程,可以从细节转到全局,也就是从线索(即错误的症状,可能是一个或多个测试用例的结果)出发,寻找线索之间的联系.

归纳的过程如图7-1所示.
软件测试的艺术未经同意,严禁以任何形式拷贝121确定相关数据组织数据研究数据间联系构造假设证明假设修改错误不能不能能能图7-1使用归纳法的调试过程归纳调试的步骤如下:1.
确定相关数据.
调试人员犯的一个主要错误是未能将所有可用的数据或症状都考虑进去.
第一步是列举出所有知道的程序执行的正确和不正确之处,这些不正确之处即是症状,让我们相信确实存在错误.
那些相似却不相同、且未引起症状出现的测试用例提供了额外的有价值的线索.
2.
组织数据.
记住,归纳意味着从特殊到一般,因此第二步是组织这些相关数据,以便观察线索间的模式,尤其重要的是要找到矛盾、事件,比如仅当客户的保险金账户收支不太平衡时出现的错误.
我们可以采用图7-2所示的表格来组织现有的数据.
"是什么"框列举的是总体的症状,"在何处"框描述了这些症状出现的地方,"多大程度"框描述了这些症状的范围和重要性.
注意"是"和"否'列,它们所描述的矛盾之处最终可能会导致对错误的假设.
IsIsnotWhatWhereWhenTowhatnext图7-2组织线索的一种方法3.
做出假设.
下一步是研究线索之间的联系,利用线索结构里可能的模式做出一个或多个关于错误原因的假设.
如果还无法做出推测,就需要更多的第7章调试(DEBUGGING)未经同意,严禁以任何形式拷贝122数据.
如果可能有多个假设存在,首先选择最有能的一个.
4.
证明假设.
考虑到调试在进行时所承受的压力,这个时期最主要的错误是忽略了这个阶段,直接跳到结论去改正问题.
但是在继续下一步之前,证明这些假设的合理性是非常重要的.
如果忽略了这一步,可能接下去只修改了问题症状,而没解决问题本身.
应将假设与其最初的线索或数据相比较,以此来证明假设的合理性,确定这些假设可以完全解释这些线索的存在.
如果无法解释,要么这些假设是无效的或不完整的,要么还有更多的错误存在.
举一个简单的例子、假设在第4章描述的考试评分软件报告了一个明显的错误.
错误是在某些但不是所有情况下,中间值似乎不正确.
在某个特殊的测试用例中,有51名学生被评分.
正确打印出宋的平均分数为73.
2,但打印出的中间值是26分,而不是预期的82分.
经过对该测试用例及其他一些测试用例结果的检查,线索按图7-3所示的形式进行组织.
IsIsnotWhat报告3中显示的中间值不正确计算平均值或标准偏差时出现Where仅在报告3中出现在其它报告中出现.
学生成绩的计算似乎正确When当测试学生为51时发生在测试学生数量为2和200时未发生Towhatnext显示的中间值为26.
当学生数量为1时也同样发生,显示的中间值.
图7-3组织线索的例子下一步是通过寻找模式和矛盾之处,做出关于该错误的假设.
我们看到的一个矛盾是这个错误似乎出现在学生人数为奇数的测试用例中,这也许是个巧合,但看来很重要,因为我们要根据学生人数为奇数或偶数而不同地计算中间值.
还有一个奇怪的模式:在些测试用例中,计算出来的中间值总是小于或等于学生的人数(26小等于51,l小等于1).
这时,一个可能的方法是再重新运行一次学生人数为51名的测试用例,给学生打与以前不同的分数,看一下是如何影响中间依的计算的.

如果中间值仍然是26,那么"否——多大程度"框可以填上"中间值似乎与实际分数无关".
尽管这个结果提供了一条有价值的线索,但即使没有它,我们可能已经软件测试的艺术未经同意,严禁以任何形式拷贝123能够猜出这个错误来.
从现有数据计算出的中间值似乎等于学生人数的一半,经过四舍五入后得到最接近的一个整数.
换句话说,如果将分数设想为存储在一个分类表里,该程序打印的是中间学生的人数而不是其成绩.
因此,我们有了一个关于该错误准确性质的坚定的假设.
下一步就是通过检查代码或执行一些附加的测试用例来证明这个假设.
7.
3演绎法调试(DebuggingbyDeduction)演绎的过程是从一些普遍的理论或前提出发,使用排除和精炼的过程,达到一个结论(错误的位置),参见图7-4.
列出可能的原因使用排除法提炼剩下的假设证明剩下的假设收集更多数据都被排除不能能修改错误图7-4使用演绎法的调试过程举个谋杀犯的例子,与归纳过程相反,首先从一系列嫌疑人入手,通过排除(花匠有当时不在现场的合理证词)和提炼(罪犯可能是红色头发)的过程,判断出管家可能犯了罪.
演绎的步骤如下:1.
列举出所有可能的原因或假设.
第一步是建立一份所有想象得到的错误线索的清单,线索不需要有完整的解释,它们纯粹是一些推测,帮助我们组织和分析现有的数据.
2.
利用数据排除可能的原因.
详细检查所有的数据,尤其寻找存在矛盾的地方(图7-2可以用在此处),然后尽量排除所有可能的原因,仅留下一条,如果所有的原因都排除掉了,需要增加额外的测试用例,得到更多的数据来设计新的推测.
如果剩下的原因多于一个,那么首先选择最有可能的原因,即主要假设.
3.
提炼剩下的假设.
此时的可能原因也许是正确的,但可能不够具体,不能指第7章调试(DEBUGGING)未经同意,严禁以任何形式拷贝124出错误来.
因此,下一步是使用现有的线索来提炼这个推侧.
举例来说,我们可能会首先想到"对文件中最后事务的处理可能存在错误",并将其提炼为"缓冲区中的最后事务被文件结束指示器覆盖".
4.
证明剩下的假设.
这个重要步骤与归纳法中的第4步骤相同.
举个例子,假设我们着手对第4章讨论的DISPLAY命令进行功能测试.
在由因果图分析方法确定的38个测试用例中,我们首先使用4个测试用例.
作为建立输入条件过程的一部分,我们对内存进行初始化,将第一个、第五个、第九个、…字的值设置为0000,将第二个、第六个、…字的值设置为4444,将第三个、第七个、…字的值设置为8888,将第四个、第八个、…字的值设置为CCCC.
也就是说,每个内存字单元都初始化为每个字的首字节地址中的低位十六进制数字(23FC、23FD、23FE和23FF地址的值为C).
图7-5显示了这些测试用例、预期的输出及测试用例的实际输出.
显然,我们遇到了一些问题,所有的测试用例都没有产生预期的结果(全部都成功了).
让我们从调试与第一个测试用例相关的错误开始.
该命令表明,从0地址开始(默认情况),要显示E(十进制中的14)个地址(回忆一下,规格说明定义所有的输出应每行包括4个字或16个字节).
测试用例的输入预期的输出实际的输出DISPLAY0000=000044448888CCCCM1INVALIDCOMMANDSYNTAXDISPLAY21v-290000020=000044448888CCCC000020=44448888CCCC0000DISPLAY.
11000010=000044448888CCCC000010=000044448888CCCC000000=000044448888CCCCDISPLAY8000-ENDM2STORAGEREOUESTEDISBEYONDACTUALMEMORYLIMITS008000=000044448888CCCC图7-5DISPLAY命令的测试用例输出结果为出现的不期望的错误信息列举可能的原因:1.
程序不能接受单词DISPLAY.
2.
程序不能接受句号.
3.
程序不允许第一个操作数为默认情况.
程序要求在句号之前声明一个存储地址.
软件测试的艺术未经同意,严禁以任何形式拷贝1254.
程序不允许E作为有效的字节数量.
下一步是尽力排除这些原因.
如果所有原因都排除掉了,那么需要退回去并扩充一下原因的清单.
如果剩下来的原因超过了一个,那么就需要检验额外的测试用例以确定惟一的错误假设,或继续使用可能性最大的原因.
由于我们手上还有其他测试用例,可以看到图7-5中的第二个测试用例似乎可以排除掉第1条假设,而第三个测试用例尽管产生了错误的结果,也似乎可以排除掉第2和第3条假设.
下一步是提炼第4条假设.
它看上去足够具体,但直觉告诉我们实质的内容要比表面上看到的多.
它看上去似乎是一个更为一般的错误实例.
那么我们可以认为程序不能正确识别特殊的十六进制字符A~F.
在其他测试用例中缺少这些字符,使得这听起来是一个行得通的解释.
然而我们不能马上得出结论,而应该首先考虑所有的已知信息.
第四个测试用例可能代表一个完全不同的错误,也可能提供了一条关于当前错误的线索.
假设系统的最高有效地址是7FFF,那么第四个测试用例将如何显示一个明显不存在的区域呢显示的值是我们初始化后的值而不是无用的信息,这个事实让我们推测该命令不知何故显示了0~7FFF之间的某些内容.
我们可能会想到,这种错误也许会发生在程序将命令的操作数当成十进制数(而不是规格说明中要求的十六进制数)的情况,第三个测试用例证实了这种假设,程序并未显示32个字节的内存单元的内容,而仅显示了16个字节,这与我们的假设是一致的,即"11"被当作了十进制数.
因此,提炼后的假设是,程序将字节数当作内存地址处理,并将输出列表中的内存地址当作十进制数.
最后的步骤是证明该假设.
看一看第四个测试用例,如果8000被解读为十进制数,则对应的十六进制数是1F40,这样就会产生我们所看到的输出.
作为进一步的证据,检查第二个测试测试用例.
输出是不正确的,但如果21和29被当作十进制数,那么内存地址15~ID中的内容将被显示出来,这是与测试用例的错误结果是一致的.
因此,我们几乎可以确切地定位错误了:程序认为操作数是十进制数,并将内存地址按十进制的值打印出来,这与规格说明是不符的.
而且,这个错误似乎是造成所有四个测试用例产生错误结果的原因.
经过一些思考,我们发现了这个错误,同时也解决了其他三个乍看起来毫不相关的问题.
注意,该错误可能在程序中的两个地方显现出来:解释输入命令的部分和在输第7章调试(DEBUGGING)未经同意,严禁以任何形式拷贝126出列表上打印内存地址的部分.
说句离题的话,这个可能由于错误理解规格说明而引起的错误进一步印证了我们的建议,即程序员不应该测试自己编写的程序.
如果程序员在犯了这个错误之后仍然去设计测试用例,很有可能在编写测试用例时犯同样的错误.
换句话说,程序员预料的输出将不同于图7-5所示的;这些输出将是按操作数是十进制数的理解而被计算出来的,因此这个基本的错误可能不会被察觉到.
7.
4回溯法调试(DebuggingbyBacktracking)在小型程序中定位错误的一种有效方法是沿着程序的逻辑结构回溯不正确的结果,直到找出程序逻辑出错的位置.
换句话说,从程序产生不正确结果(如打印了不正确的数据)的地方开始,从该处观察到的结果推断出程序变量应该是些什么值.
在头脑中,从这个位置开始逆向执行程序,重复使用"如果程序在此处的状态是这样的,那么程序在上面位置的状态就必然是那样的"过程,就能很快定位出错误.
使用这个过程,可以确定程序中从状态符合预期值的位置点,到第一个状态不符合预期值的位置点之间的范围.
7.
5测试法调试(DebuggingbyTesting)最后一个"思维型"的调试方法是使用测试用例.
这可能听起来有些奇怪,因为从本章一开始就将调试和测试区分了开来.
然而,考虑下面两种类型的测试用例.
供测试的测试用例,其目的是暴露出以前尚未发现的错误.
供调试的测试用例,其目的是提供有用的信息,供定位某个被怀疑的错误之用.
两者之间的区别是,供测试的测试用例会"胖"一些,因为我们尽量使用较少数量的测试用例来涵盖较多的条件,而供调试的测试用例则"瘦"一些,因为每个测试用例仅需要覆盖一个或几个条件.
换句话说,当发现了某个被怀疑的错误的症状之后,我们需要编写与原先有所变化的测试用例,尽量确定错误的位置.
实际上,这种方法不是一个完全独立的方法;它常常结合归纳法一起使用,以获得进行假设和/或证明假设所需的信息.
它也可以和演绎法一起使用,以排除有嫌疑的原因,提炼剩下的假设,并/或证明假设.

软件测试的艺术未经同意,严禁以任何形式拷贝1277.
6调试的原则在本节中,我们将讨论一系列的调试原则,在实质上也是心理学的原则.
与第2章的测试原则情况一样,这些调试原则有很多在直观上很明显,但却常常被遗忘或忽略.
由于调试的过程由两部分组成,即定位错误及修改错误,因此我们也将讨论两类原则,7.
6.
1定位错误的原则1.
动脑筋前面的章节隐含指出,调试是一个解决问题的过程.
最为有效的调试方法是动脑筋对错误症状的有关信息进行分析.
一个高效的程序调试人员应该不使用计算机就能定位大多数的错误.
2.
如果遇到了僵局,就留到稍后解决人类的潜意识是一个潜在的问题求解器.
我们经常提到的所谓灵感,其实就是当人类的意识停留在诸如吃东西、走路或看电影之上时,潜意识却正在思考另一个向题.
如果在合理时间内(也许小型程序为30分钟,大一点的程序为几个小时),我们还不能定位某个间题,就丢开它,做些其他的事情,因为思维的效率开始明显下降.
忘记这个问题一段时间之后,我们的潜意识可能已经解决了它,或者思维会焕然一新,可以重新检查问题的症状.
3.
如果遇到了困境,就把问题描述给其他人听与其他人交谈可能会帮助我们发现一些新的东西.
事实上,经常是仅仅将问题描述给一个好的倾听者时,我们就会突然找到问题的解决之道,而无需倾听者提供任何帮助.
4.
仅将测试工具作为第二种手段在试过了其他的方法之后才使用调试上具,并将其作为头脑思考的辅助手段,而不是替代手段.
正如本章前面所述,调试工具比如输出和跟踪工具,代表的是一种偶然的调试方法.
经验证明,不使用工具的人即使在调试并不熟悉第7章调试(DEBUGGING)未经同意,严禁以任何形式拷贝128的程序时,也要比使用工具的人更为成功.
5.
避免使用试验法——仅将其作为最后的手段调试程序的新手最常犯的错误是为了解决问题而试验性地去修改程序.
调试者可能会说:"我知道什么出错了,所以我要改动一下语句.
看一看会发生什么".
这种纯粹是无计划的方法甚至不属于调试.
它表现的是盲目的行动.
它获得成功的机会不仅很小,而且还会将新的错误引入程序,使问题更为复杂.

7.
6.
2修改错误的技术1.
存在一个缺陷的地方,很有可能还存在其他缺陷这是对本书第2章原则的重申,即发现程序某个部分存在一个错误时,该部分存在其他错误的可能性要高于没有发现错误时的可能性.
换句话说,错误有扎堆的倾向.
在修改某个问题的同时,应检查下紧临的地方,看看有没有任何可能是错误之处.
2.
应纠正错误本身,而不仅是其症状另一个普遍的错误做法是只修改了错误的症状或仅仅是该错误的一个实例,而不是错误本身.
如果所做的改正不符合错误的所有线索,那么可能只修改了错误的一部分.
3.
正确纠正错误的可能性并非100%如果将这个观点告诉一些人,他们当然会表示赞同,但是如果将它说给正在修改错误的人听,答案就可能不一样了("是的,对大多数情况是这样,但这个修改如此之小,它肯定百分之百地正确").
我们永远也不要假设为纠正错误而增加到程序中的代码是正确的.
用新的语句替换原来的语句,这种修改要远比程序中原先的代码更易发生错误.
言外之意是应对错误的修改进行测试,也许比对原先程序的测试还要严格.
一个严格的回归测试计划可以确保对某个错误的修改没有在程序的其他位置引入另外的错误.
4.
正确修改错误的可能性随着程序规模的增加而降低软件测试的艺术未经同意,严禁以任何形式拷贝129换句话说,根据我们的经验,由于修改不正确而引人的错误与原始错误之比,在规模较大的程序中呈递增趋势.
对于一个广泛使用的大型程序,每发现6个新错误,其中就有l个错误是由干先前对程序的改正而造成的.
5.
应意识改正错误会引入新错误的可能性我们不仅需要考虑到不正确的修改,而且还必须考虑到某个看似正确的修改会产生未料到的副作用,比如引入了一个新错误.
不仅存在修改无效的可能,还存在修改引入了新错误的可能.
言外之意是,不仅应在修改之后对错误的情境进行测试,还应执行回归测试以判断是否引入了新错误.
6.
修改错误的过程也是临时回到设计阶段的过程我们应该认识到修改错误也是程序设计的一种形式.
在认识到修改易产生错误的性质之后,常识告诉我们,在设计阶段使用的任何规程、方法和形式都同样适用于错误修改阶段.
举例来说,如果项目证明代码检查很管用,那么在修改错误之后进行代码检查就显得倍加重要.
7.
应修改源代码,而不是目标代码在调试大型系统,尤其是用汇编语言编写的系统时,偶尔会存在这样的修改错误的倾向,即先立即修改目标代码.
稍后再修改源程序.
这种方法带来了两个问题,(1)这通常是"通过试验进行调试"的信号;(2)目标代码与源程序不同步,这意味着当程序重新编译或重新汇编之后,同样的错误很容易又浮现出来,这是一种草率的、不专业的调试方法.
7.
7错误分析有关程序调试最后一个应认识之处是,调试除了有消灭程序中错误的价值之外,还有其他重要作用:它可以告诉我们软件错误的一些本质.
我们对此了解得非常之少.
关于软件错误本质的信息可以为改进将来的设计、编码和测试过程提供有价值的反馈信息.
任何程序员和编程机构都可以从详细分析发现的错误,或至少一部分错误的过程中获得提高.
错误分析是一项困难且费时的工作,相比"有百分之多少的错误是第7章调试(DEBUGGING)未经同意,严禁以任何形式拷贝130逻辑设计错误"、"有百分之多少的错误出现在IF语句中"这些肤浅的分类,它蕴涵的内容要多得多.
详细的错误分析会包括如下内容:z错误出现在什么地方这个问题是最难回答的问题之一,它需要通过对程序文档和项目历史进行回溯研究,但同时它也是最有价值的问题.
它要求我们指出该错误的源头和发生时间.
举例来说,错误的源头可能是规格说明中的一个模棱两可的语句,对先前错误的一次修改,或对最终用户需求的一个错误理解,z谁制造了这个错误如果发现有60%的设计错误都是由10名软件分析师中的某个人犯下的,或某程序员犯的错是其他程序员的3倍,难道这不是相当有用么(不是为了处罚某人,而是为了进行培训.
)z哪些做得不正确仅仅判断错误的发生时间和出现人员还不够,其中丢失的环节是准确地判断出错误发生的原因.
错误是由于某人写得不清楚是由干某人缺乏对该编程语言的培训是打字错误假设做得不对还是因为没有考虑有效输入z如何避免该错误的出现在下一个项目中可以进行哪些调整以避免该问题的出现此向题的答案就是我们所寻找的最为宝贵的反馈信息或知识.
z为什么错误没有早些发现如果错误是在测试引入段发现的,我们就应该研究为什么在更早些的测试阶段、代码审查和设计评审中没有发现该错误.
z该如何更早地发现错误这个问题的答案是另一个宝贵的反馈信息.
该如何改进评审和测试过程以便在将来的项目中更早地发现同类型的错误假设分析的问题不是由最终用户发现的(也就是说,是由测试用例发现的),我们就应意识发生了一些有价值的事情:我们编写了一个成功的测试用例.
这个测试用例为什么会成功我们是否能从中学习些什么,无论是针对该程序还是将来的程序,设计出更多的成功用例再一次申明,分析的过程是很艰难的,但是找到的答案为改进后续的编程实践提供极其宝贵的价值.
值得警惕的是,绝大多数的程序员和编程机构都尚未使用这种方法.
软件测试的艺术未经同意,严禁以任何形式拷贝131第8章极限测试20世纪90年代出现了一种名为极限编程(XP,ExtremeProgramming)的新型软件开发方法.
一位名叫KentBeck的项目经理设计了这种轻量、敏捷的开发过程,并于1996年在戴姆勒·克莱斯勒公司的项目中进行了首次测试.
尽管此后还出现了其他几种敏捷软件开发过程,但XP到目前为止最流行的敏捷软件开发过程.
事实上,现在已有众多的开源代码工具支持这种方法,这也证明了XP在开发人员和项目经理中的流行程度.
开发XP是为了支持诸如Java,VisualBasic及C#等编程语言的应用.
这些面向对象的语言使开发人员开发大型复杂应用的速度,比使用传统的C,C++,FORTRAN或COBOL语言更快.
使用后者开发程序常常需要构建通用的类库来支持你的工作.
诸如打印,排序,联网和统计分析等通用任务的实现方法并不是标准的构件.
而C#和Java等编程语言都含有全功能的应用编程接口(API),消除或减少了构建自定义类库的需要.
然而,伴随快速应用开发语言带来的好处,不利之处也随之而来.
虽然开发人员可以更快地开发应用程序,但其质量并未得到保证.
程序运行时经常不能满足程序规格说明的要求.
XP开发方法的目的是在短时间内开发高质量的程序.
传统的软件开发过程依然在发挥作用,但往往会花很多的时间,在竞争激烈的软件开发领域里这就相当于损失了收入.
XP模型高度依赖模块的单元和验收测试.
总的来说,对每个无论多小的递增的代码变更,都必须进行单元测试,以确保代码库满足其规格说明的要求.
事实上,测试在XP中的地位如此重要,以至于需要首先创建单元(模块)测试和验收测试,然后才创建代码库.
这种形式的测试被称为极限测试(XT,ExtremeTesting).
8.
1极限编程基础如前所述,XP是一种相当新的软件开发过程,使开发人员可以快速地产生高第8章极限测试未经同意,严禁以任何形式拷贝132质量的代码.
在这种情况下,我们可以将"质量"定义为代码库对其规格说明的满足程度.
XP重视采取简单的设计、在开发人员和客户之间建立联系、不断地测试代码库、重构以适应规格说明的变更,以及寻求用户的反馈.
XP更倾向于适合中小规模的软件开发,这些软件的规格说明的变更非常频繁,接近实时的沟通也是可能的.
XP与传统的开发过程相比有几处不同.
首先,它避免了大规模项目的综合症,即在开始编码之前客户与编程小组碰头,设计软件的每一个细节,项目经理们都知道这种做法的缺陷,因为为了反映新的业务谁则或市场情况,客户的规格说明和需求必须不停地变更.
举例来说,财务部门可能要求工资报表按处理时间,而不是按支票号码进行排序,而营销部门可能会判断出,如果它没发电子邮件的话.
用户将不会购买某产品.
XP的策划阶段将重点放在收集应用程序需求,而不是设计程序上.
XP方法的另一个不同之处是避免了编写不需要的功能.
如果客户认为某个功能虽然需要,但并不是要求实现的,那么在软件发行时通常不包含此项功能.
因此我们可以将重点集中在正在进行的任务上,为软件产品增加价植.
将精力集中在必需的功能之上,有助于在短时间内开发出高质量的软件.
然而,XP方法的主要不同之处是其将精力集中在测试上.
在经历了一个非常全面的设计阶段之后,传统的软件开发模型会建议首先编码,然后才生成测试接口,但在XP方法中,我们必须首先生成单元测试用例,然后才编写代码通过测试.
根据本书第5章中讨论的概念,可以设计出XP环境中的单元测试.
XP开发模型用12个核心实践来驱动该过程.
表8-l总结了这些实践.
简单来说.
这12个核心的XP实践可以归纳为4个概念:1.
聆听客户和其他程序员的谈话.
2.
与客户合作,开发应用程序的规格说明和测试用例.
3.
结对编码.
4.
测试代码库.
表8-l中所列的由每个实践提供的注释都是可以自我解释的.
然而,有两个更重要的原则,即计划和测试,有必要进一步讨论.
一个成功的计划阶段为整个XP软件测试的艺术未经同意,严禁以任何形式拷贝133过程奠定了基础,XP的计划阶段与传统开发模型不同,通常将需求收集与应用设计结合起来.
XP中的计划重点是确定客户的应用需求,然后设计使用场景(或案例需求)来满足客户的应用需求.
通过生成使用场景,可以深入地洞悉应用程序的目的和需求.
此外,客户在一个开发周期的最后阶段执行验收测试时也可以用到这些使用场景.
最后,计划阶段带来的一个无形的好处是,用户通过深入地参与进来,从而获得对程序的拥有感和信心.
表8-1极限编程的12个实践实践注解1.
计划与需求分析将市场和业务开发人员集中起来,共同确认每个软件特征的最大商业价值以使用场景的形式重新编写每个重要的软件特征程序员估计完成每个使用场景的时间客户根据估计时间和商业价值选择软件的功能特征2.
小规模,递增地发布努力添加细微的、实在的、可增值的特征,频繁发布新版本3.
系统隐喻,编程小组确认隐喻,便于建立命名规则和程序流程4.
简要设计实现最简单的设计,使代码通过单元测试,假设变更即将发生,因此不要在设计上花太多时间、只是不停地实现5.
连续测试在编写模块之前就生成单元测试用例.
模块只有在通过单元测试之后才告完成.
此外,程序只有在通过了所有的单元测试和验收测试完成之后才算结束6.
重构清理和调整代码库;单元测试有助于确保在此过程中不破坏程序的功能.
应在任何重构之后重新进行所有的单元测试7.
结对编程两位程序员协同工作,在同台机器开发代码库,这样就可以对代码进行实时检查,能极大地提高缺陷的发现率和纠正率8.
代码的集体所有权所有代码归全体程序员所有,没有哪个程序员只致力于开发某一个代码库9.
持续集成每天在变更通过单元测试之后将其集成到代码库中10.
每周40小时工作不允许加班.
如果每周都全力工作了40小时,就不需要加班.
在重大发布前的一星期例外11.
客户在现场开发人员和编程小组可以随时接触客户,这样可以快速、准确地解决问题,使开发过程不至于中断12.
按标准编码所有的代码看上去必须一致.
设计一个系统隐喻有助于满足该原则进行连续的测试是基于XP的方法取得成功的关键.
虽然连续测试的原则中包含了验收测试,但单元测试占了主要的部分.
我们应该确保代码的任何变更都改进了软件的质量,并且没有引入新的缺陷.
连续测试的原则同样也支持为优化和调整代码库所进行的重构.
持续的测试还会带来一个无形的好处,即建立对软件的信心.
编程小组对代码库持有信心,源子用单元测试对代码库不断地进行验证.
此外,第8章极限测试未经同意,严禁以任何形式拷贝134客户对投资的信心也会高涨,因为他们知道代码库每天都在通过单元测试.

既然我们已经描述了XP过程的12个实践,那么一个典型的XP项目是如何运作的呢下面是一个简单的例子,列举了一个基于XP的项目可能具有的特征:1.
程序员与客户会晤,决定产品需求并建立使用场景.
2.
在客户不在场的情况下,程序员进行会晤,将需求分解为独立的任务,并估计完成每项任务所需的时间.
3.
程序员向客户提交任务清单和时间估计,并要求客户产生一个功能优先级清单.
4.
编程小组依据程序员具备的能力,将任务分配给结对的程序员.
5.
每一对程序员依据应用程序的规格说明,对其编程任务生成单元测试用例.

6.
每一对程序员完成其任务,旨在编写出通过单元测试的代码库.
7.
每一对程序员在所有单元测试通过之前,不断修改和重测他们的代码.
8.
所有的结对程序员每天都整合、集成他们的代码库.
9.
编程小组发布应用程序的一个预览版本.
10.
客户进行验收测试,要么确认该应用程序,要么提交一份报告指出存在的缺陷或不足.
11.
程序员在验收测试成功的基础上发布一个产品版本.
12.
程序员根据最新的经验更新时间估计.
尽管XP方法魅力无穷,但它并不适合所有的项目和机构.
根据XP倡导者的总结.
如果编程小组充分地进行了12个实践,那么成功开发应用程序的机会就会显著提高.
而批评者则认为,由于XP是一个过程,因此要么全部做到,要么什么也别做.
如果漏掉了一个实践,那么XP应用得就不彻底,程序的质量就会受到影响.
此外,批评者还认为,在未来修改程序以增加新的功能其代价要高于起初就将功能加人需求中并进行编码的代价.
最后,一些程序员发现结对编程十分麻烦并侵犯隐私,所以,他们并不接受XP思想.
无论个人的观点如何,都应将XP视为完成项目的一种方法.
应当根据项目的具体特性,仔细衡量XP方法的利弊,并做出可能的最佳选择.
软件测试的艺术未经同意,严禁以任何形式拷贝1358.
2极限测试:概念为了满足XP的流程和思想,开发人员使用了极限测试方法,该方法强调连续测试.
正如本章前面所提到的,极限测试主要由两种类型的测试组成:单元测试和验收测试.
设计测试用例时所采用的原理与第5章描述的原理没有明显差异,但是在开发过程的哪个阶段设计测试用例则有所不同.
尽管如此,极限测试和传统测试的目标仍然相同:即确定程序中的错误.
本节的余下部分将从极限编程的角度,提供关于单元测试和验收测试的更多信息.
8.
2.
1极限单元测试单元测试是极限测试中采用的主要测试方法,它具有两个简单规则:所有代码模块在编码开始之前必须设计好单元测试用例,在产品发布之前须通过单元测试.

乍看起来,这些原则似乎不太极端.
但是,极限测试中的单元测试与前面描述的单元测试之间的最大差别在于,极限测试中的单元测试必须在模块编码之前就完成设计和生成.
起初我们可能会迷惑为什么,或者如何为尚未编写出的代码设计测试驱动程序.
我们可能还会想到,没有设计测试的时间,因为应用程序的开发必须满足时间限制.
这些考虑都是合理的,也容易克服.
下面列出了在开始编码之前设计单元测试所带来的一些好处:获得了代码将满足其规格说明的信心.
在开始编码之前,就展现了代码的最终结果.
更好地理解了应用程序的规格说明和需求.
可以先实现一些简单的设计,稍后再放心地重构代码以改善程序的性能,而无须担心破坏应用程序的规格说明.
在这些优点中,获得对应用程序规格说明和需求的洞察和理解不应被低估.
举例来说,如果编码开始在先,我们可能无法充分理解程序输入值允许的数据类型和边界值.
那么在不理解允许的输入的情况下,如何能够编写单元测试以执行边界分析呢程序是否只接受数字、字符或两者都可以如果首先设计单元测试,就必须理解规格说明.
首先设计单元测试的做法就是XP方法的闪光点,因为它迫使我们第8章极限测试未经同意,严禁以任何形式拷贝136在开始编码之前,首先理解规格说明,排除了混淆.
正如第5章所述,我们需要确定单元的范围.
由于目前常用的编程语言,如Java,C#,VisualBasic大部分是面向对象的,因此模块常常就是类,或者甚至是单个的类方法.
我们有时可以将"模块"定义为代表特定功能的一组类或方法.
仅有程序员本人才知道程序的结构,以及任何最佳地为其设计单元测试.
即使是为最小的程序人工进行单元测试,也是一项让人生畏的工作.
随着程序规模的增加,可能要设计数以百计,甚至数以千计的单元测试.
因此,通常要采用一个自动化的软件测试套件来减轻连续执行单元测试的负担.
在这些测试套件的帮助下,编写测试脚本,然后执行全部或其中的一部分.
除此之外,测试套件通常可以生成报告,并对应用程序中频繁出现的缺陷进行分类.
该信息可以帮助我们在将来主动清除这些缺陷.
非常有趣的是,一旦设计并确认了单元测试,这些"测试"用例库就与试图编写的应用软件程序一样有价值.
因此,应当将这些测试用例保存在一个代码库中.

此外,还应确保进行足够的备份,并具备所需的安全保密措施.
8.
2.
2验收测试验收测试是XP方法中第二类、也是同等重要的极限测试类型.
验收测试的目的是判断应用程序是否满足如功能性和易用性等其他需求.
在设计/计划阶段,由开发人员和客户来设计验收测试.
与迄今为止讨论的其他测试形式不同,验收测试是由客户,而不是开发人员或编程搭档来执行的.
在这种方式中,客户对应用程序是否满足他们的要求进行客观、公正的确认.
客户通过使用场景来设计验收测试.
使用场景与验收测试之比通常是一对多,也就是说,每一个使用场景都可能需要不止一个的验收测试.
极限测试中的验收测试可以是自动化或非自动化的.
举例来说.
当客户必须确认某个用户输入界面的颜色和屏幕布局是否满足其规格说明时,所进行的测试是非自动化的,当应用程序须通过采用某些数据源(比如用二维表格模拟生产数据)作为输入数据来计算工资表格的值时,所进行的测试则是自动化的.
客户使用验收测试来验证应用程序是否得到了预期的结果.
如果与预期结果不软件测试的艺术未经同意,严禁以任何形式拷贝137一致,即被当作一个缺陷,报告给开发小组,如果客户发现了多个缺陷,那么在将列表传递给开发小组之前,得对缺陷进行优先级别排序.
当缺陷被修正,或程序中发生任何变更时,客户都需要重新执行验收测试.
从这点来看,验收测试也是回归测试的一种形式.
需要特别提醒的是,程序可能通过所有的单元测试,却不能通过验收测试.
为什么会这样呢因为单元测试是确认程序单元是否满足特定的规格说明,如工资扣除计算是否正确,而并非具体的可操作性或审美特性.
对于商用软件来说,产品的外观和感觉是非常重要的部分.
理解了规格说明,但却不理解其可操作性,通常会发生这种情况.
8.
3极限测试的应用在本节中,我们开发了一个小型的Java应用程序,使用JUnit(一个基于Java的开源单元测试套件)来描述极限测试的概念,例子本身很小,但其概念可适用于大多数的编程环境.
我们的例子是一个命令行的应用程序,仅判断输入值是否为素数.
为简洁起见,程序的源代码check4Primo.
java及其测试配件(testharness)check4PrimeTestjava列在附录A中.
在本节中,我们节选了程序片段来说明主要的思路.
程序的规格说明如下:开发一个命令行的应用程序,接收一个正整数n(0<=n<=1000),判断n是否为素数.
如果为素数,程序应返回信息说明其为素数.
如果n不是素数,程序也应返回信息说明其不为素数.
知果n不是一个有效的输入,程序应显示一条帮助信息.
遵循XP方法及第5章中列举的原则,我们从设计单元测试开始.
对于这个应用程序,可以确定出两个具体的任务:确认输入和判断素数.
我们可以分别使用黑盒与白盒测试方法、边界值分析方法和判定覆盖准则.
然而,极限测试要求使用一个独立的黑盒测试方法,消除所有的偏见.
8.
3.
1测试用例设计设计测试用例首先从确认测试方法开始.
在本例中,我们将使用边界值分析方第8章极限测试未经同意,严禁以任何形式拷贝138法对输入进行确认,因为程序只能接受固定范围内的正整数.
所有其他的输入值,包括字符数据类型和负数都会产生错误,不能被使用.
当然,我们可以这样处理本例,将输入确认划归到判定覆盖准则中,因为程序必须判断输入是否有效,这里一个重要的概念是,在设计测试时必须确定使用某个测试方法.
使用确定的测试方法,根据可能的输入和预期的输出结果开发一份测试用例列表.
表8-2显示了确认的8个测试用例(注意:我们采用了一个非常简单的例子来说明极限测试的基本理论.
在实际中,会遇到更详细的程序规格说明,可能包含诸如用户界面需求和输出用语措辞等内容.
因此,测试用例列表可能会增长很多).
表8-2check4Prim.
java的测试用例用例编号输入结果注解1n=3确认n为素数对有效素数的测试对边界范围内输入的测试2n=1,000确认n不为素数对等于上边界输入的测试对n不为素数的测试3n=0确认n不为素数对等于下边界的测试4n=-1显示帮助信息对低于下边界的测试5n=1,001显示帮助信息对高于下边界的测试6两个或两个以上输入显示帮助信息对输入值得正确个数的测试7n="a"显示帮助信息输入未整数而非字符的测试8n为空(空格)显示帮助信息对输入为空的测试表8-2中的测试用例1结合了两个测试需求.
它检查了输入是否为有效的素数,以及程序如何处理有效的输入值.
可以在本测试中使用任何有效的素数.
附录B提供了一份小于1,000的可用素数的清单.
使用测试用例2同样会测试到两个需求:当输入值等于上边界,或输入值不是素数时会发生什么情况这个测试用例本可以被分解为两个单元测试,但软件测试总的目标之一是在足以检查出错误的前提下,使测试用例的数量最小.
测试用例3检查有效输入的下边界,以及测试无效的素数.
此项检查的第二部分是不需要的,因为测试用例2已经处理了此需求,但仍然被默认地包括进来,因为0不是素数.
测试用例4和测试用例5保证了输入是在规定的范围之内,即大于0,且小于软件测试的艺术未经同意,严禁以任何形式拷贝139或等干1000.
测试用例6测试应用程序是否可以正确地处理字符输入值.
由于我们所做的是数学运算,因此很明显,程序必须拒绝字符类型的输入.
该测试用例假定Jova会进行数据类型的检查,当输入无教的数据类型时,应用程序必项能够处理发生的异常.
该测试用例确保异常得到了处理.
最后,测试用例7和测试用例8检查输入值的正确个数,任何超过t1个的输入都会发生失效.
JUnit是一个可以免费获得的开源工具,用于在极限编程环境下对Java应用程序进行自动化的单元测试.
设计者KentBeck和ErichGamma开发JUnit来支持极限编程环境下进行的单元测试.
JUnit非常小,但非常灵活,并且功能丰富.
我们可以设计单个测试或一系列的测试.
可以自动生成报告,详细描述错误的信息.

在使用JUnit或任何测试套件之前,须充分了解如何使用它.
JUnit非常强大,但只有掌握了其API之后才会发挥作用.
然而,无论是否采纳XP方法,JUnit都是一个对代码进行合理检查的有用工具.
访问www.
JUnit.
org可以获得更多的信息,并可下载该测试套件.
另外,该站点还提供关于极限编程和极限测试的丰富信息.
8.
3.
2测试驱动器及其应用既然我们已经设计出了两类测试用例,那么就可以生成测试驱动器类check4primeTest.
表8-3将check4PrlmoTest中的JUnit方法与覆盖的测试用例对应起来.
表8-3测试驱动器的方法方法检查到的测试用例testCheckPrime_true()1testCheckPrime_false()2,3testCheck4Prime_checkArgs_char_input()7testCheck4Prime_checkArgs_above_upper_bound()5testCheck4Prime_checkArgs_neg_input()4testCheck4Prime_checkArgs_2_inputs()6第8章极限测试未经同意,严禁以任何形式拷贝140testCheck4Prime_checkArgs_0_inputs()8注意testCheckPrime_false()方法测试了两种状态,因为边界值并不是素数.
因此,我们可以采用一个测试方法来检查边界值错误和无效素数.
仔细检查该方法可以发现,两个测试publicvoidtestCheckPrime_false(){assertFalse(check4prime.
primecheck(0));assertFalse(check4prime.
primecheck(1000));}用例确实在其内部被执行到.
以下是附录A中列举的check4JavaTest类中一个完整的JUuit方法.
注意,JUnit方法assertFalse()对提供给它的参数进行检查,看看是否有误,参数必须是布尔类型,或是一个返回值为布尔类型的函数.
如果返回的是"false",则测试可视为成功.
这个程序片段还提供了一个首先设计测试用例和测试配件所带来好处的例子.
可以看到assertFalse()方法中的参数是check4prime.
primecheck(0)方法.
这个方法会出现在应用程序的某个类中.

首先进行测试设计迫使我们思考该应用程序的结构.
从某些方面来讲,应用程序是按照测试配件设计出来的.
这里我们需要一个方法来检查输入是否为素数,因此在应用程序中我们将其包括进来.
测试设计结束之后,就可以开始程序编码了.
根据程序规格说明,测试用例、测试配件以publicclasscheck4prime{publicstaticvoidmain(string[]args)publicvoidcheckArgs(string[]args)throwsExceptionpublicbooleanprimeCheck(intnum)}及最后的java程序都由单个check4Prime类组成,定义如下:简单地说,根据Java程序的定义.
main()过程提供了应用程序的入口点.
checkArgs()方法判断输入值n是个正数,0<=n<=1000.
Primecheck()过程对照一个已计算出的素数列表来检查输入值.
我们采用Eratosthenes筛选法来快速计算素数.
由于涉及的素数数量比较小,此方法是可以接受的.
软件测试的艺术未经同意,严禁以任何形式拷贝1418.
4小结随着软件产品间竞争的日益激烈,需要将产品非常快速地投向市场.
因此人们设计了极限编程方法来支持快速的应用程序开发.
这个轻量级的开发过程强调沟通计划和测试.
极限编程中的测试被称为"极限测试".
极限测试的重点在于单元测试和验收测试.
一旦代码库发生变更,就需要进行单元测试.
在重要的发布结点,由客户来执行验收测试.
极限测试还要求在开始程序编码之前,根据程序的规格说明设计测试配件.
在这种方式中,开发的程序要通过单元测试,从而提高程序满足其规格说明的可能性.
词汇表未经同意,严禁以任何形式拷贝142词汇表black-boxtesting(黑盒测试)将程序视为一个整体、且忽略其内部结构的测试方法.
单纯从软件的规格说明中获取测试数据.
bottom-uptesting(自底向上的测试)增量模块测试的一种形式,首先测试底层模块,再测试调用模块等等.
boundary-valueanalysis(边界值分析),一种黑盒测试方法,重点在于程序输t入区间的边界区域.
branchcoverage(分支覆盖)参见"判定覆盖".
cause-effectgraphing(因果图分析)使用简化的数字逻辑电路图(组合逻辑网络)辅助生成一组高效测试用例的技术.
codeinspection(代码检查)一套供小组代码阅读的规程和错误检查枝术,作为检查错误的测试周期的一部分,通常使用一份常见错误的列表来对照代码.

hostio荷兰10Gbps带宽,10Gbps带宽,€5/月,最低配2G内存+2核+5T流量

成立于2006年的荷兰Access2.IT Group B.V.(可查:VAT: NL853006404B01,CoC: 58365400) 一直运作着主机周边的业务,当前正在对荷兰的高性能AMD平台的VPS进行5折优惠,所有VPS直接砍一半。自有AS208258,vps母鸡配置为Supermicro 1024US-TRT 1U,2*AMD Epyc 7452(64核128线程),16条32G D...

特网云-新上线香港五区补货资源充足限时抢 虚拟主机6折,低至38元!

官方网站:点击访问特网云官网活动方案:===========================香港云限时购==============================支持Linux和Windows操作系统,配置都是可以自选的,非常的灵活,宽带充足新老客户活动期间新购活动款产品都可以享受续费折扣(只限在活动期间购买活动款产品才可享受续费折扣 优惠码:AADE01),购买折扣与续费折扣不叠加,都是在原价...

极光KVM(限时16元),洛杉矶三网CN2,cera机房,香港cn2

极光KVM创立于2018年,主要经营美国洛杉矶CN2机房、CeRaNetworks机房、中国香港CeraNetworks机房、香港CMI机房等产品。其中,洛杉矶提供CN2 GIA、CN2 GT以及常规BGP直连线路接入。从名字也可以看到,VPS产品全部是基于KVM架构的。极光KVM也有明确的更换IP政策,下单时选择“IP保险计划”多支付10块钱,可以在服务周期内免费更换一次IP,当然也可以不选择,...

免费装扮空间为你推荐
google竞价排名google竞价排名怎么做支付宝查询余额支付宝里如何查询银行卡里面的余额?中国电信互联星空中国电信互联星空是什么!怎么取消百度抢票浏览器百度浏览器怎么抢票?申请证书手机申请证书mate8价格华为mate8手机参数配置如何,多少元商标注册查询官网如何在网上查询商标是否注册?机械键盘轴机械键盘什么轴好,机械键盘轴有几种电子商务网站模板网页制作模板网站优化方案网站优化方案应该从哪些方面去分析?
深圳主机租用 国际域名抢注 vps代理 photonvps pccw l5639 魔兽世界台湾服务器 铁通流量查询 我爱水煮鱼 已备案删除域名 网站卫士 qq云端 购买国外空间 360云服务 1元域名 万网空间 注册阿里云邮箱 阿里dns 攻击服务器 石家庄服务器 更多