计算机系统概论十五章.doc
《计算机系统概论十五章.doc》由会员分享,可在线阅读,更多相关《计算机系统概论十五章.doc(9页珍藏版)》请在淘文阁 - 分享文档赚钱的网站上搜索。
1、第十五章 测试和调试15.1 绪论在1999年的十二月,美国国家航空和宇宙航行局(NASA)任务控制中心在火星极地着陆者号接近火星表面时失去了与它的联系。极地着陆者号这次的任务是去探测火星的南极地区。从那以后联系就中断了,美国国家航空和宇宙航行局宣布飞行器很可能在着陆过程中坠毁了。在评估了当时的情况后,调查组认为事故可能是由控制软件的错误造成的,它在探测器距火星表面还有40米而不是着陆时就过早的关闭了板上发动机。发送探测器在物理上的复杂性使人吃惊,而控制飞行器的软件系统同样也不简单。软件和构成一个系统的任何一个机械或电子子系统相同,而做好软件更困难是因为它是不可视的。它不像推进系统或着陆系统一
2、样可以容易地被观测到。 软件现在无处不在。它在你的手机里,在你的汽车里甚至你看到的这本书的正文出现于你面前之前,也是被无数行的软件处理于十分老式的打印页面上。由于软件在我们现实世界中扮演着重要而关键的角色,所以软件根据要求正确的运行是很重要的。设计工作程序不是自动的。只构建程序是不正确的。那时因为,仅仅写了一个程序并不意味着它可以正确的运行。在我们认为可以完成它之前,我们必须尽可能的彻底测试和调试它。程序员花在调试上的时间经常要比花在写程序上的时间长。专家们做出的一个关于这方面的普遍性调查显示:一个熟练的程序员花在调试代码上的时间和花在写程序上的时间一样多。因为写代码与测试、调试的不可分性,我
3、们会在这一章中向你介绍一些基本的测试和调试的概念。测试是找出错误的过程,而调试是去除错误的过程。为了使软件暴露出它们的错误,测试一段代码包括提供尽可能多的输入条件。例如,在前面的章节中函数ToUpper的测试过程中(回忆这个函数返回了作为参数传递的字母的大写形式),我们为了检查这个函数是否按照规格说明的那样运行,我们可能希望传递每一个可能的ASCII码值作为输入的参数,同时观察这个函数的输出。如果这个函数在一个特殊的输入下产生了一个错误的输出,那么我们就发现了一个错误。在代码还处在开发阶段发现错误,远比一个没有怀疑过软件的用户在不经意间被这个错误“绊倒”要好。要是NASA(美国国家航空和宇宙航
4、行局)的软件工程师在地球上发现火星探测者号的错误,而不是在距火星表面40米才遇到错误就好了。通过一个程序的信息和它的执行,程序员就可以应用常识找出错误产生的位置。调试一个程序有点像解决一个谜语。就像一个犯罪场景中的侦探一样,程序员为了找出问题的根源,必须检查出现的线索。如果你知道如何收集关于错误的信息,调试代码就会更容易,例如在程序执行过程中用系统的方法,收集主要变量的值。在这一章中,我们描述了一些可以用来在程序中查找和纠正错误的技术。我们先介绍一些在程序中出现的错误种类。接着,我们会描述一些快速发现这些错误的测试方法。最后我们会描述一些可以用来隔离和修复这些错误的调试技术,同时我们会提供一些
5、防御性的编程技术,使你写的代码中的错误最小化。15.2 错误类型为了更好的理解如何找出并修复程序中的错误,首先理解我们写的程序中出现的几种错误类型是很有用的。在你的代码里可能会遇到三类错误。语法错误是最容易处理的,因为它们会被编译器捕获。当编译器试图把源代码翻译成机器代码时,会把这样的错误报告给我们,通常会精确指出错误出现在哪一行。另一方面,语义错误往往是我们最难纠正的错误。它们出现在当程序语法上正确却无法如我们期望的那样运行的时候。语法和语义错误一般都是字符排列上的错误:常常出现在我们键入的字符并不是我们的本意的时候。而算法错误则是我们解决问题的方法是错误的。它们常常很难被检测到,一旦被发现
6、,也很难纠正。15.2.1语法错误在C语言中,语法错误(或句法错误,或语法分析错误)总能被编译器捕获。当我们让编译器翻译那些与C语言规范不一致的代码时,会出现这些错误。例如,图15.1中所列的代码包含一个语法错误,当这段代码被编译的时候编译器会标记出这个错误。对变量i的声明少了一个分号。作为编写C语言程序的新手,缺少分号和对变量的声明会占到你遇到的语法错误的大多数。幸运的是,这种类型的错误很容易发现,因为编译器会检测到它们,并且很容易纠正,因为编译器指明了它们在哪里出现。一旦语法错误被纠正,真正的问题就出现了,更困难的语义和算法错误还没有被纠正。15.2.2 语义错误语义错误与语法错误相似。它
7、们的产生原因相同:当我们键入一个程序的时候,我们的思维和手指不可能完全的一致。语义错误不包括不正确的语法,因此,程序可以被翻译,我们能够执行它。只有我们分析了输出后我们才会发现程序不像期望的那样运行。图15.2列出了一个存在一个简单的语义错误(语法错误被纠正了)的和图15.1相同的例子。这个程序应该打印出一个数字7的乘法表。在此,程序的一次执行揭示了这个问题。只打印出乘法表中的一行。以你所掌握的C程序设计语言的知识,你应该能够推论出,为什么这个程序错误地执行。为什么打印出117=70?这个程序显示了被称为控制流程错误的一种错误。在此,程序的控制流程,或语句被执行的顺序,与我们所期望的不同。图1
8、5.3中列出的代码包含一个常见的,却是难处理的包含局部变量的语义错误。这个例子与我们在14.2节中讨论的阶乘的程序相似。这个程序计算的是所有小于或等于从键盘中输入的数字的和(即计算1+2+3+.+n)。试运行这个程序,你将会发现,输出的结果不是你期望的结果。为什么这个程序不能正常地运行呢?提示:画出一个这个程序执行的运行时栈。语义错误是特别麻烦的,因为它们经常不能被编译器和程序员检测到,直到某个特别的输入集引起错误发生。回到图15.3中的AllSum程序,但是要修改之前的语义错误,并且要注意如果传给AllSum的数值小于或等于0或太大,那么AllSum将会返回一个错误的结果,因为它超过了整数变
9、量result的范围。纠正前一个的错误,编译这个程序,并且输入一个小于1的数,你将会注意到另外一个错误。一些错误在执行程序的过程中被发现,因为一个非法的行为被程序执行。几乎所有的计算机系统都有阻止一个程序去执行可能会影响其它无关的程序的行为的保护措施。例如,不希望一个用户程序去修改存储了操作系统的存储单元,或是去写一个可能影响其它程序的控制寄存器,例如一个引起计算机关机的控制寄存器。当那样的非法行为被一个程序执行时,操作系统就会终止它的执行,并输出一个运行时错误消息。修改AllSum例子中的scanf语句为如下语句:scanf(%d,in);在这种情况下,字符“&”,正如我们将会在16章看到的
10、,是C语言中的一个特殊运算符。在这里省略它,将产生一个运行时错误,因为程序正试图去修改一个它不能访问的存储单元。我们将会在后面的章节中详细查看这个例子和错误产生的原因。15.2.3 算法错误算法错误是不正确的程序设计的结果。也就是说,程序本身准确的按照我们所设计的运行了,但是这种设计本身有缺陷。这种错误非常隐蔽,它们可能经过许多次运行程序的试验才会被发现。甚至当它们被检测到并且被隔离后,仍然很难修复。好消息是在写代码之前的设计阶段,通过一个恰当的规划,这种错误经常能够被减少甚至消除。图15.4 提供了一个有个简单的算法缺陷的程序的例子。这段代码要求输入一个年份并且判断这一年是否为闰年。初看一眼
11、,这段代码似乎是对的。闰年的确是4年出现一次。但是闰年除了每4个世纪之外,还要忽略掉每一个世纪的交界处(即,2000年是闰年,但是2100、2200、2300都不是闰年)。这段代码对几乎所有年份都用效,除了那些在这些例外中的情况。我们把这个归为算法错误或者设计缺陷的类型中。另一个算法错误的例子也与日期有关,这就是臭名昭著的千年虫问题(Y2K)。许多计算机程序把存储日期所需要的存储量减到最少。它们采用的位数只够存储年份的最后两位数字。因此,2000年无法与1900年区别开来(或1800或2100也是这样)。在最近的世纪相交期间的1999年12月31日,这引起了一个问题。也就是说,例如,你已经在1
12、999年末从大学图书馆借了一本书,它应该在2000年初按期归还。如果图书馆计算机系统遭受了千年虫,你就会受到一个过期警告的邮件通知,上面有一张高额的罚单。因此,大量的人力财力投入到在2000年1月1日前解决千年虫的问题上来。15.3 测试具有丰富经验的程序员之间有这么一句谚语:任何一行没有经过测试的代码都可能是有错误的。好的测试技术对编写一个好的软件起至关重要的作用。什么是测试呢?使用测试,基本上我们是将这个软件进行试验,对其应用不同的输入组合(为了模拟在真实的情况下软件将要遇到的问题),并且将程序的输出结果进行正确性检查。现实世界中的软件在发布之前都需要经过无数次的试验。在一个理想的情况下,
13、我们可以通过在所有可能的输入条件下,检查程序的操作,对其进行测试。但是对于一个程序而言,测试并不只是试验,对所有的输入情况进行测试是不可能的。例如,如果我们想对一个判断从A到B之间某个数字是不是素数的程序进行测试,其中A和B都是32位的输入的数值,那么就有(232)2种可能的输入组合。即使我们一秒钟能进行一百万次测试,那么也会需要花费50万年来完成这项测试工作。很明显,对每一个输入组合进行测试不是一种可选的方法。那么我们应该对哪些输入组合进行测试呢?我们可以随机的选择一些输入,希望这些随机的组合中的一些输入能够揭示出这个程序的错误所在。软件工程师典型的依赖于一些更加系统化的方法来对他们的代码来
14、进行测试。特别的,黑盒测试是用来检查一个程序是否符合它的规格说明,而白盒测试则是为了确保每一行代码都被测试,以实现程序的不同方面为目标。15.3.1 黑盒测试通过黑盒测试,我们来检查程序是否满足其输入和输出规格说明,而忽略程序内部。也就是说,使用黑盒测试,我们只关心这个程序是做什么的,而不是它是怎么做的。例如,在图15.3中的AllSum程序的一个黑盒测试可能包括运行程序,键入一个输入的数字,以及将输出结果与你所手算的结果相比较。如果两者不相符,那么要么是程序包含一个错误,要么是你的算术技巧不是很好。我们会继续试验,直到有理由相信程序是可以使用的。对于测试更大的程序,为了在单位时间内运行更多的
15、测试,测试过程是自动的。也就是说,我们创建另一个程序,此程序可以自动运行原来的程序,提供一些随机的输入,检查输出是否符合规格说明,然后重复。使用那样的过程,与每次试验由人来执行相比,我们无疑可以运行更多的试验。为了使黑盒测试自动化,然而,我们需要一种方法去自动测试程序的输出结果是否正确。此时,我们可能需要去构建一个检查器程序,它与原来的程序不同,但都执行类似的计算。若原来的程序与检查器程序有着相同的错误,那么通过黑盒测试过程,错误是检查不出来的。基于这个原因,那些写检查器程序的黑盒测试员通常不允许去看他们正在测试的黑盒里面的代码,以便我们可以获得一个真正独立的检查器的版本。15.3.2 白盒测
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 计算机系统 概论 十五
限制150内