赞
踩
本文出处忘记了,从我的电脑里搜索到了,费了老半天编辑啊,手累。。。
编程大师说:“任何一个程序,无论它多么小,总存在着错误。”
初学者不相信大师的话,他问:“如果有个程序小得只执行一个简单的功能,那会怎么样?”
“这样的程序没有意义,”大师说,“但如果这样的程序存在的话,操作系统最后将失效,产生错误。”
但初学者不满足,他问:“如果操作系统不失效,那会怎么样?”
“没有不失效的操作系统,”大师说,“但如果这样的操作系统存在的话,硬件最后将失效,产生错误。”
初学者仍不满足,再问:“如果硬件也不失效,那会怎么样?”
大师长叹一声道:“没有不失效的硬件。但如果这样的硬件存在的话,用户就会想让那个程序做一件不同的事,这件事也是错误。”
没有错误的程序世间难求。(摘自《编程之道》)
错误是一种严重的软件缺陷。测试的目的是为了发现尽可能多的缺陷,并期望通过改错来把缺陷统统消灭,以期提高软件的质量。由于测试与改错并不能体现软件开发人员的聪明才智,相反地,它们带来了更多的烦恼与牢骚。因此在教学和开发实践中,软件测试总是遭受冷遇。
医生犯的错误最终会被埋葬在地下,从此一了百了。但软件的错误不会自动消失。据统计,对于大多数的软件产品而言,用于测试与改错的时间将占整个软件开发周期的30%。如果不懂得有效地进行测试、改错,却花了那么高的代价,你不仅得不到功劳,也没人欣赏你的苦劳,你拥有最多的将只是疲劳。所以我们必须学会测试与改错,并且要把测试与改错工作做好。
在软件开发过程中,编程和测试是紧密相关、相辅相成的技术活动,缺一不可。从理论上讲,两者不分贵贱,同等重要。但在大多数软件企业中,程序员的待遇普遍要高于专职的测试人员。即使不考虑待遇问题,大多数人认为开发工作比测试工作有乐趣、有成就感、有前途。所以计算机专业人员通常会把编程当成一种看家本领,舍得下功夫学习和专研,但极少有人以这种态度对待软件测试。这种意识导致软件测试被过于轻视。不仅学生们在读书时懒得学习测试(目前国内高校似乎没有“软件测试”的课程),就连有数年工作经验的软件开发人员也未必懂得测试。
我在读博士学位时,某天有一位比我聪明、编程比我快、学习能力比我强的计算机专业博士生恭恭敬敬地请我坐好,并且史无前例地削了苹果请我吃,为的是向我请教软件测试问题。你必定以为这位仁兄好学之极,非也!他和我同窗三年,从未探讨过软件工程。只因为他明天要去应聘,生怕在面试时被人问倒,就央我当晚为他恶补一把。他还特地问起“白盒测试和黑盒测试”,因为那个公司曾经面试过这类问题。我讲了一会儿测试的概念与方法,他叹了一口气说:“这些玩意儿我读大学十年从来没搞过,怎么能记得住、讲得出来。唉,就去碰碰运气吧。”
我在公司里遇到的软件测试问题要比学校里多得多。曾有一位项目经理来诉苦,说有个项目成员很差劲,不会测试,给了他参考书,还是不知道如何下手,问我怎么办。
我说:“既然他自己学不会,那么你就花点时间指导他,他不至于笨到教不会吧!”
项目经理说:“测试又不是我的工作,我不懂也没时间,我还有很多管理工作要做。”
面对这样的项目经理,你无话可说,只能叹气。
人力资源是有限的,很多情况下软件开发人员不得不兼任测试人员的角色。所以开发人员学会测试只有好处没有坏处,否则会误事的。我发现在小公司干过的人通常技能比较全面,他们从需求开发、系统设计、编程、测试、直到维护,一路滚爬过去,做过一个项目后,该学的全学会了。在这方面大公司的员工反而不及小公司的,由于大公司的岗位安排是“一个萝卜一个坑”,员工们老是做岗位所指定的工作,技术覆盖面相对而言比较窄。
不少开发人员虽然不敌视测试工作,但是有“临时抱佛脚”的坏习惯,往往事到临头才到处找测试资料,向人请教。经常有人向我要关于测试的文档模板,对付测试。
你以为有了文档模板就懂得如何测试了吗?否!
测试虽然并不深奥,但是学好并不容易。不懂得“有效”测试的项目小组往往面临这样的问题:计划中的时间很快就用完了,即使有迹象表明软件中还遗漏着不少缺陷,也只好草草收场,把麻烦留在将来。
由于软件之中有缺陷,要靠测试把缺陷找出来。缺陷是软件“毛病”的统称,其范畴比“错误”更广。例如有些缺陷虽然不会导致软件发生错误,但可能使软件的性能严重下降。
Bug是缺陷的形象比喻,人们喜欢说Bug是因为可以把Bug当作“替罪羊”。软件的缺陷明明是人造成的,有了Bug这词后就可以把责任推给Bug——“都是Bug搞的鬼”。Bug真是太冤枉了。
为什么需要测试?因为软件中有Bug。
为什么软件中有Bug?
以下是一些原因:
(1)开发人员不太了解需求,不清楚应该“做什么”和“不做什么”,常常做不合需求的事情,因此产生了Bug。
(2)软件系统越来越复杂,开发人员不太可能精通所有的技术,如果不能正确地使用技术,将产生Bug。
(3)技术文档普遍比较糟糕,文档本身就有Bug,导致使用者产生更多的Bug。
(4)软件需求、设计报告、程序经常发生变更,每次变更都可能产生新的Bug。
(5)任何人在编程时都可能犯错误,导致程序中有Bug。
(6)人们常处于进度的压力之下,急忙之下容易产生Bug,尤其是在期限临近之际。
(7)人们过于自信,喜欢说“没问题”,不真实的“没问题”将产生真正的问题。
测试的目的是为了发现尽可能多的缺陷。可是这个观念不容易被人接受。
正确理解测试的目的十分重要。如果认为测试的目的是为了说明程序中没有缺陷,那么测试人员就会向这个目标靠拢,因而下意识地选用一些不易暴露错误的测试示例。这样的测试是不真实的。
目前高校的科技成果鉴定普遍存在虚假现象。“成果鉴定”相当于软件开发过程中的“验收测试”,但是大部分人在测试时“只报喜不报忧”。我在读硕士时就亲身经历过这样的事情。我们的项目主要研究集成电路制造过程中的成品率问题。当时国内大多数工厂的集成电路成品率只有百分之几,我们开发的软件可以把“精心挑选”的集成电路的成品率优化到98%。演示效果是如此的好,以致一位评委(某厂的总工程师)不无讽刺地说:“采用你们的成果,我们可要发大财了。”这个项目就轻易地通过了鉴定,并且不久后获得了电子工业部科技进步二等奖。这就象在考试时通过作弊取得了好成绩而被表扬。我那时尚且纯真,羞愧之余,不禁对高校科研成果的水平和真实性大失所望。当我在大学里呆了十年后,已经不再失望,因为很少抱希望。
如果为了说明软件有多么好,那么应当制作专门的演示。千万不要将“测试”与“演示”混为一谈。
看来测试并不单单是个技术问题,还是个职业道德问题。
根据测试的目的,可以得出一个推论:成功的测试在于发现了迄今尚未发现的缺陷。所以测试人员的职责是设计这样的测试用例,它能有效地揭示潜伏在软件里的缺陷。
如果测试工作很全面、很认真,但是的确没有发现新的缺陷。那么这样的测试是否毫无价值?
不,那不是测试的过失,应当反过来理解:由于软件的质量实在太好了,以致于这样的测试发现不了缺陷。
所以,如果产品通过了严格的测试,大家不要不吭气,应当好好地宣传一把,把测试的成本捞回一些。
(1)测试能提高软件的质量,但是提高质量不能依赖测试。
(2)测试只能证明缺陷存在,不能证明缺陷不存在。“彻底地测试”难以成为现实,要考虑时间、费用等限制,不允许无休止地测试。我们应当祈祷:软件的缺陷在产品被淘汰之前一直没有机会发作。
(3)测试的主要困难是不知道如何进行有效地测试,也不知道什么时候可以放心地结束测试。
(4)每个开发人员应当测试自己的程序(份内之事),但是不能作为该程序已经通过测试的依据(所以项目需要独立测试人员)。
(5)80-20原则:80%的缺陷聚集在20%的模块中,经常出错的模块改错后还会经常出错。
(6)测试应当循序渐进,不要企图一次性干完,注意“欲速则不达”。
如果一股脑地罗列出测试的种类,常见的大体有20种,如表7-1所示(参见文献 [Hower2001])。
名称 | 说明 |
---|---|
黑盒测试 | 基于软件需求,而不是基于软件内部设计和程序实现的测试方式。 |
白盒测试 | 基于软件内部设计和程序实现的测试方式。 |
单元测试 | 主要测试软件模块的源代码。一般由开发人员而非独立测试人员来执行,因为测试者需要懂得该单元的设计与程序实现,测试者可能需要编写额外的测试驱动程序。 |
集成测试 | 将一些“构件”集成一起时,测试它们能否正常运行。这里“构件”可以是程序模块、客户机-服务器程序等等。 |
功能测试 | 测试软件的功能是否符合功能性需求,通常采用黑盒测试方式。一般由独立测试人员执行。 |
系统测试 | 测试软件系统是否符合所有需求,包括功能性需求与非功能性需求。一般由独立测试人员执行,通常采用黑盒测试方式。 |
回归测试 | 指错误被修正后或软件功能、环境发生变化后进行的重新测试。回归测试的困难在于不好确定哪些内容应当被重新测试。 |
验收测试 | 由客户或最终用户执行,测试软件系统是否符合需求规格说明书。 |
负载测试 | 测试软件系统的最大负载,超出此负载软件可能会失常。 |
压力测试 | 概念上与负载测试相似,叫法不同。 |
性能测试 | 测试软件在各种状况下的性能,如在正常或最大负载下的状况。 |
易用性测试 | 测试软件是否易用,主观性比较强。一般要根据很多用户的测试反馈信息,才能评价易用性。 |
安装与反安装测试 | 测试软件在“全部、部分、升级”等状况下的安装/反安装过程。 |
安全性测试 | 测试该系统防止非法侵入的能力。 |
兼容性测试 | 测试该系统与其它软件硬件兼容的能力。 |
比较测试 | 通过与同类产品比较,考察该系统的优点、缺点。 |
Alpha 测试 | 一种先期的用户测试,此时系统刚刚开发完成。 |
Beta测试 | 一种后期的用户测试,此时系统已经通过内部测试,大部分错误已经改正,即将正式发行。 |
表7-1 测试的常见种类
表7-1中那么多的测试会把人吓住,让人们不知如何下手。如果不把测试好好分类,理清关系,人们理解和执行起来都会很费力。
按测试方式分类,可以把不关心软件内部实现的测试通称为“黑盒测试”。反之,将依赖软件内部实现的测试通称为“白盒测试”。黑盒测试的主要依据是“需求”,而白盒测试的主要依据是“设计”。
按测试阶段分类,测试可分4个主要阶段:单元测试、集成测试、系统测试和验收测试。这是一种“从小到大”、“由内至外”、“循序渐进”的测试过程,体现了“分而治之”的思想。
单元测试的粒度最小,一般由开发小组采用白盒方式来测试,主要测试单元是否符合“设计”。
集成测试界于单元测试和系统测试之间,起到“桥梁作用”,一般由开发小组采用白盒加黑盒的方式来测试,既要验证“设计”又要验证“需求”。
系统测试的粒度最大,一般由独立测试小组采用黑盒方式来测试,主要测试系统是否符合“需求规格说明书”。
验收测试与系统测试非常相似,主要区别是测试人员不同,验收测试由用户执行。
如果软件开发过程采用严格的瀑布模型,那么开发与测试有“V”型的对应关系,如图7-1所示(参见文献[Wiegers2000, p116])。
从测试的内容划分,适合采用白盒测试方式的主要有:接口测试、路径测试等。比较适合采用黑盒测试方式的主要有:功能测试、健壮性测试、性能测试、用户界面测试、安全性测试、压力测试、可靠性测试、安装/反安装测试等。上述测试阶段、测试方式、测试内容的关系如表7-2所示:
“黑盒”、“白盒”都是比喻。“黑盒”表示看不见盒子里头的东西,意味着黑盒测试不关心软件内部设计和程序实现,只关心外部表现,即通过观察输入与输出即可知道测试的结论。任何人都可以依据软件需求来执行黑盒测试。
白盒测试关注的是被测对象的内部状况,需要跟踪源代码的运行。白盒测试者必须理解软件内部设计与程序实现,并且能够编写测试驱动程序,一般由开发人员兼任测试人员的角色。
黑盒测试与白盒测试的对比如表3所示。
通常用户关心的是软件是否符合既定的需求,而不关心软件是如何设计与实现的。既然如此,我们为什么不把精力集中在黑盒测试上,为什么还要进行白盒测试?
道理如下:
(1)黑盒测试只能观察软件的外部表现,即使软件的输入输出都是正确的,却并不能说明软件就是正确的。因为程序有可能用错误的运算方式得出正确的结果,例如“负负得正,错错得对”,只有白盒测试才能发现真正的原因。
(2)白盒测试能发现程序里的隐患,象内存泄漏、误差累计问题。在这方面,黑盒测试存在严重的不足。
问题还可以反过来问:如果程序通过了白盒测试,为什么还需要黑盒测试?
道理如下:通过了白盒测试只能说明程序符合设计要求,并不能说明最终的软件符合用户需求。如果系统设计偏离了用户需求,那么100%正确的程序也不是用户想要的。将错就错不等于正确,所以还需要黑盒测试。
可见黑盒测试与白盒测试都不能取代对方,只有两者结合才能弥补对方的不足。
在系统设计阶段,整个系统最终被细分为许多模块,这里可以把模块理解为单元。每个单元的接口、数据结构与算法都已经设计完成。在实现阶段,程序员首先编写这些单元,然后把单元集成为子系统,再把子系统集成为最终的目标系统。在做集成之前,应当先执行单元测试,以保证单元本身正确无误。为了测试单元是否符合设计要求,必须跟踪到单元内部,检查所有的源代码。因此单元测试采用白盒测试方式。
由于单元通常不是可运行程序(如.exe文件),因此无法直接测试。测试者必须编写额外的可运行的测试驱动程序,通过测试驱动程序调用单元的接口,从而跟踪到单元的内部。单元测试的主要麻烦就在此处,并且由于测试驱动程序不是目标系统的组成部分,测试完了也就用不着了(至多被备份起来),让人有一种“不值得”的感觉。每当你有这种思绪时,请尽量想开点。人一生中会干不知多少“不值得”的事情,就别在乎单元测试中那一点浪费了!何况单元测试做好了,以后的工作就会轻松一些,好处还是蛮多的。
单元测试人员既要了解单元的详细设计,又要有本事为该单元编写测试驱动程序。这样的测试人员不好找,可以就地取材,让开发小组成员兼任测试人员的角色。通常做法是,开发人员编写完成某个单元后,先自我检查,然后请同伴进行代码审查,再请同伴进行单元测试,如果发现缺陷,原开发者应当及时修正程序。
这样边开发、边审查、边测试,可以高效率地发现并排除单元中的缺陷。如果单元通过了同伴的审查与测试,就可以升级成为基准(Baselined)文件,以后再对该单元修改时,必须遵循变更控制规则,避免修改过于频繁而失控。
一个系统的单元通常比较多,看起来单元测试的工作量比较大,挺烦人的。但由于每个单元比较小,而且相对独立,因此测试与改错的难度比较底。综合考虑,单元测试并不可怕。
能否等到整个系统全部开发完后,再集中精力进行一次性地单元测试呢?这样是否会提高单元测试的效率呢?
不会!如果这样做,在开发过程中,缺陷会越积越多并且分布得更广、隐藏得更深,反而导致测试与改错的代价大大增加。最糟糕的是无法估计测试与改错的工作量,使进度失去控制。因此为图眼前省事而省略单元测试或者“偷工减料”,是“得不偿失”的做法。
软件实现一般是渐增式的,从编写单元到完成整个系统,通常要经历数次集成(除非软件的规模很小)。于是每次单元集成都要进行相应的集成测试。
有人可能问:“如果每个单元都通过了测试,把它们集成一起难道会有什么不妥吗?”
的确有可能!
要把N个单元集成一起肯定靠接口耦合,这时可能会产生在单元测试中无法发现的问题。例如:数据通过不同的接口时可能出错;几个函数关联在一起时可能达不到预期的功能;在某个单元里可以接受的误差可能在集成后被扩大到无法接受的程度。所以集成测试是必要的,不是多此一举。
集成测试界于单元测试和系统测试之间,如何测试通常取决于“集成体”的特征。
刚开始时,“集成体”的规模比较小,离目标系统比较遥远,那么要以白盒测试为主。不仅要跟踪“集成体”的那部分新代码(从语义上理解,它们不能算是单元),有时还要跟踪到那些被测试过的单元的内部,如果错误是单元造成的话。
随着集成次数的增加,“集成体”的规模越来越大,离目标系统越来越近,此时要以黑盒测试为主。可以提前做系统测试阶段的部分工作,例如子系统的功能测试、性能测试等等。
由于每个“集成体”并不是最终的目标系统,所以测试者还得编写测试仿真程序。集成测试工作通常让开发小组成员承担,采用白盒加黑盒的混合测试方式。
集成测试属于软件实现阶段,如何安排测试取决于集成开发的方式。常见的集成方式有两种:“自顶向下”和“自底向上”。因此产生“自顶向下”和“自底向上”两种集成测试方式,一种集成测试方式的优点差不多就是另一种的缺点 [Pressman99, p345-348]。我们并不建议严格按照“自顶向下”和“自底向上”的方式开发软件,折衷办法是:软件结构的高层采用“自顶向下”方式开发,软件结构的底层采用“自底向上”方式开发。
如果软件系统要分若干次集成,能否只在最后一次做集成测试?以便降低集成测试的工作量呢?
道理如同单元测试不能等到所有单元开发完了再做一样,把集成测试拖到最后执行也会导致缺陷的累积和扩散,使集成测试与改错的代价增加。
当软件开发完毕后,需要进行全面的系统测试。系统测试采用黑盒测方式,其目的是检查系统是否符合软件需求。系统测试的主要内容有:功能测试、健壮性测试、性能-效率测试、用户界面测试、安全性测试、压力测试、可靠性测试、安装/反安装测试等。
在集成测试的时候,已经对一些子系统进行了功能测试、性能测试等等,那么在系统测试时能否跳过相同内容的测试?
不能!
因为集成测试是在仿真环境中开展的,那不是真正的目标系统。再者,单元测试和集成测试通常由开发小组执行。根据测试心理学的分析,开发人员测试自己的工作成果虽然是必要的,但不能作为成果已经通过测试的依据。
为了保证测试的客观性,应当由机构的独立测试小组来执行系统测试。
验收测试的内容与系统测试的内容几乎是相同的,主要区别在于测试人员不同。验收测试人员来自于客户方,而系统测试人员则来自于开发方。
难道开发方的测试人员执行的系统测试还不够客观公正吗?非得要请用户重新测试吗?
这不仅仅是“客观公正”的问题,主要原因如下:
(1)首先是“信任”问题。对于合同项目而言,如果测试小组是开发方的人员,客户怎么能够轻易相信“别人”呢? 所以当项目进行系统测试之后,客户再进行验收测试是情理之中的事。否则,那是客户失职。
(2)其次,即使开发方测试人员对天发誓“他们在系统测试时铁面无私、绝无半点虚假”,也不能因此省略客户验收测试。不论是合同项目还是非合同项目,软件的最终用户各色各样(如受教育程度不同、使用习惯不同等等)。测试小组至多能够模仿小部分用户的行为,但并不具有普遍的代表性。
如此说来,何不让用户取代测试小组,把系统测试和验收测试合二为一呢?
理论上是可以的,但在现实中却行不通,因为:
(1)系统测试不是一会儿就能做完的,比较长时间的用户测试很难组织。用户还有自己的事情要做,他们为什么要为别人测试呢?即使用户愿意做系统测试,他们消耗的时间、花费的金钱大多比测试小组的高。
(2)系统测试时会找出相当多的软件缺陷,软件需要反反复复地改错。如果让用户发现这样的“内幕”,一是丢脸,二是会吓跑买主。所以还是关起门来,先让测试小组做完系统测试的好。
验收测试可以分成两类:Alpha测试和Beta测试。两者的主要区别是测试的场所不同。Alpha测试是指把用户请到开发方的场所来测试,Beta测试是指在一个或多个用户的场所进行测测试。
Alpha测试的环境是受开发方控制的,用户的数量相对比较少,时间比较集中。而Beta测试的环境是不受开发方控制的,谁也不知道用户如何折磨软件,用户数量相对比较多,时间不集中。一般地,Alpha测试先于Beta测试执行。通用的软件产品需要较大规模的Beta测试,测试周期比较长。如果产品通过了Beta测试,那么就可以正式发行了。
如果是合同项目,应当在合同中说明验收测试是Alpha测试还是Beta测试,或者两个都要。
回归测试是指对某些已经被测试过的内容进行重新测试。每当软件增加了新的功能,或者软件中的缺陷被修正,这些变更都有可能影响软件原有的功能和结构。为了防止软件的变更产生无法预料的副作用,不仅要对新内容进行测试,还要对某些老内容进行回归测试。
回归测试不象单元测试、集成测试、系统测试、验收测试那样能划分出清晰的阶段,实事上每一阶段的测试都可能附带回归测试。所以任何测试文档都必须纳入配置管理系统,否则弄丢了就没法进行“回归”测试。
回归测试没有新花样,其特点就是“重复”,让人厌烦。最笨的回归测试就是老老实实地从头到尾把上一次测试工作重复一遍。直觉告诉我们,这样的测试浪费太大,实在不值得。我们期望的是:找出“需且必需”回归测试的那个子集,使回归测试的代价降到最低。这种本事只能意会不可言传,难以写出操作规则来。
既然回归测试没有创新之处,最好设法使其自动化地执行,这样省时省力又免得遗漏。回归测试是自动化测试的一个重点研究对象,目前市场上有一些专用的回归测试工具。
在公司内部测试产品时,需要开发人员参与吗?需要成立独立的测试小组吗?
应当如何组织人员开展测试工作?
让我们先看一看Microsoft公司关于测试的经验教训,再讨论上述问题。
在20世纪80年代初期,Microsoft公司的许多软件产品出现了Bug。比如,在1981年与IBM PC机一起推出的BASIC软件,用户在用“.1”(或者其他数字)除以10时,就会出错。在FORTRAN软件中也存在破坏数据的Bug。由此激起了许多采用Microsoft操作系统的PC厂商的极大不满,而且很多个人用户也纷纷投诉。
Microsoft公司的经理们发觉很有必要引进更好的内部测试与质量控制方法。但是遭到很多程序设计师甚至一些高级经理的反对,他们固执地认为在高校学生、秘书或者外界合作人士的协助下,开发部门可以自己测试产品。在1984年推出Mac机的Multiplan(电子表格软件)之前,Microsoft曾特地请Arthur Anderson咨询公司进行测试。但是外界公司一般没有能力执行全面的软件测试。结果,一种相当厉害的破环数据的Bug迫使Microsoft公司为它的2万多名用户免费提供更新版本,代价是每个版本10美元,一共化了20万美元,可谓损失惨重。
痛定思痛后,Microsoft公司的经理们得出一个结论:如果再不成立独立的测试部门,软件产品就不可能达到更高的质量标准。IBM和其它有着成功的软件开发历史的公司便是效法的榜样。但Microsoft公司并不照搬IBM的经验,而是有选择地采用了一些比较先进的方法,如独立的测试小组,自动测试以及为关键性的构件进行代码审查等。Microsoft公司的一位开发部门主管戴夫•穆尔回忆说:“我们清楚不能再让开发部门自己测试了。我们需要一个单独的小组来设计测试用例和执行测试,并把测试信息反馈给开发部门。这是一个伟大的转折点。”
但是有了独立的测试小组后,并不等于万事大吉了。自从Microsoft公司在1984年与1986年之间扩大了测试小组后,开发人员开始“变懒”了。他们把代码扔在一边等着测试,忘了唯有开发人员自己才能阻止错误的发生,才能防患于未来。此时,Microsoft公司历史上第二次大灾难降临了。原定于1986年7月发行的Mac机的Word 3.0,千呼万唤方于1987年2月问世。这套软件竟然有700多处错误,有的错误可以破坏数据甚至摧毁程序。一下子就使Microsoft名声扫地。公司不得不为用户免费提供升级版本,费用超过了100万美元。(请参考《微软的秘密》[Cusumano])
只有开发人员最了解自己的程序,那么让开发人员测试自己的程序不是顺理成章的事情吗?
必须先解开这个疑问,我们才能合理地组织测试队伍。
开发人员应当测试自己的程序,这是他分内的工作。问题是开发人员在测试自己的程序时,很难做到客观、公正,所以自我测试不具有说服力。主要原因有:
(1)测试的目的是找出尽可能多的缺陷。所以测试是“破坏性”的,而开发却是“建设性”的。从行为学角度看,开发与测试是对立的。开发人员总是喜欢欣赏程序的成功之处,而不愿看到失败之处。让开发者去做“蓄意破坏”的测试,就象杀自己的孩子一样难以接受。即便开发者非常诚实,但“珍爱程序”的心理让他在测试时不知不觉地带入了虚假成份。
(2)开发者对自己的程序印象深刻,并总以为是正确的(自信是应该的)。倘若在设计时就存在理解错误,或因不良的编程习惯而流下了隐患,那么他本人很难发现这类错误。
(3)开发者对自己的程序的功能、接口十分熟悉,他自己几乎不可能因为使用不当而引发错误,这与大众用户的情况不太相似,所以测试自己的程序不具备典型性。
不少人有这样的误解:开发人员根本不应该参加测试,应该让无情的、爱挑剔的项目外的人来测试。这种想法显然走极端了。
从Microsoft公司的教训中可知,公司内部对产品的测试,最好是开发小组与独立测试小组共同参与。独立测试小组适合于从事黑盒测试,能保证测试的客观性,但他们通常缺乏白盒测试的能力。开发小组适合于从事白盒测试,因为他们最了解软件的内部结构。为了避免“心理作怪”,在进行白盒测试时,应当让开发小组的成员相互测试对方的程序。
不同的企业其人力资源状况也不同,组织测试人员应当考虑企业的现实情况:
(1)条件特别好的公司(如Microsoft),可以为每一个开发人员分配一名独立的测试人员。这样的测试人员职业化程度很高,可以完成单元测试、集成测试和系统测试工作,能够实现开发与测试同步进行。开发人员就可以专心地搞开发了。
(2)条件比较好的公司,可以设置一个独立的测试小组,该测试小组轮流参加各个项目的系统测试。而单元测试、集成测试工作由项目的开发小组承担。一般大型企业都有独立的测试小组,并与质量保证小组有一定的关系。
(3)条件一般的公司,养不起独立的测试小组。单元测试、集成测试工作由项目的开发小组承担。当项目进展到系统测试阶段,可以从项目外抽调一些人员,加上开发人员,临时组织系统测试小组。
(4)条件比较差的公司,也许只有一个项目和为数不多的一些开发人员。那么就让开发人员一直兼任测试人员的角色,相互测试对方的程序。如果人员实在太少了,只好让开发者测试自己的程序,有测试总比没有测试好吧!
开发人员不能很好地测试自己的程序是因为做不到“无情”。但如果测试人员真的做到了“无情”却会引起开发人员的愤怒,遭人白眼。由于开发与测试存在“对立”关系,开发人员与测试人员很容易产生矛盾,这对项目而言是一种伤害。
开发人员的注意事项:
(1)不要敌视测试人员。要理解测试的目的就是发现缺陷,是测试人员的工作职责。不要以为测试人员吃饱了没事干,存心找茬。
(2)不要轻视测试人员,别说人家技术水平差,不配搞开发只好搞测试。
测试人员的注意事项:
(1)发现缺陷时不要嘲笑开发人员,别说他的程序真臭、到处是Bug。
(2)在开发人员压力太大时或心情不好时不要火上浇油,发现缺陷时别大声嚷嚷。
尽量不要相互讽刺对方,例如:
A对B说:你唯一的特点就是无能。
B对A说:你唯一的特点就是粗鲁。
还要注意的是,如果测试人员与开发人员的关系非常好,可能会导致在测试的时候“手下留情”,这对项目也是一种伤害。
企业的主要目的是获取利润,降低成本也是盈利的一种方式。企业对待测试的正确态度是:用较低的代价实现有效的测试,不应为了追求完美的测试而不失一切代价。
据调查,对于大多数软件产品而言,用于测试与改错的时间一般会占软件开发周期的30%,这是一笔很大的花费。由于测试并没有为产品增加功能或添加特色,那么大的开销实在让人心疼。企业应当设法制定一些有效的策略来降低测试的代价。设想如果能把“30%”压缩至“10%”,真的可以榨出很多油水来!
(1)不论是开发人员还是独立测试人员,都应当对他们进行必要的测试技能培训。这样可以提高他们的工作效率。注意,很多开发人员并不懂得如何测试,出于本能的测试通常是低效率的。
(2)测试必须有计划,不可以胡乱测试。否则东测一下,西测一下,漏洞百出。
(3)测试过程中产生的所有文档,如测试计划、测试用例、测试报告、改错记录等等,必须纳入配置管理系统。
(4)在白盒测试之前,应当先进行代码审查,这样可以降低白盒测试的代价。
(5)对测试工作进行必要的审查,可由质量保证人员监督,防止测试流于形式。
航空航天、武器、金融等领域的软件系统,要么性命攸关要么涉及重大财产。这类软件系统的质量重于“泰山”,因此对测试要求非常严格。有时对测试的投入甚至比对开发的投入还要高。之所以能这样做,一是值得,二是花费得起(有足够的经费和时间)。可是这样的系统毕竟是少数。
对于一般性的软件系统而言,开发商对测试的投入是有限度的。如果测试的代价过高,导致产品的利润低微甚至赔本,开发商绝对不会干这样“吃力不讨好”的事情。所以降低软件测试的代价是企业普遍关注的问题。
降低软件测试代价有两种基本方法:
(1)减少冗余的和无价值的测试。这种方法精炼了测试工作,并不会危害软件的质量。
(2)“偷工减料”法。例如压根不作单元测试和集成测试,只草草地做一次系统测试。显然这种方法将危害软件的质量,导致维护的代价增加。表面上看起来测试的代价明显降低了,实质上代价没有降低而是转移了。
上述第(1)种方法是我们追求的,第(2)种方法只能在万般无奈的情况下采用。
一、如何减少冗余的测试
(1)白盒测试与黑盒测试的方式虽然不同,但往往有“异曲同工”之妙。在很多地方,白盒测试与黑盒测试会产生一模一样的效果(或者能推理出来),这样的测试是冗余的。一般地,白盒测试要编写测试驱动程序、逐步跟踪源程序,比黑盒测试麻烦。如果能减少白盒测试之中的冗余,就可以降低不少代价。所以在执行白盒测试时,我们应当将精力集中在黑盒测试无能为力的方面,例如内存泄漏、误差累积、数据溢出等等。
(2)在集成测试、系统测试阶段,可能要执行多次“回归测试”。每一次“回归测试”都会存在不少的冗余,应当设法剔除不必要的重复测试工作。
二、如何减少无价值的测试
无价值的测试通常是由于不懂得测试技术引起的。例如功能测试,在等价区间之中,本来只要测试一个典型的输入就行了,如果有人在此区间测试了100次,那么其中99次就是无价值的。(请参见本章第7节)
减少冗余测试和无价值测试是需要动脑筋的,做起来并不象说的那么轻松。实践出真知啊。
三、如何“偷工减料”
有一些“短、平、快”的项目,经费本来就少,用户对质量要求也马马虎虎。为了能多挣一点钱,开发方不得不采用“偷工减料”的方式来降低测试代价。几乎没有软件工程书籍会论述这种学者们所不齿的做法。但目前国内大多数小型软件企业的条件比较差,“原始积累”总是不会很正规的。这是社会现实问题,不要回避,值得讲一讲。
偷工减料的途径无非就是减少测试的内容和频度。但不能砍得太狠,否则软件拿不出手。基本方法是找出软件中需要优先测试的部分(见表7-4),其它次要部分可以忽略或将来再测试。
测试什么时候可以结束?有如下几种答复:
(1)在软件消亡之前,测试永远不可能结束。它只是从测试人员转移到用户或维护人员身上而已。
(2)从统计学角度讲,如果软件运行1000个CPU小时内不出错的概率大于0.995的话,那么我们就有95%的信心说测试可以结束了。
(3)当期限来临或经费用光了的时候,测试就可以结束了。
第一种答复是真理,但毫无参考价值。第二种答复比较科学,但太抽象,普通技术人员难以掌握。第三种答复特别实在,但很无奈,因为测试是被迫终止的。
以下是三种比较实用的规则,可以结合使用。
一、基于测试用例的规则
(1)先构造测试用例(并请有关人员进行评审)。
(2)在测试过程中,当测试用例的不通过率达到20%时,则拒绝继续测试,待开发人员修正软件后再进行测试。
(3)当功能性测试用例通过率达到100%,非功能性测试用例通过率达到90%时,允许正常结束测试。
该规则的优点是适用于所有的测试阶段,缺点是太依赖于测试用例。如果测试用例非常糟糕,那么该规则就失效了。
二、基于“测试期缺陷密度”的规则
把测试一个CPU小时发现的缺陷数称为“测试期缺陷密度”。绘制“测试时间-缺陷数”的关系图,如果在相邻n个CPU小时内“测试期缺陷密度”全部低于某个值m时,则允许正常结束测试。例如n大于10,m小于等于1。该规则比较适用于系统测试阶段。
三、基于“运行期缺陷密度”的规则
把软件运行一个CPU小时发现的缺陷数称为“运行期缺陷密度”。绘制“运行时间-缺陷数”的关系图,如果在相邻n个CPU小时内“运行期缺陷密度”全部低于某个值m时,则允许正常结束测试。例如n大于100,m小于等于1。该规则比较适用于验收测试阶段,即客户试运行软件期间。
需求变更可能会让项目所有成员遭殃,如何“预防变更”以及“降低变更的代价”是软件工程的经典问题。本节仅论述需求变更对测试的影响。
需求变更将导致软件设计和实现的变更,也导致了测试变更。最让人难过的是上一次测试有可能白做了,如果软件变更比较大的话。
测试人员不要只是自认倒霉,应当主动作些应变:
(1)及时了解需求变更的详细情况,尽早调整测试计划,不要闷头按原计划测试。
(2)将软件中稳定的部分与易变的部分区别对待,前者先测试,后者后测试。
(3)向领导反映需求变更对测试造成的影响,为自己争取余地。
(4)设计一些比较灵活的测试用例,能适应某些变更(不过技术难度比较高)。
引申问题:如果在系统测试时,对照需求文档,发现软件多了功能或少了功能,该怎么办?
如果发现软件少了功能,测试人员不可为了少干些活而隐瞒事实。如果发现软件多了功能,测试人员不可认为这些功能反正是“锦上添花”,便自作主张地测试了事。两种情况都要报告给项目经理,有可能导致一系列的变更。
在系统测试和验收测试阶段,应当设法让更多的人参与测试,以便尽早发掘并消灭缺陷。为了调动测试者的积极性,企业或项目应当设立奖励机制:
(1)根据缺陷的危害程度,把奖金分等级。
(2)每个新缺陷对应一份奖金,把奖金发给第一个发现该缺陷的人。
注意,奖金额要适当,太低了人们不感兴趣。若奖金额比较高,如果软件错误百出,会让项目破产的。
所有阶段的测试都应当遵循如下流程(如图7-2所示):
第一步:制定测试计划。该计划被批准后转向第二步。
第二步:设计测试用例。该用例被批准后转向第三步。
第三步:如果满足“启动准则”(Entry Criteria),那么执行测试。
第四步:撰写测试报告。
第五步:消除软件缺陷。如果满足“完成准则”(Exit Criteria),那么正常结束测试。
一、测试的“启动准则”
同时满足以下条件,允许开始测试:
(1)测试计划已经制定并且通过了审批;
(2)测试用例已经设计并且通过了审批;
(3)被测试对象已经开发完毕并等待测试。
二、测试的“完成准则”
对于非严格系统可以采用“基于测试用例”的准则。同时满足以下条件,允许结束测试:
(1)功能性测试用例通过率达到100%;
(2)非功能性测试用例通过率达到90%时。
对于严格系统,应当补充“基于测试期缺陷密度”的规则:
(3)相邻n个CPU小时内“测试期缺陷密度”全部低于某个值m。例如n大于10,m小于等于1。(参见7.4.3节)
由于测试的种类多、内容广并且时间分散,建议把单元测试、集成测试、系统测试、验收测试各阶段的《测试计划》分开写。
《测试计划》的撰写宜早不宜迟,只要信息足够就可以提前起草,不要拖到快要测试时才开始写。制定与审批《测试计划》的角色及递交时间如表7-5所示。
表7-6是《测试计划》的参考模板,使用时应根据实际情况作适当的修改。
《测试计划》撰写完毕后应当请有关人员(如项目经理)对其审批,表7-7为测试计划的审批表。
什么是测试用例?
测试用例是用于检验软件是否符合要求的一种“示例”,其基本要素有:目的、前提条件、输入数据或动作、期望的响应。《测试用例》就是描述各种测试用例的文档,相当于一本“测试操作手册”。关于测试用例的一些常识如下:
(1)设计测试用例的目的是找出需求、设计、代码中的毛病,因此最好尽可能早地设计。
(2)测试用例的设计需要动脑筋,不见得比“正向设计”简单。
(3)不同的测试用例其用途应当不一样,不要累赘。
每个测试阶段可能有各色各样的测试用例,撰写《测试用例》显然比写《测试计划》费时费力,所以不要只让一个人干,应当让大家分担。设计与审批《测试用例》的角色及递交时间如表7-8所示。
本章7.6节和7.7节提供了各种测试用例的参考模板,建议读者在使用时应根据实际情况进行组合或修改。
《测试用例》撰写完毕后应当对其审批,表7-9是测试用例的审批表。
《测试报告》的主要用途是:(1)记录测试实况;(2)对本次测试进行分析,提出建议。表7-10是《测试报告》的参考模板,使用时应根据实际情况进行修改。
软件单元测试的主要内容是接口测试和路径测试,毫无疑问应当采用白盒测试方式。
对于功能测试、健壮性测试、性能测试、用户界面测试、安全性测试、压力测试、可靠性测试、安装/反安装测试而言,建议先采用黑盒测试方式,这样测试效率比较高。当黑盒测试方式不奏效或效果不好的时候,再采用白盒测试方式。
如果对源代码中的某个函数进行白盒测试,那么要跟踪到函数的内部,检查所有代码的运行状况。初看起来,白盒测试可获得100%的正确性。但不幸的是,即使一段很小的程序,它的逻辑路径可能多得让人无法彻底地进行白盒测试。
如图7-2所示的程序结构中,代码总数少于100行,但逻辑路径却有10**14之巨。假设我们有能力在一秒钟测试1000条路径,那么不分白天黑夜需要3170年才能将全部路径测试完。
图7-2的程序结构还真算不上是复杂的呢!但我们也不要对白盒测试绝望了,人的智力胜过计算机千万倍,谁会笨到用穷举法来判断程序的对错呢?一般地,人们只需要对几条重要路径上的几个关键点进行测试就行了,其它状况通过推理就可知道。白盒测试的重点是“接口”和“路径”。注意,这里将接口、路径二者分开讨论,在实际测试时二者是关联的。
数据一般通过接口输入和输出,所以接口测试是白盒测试的第一步。每个接口可能有多个输入参数,每个参数有“典型值”、“边界值”、“异常值”之分,所以输入的组合数可能并不少。根据接口的定义,可以推断某种输入应当产生什么样的输出。输出包括函数的返回值和输出参数。如果实际输出与期望的输出不一致,那么说明程序有错误。白盒方式的接口测试和黑盒方式的功能测试,其方法十分相似,请参见7.7.1节。
由于接口测试只关心输入和输出,并不知道函数体内是怎样运行的。有时候,输入、输出都是正确的,而函数体内却可能有错误(或者隐藏了错误)。所以仍需要进行路径测试。
一个函数体内的语句可能只有十几条,但逻辑路径可能有成千上万条。想遍历测试几乎是不可能的,不测试或者胡乱找几条路径测试却又不行。
对于严格系统而言,采用形式化或准形式化的方式来推导出的那些路径才能让人信服。文献 [Pressman99, p315-p326] 介绍了“流图符号”、“环形复杂性估算”、“图矩阵”、“条件测试”、“数据流测试”、“循环测试”等方法。这些方法似乎比编程还要复杂,普通技术人员很难掌握。
对于非严格系统而言,在分析路径方面化费很多精力是不值得的。我认为在构造接口测试的同时已经建立了测试路径。因为每一种输入将产生唯一的输出,输入与输出之间的路径也是唯一的。由于接口测试中的输入是有代表性的,因此相应的路径也具有代表性,用得着费煞苦心地去找测试路径吗?
如果路径已经选好了,接下来的工作就是沿着路径一条一条地检查语句。遇到循环时作适当的处理后可以跳出。路径测试的检查表如表7-11所示:
检查项 | 结论 |
---|---|
数据类型问题(1)变量的数据类型有错误吗?(2)存在不同数据类型的赋值吗?(3)存在不同数据类型的比较吗? | |
变量值问题(1)变量的初始化或缺省值有错误吗?(2)变量发生上溢或下溢吗?(3)变量的精度不够吗? | |
逻辑判断问题(1)由于精度原因导致比较无效吗?(2)表达式中的优先级有误吗?(3)逻辑判断结果颠倒吗? | |
循环问题(1)循环终止条件不正确吗?(2)无法正常终止(死循环)吗?(3)错误地修改循环变量吗?(4)存在误差累积吗? | |
内存问题(1)内存没有被正确地初始化却被使用吗?(2)内存被释放后却继续被使用吗?(3)内存泄漏吗?(4)内存越界吗?(5)出现野指针吗? | |
文件I/O问题(1)对不存在的或者错误的文件进行操作吗?(2)文件以不正确的方式打开吗?(3)文件结束判断不正确吗?(4)没有正确地关闭文件吗? | |
错误处理问题(1)忘记进行错误处理吗?(2)错误处理程序块一直没有机会被运行?(3)错误处理程序块本身就有毛病吗?如报告的错误与实际错误不一致,处理方式不正确等等。(4)错误处理程序块是“马后炮”吗?如在被它被调用之前软件已经出错。 |
表7-11 路径测试的检查表
当然,由于接口测试是枚举的,有可能漏掉某些状况,导致一些重要的路径没有被测试。预防措施有:
(1)观察是否有程序语句从来没有被执行过。如果发生在这种情况,要么是程序有错误,存在无用的代码;要么是接口测试不充分,漏掉了一些路径。
(2)要特别留意函数体内的错误处理程序块(如果存在的话),这是最易被人疏忽的路径,隐患最多。
(3)采用自动化的测试工具,可以自动分析路径测试的“覆盖率”。
单元测试只能采用白盒测试方式。由于单元本身不能运行,必须先构造相应的测试驱动程序。单元的接口测试语句全部存放在测试驱动程序中。使用编译器提供的调试工具,可以从测试驱动程序跟踪到单元里面,实现接口测试和路径测试。
如果集成测试阶段需要白盒测试,可以把“集成体”当成大的单元看待。如果“集成体”本身就可以运行,那么不必构造额外的测试驱动程序。直接通过用户界面输入接口测试用例,再用调试工具跟踪到“集成体”内部即可。
接口与路径测试用例的参考模板如表7-12所示。
功能测试就是检查软件的功能是否正确,其依据是需求文档,如《需求规格说明书》。由于正确性是软件最重要的质量因素,所以功能测试必不可少。
功能测试的基本方法是构造一些合理输入(在需求范围之内),检查输出是否与期望的相同。如果两者不一致,即表明功能有误。也有例外的情况,如《需求规格说明书》中的某个功能写错了,而实际上软件的功能却是正确的,这时要更改的是《需求规格说明书》。
功能测试看起来比较简单,只要看得懂《需求规格说明书》,谁都会做。难点在于如何构造有效的输入。由于输入空间通常是无限的,穷举测试显然行不通。那么随便输入一些东西,碰运气行不行?
有一次语文考试,问高尔基是哪国人。一考生乐极而吟:“尔基啊尔基,你若不姓高,我怎知你是中国人。”
这是瞎猜法。如果把这种方法用于功能测试,人累死也测不出什么结果来。
功能测试有两种比较好的测试方法:等价划分法和边界值分析法。
一、等价划分法
等价划分是指把输入空间划分为几个“等价区间”,在每个“等价区间”中只需要测试一个典型值就可以了。等价区间有如下特征:
记(A, B)是函数f(x) 的一个等价区间,在(A, B)中任意取x1进行测试。
如果f (x1) 错误,那么f (x) 在整个(A, B)区间都出错。
如果f (x1) 正确,那么f (x) 在整个(A, B)区间都正确。
注意,等价区间不是数学概念,无法证明,也并不绝对正确,但大多数程序的确存在这种现象。等价划分法来源于人们的直觉与经验,可令测试事半功倍。
二、边界值分析法
关于软件缺陷,有这么一句谚语:“缺陷遗漏在角落里,聚集在边界上”。这是由于人们容易疏忽边界情况造成的。
边界值测试法是对等价划分法的补充。如果A和B是输入空间的边界值,那么除了典型值外还要用A和B作为测试用例。
例如测试函数 。凭直觉,等价区间应是(0, 1)和(1, +∞)。可取典型值x=0.5以及x=2.0进行“等价划分”测试。再取 x=0以及x=1进行“边界值”测试。
有一些复杂的程序,我们实在难以找到等价区间和边界值,这时功能测试就相当有难度。只好凭感觉,多构造几个用例来测试。
功能测试用例的参考模板如表7-13所示。
健壮性是指在异常情况下,软件还能正常运行的能力。健壮性有两层含义:一是容错能力,二是恢复能力。
容错是指发生异常情况时软件不出错误的能力,容错是非常健壮的意思。比如Unix的容错能力很强,很难搞死它。
恢复是指软件发生错误后(不论死活)重新运行时,能否恢复到没有发生错误前的状态的能力。
从语义上理解,恢复不及容错那么健壮。例如某人挨了坏蛋一顿拳脚:特别健壮的人一点事都没有,表示有容错能力;比较健壮人,虽然被打到在地,过了一会还能爬起来,除了皮肉之痛外倒也不用去医院,表示恢复能力比较强;而虚弱的人可能在短期恢复不过来,得在病床上躺很久。
恢复能力是很有价值的。Microsoft公司早期的窗口系统如Windows 3.x和Windows 9x,动不动就死机,其容错性的确比较差。但它们的恢复能力还不错,机器重新启动后一般都能正常运行,看在这个份上,人们也愿意将就着用。
比较温柔的容错性测试通常构造一些不合理的输入来引诱软件出错,例如:
(1)输入错误的数据类型。如“猴”年“马”月。
(2)输入定义域之外的数值。如上海人常说的“十三点”(钟表上最大的数值是十二点没有十三点,上海人常用“十三点”来讽刺神经兮兮的人)。
粗暴一些方式俗称“大猩猩”测试法。除了不能拳打脚踢嘴咬外,什么招术都可以使出来。例如在测试客户机-服务器模式的软件时,把网络线拔掉,造成通信异常中断。这里我举不出更多的例子来,因为我从没有对软件粗暴过,并且这辈子也不打算学会粗暴。让别人去做“大猩猩”测试吧。
恢复测试重点考察一下几项:
(1)系统能否重新运行;
(2)有无重要的数据丢失;
(3)是否毁坏了其它相关的软件硬件。
健壮性测试用例的参考模板如表7-14所示。
性能测试即测试软件处理事务的速度,一是为了检验性能是否符合需求,二是为了得到某些性能数据供人们参考(例如用于宣传)。有时人们关心测试的“绝对值”,如数据送输速率是每秒多少比特。有时人们关心测试的“相对值”,如某个软件比另一个软件快多少倍。
在获取测试的“绝对值”时,我们要充分考虑并记录运行环境对测试的影响。例如计算机主频,总线结构和外部设备都可能影响软件的运行速度;若多个计算机共享资源,软件运行可能慢得像蜗牛爬行。
在获取测试的“相对值”时,我们要确保被测试的几个软件运行于完全一致的环境中。硬件环境的一致性比较容易做到(用同一台计算机即可)。但软件环境的因素较多,除了操作系统,程序设计语言和编译系统对软件的性能也会产生较大的影响。如果是比较几个算法的性能,就要求编程语言和编译器也完全一致。
一些注意事项:
(1)计算机的运算很快,通常人们来不及反应就结束了。所以不要试图让人拿着钟表去测时间,应当编写一段程序用于计算时间以及相关数据。
(2)应当测试软件在标准配置和最低配置下的性能。
(3)不仅要记录软件硬件环境,还要记录多用户并发工作情况。
(4)为了排除干扰,应当关闭那些消耗内存、占用CPU的其它应用软件(如杀毒软件)。
(5)系统要测试的性能的种类可能比较多,应当分别赋予唯一的名称,切勿笼统地用“性能”两字。例如文档管理软件的性能种类有“文件上载速度”、“文件下载速度”等等。
(6)不同的输入情况会得到不同的性能数据,应当分档记录。例如传输文件的容量从100K到1M可以分成若干等级。
(7)由于环境的波动,同一种输入情况在不同的时间可能得到不同的性能数据,可以取其平均值。
性能测试用例的参考模板如表7-15所示。
绝大多数软件拥有图形用户界面。图形用户界面测试和评估的重点是正确性、易用性和视觉效果。在评价易用性和视觉效果时,主观性非常强,应当考虑多个人的观点。
常见的界面元素有窗口、菜单、工具条、按钮、输入框、列表等等,其组合数量可能非常多。好在界面元素大多是成熟的、标准的构件,它们本身一般不会出错,我们可以把精力集中在使用上,省事不少。用户界面测试用例的参考模板如表7-16所示。
信息安全性(security)是指防止系统被非法入侵的能力,既属于技术问题又属于管理问题。信息安全是一门比较深奥的学问,其发展是建立在正义与邪恶的斗争之上。这世界似乎不存在绝对安全的系统,连美国军方的系统都频频被黑客入侵。如今全球黑客泛滥,真是“道高一尺,魔高一丈”啊。
对于大多数软件产品而言,杜绝非法入侵既不可能也没有必要。因为开发商和客户愿意为提高安全性而投入的资金是有限的,他们要考虑值不值得。究竟什么样的安全性是令人满意的呢?
一般地,如果黑客为非法入侵花费的代价(考虑时间、费用、危险等因素)高于得到的好处,那么这样的系统可以认为是安全的。
信息安全性测试有如下步骤:
(1)为非法入侵设立目标,例如“盗窃某个文件”或“更改数据库记录”等。
(2)邀请(或悬赏)一些人扮演黑客,让他们想尽办法入侵系统,实现“目标”。
(3)如果有人成功了,请他详述入侵的过程。别忘了给予奖励。
根据“黑客”的“口供”,开发人员找出系统的漏洞。如果修补漏洞很容易,那么马上就改正。如果修补的代价很高,那么就与“损失的代价”相比较,分析值不值得修改漏洞。
信息安全性测试用例的参考模板如表7-18所示。
压力测试也叫负荷测试,即获取系统能正常运行的极限状态。了解“极限”是很有价值的。例如某种潜艇的正常下潜深度是100米,最大下潜深度是200米。那么平时潜艇会在0-100米处行驶,在危机时候会下潜到200米,但是不敢再往下潜,否则可能要完蛋。
压力测试的主要任务是:构造正确的输入,使劲折腾系统却让它刚好不瘫痪。
例如对服务器进行压力测试时,可以增加并发操作的用户数量;或者连续不停地向服务器发请求;或者一次性向服务器发送特别大的数据等等。看看服务器能否吃得消,如果服务器忙得不能自拔,当前状况就是极限。
压力测试的一个变种是敏感测试。在某种情况下,微小的输入变动会导致系统的表现(如性能)发生急剧的变化。敏感测试目的是发现什么样的输入可能会引发不稳定现象。
压力测试用例的参考模板如表7-19所示。
口语中的可靠性含义宽泛,几乎把正确性、健壮性全部囊括。只要人们发现系统有毛病,便归结为可靠性差。如果这样理解,可靠性测试就会与功能测试、健壮性测试重复。
严格地讲,可靠性是指在一定的环境下、在给定的时间内、系统不发生故障的概率。由于软件不像硬件那样可以“加速老化”,按此定义,软件可靠性测试可能会花费很长时间。
比较实用的办法是,让用户使用该系统,记录每一次发生故障的时刻。计算出相邻故障的时间间隔,注意要去掉非工作时间。这样我们可以方便地统计出不发生故障的“最小时间间隔”、“最大时间间隔”和“平均时间间隔”。其中“平均时间间隔”会让人们大体了解到系统“可靠”的程度。
可靠性测试用例的参考模板如表7-20所示。
功能测试、健壮性测试、性能测试、安全性测试、压力测试、可靠性测试都是在系统已经存在的前提下开展的。即使上述测试都通过了,如果系统在安装时出了错误,用户也不能接受,这真是“大风浪都挺过来了,却在阴沟里翻了船”。
安装测试的主要工作:
(1)至少在标准配置和最低配置两种环境下安装;
(2)如果有安装界面,应当尝试各种选项,如选择“全部安装”、“部分安装”、“升级安装”等。
反安装是安装的逆过程。如果系统不提供反安装功能,那么当用户不要该系统时,只好手工删除。这样可能会遇到麻烦:要么删除得不干净,留下不少垃圾;要么把不该删除的东西删除了,导致其它软件不能运行。
目前市面上有非常流行的、专门制作安装/反安装程序的一些工具,如Install Shelled。制作安装/反安装程序不再是件难事,关键是不要麻痹大意。
安装/反安装测试用例的参考模板如表7-21所示。
如果在软件测试过程中发现了缺陷,应当请开发人员马上改错。改错并不是件容易的事情,开发人员不仅要有勇气改错,还应当掌握改错的方法。本节摘自于作者所著的《高质量程序设计指南——C++/C语言》([林锐2002,p27-p29])。
改错是个大悲大喜的过程,一天之内可以让人在悲伤的低谷和喜悦的颠峰之间跌荡起伏。如果改过了成千上万个程序错误,那么少男少女们不必经历失恋的挫折也能变得成熟起来。
我从大三开始真正接受改错的磨练,已记不清楚多少次汗流浃背、湿透板凳。改不了错误时,恨不得撞墙。改了错误时,比女孩子朝我笑笑还开心。
在做本科毕业设计时,一天夜里,一哥们流窜到我的实验室,哈不拢嘴地对我嚷嚷:“你知道什么叫茅塞顿开吗?”
我象文盲似地摇摇头。
他说:“今天我化了十几个小时没能干掉一个错误,刚才我去了厕所五分钟,一切都解决了。”
他还用那没洗过的手拉我,一定要请我吃“肉夹馍”。那得意劲儿仿佛同时谈了两个女朋友。
软件中的错误通常只有开发者自己才能找出并改掉。如果因畏惧而拖延,会让你终日心情不定,食无味,睡不香。所以长痛不如短痛,要集中精力对付错误。
东北有个林场工人,工作勤奋,一人能干几个人的活。前三十年是伐树劳模,受到周总理的接见。忽有一天醒悟过来,觉得自己太对不起森林,决心补救错误。后三十年成了植树劳模,受到朱总理的接见。若能以此大勇来改错,正是无往而不胜也。我们软件开发人员应当向这位可敬的林场工人学习。
改错的第一步是找出错误的根源,如同医生治病,必须先找出病因才能“对症下药”。
有人问阿凡提:“我肚子痛,应该用什么药?”
阿凡提说:“应该用眼药水,因为你眼睛不好,吃了脏东西才肚子痛。”
根据软件错误的症状推断出根源并不是件容易的事,因为:
(1)症状和根源可能相隔很远。也就是说,症状可能在某一个程序单元中出现,而根源实际上在很远的另一个地方。高度耦合的程序结构加剧了这种情况。
(2)症状可能在另一个错误被纠正后暂时性消失。
(3)症状可能并不是由某个程序错误直接引发的,如误差累积。
(4)症状可能是由不太容易跟踪的人工错误引起的。
(5)症状可能时隐时现,如内存泄漏。
(6)很难重新产生完全一样的输入条件,难以恢复“错误的现场”。
(7)症状可能分布在许多不同的任务中,难以跟踪。
把寻找错误根源的过程称为调试(debugging)。调试的最大忌讳是“急躁蛮干”。人们常说“急中生智”,我不信。我认为大多数人着急了就会蛮干,早把“智”丢到脑后。不仅人如此,动物也如此。
我们经常看到,蜜蜂或者苍蝇想从玻璃窗中飞出,它们会顶着玻璃折腾几个小时,却不晓得从旁边轻轻松松地飞走。我原以为蜜蜂和苍蝇长得太小,视野有限,以致看不见近在咫尺的逃生之窗,所以只好蛮干。可是有一天夜里,有只麻雀飞进我的房间,它的逃生方式竟然与蜜蜂一模一样。我用灯光照着那扇打开的窗户为其引路,并向它打手势,对它说话,均无济于事。它是到天亮后才飞走的,这一宿我和它都没有休息好。
调试的基本方法是“粗分细找”。对于隐藏得很深的Bug,我们应该运用归纳、推理、“二分”等方法先“快速、粗略”地确定错误根源的范围,然后再用调试工具仔细地跟踪此范围的源代码。如果没有调试工具,那么只好用“土办法”:在程序中插入打印语句如printf(…),观看屏幕的输出。
有些时候,世界上最好的调试工具恐怕是那些有经验的人。我们经常会长时间地追踪某个Bug,苦恼万分。恰好有高手路过,被他一语“道破天机”。顿时沮丧的阴云就被驱散,你不得不说“I服了You”。
(1)找到错误的代码时,不要急于修改,先思考一下:修改此代码会不会引发其它问题?如果没有问题,可以放心修改。如果有问题,那么可能要改动程序结构,而不止一行代码。
(2)有些时候,软件中可能潜伏同一类型的许多错误(例如由不良的编程习惯引起的)。好不容易逮住一个,应当乘胜追击,全部歼灭。
(3)在改错之后一定要马上进行回归测试,以免引入新的错误。有人在马路上捡到钱包后得意忘形,不料自己却被汽车撞倒。改了一个程序错误固然是喜事,但要防止乐极生悲。更加严格的要求是:不论原有程序是否绝对正确,只要对此程序作过改动(哪怕是微不足道的),都要进行回归测试。
(4)上述事情做完后,应当好好反思:我为什么会犯这样的错误?怎么能够防止下次不犯相似的错误?最好能写下心得体会,与他人共享经验教训。
优秀的程序员敢于声称自己编写的代码没有错误,这种自信让人羡慕不已。一个错误自身也许很微小,但是程序存在错误这件事很严重。能否做好测试与改错工作,态度是很关键的。
程序员应该把测试当成份内之事,不要过分依赖于外界的“黑盒测试”。“黑盒测试”就象通过提问题来判断一个人是否是个疯子,但无法知道他为什么成了疯子。让程序员先对自己的代码进行白盒测试并非多此一举,这将使你以后的日子更加轻松,并且习惯了你就感觉不到有什么不方便。
改错过程很像侦破案件,有些坏事发生了,而仅有的信息就是它的确发生了。我们必须从结果出发,逆向思考。一旦找到了根源,我们就知道如何改正了。在这个过程中,我们甚至还能发现一些没有预料到问题。
程序出了错误一定要改错,但是“编写优质无错”的程序才是根本的解决之道。在此,我竭力建议大家阅读Steve Maguire著的《Writing Clean Code : Microsoft Techniques for Developing Bug-free C Programs》(有中文译本,[Maguire 1993])。我深受此书的教诲,获益非浅。
店铺地址:https://shop66907778.taobao.com/
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。