本节讲述软件测试的基本概念和基础知识。
表面看来,软件测试的目的与软件工程所有其他阶段的目的都相反。软件工程的其他阶段都是“建设性”的:软件工程师力图从抽象的概念出发,逐步设计出具体的软件系统,直到用一种适当的程序设计语言写出可以执行的程序代码。但是,在测试阶段测试人员努力设计出一系列测试方案,目的却是为了“破坏”己经建造好的软件系统——竭力证明程序中有错误,不能按照预定要求正确工作。
当然,这种反常仅仅是表面的,或者说是心理上的。暴露问题并不是软件测试的最终目的,发现问题是为了解决问题,测试阶段的根本目标是尽可能多地发现并排除软件中潜藏的错误,最终把一个高质量的软件系统交给用户使用。但是,仅就测试本身而言,它的目标可能和许多人原来设想的很不相同。
什么是测试?它的目标是什么?G.Myers给出了关于测试的一些规则,这些规则也可以看作是测试的目标或定义。
(1) 测试是为了发现程序中的错误而执行程序的过程。
(2) 好的测试方案是极可能发现迄今为止尚未发现的错误的测试方案。
(3) 成功的测试是发现了至今为止尚未发现的错误的测试。
从上述规则可以看出,测试的正确定义是“为了发现程序中的错误而执行程序的过程”。这和某些人通常想象的“测试是为了表明程序是正确的”,“成功的测试是没有发现错误的测试”等等是完全相反的。正确认识测试的目标是十分重要的,测试目标决定了测试方案的设计。如果为了表明程序是正确的而进行测试,就会设计一些不易暴露错误的测试方案;相反,如果测试是为了发现程序中的错误,就会力求设计出最能暴露错误的测试方案。
由于测试的目标是暴露程序中的错误,从心理学角度看,由程序的编写者自己进行测试是不恰当的。因此在综合测试阶段通常由其他人员组成测试小组来完成测试。
此外,应该认识到测试决不能证明程序是正确的。即使经过了最严格的测试之后,仍然可能还有没被发现的错误潜藏在程序中。测试只能查找出程序中的错误,不能证明程序中没有错误。关于这个结论下面还要讨论。
怎样才能达到软件测试的目标呢?为了能设计出有效的测试方案,软件工程师必须深入理解并正确运用指导软件测试的基本准则。下面讲述主要的测试准则。
(1)所有测试都应该能追溯到用户需求。 正如上一小节讲过的,软件测试的目标是发现错误。从用户的角度看,最严重的错误是导致程序不能满足用户需求的那些错误。
(2)应该远在测试开始之前就制定出测试计划。实际上,一旦完成了需求模型就可以着手制定测试计划,在建立了设计模型之后就可以立即开始设计详细的测试方案。因此,在编码之前就可以对所有测试工作进行计划和设计。
(3)把原理应用到软件测试中。Pareto原理说明,测试发现的错误中的80%很可能是由程序中的20%模块造成的。当然,问题是怎样找出这些可疑的模块并彻底地测试它们。
(4)应该从“小规模”测试开始,并逐步进行“大规模”测试。通常,首先重点测试单个程序模块,然后把测试重点转向在集成的模块簇中寻找错误,最后在整个系统中寻找错误。
(5)穷举测试是不可能的。所谓穷举测试就是把程序所有可能的执行路径都检查一遍的测试。即使是一个中等规模的程序,其执行路径的排列数也十分庞大,由于受时间、人力以及其他资源的限制,在测试过程中不可能执行每个可能的路径。因此,测试只能证明程序中有错误,不能证明程序中没有错误。但是,精心地设计测试方案,有可能充分覆盖程序逻辑并使程序达到所要求的可靠性。
(6)为了达到最佳的测试效果,应该由独立的第三方从事测试工作。所谓“最佳效果”是指有最大可能性发现错误的测试。由于前面已经讲过的原因,开发软件的软件工程师并不是完成全部测试工作的最佳人选(通常他们主要承担模块测试工作)。
测试任何产品都有两种方法:如果已经知道了产品应该具有的功能,可以通过测试来检验是否每个功能都能正常使用;如果知道产品的内部工作过程,可以通过测试来检验产品内部动作是否按照规格说明书的规定正常进行。前一种方法称为黑盒测试,后一种方法称为白盒测试。
对于软件测试而言,黑盒测试法把程序看作一个黑盒子,完全不考虑程序的内部结构和处理过程。也就是说,黑盒测试是在程序接口进行的测试,它只检查程序功能是否能按照规格说明书的规定正常使用,程序是否能适当地接收输入数据并产生正确的输出信息,程序运行过程中能否保持外部信息(例如,数据库或文件)的完整性。黑盒测试又称为功能测试。
白盒测试法与黑盒测试法相反,它的前提是可以把程序看成装在一个透明的白盒子里,测试者完全知道程序的结构和处理算法。这种方法按照程序内部的逻辑测试程序,检测程序中的主要执行通路是否都能按预定要求正确工作。白盒测试又称为结构测试。
除非是测试一个小程序,否则一开始就把整个系统作为一个单独的实体来测试是不现实的。根据第4条测试准则,测试过程也必须分步骤进行,后一个步骤在逻辑上是前一个步骤的继续。大型软件系统通常由若干个子系统组成,每个子系统又由许多模块组成,因此,大型软件系统的测试过程基本上由下述几个步骤组成。
1.模块测试
在设计得好的软件系统中,每个模块完成一个清晰定义的子功能,而且这个子功能和同级其他模块的功能之间没有相互依赖关系。因此,有可能把每个模块作为一个单独的实体来测试,而且通常比较容易设计检验模块正确性的测试方案。模块测试的目的是保证每个模块作为一个单元能正确运行,所以模块测试通常又称为单元测试。在这个测试步骤中所发现的往往是编码和详细设计的错误。
2.子系统测试
子系统测试是把经过单元测试的模块放在一起形成一个子系统来测试。模块相互间的协调和通信是这个测试过程中的主要问题,因此,这个步骤着重测试模块的接口。
3.系统测试
系统测试是把经过测试的子系统装配成一个完整的系统来测试。在这个过程中不仅应该发现设计和编码的错误,还应该验证系统确实能提供需求说明书中指定的功能,而且系统的动态特性也符合预定要求。在这个测试步骤中发现的往往是软件设计中的错误,也可能发现需求说明中的错误。
不论是子系统测试还是系统测试,都兼有检测和组装两重含义,通常称为集成测试。
4.验收测试
验收测试把软件系统作为单一的实体进行测试,测试内容与系统测试基本类似,但是它是在用户积极参与下进行的,而且可能主要使用实际数据(系统将来要处理的信息)进行测试。验收测试的目的是验证系统确实能够满足用户的需要,在这个测试步骤中发现的往往是系统需求说明书中的错误。验收测试也称为确认测试。
5.平行运行
关系重大的软件产品在验收之后往往并不立即投入生产性运行,而是要再经过一段平行运行时间的考验。所谓平行运行就是同时运行新开发出来的系统和将被它取代的旧系统,以便比较新旧两个系统的处理结果。这样做的具体目的有如下几点。
(1)可以在准生产环境中运行新系统而又不冒风险。
(2)用户能有一段熟悉新系统的时间。
(3)可以验证用户指南和使用手册之类的文档。
(4)能够以准生产模式对新系统进行全负荷测试,可以用测试结果验证性能指标。
以上集中讨论了与测试有关的概念,但是,测试作为软件工程的一个阶段,它的根本任务是保证软件的质量,因此除了进行测试之外,还有另外一些与测试密切相关的工作应该完成。这就是下一小节要讨论的内容。
图6.1描绘了测试阶段的信息流,这个阶段的输入信息有两类:(1)软件配置,包括需求说明书、设计说明书和源程序清单等;(2)测试配置,包括测试计划和测试方案。所谓测试方案不仅仅是测试时使用的输入数据(称为测试用例),还应该包括每组输入数据预定要检验的功能,以及每组输入数据预期应该得到的正确输出。实际上测试配置是软件配置的一个子集,最终交出的软件配置应该包括上述测试配置以及测试的实际结果和调试的记录。
图6.1 测试阶段的信息流
比较测试得出的实际结果和预期的结果,如果两者不一致则很可能是程序中有错误。设法确定错误的准确位置并且改正它,这就是调试的任务。与测试不同,通常由程序的编写者负责调试。
在对测试结果进行收集和评价的时候,软件可靠性所达到的定性指标也开始明朗了。如果经常出现要求修改设计的严重错误,那么软件的质量和可靠性是值得怀疑的,应该进一步仔细测试。反之,如果看起来软件功能完成得很正常,遇到的错误也很容易改正,则仍然应该考虑两种可能:(1)软件的可靠性是可以接受的;(2)所进行的测试尚不足以发现严重的错误。最后,如果经过测试,一个错误也没有被发现,则很可能是因为对测试配置思考不充分,以致不能暴露软件中潜藏的错误。这些错误最终将被用户发现,而且需要在维护阶段改正它们,(但是改正同一个错误需要付出的代价比在开发阶段高出许多倍)。
在测试阶段积累的结果,也可以用更形式化的方法进行评价。软件可靠性模型使用错误率数据估计将来出现错误的情况,并进而对软件可靠性进行预测。