软件测试概论

软件测试

原文出自于:http://www.ece.cmu.edu/~koopman/des_s99/sw_testing/

 

 

作者:Jiantao Pan

jpan@cmu.edu卡耐基梅隆大学 可依赖嵌入式系统

 

译者:陈尚义

中软通用产品研发中心

软件测试是任何一种旨在评价一个程序和系统的属性和能力的活动,以及判定程序或系统是否满足了所要求的结果。尽管测试对于软件质量是很关键的,且被程序员和测试工程师广泛使用,但是软件测试仍然还是一种艺术,其原因还是对于软件原理的理解有限。软件的测试的难处来自于软件的复杂性:我们甚至不能对一个中等复杂程度的程序进行完整的测试。测试不只是排错(debugging)。测试的目的可以是质量保证,验证和确认,或者可靠性估计。测试也可以用来作为通用的度量数据。纠错测试和可靠性测试是测试的两个主要方式。软件测试在预算、时间和质量要求上取得平衡。

一、概述

软件测试是这样的一个过程,它执行一个程序或一个系统,目的是发现错误。或者,它包括这样一些活动,只要这些活动是评价一个程序(或系统)的属性和能力、以决定程序或系统是否满足了要求。软件和物理加工不一样,物理加工接受了输入,就产生输出。软件不一样的地方在于它的失效方式不同。绝大部分物理系统以固定(通常比较少)的方式失效。然而,软件却有多种奇异的失效方式。检测所有的失效模式,通常是行不通的。

和大多数物理系统不同,软件中的大部分缺陷是设计的错误,不是制造上的缺陷。软件不会用坏,也不会磨损一般地说,若不升级和退市,它就不会改变。所以,软件一旦发布了,设计上的缺陷或者叫bug就会埋入到软件之中并一直留在那里,直到有一天它会被触发而发作。

在一个中等大小的软件模块里,软件的bugs几乎总是存在的。这不是因为程序员的粗心和不负责任,而是因为软件的复杂性通常是不可处理的,人管理复杂性的能力是有限的。还有一点,对应复杂系统,设计的缺陷是不可能根除的。

同样由于复杂性,发现软件中设计的缺陷也是很困难的。因为软件和任何数字系统不是连续的,测试边界值对保证其正确性是不够的。所有可能的值都需要测试和验证,但是完全的测试是不可行的。对于一个简单不过的小程序,两个32位的整数相加,会有264次方个测试用例,即使每秒钟测试几千个用例,完全测试这个小程序也需要几百年。很显然,对于一个实际的软件模块,其复杂性远远超过刚才举的那个例子。如果输入来自真实世界,问题将变得更加糟糕,因为时间和不可预测的环境因素,以及人的交互,都有可能被考虑成为输入参数。

程序的动态性使问题更加复杂。如果在初步测试中发现了一个失效,给代码做了修改,软件可能现在通过了一个原来不能通过的测试用例。但是原来通过的测试用例现在不能再保证通过了。考虑到这种可能性,测试应该重新开始。可测试的费用成本可是有限制的。

一个和困难相类似的有趣现象是杀虫剂现象(Pesticide Paradox)。你使用的每一个避免和发现bug的方法都会在狡猾的bug面前逐步变得无效。But this alone will not guarantee to make the software better,因为复杂性的屏障原理告诉我们:软件的复杂性(以及bug的复杂性)增长,超过了我们管理复杂性的能力。

尽管有这些限制,测试在软件开发中还是一个完整的部分。在软件开发的各个阶段都得到广泛运用。一般情况下,多于50%的软件开发时间都用在测试上。测试的目的通常是:

  • 提高质量

在关键应用中用到计算机和软件,bug造成的结果是严重的。Bug可以造成巨大损失。关键系统中的bug已经造成了飞机失事,使太空飞行使命夭折,股票市场停止交易。所谓的2000年问题……在计算机化的世界里,软件的质量和可靠性关系到生和死。

质量意味着与规定的设计要求的符合性。在规定的环境下按照要求进行,这是最小的质量要求。调试(Debugging),是狭义的软件测试,主要是指程序员发现设计缺陷。人类的天性就不是完美无缺的,这一点使得一个中等复杂的程序在第一时间没有错误。发现这些问题并纠正它们,是程序设计阶段Debugging的目的。

  • 验证和确认

就象Verification and Validation那个话题所指出的那样,测试的另一个重要的目的是验证和确认。测试可以被看成度量。这个在V&V过程中被广泛采用。测试者可以在测试结果的基础上作出声明:软件在一定的情形下,要么能够工作,要么它不能工作。我们也可以在同样的规格说明之下用同样的测试用例,比较多个不同的产品。

  我们不能直接测试质量,但我们可以通过测试相关因子,让人们看得见产品质量。质量有三个因子的集合:功能、工程和适应性。这三个因子集合可以软件质量空间的三个维度。每一维都可以在后续的较低层次上分解成它的模块因子。表1列举了一部分最常用的质量考虑。

 

功能(表面质量)

工程(内部质量)

适应性(未来质量)

正确性

效率

灵活性

可靠性

可测试性

可重用性

可用性

文档资料

可维护性

一致性

结构化

 

1:典型的软件质量因子[Hetzel88]

 

好的测试能给所有相关因子的度量提供数据。任何一个特定因子的重要性随着应用系统的不同而不同。任何一个涉及到人身安全的系统必须强调可靠性和一致性的极端重要性。在一个有代表性的商业系统中,可用性和可维护性是关键的,而一个一次性使用的科学计算程序这两者都不很要紧的。我们的测试要做到充分有效,就必须度量每一个相关因子,让质量变得看得见、摸得着。

确认目的的测试也叫正面测试或干净测试(clean test)。缺点是他仅能够确认软件在特定的条件下能够工作。有限的测试不能确认软件在所有的情形下都能正常工作。相反,仅仅一个未通过的测试就可以足够证明软件不能工作。脏测试(Dirty test)也叫负面测试,是打破软件的,或者说是软件不能工作。一个软件的小片段必须有足够的异常处理能力,才能在相当程度上的脏测试过程中生存下来。

一个可以测试的设计是这样一个设计:它可以很容易地得到确认(或否认)和维护。因为测试是一个严格的工作,需要足够的时间和成本,在软件开发过程中,可测试性的设计也是一个重要的设计原则。

  • 可靠性估计

软件可靠性和软件的很多方面都有着重要的联系,包括结构和测试的数量。基于运行的资料(不同输入的相对使用频率),测试可以作为统计样本方法,获得失效数据,作为可靠性估计的依据。

软件测试还不成熟。到目前还是个艺术,因为我们还不能把它变成一门科学。我们还在使用20-30年前同样的技术,其中的一些技术中,工艺和启发式的成分多于工程化的思想方法。软件测试是昂贵的,但是不测试,其代价更加昂贵,特别是在那些人命关天的重要系统中。解决软件测试问题不比解决Turing halting容易。我们从来不能保证一个软件是正确的。不存在这样一个验证系统,它能够验证每个正确的程序。我们甚至不能确定那个验证系统本身是否正确!

二、关键概念

2.1 分类

测试方法和测试技术,在生命周期的不同阶段有不同的目的。按照目的分类,软件测试可以分成:正确性测试、性能测试、可靠性测试和安全测试。按照阶段划分成:需求阶段测试、设计阶段测试、编程阶段测试、评价测试结果,安装阶段测试、接受测试和维护测试。按照范围划分:单元测试,模块测试、集成测试和系统测试。

2.1.1 正确性测试

正确性是软件的最低需求了,也是测试的最本质的目的。正确性测试需要一些圣贤,告诉哪些行为是正确的。测试者本人可能或不能知道被测软件内部的详细情况,如控制流和数据流。所以,白合观点和黑合观点都可以用来测试软件。我们必须注意到,白合和黑合的点子不仅仅局限在正确性测试上。

  • 黑盒测试

在黑合测试中,测试数据来自于功能需求的描述,而不是程序结构。也称做数据驱动、输入/输出驱动,或基于需求的测试。因为仅关心软件模块的功能,黑合测试也叫做功能测试一种强调功能执行、检查输入/输出数据的测试方法。测试者将被测软件看成是个黑合仅输入、输出和规格说明是可见的,功能是从什么样的输入产生什么样的输出来确定的。在测试中,输入不同的数据,根据规格说明比较不同的输出值,来确认软件的正确性。所有的测试用例都是从规格说明中分析出来的。不考虑代码的实现细节。

很明显,我们覆盖输入空间越大,我们发现的问题越多,我们对软件质量的信心也随之越大。理想的情况是,我们彻底覆盖所有的输入空间。但是正如我们刚才所说的,考虑无效的输入、时间、次序和可用的资源,彻底的测试各种输入的组合对绝大多数程序来说是不可能的。爆炸式增长的组合是功能测试的障碍。让问题变得更糟的是,我们甚至不知道规格说明本身是否正确和完整。由于规格说明使用的语言(一般是自然语言),二义性和模糊是不可避免的。即使我们使用了正式的或受限的语言,我们还是不可能在规格说明中写出所有的情况。有时侯,规格说明本身就是一个难以解决的问题:不可能有有限的语言精确地描述每一个可能遇到的情形。人们很少能清楚地描述他们想要什么他们通常会告诉一个原形是或不是他们所想要的。规格说明的问题引起的bug占到整个软件bug数量的30%左右。

黑合测试的研究主要集中在如何用最小的代价(通常是指测试用例)达到最大的效果。穷尽输入用例是不可能的,但完全测试一个输入空间的子集还是有可能的。分割(Partitioning)是常用的技术之一。如果我们对输入空间进行分割,并且假设所有的分区内的输入值都是等价的,那么我们就只需测试那些在区内有代表性的值,就足够覆盖全输入空间。域测试(Domain testing)将输入域(domain)分成区(region),考虑每个域中的输入值是个等价类。通过在每个域中选择一个或多个值来做测试,就可以穷尽每个域。边界自得其是特殊的兴趣点。经验表明,探寻边界条件的测试用例常常有较高的回报。边界值分析需要一个或多个边界值被选择来作为代表性的测试用例。域测试的困难在于规格说明中不正确的域定义不能被有效发现。

好的分区需要对软件结构的了解。好的测试计划不仅包含黑合测试,也包含白合途径,以及两者的混合使用。

  • 白合测试

相对于黑合测试,在白合测试里,我们把软件看成是个白合子,或者是个玻璃合子。 软件程序结构和流程对测试来说是可见的。测试佳话是按照软件的实现细节,如程序语言、逻辑和风格,来制定的。测试用例从程序结构的分析而得来的。白合测试也叫做玻璃合测试、逻辑驱动测试或基于设计的测试。

因为我们注意到程序的内部结构和对其相关知识的了解,棘手的问题得以缓解,所以在白合测试方面,现在有了很多可用的技术。在白合测试里,对软件的某些方面进行彻底测试的意图还是很强烈的,某种程度的穷举是可以达到的,诸如至少程序的每一行都覆盖一次(语句覆盖),遍历每一个分支程序(分支覆盖)或覆盖所有可能的“真”、“假”条件的混合(多重条件覆盖)。

控制流测试,循环测试和数据流测试,都将相应的软件流结构映射到一个定向图上(directed graph)。测试用例是根据条件精心选择的,所有的节点和路径都至少被覆盖(或被遍历一次)。这样做,我们可能发现一些不必要的“死”代码,即那些从来都用不到的代码,或从来没有得到执行过,在功能测试中,我们不能发现这些“死”代码。

在突变测试中,原始的程序代码是混乱的,存在很多变异程序,每个变异程序包含一个缺陷。程序的每个缺陷版本叫做一个变异。测试数据是根据破坏变异的效力来选择的。一个测试用例杀死的变异越多,它就被认为越好。变异测试的问题在于,计算的工作量太大。黑合方法和白合方法的边界划分得不是很清楚。上面提到的很多测试策略,可能不是很有把握地划入到白合或是黑合测试里。没有在这里讨论,如事务流测试、语法测试,有限状态机测试和很多其它测试策略,也很难说它是白合还是黑合。其中一个原因是,这些测试技术需要对被测软件的规格说明有一定的了解。另一个原因是,规格说明本身也是很宽泛的它可能含有各种需求,甚至包括了程序的结构、程序设计语言和程序风格,把他们也作为规格说明的一部分了。

我们也许不愿意将随机测试考虑成一种测试技术。测试用例的选择简单明了:随机抽取。[Duran84]里的研究表明,随机测试对很多程序而言是比较划算的。某些小的错误可以用较小的代码发现。它也不比其它精心设计的测试技术差。人们也可以根据操作的信息(operational profiles),使用随机测试结果获取可靠性的估计。将随机测试和其它测试技术混合使用,还能衍生出更强大、更经济有效的测试策略。

2.1.2 性能测试

并不是所有的软件都有明确的性能规格说明。但是,每个系统都有隐含着的性能需求。软件不应该无限制地占用资源和时间。有时候,在讨论引起系统性能下降的软件设计问题时,我们将这些问题称之为“性能bug”。

性能从来是个很大的关注点,驱使着计算机的升级换代。软件系统的性能评价通常包括:资源占用,吞吐量、反应时间、等待处理的任务的最大或平均队长(queue length)。需要考虑的有代表性的资源包括:网络带宽需求、CPU周期、磁盘空间,磁盘访问操作和内存占用。性能测试的目标可以是辨认性能瓶颈、性能对比和评价,等等。性能测试的有代表性的方法是使用基准(benchmark)。基准四一个程序,被设计用来作为典型系统使用的代表。

2.1.3 可靠性测试

软件可靠性是指系统0失效操作的概率。它和软件的很多方面相关,包括测试过程。靠量化与之相关的因子来直接估计软件的可靠性恐是非常困难的。测试倒是个有效的方法来衡量软件的可靠性。通过操作信息(operational profile)的指导,软件测试(通常是黑合测试)可以用来抓获失效数据,进一步,可以使用一个估计模型分析这些数据、估计现在的可靠性、预测未来的可靠性。所以,在可靠性估计的基础上,开发者可以决定是否发布软件,用户可以决定是否购买或使用该软件。使用软件的风险也可以根据可靠性数据得到评估。[Hamlet94]提倡测试的主要目标应该是度量被测试的软件的可依赖性(dependability)。

关于可依赖的软件,这里有一个直观的定义:它不会意外地或以灾难性的方式失效。根据这一简单的标准,鲁棒性测试和压力测试是可靠性测试的变异。

软件模块的鲁棒性,是指软件在压力的环境里,对异常输入的正确处理的程度。鲁棒性测试区别于正确性测试,在这里,程序的正确性不在考虑之列。它只关注鲁棒性问题,如机器瘫痪、进程刮死或不正常终止。道理相对来说简单,所以鲁棒性测试相对于正确性测试来得更轻便、更具有伸缩性。这一研究近来引起越来越多的兴趣,大多数研究使用商用操作系统作为他们的研究对象。

压力测试,或负载测试,常常被用来测试整个系统,而不是仅仅针对软件。在这样的测试里,软件或系统在、或超出规定的限制范围内运行。有代表性的压力测试包括资源耗尽、活动爆发,以及维持高负荷。

2.1.4 安全性测试

软件质量、可靠性和安全性是紧密相关的。软件的安全缺陷可以用入侵的方式来发现。随着互联网的发展,软件安全问题变得更为严重。

很多关键应用软件和服务对于恶意的攻击,具有集成的安全措施。这些系统的安全性测试包括识别和消除软件中可能导致潜在安全漏洞的缺陷,确认安全措施的效力。模拟的安全攻击可以发现安全弱点漏洞。

2.2 测试自动化

软件测试是非常昂贵的。自动化是销减成本的好办法。软件测试工具和技术通常缺乏适应性和可伸缩性。原因是不言而喻的。为了使过程自动化,我们不得不从规格说明中生成预言(oracle),生成测试用例来测试目标软件,以决定他们的正确性。今天,我们还没有一个系统全面达到这个目标。一般还是需要一定的人为干预,自动化的程度停留在自动化测试脚本的水平。

在可靠性测试和性能测试中问题减少了。在鲁棒性测试中,简单的规格说明和预言:不瘫痪,不挂死。类似的简单度量也可以用在压力测试中。

2.3 什么时候停止测试?

测试是个永无止境的工作。我们不可能把所有的缺陷都测试出来并将他们消除。在一些点上,我们不得不停止测试将软件发布出去。问题是什么时候我们可以停止测试呢?

现实地讲,测试是个预算、时间和质量三这的平衡。它受利益模型驱动。悲观的途径,也是通常采用的途径,是当被分配的资源时间、预算或测试用例用完了的时候停止测试。乐观的停止测试准则,是当可靠性满足了需求,或继续测试不能带来更多利益时,停止测试。这通常需要使用可靠性模型来评价或预测软件的可靠性。每个评价需要重复运行:失效数据收集建模预测。然而,这个方法不能很好地适合高度可依赖(ultra-dependable)的系统,因为实际领域里的失效数据需要太长时间的积累。

2.4 测试的替代

软件测试被越来越多地认为是一种有问题的解决质量问题的方法。依靠测试来定位或纠正问题是一个永无止境的工作。缺陷不能彻底根除。正如复杂性屏障(complexity barrier)所指出的:测试并修复问题可能并不一定提高软件的质量和可靠性。有时侯修复一个问题可能引入更加严重的问题。1991年发生在美国加州和东部海岸的例子:在信号系统中只改了3行代码就引起了灾难的发生。

狭义地来看,很多测试技术也有暇疵。例如,覆盖测试。在代码覆盖、分支覆盖真的和软件质量相关吗?没有明确的证据表明这一点。所谓的“人测试”如代码审查、走查和评审,被建议成作为传统测试方法的替代。代码审查(inspection)可作为单元测试的高效的替代品。实验结果表明,在发现缺陷的数量和发现缺陷的成本上来讲,逐步提取代码来阅读,至少和实际运行功能和结构化测试的功效相当。

使用形式化的方法来证明软件的正确性也是一个具有诱惑力的研究方向。但这个方法也没有超越复杂性屏障。对于相对简单的软件,这个方法很好用。对于那些复杂的系统,就不一样了。

广义地讲,我们可以开始质疑测试的终极目标。因为发现缺陷并消除那些缺陷并不一定就能提高质量,那么我们为什么需要更有效的测试方法呢?类似的问题就象汽车制造过程。在手工艺时代,我们制造汽车,砍掉问题和缺陷。但是这样的方法早已被流水线和好的质量工程过程所淘汰了,汽车可以在生产阶段做到无缺陷。这表明将设计工程化(如clean-room软件工程),对于减少缺陷,比起测试工程工程化来得更有效。测试只不过是用来做质量检测和管理的,或者,“为可测试性而设计”。对软件来说,这是一次从手工艺生产到工程化的跳跃。

2.5 可用的工具、技术和度量

大量的软件测试工具业已存在。正确性测试工具通常是为一定的系统特制的,具有能力和通用性的局限。鲁棒性和压力测试工具越来越具有通用性。

Mothora是一个自动化的变异测试工具集,是Purdue University开发的。使用这个工具,测试者可以创建和执行测试用例、度量测试用例足够与否、决定输入/输出的正确性、定位并消除问题,以及控制测试和将测试文档化。

Purify是在程序运行时帮助检查和调试的工具。他们既可检查、又可针对内存泄露和指针问题进行保护。

Ballista商用软件鲁棒性测试工具。它是全自动化的鲁棒性测试工具。第一个版本在UNIX 操作系统中,支持多达233POSIX功能调用。假如数据类型可以被测试服务器识别,第二个版本也支持用户函数的测试。Ballista测试工具给出了操作系统鲁棒性的量化对比数据。目标是自动化地测试商业软件的鲁棒性,以提高其质量。

2.6 和其它主题的关系

软件测试是软件开发的一部分。和软件质量直接相关。和软件、软件质量、软件可靠性和系统可靠性等主题都有一些关系。

三、相关主题

  • 软件可靠性。软件测试和软件可靠性关系很近。软件可靠性可以通过测试来增强。测试还可以作为软件可靠性的度量。
  • 错误注入。错误注入可以被认为一个特殊的测试方法。错误注入和测试通常联合使用,以确定关键的容错软件和硬件的可靠性。
  • 验证、确认和发证。软件测试的目的不仅仅是为了发现错误和消除错误。也是验证、确认和发证的工具

 

四、结论

  • 测试是一门艺术。测试方法和实践,绝大多数还和20年前一样。尽管已有了很多工具和方法,但远没有成熟。好的测试活动除了使用正确的测试技术之外,还需要测试者的创造性、经验和知觉。
  • 测试不仅仅是调试。测试不仅用来定位缺陷,还用来纠正错误。还应用在确认、验证过程,以及用在可靠性度量方面。
  • 测试是昂贵的。自动化是削减成本和时间的好法子。对于基于覆盖的测试技术而言,测试的有效性和效力是标准。
  • 完全的测试是不可行的。复杂度是问题的根本。在某些点上,软件测试不得不停下来、产品不得不发布。停止时间是成本和时间的平衡。或者,可靠性估计满足了要求之后,测试才停。
  • 测试可能不是最有效地提高软件质量的方法。替代的方法,如审查、clean-room工程可能会更好。

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值