测试的目的是显示一个程序做了希望做的事情以及在程序投入使用之前发现其中的缺陷。当测试软件时,会使用人造的数据执行程序。要对测试运行的结果进行检查以发现错误、异常现象,或者关于程序的非功能性属性信息。

测试软件时,你在试图做以下两件事情:

1.向开发人员和客户展示软件满足其需求。对于定制化软件,这意味着针对需求文档中的每一个需求都应该至少有一个测试。对于通用的软件产品,这意味着所有将被包含在产品发布中的系统特征都要有测试。你还可以测试特征的组合以检查是否存在不希望出现的特征交互。

2.找出可能导致软件行为不正确、出现不希望的行为,或行为不符合规格说明的输入或输入序列。这些事由于软件中的缺陷(bug)导致的。当你测试软件以发现缺陷时,你将试图根除不希望看到的系统行为,例如系统崩溃、不希望看到的与其他系统的交互、不正确的计算,以及数据损坏等。

上面的第一点是确认测试,你会使用一组反映系统的期望使用方式的测试用例来测试系统是否正确运行。第二点是缺陷测试,设计测试用例来暴露缺陷。缺陷测试中的测试用例可以故意有一些不清楚的地方,并且不需要反映软件在正常情况下是如何使用的。当然,两种方法并没有明确的边界。在确认测试过程中,会发现系统中的缺陷;在缺陷测试过程中,一些测试会显示程序满足其需求。

下图显示了确认测试和缺陷测试之间的区别。将被测试的系统想象成一个黑盒。系统从输入集合I那里接收输入,生成一个输出集合O中的输出。输出中有一些会是错误的。这些错误的输出集合Oe是系统对输入集合Ie中的输入的响应而生成的。缺陷测试中优先考虑的是找到Ie中的输入,因为这些输入揭示了系统中的问题。确认测试包含使用Ie之外的正确输入的测试。这些输入激励系统生成所期望的正确输出。

测试无法显示软件没有缺陷或者软件在每种情形下的行为都与规格说明一致。你所忽视的某一个测试总是有可能进一步发现系统中新的问题。测试是更广阔的软件验证和确认(Verification and Validation,V&V)过程的一部分。验证和确认并不相同,虽然它们经常被混淆。二者之间的区别可以简洁地表达为:

确认(validation):我们在构造正确的产品吗?

验证(verification):我们在以正确的方式构造产品吗?

验证和确认过程关注检查所开发的软件是否满足其规格说明,并且是否提供了为软件开发付费的人所期望的功能。这些检查过程在有了需求之后马上就开始了,并且一直持续并贯穿整个软件开发过程的所有阶段。

软件验证是检查软件是否满足其所声明的功能性和非功能性需求的过程。软件确认是一个更加泛化的过程。确认的目的是确保软件满足客户的期望。确认的含义不仅仅是检查与规格说明的符合性以展示软件做了客户期望它做的事情。确认十分重要,因为需求陈述并不总是反映系统客户和用户的真实愿望或需要。

验证和确认过程的目的是建立软件系统“符合目的”的信心。这意味着系统必须能够足够好地地满足所期望的使用方式。所需要的信心水平取决于系统的目的、系统用户的期望,以及针对该系统的当前市场环境。

1.软件目的。软件越关键,它的可靠性越重要。

2.用户期望。由于此前对于问题频现、不可靠的软件的经历,用户有时对新系统的软件质量的期望并不高。然而,随着一个软件产品的位置逐渐巩固,用户会期望它变得越来越可靠。

3.市场环境。一个软件公司将在一个系统推向市场时必须考虑竞争产品、客户愿意微系统支付的价格,以及系统支付所需要的日程。在一个竞争性环境中,公司可能会决定在充分测试和调试之前发布一个程序,这样可以最先进入市场。如果一个软件产品或应用非常便宜甚至是免费的,在没有其他强有力的竞争对手的前提下,用户可能会容忍较低的可靠水平。

除了软件测试,验证和确认过程还可以包括软件审查(inspection)和评审(review)。审查和评审分析并检查系统需求、设计模型、程序源代码,甚至所建议的系统测试。这些都属于“静态的”验证和确认技术,其中不需要执行软件来进行验证。下图显示了软件审查和测试在软件过程的不同阶段支持验证和确认。其中的箭头表示这些技术可以使用的过程阶段。

审查和测试

审查主要关注系统的源代码,但其他任何可读的软件表示(例如,需求或设计模型)也可以进行审查。当你审查一个系统时,你要利用关于系统、其应用领域、编程或建模语言的知识来发现错误。

与测试相比软件审查有如下3个优势:

1.在测试过程中,一个错误可能会掩盖(隐藏)其他错误。当一个错误导致非预期的输出时,你永远无法确定此后的输出异常是由于新的错误导致的,还是原来那个错误的副作用导致的。由于审查并不会运行系统,你并不需要担心错误之间的相互影响。因此,一次审查会议可能会发现一个系统中的很多错误。

2.一个系统的不完整版本可以在不需要额外成本的情况下进行审查。如果一个程序是不完整的,那么需要开发特殊的测试装置来测试已有的部分。显然,这将增加系统的开发成本。

3.除了查找程序中的缺陷之外,审查还可以考虑范围更广阔的程序质量属性,例如,与标准的符合性、可移植性、可维护性等。通过审查可以寻找效率不高、不合适的算法,以及可能导致系统难以维护和更新的不好的编程风格。

程序审查是一个比较老的思想,一些研究和实验已经表明审查在缺陷发现方面比程序测试更有效。

然而,审查无法取代软件测试。审查不善于发现由于程序的不同部分之间的非预期交互、时间性问题或者系统性能上的问题而导致的缺陷。在小的公司或开发小组中,设立一个独立的审查团队可能很困难并且昂贵,因为所有潜在的团队成员可能都是软件的开发者。

下图是一个计划驱动的开发所使用的关于传统测试过程的抽象模型。测试用例是关于测试的输入以及期望的系统输出(测试结果)的规格说明,加上一个关于所测试的是什么的陈述。测试数据有时可以自动生成,但是测试用例的自动生成是不可能。理解假设让系统做什么的人必须参与定义所期望的测试结果。然而,测试执行可以被自动化。测试结果可以自动与期望的结果进行比较,因此不需要人来寻找测试运行中的错误和异常。

软件测试过程模型

通常,一个商业化软件系统必须经历以下3个测试阶段:

1.开发测试,系统在开发过程中进行测试以发现bug和缺陷。

2.发布测试,一个独立的测试团队在系统发布给用户之前测试系统的完整版本。

3.用户测试,一个系统的用户或潜在的用户在他们自己的环境中测试系统。

【测试计划】

测试计划关注测试过程中所有活动的进度和资源安排。其中包括定义测试过程,考虑可用的人和时间。通常都会创建一个测试计划,其中定义了测试什么、所预测的测试进度、测试将如何记录等。对于关键性系统,测试计划还可以包括要在软件上运行的测试的详细信息。

在实践中,测试过程通常会混合手工测试和自动化测试。在手工测试中,测试人员使用一些测试数据运行程序,并将结果与他们的期望相比较,然后记录所发现的不符之处并报告给程序开发者。在自动化测试中,测试被编码在程序中,在所开发的系统每次测试时运行这些测试程序。这种方式比手工测试要快,特别是当测试中包括回归测试(重新运行前面的测试以确定对程序的修改没有引入新的bug)时。

不幸的是,测试永远无法做到完全自动化,因为自动化测试只能确定一个程序做了人们认为它应该做的事情。在实践中不可能使用自动化测试来测试系统看起来的样子(例如GUI),或者测试一个程序意料之外的副作用。

§8.1开发测试

开发测试包括由开发系统的团队所执行的所有测试活动。软件的测试者通常是开发该软件的程序员。一些开发过程使用程序员/测试人员对,其中每个程序员都有一个相关联的测试人员来开发测试并协助进行测试过程。对于关键性系统可能要使用一个更加正式的过程,其开发团队中有独立的测试小组,该小组负责开发测试并维护详细的测试结果记录。

开发测试包括如下3个阶段:

1.单元测试,对各个程序单元或对象类进行测试。单元测试应当关注测试对象或方法的功能。

2.构件测试,对多个不同的单元进行集成以创建一个复合构件。构件测试应当关注测试提供对构件功能的访问的构件接口。

3.系统测试,对一个系统中的一些或全部构件进行集成并将系统作为一个整体进行测试。系统测试应当关注测试构件的交互。

开发测试主要是一个缺陷测试的过程,其中测试的目的是发现软件中的bug。因此,开发测试通常和调试(在代码中定位问题并修改程序以修复问题的过程)交织在一起。

【调试】

调试是修复测试所发现的错误和问题的过程。基于来自程序测试的信息,调试人员使用他们的编程语言知识以及测试的期望输出来定位并修服程序错误。

8.1.1单元测试

单元测试是测试程序构件的过程。各个函数或方法是构件的最简单的形式。测试应当用不同的输入参数来调用这些程序。

在测试对象类时,应当设计测试来实现对该对象所有特征的覆盖。这意味着应当测试与该对象相关的所有操作,设置并检查与该对象相关的所有属性的值,并且将该对象置于所有可能的状态之中。这意味着应当模拟导致状态变更的所有事件。

泛化或继承使得对象类的测试更加复杂。无法简单地在定义一个操作的类中对其进行测试,然后假设这个方法可以在继承该操作的所有子类中都能按照预期进行工作。被继承的操作可能会对于其他操作和属性做出一些假设。这些假设在一些继承该操作的子类中可能并不成立。因此,必须在使用所继承的每一个地方对其进行测试。

为了测试一个类的状态,可以使用状态图辅助测试;使用这种模型,可以识别必须测试的状态转换序列并定义驱使这些状态转换的事件序列。从原则上讲,应当测试每种可能的状态转换序列(即状态图的路径覆盖),虽然在实践中这可能相当昂贵。

只要有可能都应当尽量自动化单元测试,在自动化单元测试中,可以使用一个测试自动化框架,例如JUnit来编写并运行程序测试。单元测试框架提供了可以扩展用来创建特定测试用例的通用测试类。它们可以运行你所实现的所有测试,并且报告测试是否成功(经常通过一些图形化用户界面)。

一个自动化测试包括以下3个部分:

1.一个设置部分,其中会按照当前测试用例(即输入和期望输出)对系统进行初始化。

2.一个调用部分,其中会调用要进行测试的对象或方法。

3.一个断言部分,其中会将调用的结果与所期望的结果进行比较。如果断言结果是真,那么测试是成功的;否则测试失败。

有时候,测试的对象会依赖其他还没有实现的对象,或者依赖会导致测试过程很慢的对象。在这种情况下,你可能会决定使用Mock对象。

Mock对象与所模拟的外部对象具有同样的接口。访问这些Mock对象很快,不存在调用数据库和访问磁盘带来的额外开销。与之相似,Mock对象可以被用于模拟异常操作或稀有事件。

8.1.2选择单元测试用例

测试很昂贵并且很耗时间,因此选择有效的单元测试用例是很重要的。这里的“有效”意味着:

1.测试用例应当显示出,当按照期望的方式使用时正在测试的构件做了期望它做的事情;

2.如果构件中存在缺陷,那么测试用例可以揭示这些缺陷。

因此,应当设计两类测试用例。第一类测试用例应该反映程序的正常运行,并且显示出构件可以工作。另一类测试用例应当基于常见的问题通常会在哪里出现的测试经验。这些测试用例应当使用异常的输入来确定这些输入得到了适当的处理,并且不会使构件崩溃。

以下是两种可以帮助你选择测试用例的策略:

1.划分测试,识别出各个具有共同特性并且应当以同样的方式处理的输入分组。你应当从这些分组中的每一个中都选择测试用例。

2.基于指南的测试,使用测试指南来选择测试用例。这些指南反映了程序员在开发构件时经常会犯的错误的经验。

一个程序的输入数据和输出结果可以看作是具有共同特性的集合成员。程序通常对一个集合中的所有成员都表现出近似的行为。

由于这种等价行为,这些类有时候被称为“等价划分”(equivalence partition)或“等价域”。一种系统性的测试用例设计方法就是建立在面向一个系统或构件识别所有输入和输出划分的基础上。测试用例设计就可以从这些划分中选取输入或输出。划分测试既可以用于为系统设计测试用例,又可以用于为构件设计测试用例。

下图所示,左侧的大阴影椭圆表示所测试的程序所有可能的输入集合。较小的不带阴影的椭圆表示等价划分。一个被测程序应当以同样的方式对一个输入等价划分中的所有成员进行处理。

等价划分

输出等价划分是其中所有输出划分都有一些共性的成分。有时候输入和输出等价划分存在一对一映射。然而并不总是如此,有时候可能需要单独定义输入等价划分,其中输入唯一的共性特征是它们所产生的输出在同一个输出划分之中。上图左边椭圆中的阴影区域表示非法输入。右边椭圆中的阴影区域表示可能发生的异常,即对非法输入的响应。

一旦已经识别出了一组划分,就可以从每一个划分中选择测试用例。一个关于测试用例选取的有效经验法则是,选取划分边界上的测试用例,再加上靠近划分中点的测试用例。其原因是设计者和程序员在开发一个系统时倾向于考虑典型的输入值。这些值可以通过选取划分的中点来进行测试。边界值经常是非典型的。程序经常会在处理这些非典型的值时发生错误。

可以通过使用程序规格说明或用户文档,并且根据预测哪种类型的输入值最有可能发现错误的经验来识别划分。当使用一个系统规格说明来识别等价划分时,可以称为“黑盒测试”。你不需要了解任何关于系统如何工作的知识。有时候需要通过“白盒测试”来对黑盒测试进行补充。白盒测试中会考虑程序的代码来找到其他可能的测试。

【路径测试】

路径测试是一种测试策略,其目的是运行贯穿一个构件或程序的每一个独立的执行路径。如果每一个独立路径都被执行,那么构件中所有的语句都被执行了至少一次。所有的条件语句的真和假两种情况也都进行了测试。在一个面相对象开发过程中,路径测试可以用来测试与对象相关的方法。

等价划分是一种有效的测试方法,因为它有助于说明程序员在处理划分边缘上的输入时经常犯的错误。也可以使用测试指南来帮助选择测试用例。指南封装了关于何种类型的测试用例能够有效发现错误的知识。

【例】

当你测试带有序列、数组或列表的程序时,可以帮助发现缺陷的指南通常包括以下这几项:

1.使用只有单个值的序列来测试软件。程序员经常很自然地认为序列由多个值组成,并且有时会在他们的程序中体现这一假设。因此,如果碰到只有单个值的序列,那么程序可能会出错。

2.在不同的测试中使用多个大小不同的序列。这降低了具有缺陷的程序由于输入的一些偶然特性而碰巧产生正确输出的可能性。

3.考虑让测试访问到序列中最前面、中间、最后面的元素。这种方法可以揭示划分边界上的问题。

测试用例设计指南的启发规则:

1.选择迫使系统生成所有错误消息的输入;

2.设计导致输入缓冲溢出的输入;

3.多次重复同样的输入或输入序列;

4.驱使生成非法的输出;

5.驱使计算结果太大或太小。

8.1.3构件测试

软件构件经常由多个经常由多个相互交互的对象组成。因此,测试复合构件应当关注显示构件接口的行为与其规格说明相一致。可以假设针对构件中的各个对象的单元测试都已经完成了。

下图描述了构件接口测试的想法。假设构件A、B和C已经被集成到一起以创建一个更大的构件或子系统。测试用例并不是应用于单个构件而是通过组合这些构件形成的复合构件的接口。复合构件中的接口错误可能无法通过测试各个对象来发现,因为这些错误来自于构件中对象之间的交互。

接口测试

程序构件之间存在不同类型的接口,因此可能发生的接口错误有如下这些类型:

1.参数接口。这种接口中数据或者有时候函数引用从一个构件传递到另一个。一个对象中的方法有一个参数接口。

2.共享存储接口。这种接口会在构件之间共享一块存储。数据由一个子系统放在存储中,然后其他子系统从那里读取数据。这种接口在嵌入式系统中使用,其中的传感器会创建由其他系统构件读取和处理的数据。

3.过程式接口。这种接口中一个构件封装了一组可以由其他构件调用的过程。对象和可复用构件有这种形式的接口。

4.消息传递接口。这种接口中一个构件通过传递消息来从另一个构件那里请求服务。返回消息包括执行服务的结果。一些面向对象系统有这种形式的接口,客户-服务器系统也是这样。

接口错误是复杂系统中最常见的一种错误形式之一。这些错误包括以下3种类型:

1.接口误用。一个调用构件在调用其他构件并且在使用其他构件的接口时犯了一个错误。这类错误在参数接口中很常见,其中参数可能类型错误、传递顺序不对或者传递参数数量不对。

2.接口误解。一个调用构件误解了所调用构件的接口的规格说明,并对它的行为做出了假设。所调用的构件行为与预期不符,从而导致调用构件中的非预期行为。

3.时间性错误。这些错误在使用共享存储或消息传递接口的实时系统中发生。数据的生产者和消费者可能以不同的速度运行。除非在接口设计中进行了特殊考虑,否则消费者可能会访问到过时的信息,因为信息的产生者没有更新共享接口的信息。

测试接口缺陷很困难,因为一些接口缺陷只会在一些非常条件下才会表现出来。不同模块或对象中的缺陷之间的交互可能会产生进一步的问题。一个对象中的缺陷可能只在一些其他对象按照非预期的方式表现时才会被发现。

接口测试的一些通用指南包括以下几条:

1.检查要测试的代码,识别每一个对于外部构件的调用。设计一组测试,其中对于外部构件的参数值处于它们取值范围的极限边界上,这些极限值最可能揭示接口不一致性。

2.对于任何在接口上传递的指针,总是使用空指针参数测试接口。

3.对于任何通过过程接口的调用的构件,设计故意导致构件失效的测试。不同的失效假设是规格说明误解最常见的一种情况。

4.在消息传递系统中使用压力测试。这意味着你应当设计一些测试,会产生比实践中正常情况下可能出现的多得多的消息。这是一种发现时间性错误的有效方法。

5.当多个构件通过共享存储交互时,设计改变这些构件被激活的顺序的测试。这些测试可能会发现程序员对于共享数据产生和消费的顺序的隐式假设。

有时候使用审查和评审而不是测试来寻找接口错误会更好。审查可以关注构件接口以及在审查过程中问到的关于所假设的接口行为的问题。

8.1.4系统测试

开发过程中的系统测试包括集成构件以创建一个系统版本,然后测试集成后的系统。系统测试检查构件是否兼容、是否能正确交互,以及是否能在正确的时间跨越接口传输正确的数据。系统测试显然与构件测试存在重叠,但是二者存在以下两个重要的区别:

1.在系统测试过程中,独立开发的可复用构件和成品系统可能会与新开发的构件相集成。这样就可以对完整的系统进行测试了。

2.由不同团队成员或子团队开发的构件可以在这个阶段进行集成。系统测试是一个集体的过程而不是个别的过程。在有些企业中,会由一个独立的没有参加过该系统设计和实现的测试团队来进行系统测试。

所有系统都有一些涌现性的行为。这意味着一些系统功能和特性只有当把构件放到一起时才会显现出来。这些行为可能是计划好的涌现性行为,这种行为必须进行测试。然而,有时候涌现性行为不是计划好的并且是不希望看到的。必须开发测试来确认系统只做那些希望它做的事情。

系统测试应当关注测试构成一个系统的构件和对象之间的交互,还可能会在可复用构件或系统与新构件集成时对它们进行测试,以确认它们是否如预期的一样运作。这一集成测试应当发现那些只有当一个构件在一个系统中被其他构件使用时才会发现的构件bug。交互测试还有助于发现构件开发者对系统中的其他构件所产生的误解。

由于用况关注交互,因此基于用况的测试是一种有效的系统测试方法。系统中的每一个用况都是由多个构件或对象实现的。测试用例会驱动这些交互发生。如果开发了一个顺序图来建模用况实现,那么可以看到参与交互的对象或构件。

顺序图帮助设计人员设计出所需要的特定的测试用例,因为它显示了需要什么样的输入以及产生什么样的输出。一个完整的用况/场景测试必须考虑这些异常并且确保它们都得到了正确的处理。对于大多数系统,想知道有多少系统测试是重要的以及何时应该停止测试都是很难的。彻底的测试,即对每一个可能的程序执行序列都进行测试,是不可能的。因此,测试必须基于所有可能的测试用例的一个子集。理想情况下,软件公司应当有如何挑选这一子集的策略。这些策略可能会基于通用的测试策略,例如所有的程序语句都应该至少被测试一次;或者也可以基于系统使用的经验并关注测试运行系统的特征。例如:

1.通过菜单访问的所有系统功能都应该进行测试。

2.通过同样的菜单访问的功能组合必须进行测试。

3.在任何提供了用户输入的地方,所有的功能必须同时使用正确的和不正确的输入进行测试。

从来自于一些主要的软件产品的经验来看,产品测试过程中通常会使用相似的指南。当软件的特征分开独立使用时,它们通常都可以工作。但是,当没那么常用的特征的组合没有被一起测试过的时候,经常会出问题。

系统测试的自动化通常比单元测试或构件测试要难。自动化的单元测试依赖于预测输出然后将这些预测编码到程序中。这一预测接下来会与结果进行比较。然而,一个系统实现所产生的输出则会很大或者很难预测。也许可以在不需要提前进行预测的情况下检查出一个输出并确定它的可信度。

【增量的集成和测试】

系统测试包括集成不同的构件然后对所创建的集成后的系统进行测试。应当总是使用一种增量的方法来进行集成和测试,其中要集成一个构件,测试系统,集成另一个构件,再进行测试,等等。如果发生问题,那么问题很可能是由于最近集成进来的构件的交互导致的。

增量的集成和测试对于敏捷方法很重要,会在每次集成一个新的增量之后执行回归测试。

§8.2测试驱动的开发

测试驱动的开发(Test-driven development,TDD)是一种软件开发方法,其中将测试和代码开发交织在一起。可以增量地开发代码以及开发一组针对该增量的测试。已经开发好的代码通过所有它的测试之前,不会开始开发下一个增量。测试驱动的开发是作为XP(极限编程)敏捷开发方法的一部分被引入的。然而,测试驱动的开发现在已经得到了主流的认同,既可以用于敏捷过程又可以用于基于计划的过程。

测试驱动的开发

基本的测试驱动开发的过程如上图所示,该过程中的步骤如下:

1.首先识别所需要的功能增量。这通常应当比较小,并且可以通过不多的代码行就可以实现。

2.为这个功能编写一个测试,并且将其实现为一个自动化测试。这意味着该测试可以执行并且会报告测试通过与否。

3.接下来运行该测试以及所有其他已经实现的测试。起初还没有实现该功能,因此新的测试会失败。这是故意而为的,因为这表明该测试向测试集中增加了一些新东西。

4.然后实现该功能并重新运行测试。其中还可能会包含重构已有的代码以对其进行改进,并且向已有的实现中增加新的代码。

5.一旦所有测试运行都成功了,就可以转而实现下一个功能了。

一个自动化测试环境,对于测试驱动的开发很重要。由于代码是以很小的增量来开发的,因此必须能够在每次增加新功能或或者重构程序时运行每一个测试。因此,测试嵌入在一个独立的程序中,其中会运行测试并且调用所测试的系统。使用这种方法,可以在几秒之内运行成百上千个不同的测试。

测试驱动的开发帮助程序员阐明他们关于一个代码段实际上应该做什么的想法。为了编写一个测试,需要理解代码要做什么,因为这一理解会使编写所需要的代码变得更容易。当然,如果只有不完整的知识或理解,那么测试驱动的开发可能没什么帮助。如果没有了解足够的信息以编写测试,那么就无法开发所需要的代码。

除了更好的问题理解,测试驱动开发的其他好处还包括:

1.代码覆盖。原则上讲,所编写的每个代码段都应当有至少一个相关的测试。这样,就可以有把握地相信系统中的所有代码都被执行过了。

2.回归测试。测试集随着程序的开发增量进行开发。

3.简化的测试。当测试失败时,问题出现在哪里会很明显,这就要求我们检查和改写新的代码。

4.系统文档化。测试自身可以作为某种形式的文档来描述代码应该做什么。

测试驱动的开发的一个最重要的好处是降低了回归测试的开销。回归测试要在对一个系统进行修改之后运行已经成功执行过的测试集合。回归测试要确保这些修改没有向系统引入新的bug,并且新的代码与已有的代码按照预期的方式进行交互。当一个系统采用手工测试时,回归测试很昂贵,有时候不切实际,因为所需要的时间和工作量开销非常高。因此不得不试图选择最相关的测试来重新运行,而这样很容易遗漏重要的测试。而自动化测试极大降低了回归测试的开销。已有的测试可以快速、便宜地重新运行。在测试先行的开发中修改一个系统之后,所有已有的测试都必须在增加任何进一步的功能之前成功运行。

测试驱动的开发在开发新的软件时最有价值,此时功能要么在新代码中实现、要么使用来自标准库中的构件来实现。如果复用大的代码或遗留系统,那么需要作为一个整体为这些系统编写测试,因为很难将它们分解为不同的可测试元素,增量的测试驱动开发也就不现实了。测试驱动的开发对于多线程系统效果可能也不好。不同的线程在不同的测试运行中可能会在不同的时间交织在一起,因此会产生不同的结果。

如果使用测试驱动的开发,那么仍需要一个系统测试过程来验证系统,也就是说确保系统满足所有系统利益相关者的需求。系统测试也会测试性能、可靠性、并且确保系统没有做不应该做的事情,例如产生不希望的输出。

§8.3发布测试

发布测试是指对一个系统在开发团队以外进行使用的一个特定发布进行测试的过程。通常,系统发布是面向客户和用户的。然而,在一个复杂的项目中,发布也可能会面向开发相关系统的其他团队。对于软件产品而言,发布可能会面向后续会准备对其进行销售的产品管理人员。

在开发过程中,发布测试和系统测试有以下两个重要的区别:

1.系统开发团队不应该负责发布测试。

2.发布测试是一个确认检查的过程,其目的是确保一个系统满足它的需求,并且运行得足够好可以由系统客户进行使用。由开发团队进行的系统测试应当关注发现系统中的bug。

发布测试必须表明系统提供了所要求的功能、性能以及可依赖性,并且系统不会在正常使用过程中失效。发布测试通常是一个黑盒测试过程,其中测试是基于系统规格说明产生的。系统被视为一个黑盒,它的行为只能通过研究它的输入和相关的输出来确定。这一测试的另一个名字是功能测试。

8.3.1基于需求的测试

好的需求工程实践的一个一般原则是需求应该是可测试的。也就是说,需求的编写应当使得可以为其编写测试,这样测试人员就可以确认需求是否满足。因此,基于需求的测试是一种系统性的测试用例设计方法,其中需要考虑每一个需求并为其创建一组测试。基于需求的测试是一种确认而非缺陷测试——试图展示系统已经适当地实现了它的需求。

测试一个需求并不意味着只写一条测试就够了。通常不得不编写多个测试来确保已经很好地覆盖了该需求。还应当保持基于需求的测试的追踪关系记录,该追踪关系将测试链接到测试的特定需求上。

8.3.2场景测试

场景测试是一种发布测试方法,即设想出典型的使用场景并使用这些场景来为系统开发测试用例。一个场景是一个描述了系统可能的使用方式的故事。场景应当是现实的,真是的系统用户应当能够与其建立联系。

在一个关于场景测试的短文中,场景测试应当是一个具有可信性并且有一定复杂性的叙述性的故事。它应当触发利益相关者的积极性,也就是说利益相关者应当与该场景建立联系并且相信系统通过测试是很重要的;场景测试应该很容易进行评估。如果系统中存在问题,那么发布测试团队应该发现问题。

在使用基于场景的方法时,通常都会在同一个场景之中测试多个需求。因此,除了检查确认单个需求,也应检查确认这些需求的组合没有造成问题。

8.3.3性能测试

一旦一个系统已经完成了集成,那么就可以对涌现性属性(例如性能和可靠性)进行测试了。必须设计性能测试以确保系统可以处理预计承受的负载。这通常都要运行一系列测试,其中会不断增加负载直到系统性能变得无法接受。

跟其他类型的测试一样,性能测试既关注展示系统满足其需求又关注发现系统中的问题和缺陷。为了测试性能需求是否得到满足,可能要构造一个运行说明。一个运行说明是一组反映将由系统进行处理的各种实际工作的测试。因此,如果一个系统中90%得事务属于类型A,5%属于类型B,而剩下的都是类型C、D、E,那么设计的运行说明中大部分测试要针对类型A。否则的话,就无法获得对于系统运行性能的准确测试。

当然,该方法并不是缺陷测试的最佳方法。经验表明发现缺陷的一种有效方式是围绕系统的限制设计测试。在性能测试中,这意味着以超出软件的设计极限的方式发出请求、给予系统压力。这被称为压力测试。

压力测试可以帮助你做以下两件事情:

1.测试系统的失效行为。意料之外的一些事情组合可能会导致系统负载超出预计的最大负载的情形发生。在这些情况下,系统失效不应该造成数据丢失或非预期的用户服务损失。压力测试检查是确保系统在过载时导致系统“软性失效”而不是在过量负载下发生严重崩溃。

2.揭示那些只会在系统满负载运转时出现的缺陷。虽然有人会说这些缺陷在正常使用时不太会导致系统失效,但是压力测试所模拟的非正常的情形组合仍然可能发生。

压力测试对于基于处理器网络的分布式系统尤其重要。这些系统经常会在负载很重时表现出严重的退化。网络会被不同处理器必须交换的协调数据所淹没。相关的处理过程会在等待所需要的来自于其他过程的数据时变得越来越慢。压力测试帮助发现何时系统会发生退化,这样就可以在系统中加入检查从而在超过临界点后拒绝新的事务请求。

§8.4用户测试

用户或客户测试是一个测试过程阶段,其中用户或测试提供他们对于系统测试的输入和建议。这可以是接受来自一个外部供应商的委托对一个系统进行正式的测试,也可以是一个非正式的过程,其中用户使用一个新的软件产品进行试验,看看是否喜欢这个产品,并检查确保该产品做了他们所需要的事情。用户测试很重要,即使已经进行了全面的系统测试和发布测试,来自用户工作环境的影响可能会对一个系统的可靠性、性能、可用性和鲁棒性产生重要的影响。

在实践中,对于系统开发者而言,复制系统的工作环境一般都是不可能的,因为开发者环境中的测试不可避免地会包含人为构造的成分。有3种不同类型的用户测试:

1.α测试,一组经挑选的软件用户与开发团队密切配合工作,对软件的早期发布进行测试;

2.β测试,向一组更大规模的用户提供一个软件的发布版本,允许他们在上面进行试验,并将他们所发现的问题报告给系统开发者;

3.验收测试,客户对一个系统进行测试以确定其是否已经就绪,是否可以从系统开发者那里接收过来并且在客户环境中进行部署。

在α测试中,用户和开发者在系统开发过程中一起工作来对系统进行测试。这意味着用户可以发现那些对开发测试团队来说不那么明显的问题。开发者实际上只能基于需求进行工作,但是这些需求经常没有反映其他影响软件实际使用的因素。因此,用户可以提供关于实践的信息,从而帮助设计出更加现实的测试。

α测试经常在开发软件产品或应用程序时使用。对于这些产品有经验的用户可能会愿意参与α测试过程,因为这使得他们可以较早地了解他们可以进一步探索的关于新系统特性的信息,这也可以降低软件发生意料之外的变化从而干扰用户的业务的风险。α测试也可以在开发定制化软件的时候进行使用。敏捷开发方法追求用户参与开发过程,并且用户应当在设计系统测试时扮演关键的角色。

β测试用于将一个软件系统的早期(有时候还未完成)发布提供一个更大范围的客户和用户群体进行评估。β测试的参与人员可以是一组经过挑选的客户,他们一般都是系统的早期采用者。或者,也可以将软件公开提供出去,使任何有兴趣试用的人都可以使用。

β测试主要用于要在许多不同的条件下使用的软件产品。β测试很重要,因为与定制的化产品开发者不同,普通产品开发者无法限制软件的运行环境。产品开发者也无法知道并复制所有软件产品未来的使用条件。因此,可以利用β测试发现软件与它的运行环境特性之间的交互。β测试也可以成为某种形式的市场营销。客户通过β测试了解系统以及系统可以为他们做些什么。

验收测试是定制化系统开发的一个内在部分。客户使用他们自己的数据对一个系统进行测试,并决定是否可以从系统开发者那里接收系统。验收意味着应该为软件进行最终的付款。

验收测试过程

上图描述了验收测试过程所包含的如下6个阶段:

1.定义验收准则。理想情况下,这个阶段应当在开发过程中很早就发生,一般在系统的开发合同签署之前。验收准则应当成为系统开发合同的一部分,并且得到客户和开发方的批准。然而,在实践中,在开发过程中很早就定义准则是很困难的。此时可能还没有详细的需求,而且需求在开发过程中几乎总是会发生变化。

2.计划验收测试。这个阶段会确定验收测试所需要的资源、时间和预算,并建立一个测试日程表。验收测试还应当探讨所需要的需求覆盖度以及系统特征的测试顺序。计划中应当定义测试过程的风险,例如系统崩溃以及性能不足,并讨论如何缓解这些风险。

3.设计验收测试用例。一旦建立验收测试准则后,就可以设计测试用例来检查系统是否可以接受。验收测试的目标应该是同时测试系统的功能和非功能性特性。理想情况下这些测试应该提供完整的系统需求覆盖。然而在实践中,建立完整的客观验收准则是很困难的。因此,关于某个测试是否足以显示某个准则已经肯定被满足经常会存在争议。

4.运行验收测试。协商达成一致的验收测试在系统上执行。理想情况下,这一步骤应当在系统将运行的真实环境中进行,然而这样可能会带来干扰并且不实际。因此,必须要建立一个用户测试环境来运行这些测试。这一过程很难自动化,因为验收测试中的一些部分会包含对最终用户和系统之间交互的测试。可能需要对最终用户进行培训。

5.协商测试结果。所有已定义的验收测试都通过并且系统没有任何问题,这是不太可能的。如果真是这样的话,那么验收测试完成,系统就可以交接了。然而更常见的情况是会发现一些问题。这种情况下,开发者和客户必须进行协商以决定系统是否足够好并且可以使用了。他们还必须就开发者将如何修复所发现的问题达成一致意见。

6.接受或拒绝系统。这个阶段中开发者和客户要进行会谈以确定系统是否可以接受。如果系统不够好,还无法使用,那么需要进一步的开发来修复所发现的问题。一旦完成,还要重新进行验收测试阶段。

在现实中往往更加复杂,客户由于立即部署可以获得一些好处,因此想尽快使用软件。他们可能会在软件还存在问题的情况下就愿意接受该软件,因为不使用软件的成本会比处理那些软件中的问题的成本更高。因此,协商的结果可能是有条件地接受系统。客户会接受系统,这样部署可以开始。系统提供者同意修复紧急的问题并向客户尽快交付一个新版本。

在敏捷方法(例如极限编程)中,可能没有独立的验收测试活动。最终用户是开发团队的一部分,并且以用户故事的方式提供系统需求。他也会负责定义测试,这些测试决定了所开发的软件是否支持用户故事。因此,这些测试等同于验收测试。相关测试是自动化的,在用户故事的验收测试没有成功执行之前,开发不会向前推进。

当用户被纳入一个软件开发团队之中的时候,理想情况下他们应该是具有关于系统如使用的一般知识的“典型”用户。然而,寻找这样的用户可能很难,因此验收测试实际上可能无法真正反映系统在实践中是如何使用的。而且,自动化测试的要求限制了测试交互式系统的灵活性。对于这样的系统,验收测试可能要求一些最终用户按照自己日常工作的方式来使用系统。因此,虽然原则上“用户参与”是一个很有吸引力的思想,但是它并不一定会得到高质量的系统测试。

用户参与敏捷团队会产生一些问题,因此许多公司将敏捷和更传统的测试方法混合使用。系统可能使用敏捷技术开发,但是完成一个主要的发布后会使用独立的验收测试来确定是否应该接受该系统。

《软件工程》第8章软件测试相关推荐

  1. 软件工程导论 07章软件测试

    1.软件测试的目标 尽可能多地发现并排除软件中潜藏的错误,最终给用户一个高中质量的软件系统. 2.软件测试的定义 为了发现程序中的错误而执行程序的过程 3..软件测试的原则 ①所有测试都应追溯到需求 ...

  2. 慕课软件工程(第十七章.习题5.1)

    慕课北京大学.软件工程.第十七章.软件测试-1.习题5.1 0 目录 17 软件测试-1 17.1 习题5.1 17.1.1课堂重点 17.1.2测试与作业 6 下一章 0 目录 17 软件测试-1 ...

  3. 第四章——软件测试流程和规范

    第四章 软件测试流程和规范 学完本章应该明白要做测试或者验证应该分几步,每一步应该干什么,明确一个流程.这个流程是比较标准化的. 本章将从软件过程模型出发,讨论传统的测试过程和敏捷测试过程,进而扩展到 ...

  4. 第二章 软件测试基础

    ** 第二章 软件测试基础 ** 2.1.1什么是软件测试 "软件测试"的经典定义是在规定条件下对程序进行操作,以发现错误,对软件质量进行评估. 注:软件需求和设计阶段是相当重要的 ...

  5. 软件测试之第一章 软件测试和测试环境

    第一章 软件测试和测试环境 一. 软件的含义和分类 1 软件的含义 软件是程序.数据和文档的集合. 程序:编程语言:C.C++.Java.php 等. 数据:使用文件或数据库来存储数据. 文档:安装说 ...

  6. 软件工程-第五章-总体设计

    软件工程-第五章-总体设计 5. 总体设计 5.1 设计过程 5.2 设计原理 5.2.1 模块化 5.2.2 抽象 5.2.3 逐步求精 5.2.4 信息隐藏和局部化 5.2.5 模块独立 5.3 ...

  7. 软件工程导论-实验报告-软件测试

    软件工程导论-实验报告-软件测试 [问题描述] 一元二次方程式 ax2+bx+c=0的求根程序有以下功能: 1)输入A.B.C三个系数: 2)根据根的性质的:两个相等或不相等的实根,或无实根,输出相应 ...

  8. 软件工程导论 1-13章 重点定义及解释

    软件工程导论 第一章 概述 第二章 可行性研究 第三章 需求分析 第五章 总体设计 第六章 详细设计 第七章 实现 第八章 维护 第九章 面向对象方法学引论 第十章 面向对象分析 第十一章 面向对象设 ...

  9. 软件测试技术指南-第一章软件测试理论(此章完结)

    1.3软件测试基本概念 测试(Test)就是检测特定的目标,是否符号标准而采用专用工具或方法进行验证,并最终得出特定结构,软件测试(Software Testing)伴随着软件的诞生而产生,软件测试就 ...

  10. 软件工程复习11:软件测试

    作者:非妃是公主 专栏:<软件测试> 个性签:顺境不惰,逆境不馁,以心制境,万事可成.--曾国藩 专栏地址 软件工程专栏地址 专栏系列文章 软件工程复习01:软件工程概述 软件工程复习02 ...

最新文章

  1. eclipse修改文件代码不起作用,输出时还是老的,估计是缓存问题
  2. Web应用扫描工具Wapiti
  3. 【转】全排列算法非递归实现和递归实现
  4. 使用说明 思迅收银系统_便利店收银使用的收银系统应该取决于什么?
  5. Format Currency Sample
  6. rap2检测哪些接口在使用_Apifox for Mac(接口调试管理工具)
  7. 删除单链表中倒是第K个结点
  8. 来不及解释!Linux常用命令大全,先收藏再说
  9. Oracle报错:IO Error: Invalid number format for port number
  10. jmeter mysql数据库_jmeter连接mysql数据库
  11. 关于svn目录地址迁移
  12. 云瓣影音网站微信端(已开源)
  13. 给定一段IP地址172.18.18.128/26,试为该网络做一个IP地址规划,要求开 发室1、开发室2以及制造部各为一个VLAN。
  14. json解析与XML解析
  15. 自定义Msgbox密码登录
  16. 自动动态调整广告出价OCPC
  17. cmd脚本win10使用schtasks命令实现定时任务
  18. 高等数学笔记-乐经良老师-第九章-重积分
  19. mysql 5.7的my.ini的位置在隐藏文件夹“ProgramData”下面
  20. C语言字符串笔试题含答案

热门文章

  1. div 边框颜色设置
  2. HDU - 1700(计算几何)
  3. MySQL 删除表数据,释放空间
  4. 计算机类专业用游戏本,作为专业的吃鸡游戏本,机械革命真的是牛
  5. Android 5.1 解决打开手电筒后,无法打开相机问题
  6. C for Graphic:ddx/ddy
  7. 111 多线程JUC包下代码分析
  8. wincc终端和服务器滞后,wincc做服务器和客户端,Simatic Shell中看不到服务器计算机...
  9. 抖音上会视频剪辑的赚钱路子有多野?
  10. 基于SpringBoot+ Spring Data Jpa的后台管理系统【源码开源】