软件测试的艺术毕业论文
第1章 一次自评价测试
子本书25年前首次出版以来,软件测试变得比以前容易得多,也困难德多。
软件测试何以变得更困难?原因在于大量编程语言,操作系统以及硬件平台的出现。在20世纪70年代只有相当少的人使用计算机,而今天在商业界和教育界,如果不使用计算机,几乎没有人能完成日常工作。况且,计算机本身的功能也比以前增强了数百倍。
因此,我们现在编写的软件会潜在地影响到数以百万计的人,使他们更高地完成工作,反之也会给他们带来数不清的麻烦,导致工作或事业的损失。这并不是说今天的软件比本书第一版发行是更重要,但可以肯定地说,今天的计算机—以驱动它的软件—无疑已影响到了更多的人、更多的行业。
就某些方面而言,软件测试变得容易了,因为大量的软件和操作系统比以往更加复杂,内部提供了很多已充分的例程供应程序集成,无须程序员从头进行设计。例如,图形用户界面(GUI)可以从开发语言的类库中建立起来,同时,由于它们是经过充分调试和测试的可编程对象,将其作为用户应用程序测组成部分进行测试 的要求就减少了许多。
所谓软件测试就是一个过程或一系列过程,用来确认计算机代码完成了其应该完成的功能,不执行其不该有的操作。软件应当是可预测且稳定的,不会给用户带来意外的惊奇。在本书中,我们将讨论多种方法来达到这个目标。
好了,在开始阅读本书之前,我们想让读者做一个小测验。
我们要求设计一组测试用例(特定的数据集合),使当地测试一个相当简单的程序。为此要为该程序建立一组测试数据,程序须对数据进行正确处理以证明自身的成功。下面是对程序的描述:
这个程序从一个输入对话框中读取三个整数数值。这三个整数数值代表了三角形三边的长度。程序显示提示信息,指出该三角形究竟是不规则三角形、等腰三角形还是等边三角形。
注意,所谓不规则三角形是指三角形任意两条边不相等,等腰三角形是指有两条边相等,而等边三角形则是指三条边相等。另外,等腰三角形等边的对角也相等(即任意三角形等边的对角也相等),等边三角形的所有内角都相等。 用你的测试用例集回答下列问题,借以对其进行评价。对每个回答“是”的答案,可以得1分:
1. 是否有这样的测试用例,代表了一个有效的不规则三角形?(注意,如1,2,3,
和2,5,10这样的测试用例并不能确保“是”的答案,因为具备这样边长的三角形不存在。)
2. 是否有这样的测试用例,代表一个有效的等边三角形?
3. 是否有这样的测试用例,代表一个有效的等腰三角形?(注意如2,2,4的测试用
例无效,因为这不是一个有效的三角形。) 4. 是否至少有三个这样的测试用例,代表有效的等腰三角形,从而可以测试到两等边的所有三种可能情况(如3,3,4;3,4,3;4,3,3)? 5. 是否有这样的测试用例,某边的长度等于0? 6. 是否有这样的测试用例,某边的长度为负数?
7. 是否有这样的测试用例,三个整数皆大于0.其中两个整数之和等于第三个?(也就
是说,如果程序判断1,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. 是否至少有一个这样的测试用例,输入的边长为非整数(如1.5,3.5,5.5)? 13. 是否至少有一个这样的测试用例,输入的边长个数不对(如仅输入了两个而不是三
个整数)? 14. 对于每一个测试用例,除了定义输入值之外,是否定义了程序针对该输入值的预期
输出值? 当然,测试用例即使满足了上述条件,也不能确保能查处所有可能的错误。但是,由于问题1至问题13代表了该程序不同版本中已经实际出现的错误,对该程序的充分测试至少应该能够暴露这些错误。
开始关注自己的得分之前,请考虑以下情况:以我们的经验来看,高水平的专业程序员平均得分仅7.8(满分14)。如果读者的得分更高,那么祝贺你。如果没那么高,我们将尽力帮助你。
这个测验说明,即使测试这样一个小的程序,也不是件容易的事。如果确实是这样,那么想象一下测试一个十万行代码的空中交通管理系统,一个编译器,甚至一个普通的工资管理程序的难度。随着面向对象编程语言(如Java、C++)的出现,测试也变得更加困难。举例来说,为测试这些语言开发出来的应用程序,测试用例必须要找出与对象实例或内存管理有关的错误。
从上面这个例子来看,完全地测试一个复杂的、实际运行的程序视乎是 不太可能的。情况并非如此!尽管充分的测试的难度令人望而生畏,但这是软件开发中一项非常必须的任务,也是可以实现的一部分工作,通过本书我们可以认识到这一点。
第2章
软件测试的心理学和经济学
软件测试是一项技术性工作,但同时也涉及经济学和人类心里学的一些重要因素。
在理想情况下,我们会测试程序的所有可能情况。然而,在大多数情况下,这几乎是不可能的。即使一个看起来非常简单的程序,其可能输入与输出组合可达到数百种甚至数千种,对多有的可能情况都设计测试用例是不切实际的。对一个复杂的应用程序进行完全的测试,将耗费大量的时间和人力资源,以至于经济上是不可能的。
另外,要成功地测试一个软件应用程序,测试人员也需要有正确的态度(也许用“愿景(vision)”这个词会更好一些)。在某些情况下,测试人员的态度可能比实际的测试过程本是还要重要。因此,在深入探讨软件测试更加技术化的本质之前,我们先探讨一下软件测试的心理学和经济学问题。
2.1软件测试的心理学
测试执行的差,其中一个重要原因在于大多数的程序员一开始就 把“测试”这个术语的定义搞错了。他们可能会认为:
“软件测试就是证明软件不存在错误的过程。”
“软件测试的目的在于证明软件能够正确完成其预定的功能。” “软件测试就是建立一个‘软件做了其应该做的’信心的过程。”
这些定义都是本末倒置的。
每当测试一个程序时,总是想为程序增加一些价值。通过测试来增加程序的价值,是指测试提高了程序的可靠性后质量。提高了程序的可靠性,是指找出并最终修改了程序的错误。 因此,不要只是为了证明程序能够正确运行而去测试程序;相反,应该一开始就假设程序中隐藏着错误(这种假设对于几乎所有的程序都成立),然而测试程序,发现尽可能多的错误。
那么,对于测试,更为适合的定义应该是: “测试是为发现错误而执行程序的过程”。
虽然这看起来像是个微妙的文字游戏,但确实有重要的区别。理解软件测试的真正定义,会对成功地进行软件测试有很大的影响。
人类行为总是倾向于具有高度目标性,确定一个正确的目标有着重要的心理学影响。如果我们的目的是证明程序中不存在错误,那就会潜在意识中倾向于实现这个目标; 也就是说,我们会倾向于选择可能较少导致程序失效的测试数据。另一方面,如果我们的目标在于证明程序中存在错误,我们设计的测试数就有可能更多地发现问题。与前一种方法相比,后
一种方法会更多的增加程序的价值。
这种对软件测试的定义,包含着无穷的内涵,其中的很多都蕴含在本书各处。举例来说,它暗示了软件测试是一个破坏性的过程,甚至是一个“施虐”的过程,这就是说明为什么大多数人都觉得它困难,这种定义可能是违反我们愿望的;所幸的是,我们大多数人总是对生活充满建设性而不是破坏性的愿景。大多数人都本能地倾向于创造事物,而不是将事物破坏。这个定义还暗示了对于一个特定的程序,应该如何设计测试用例(测试数据),哪些人硬挨而哪些人又不应该执行测试。
为增进对软件测试正确定义的理解,另一条途径是分析一下对“成功的”和“不成功的”这两个问题的使用。当项目经理归纳测试用例的结果时,尤其会用到这两个词。大多数的醒目经理没发现错误的测试用例称为一次“成功测试”,而将发现了某个错误的测试称为“不成功的测试”。 这不仅是一次本末倒置。“不成功的”表示事情不遂人意或令人失望。我们认为,如果测试某段程序时发现了错误,而且这些错误是可以修复的,就将这次合理设计并得到有效的测试称作是“成功的”。如果本次测试可以最终确定再无其他可查处的错误,同样也被称为是“成功的”。所谓“不成功的”测试,仅指未能适当地对程序检查,在大多数情况下,未能找出错误的测试被认为是“不成功的”。这是因为认为软件中不包含错误的观点基本上是不切实际的。
能发现新错误的测试用例不太可能被认为是“不成功的”;相反能发现错误就证明它是值得设计的。一个“不成功的”测试用例,会使程序输出正确的结果,但不能发现人恶化错误。
我们可以类比一下病人看医生的情况,病人因为身体不舒服而去看医生。如果医生对病人进行了一些实验检测,却没有诊断出任何病因,我们就不会认为这些实验检测是“成功的”。之所以是“不成功的”检测,是因为病人支付了昂贵的实验检测费用,而病状却依然如故。病人会因此而质疑医生的诊断能力。但是,如果实验检测诊断出病人是胃溃疡,那么这次检测就是“成功的”,医生可以开始进行适当的治疗。因此,医疗行业会使用“成功的”或“不成功的”来表达适当的意思。我们当然可以类推到软件测试中来,当我们开始测试某个程序是,它就是好似我们的病人。
“软件测试就是证明软件不存在错误的过程”,这个定义会带来第二个问题。对于几乎所有的程序而言,甚至是非常小的程序,这个目标实际上也就是无法达到的。
另外,心理学研究表明,当人们开始一项工作是,如果已经知道他说不可行的或无法实现时,人的表现就会相当糟糕。举例来说,如果要求人们在15分钟之内完成星期日《纽约时报》里的纵横填字游戏,那么我们会观察到10分钟之后的进展非常小,因为大多数人都会却步于这个现实,即这个任务似乎是不可能完成的。但是如果要求在四个小时之内完成填字游戏,恶魔很可能有理由期望最初10分钟之内的进展会比前一种情况下的大。将软件测试定义为发现程序错误的过程,即使测试是个可以完成的任务,从而克服了这个心理障碍。 诸如“软件测试就是证明‘软件做了其应该做的’的过程”此类的定义所带来的第三个问题是,程序即使完成预定的功能,也仍然可能隐藏错误。也也就是说,当程序没有实现预期功能是,错误是清晰地显示出来的;如果做了其不该做的,这同样是一个错误。考虑一下第1章的三角形测试程序。即使我们证明了程序能够识别出不规则三角形,等腰三角形和等边三角形,但是在完成了不该执行的任务后(例如将1,2,3说成是一个不规则三角形后将
0,0,0说成是一个等边三角形),程序仍然是错的。如果我们将软件测试视作发现错误的过程,而不是将其视为证明“软件做了其应该做的”的过程,我们发现后一类错误的可能性会大很多。
总结一下,软件测试更适合被视为试图发现程序中错误(假设其存在)的破坏性东东过程。一个成功的测试用例,通过诱发程序发生错误,可以在这个方向上促进软件质量的改进。当然,最终我们还是要通过软件测试来建立某种程度的信心:软件做了其该做的,未做其不该做的。但是通过对错误的不断研究是实现这个目的的最佳途径。
有人可能会声称“本人的程序完美无缺”(不存在错误),针对这种的情况建立起信心的最好办法就是尽量反驳他,即努力发现不完美之处,而不是确认程序在某些输入情况下能够正确地工作。
2.2 软件测试的经济学
给出了软件测试的适当定义之后,下一步就是确定软件测试是否能够发现“所以”的错误。我们将证明答案是否定的,即使是规模很小的程序。一般说来,要发现程序中的所以错误也是不切实际的,常常也是不可能的。这个基本的问题反过来暗示出软件的经济学问题,测试人员对被测试的期望,以及测试用例的设计方式。
为了应对测试经济学的挑战,应该开始测试之前建立某些策略。黑盒测试和白盒测试是两种最普遍的策略,我们将在下面两节中讨论。
2.2.1 黑盒测试
黑盒测试是一种重要的测试策略,又称为数据驱动的测试或输入/输出驱动的测试。使用这种测试方法时,将程序视为一个黑盒子。测试目标与程序额内部机制和结构完全无关,而是将重点集中放在发现程序不按其规范正确运行的环境条件。
在这种方法中,测试数据完全来源于软件规范(换句话说,不需要去了解程序的内部结构)。如果想用这种方法来发现程序的所以错误,判定的标准就是“穷举输入测试”,将所有可能的输入条件作为测试用例。为什么这样做?比如说在三角形测试的程序中,试过了三个等边三角形的测试用例,这不能确保正确地判断出所有的等边三角形。程序中可能包含对边长为3842,3842,3842的特殊检查,并指出此三角形为不规则三角形。由于是个黑盒子,因此能够确定此条语句存在唯一的方法,寄是实验所有的输入情况。
要穷举测试这个三角形程序,可能要为所有有效的三角形创建测试用例,只要三角形边长在开发语言允许的最大整数值范围内。这些测试用例本是就是个天文数字,但这还不是所谓的穷举的;当程序指出一3,4,5是一个不规则三角形或2,A,2是一个等腰三角形时,问题就暴露出来了。为了确保能够发现所有这样的错误,不仅得用所有有效的输入,而且还得用所有可能的输入进行测试。因此,为了穷举测试三角形程序,实际上需要创建无限的测试用例,这当然是不可能的。
如果测试这个三角形程序都这么难的话,那么要穷举测试一个稍大些的程序的难度就更大了。设想下,如果要对一个C++编译器进行黑盒穷举测试,不进要创建代表所有有效C++程序的测试用例(实际上,这又是一个无穷数),还需要创建代表所有无效C++程序的测试用例(无穷数),以确定编译器能够检测出他们是无效的。也就是说,编译器必须进行测试,