本章简要介绍了软件开发项目中常用的生命周期模型,并解释了测试在每个模型中扮演的角色。它讨论了各种测试级别和测试类型之间的区别,并解释了这些在开发过程中的应用位置和方式。
大多数软件开发项目是按照事先选择的软件开发生命周期模型来计划和执行的。这种模型也被称为软件开发过程模型,或者更简洁地称为开发模型。
这样的模型将项目划分为独立的部分、阶段或迭代,并将由此产生的任务和活动安排在相应的逻辑顺序中。此外,该模型通常描述了每项任务所分配的角色,以及项目的哪位参与者负责每项任务。在各个阶段要使用的开发方法通常也会被详细描述。
每个开发模型都有自己的测试概念,这些概念在意义和范围上可能有很大的不同。下面几节将从测试人员的角度详细介绍流行的开发模式。
B站2023年最牛的Python接口自动化测试框架全栈测试开发技术入门到入职教程
目前使用的两种基本的开发模式是顺序式和迭代式/增量式。下面的章节包括对这两种类型的讨论。
顺序式开发模型将开发过程中涉及的活动以线性方式安排。这里的假设是,当开发模型的所有阶段都完成后,产品及其特征集的开发就完成了。这个模型没有考虑到各阶段或产品迭代之间的重叠。以这种方式运行的项目的计划交付日期可能在未来几个月甚至几年。
每个开发阶段只有在前一个阶段完成后才能开始。然而,该模型可以在相邻的阶段之间产生反馈回路,要求在前一个阶段进行修改。图3-1显示了罗伊斯的原始模型中所包含的阶段:
这个模型的主要缺点是,它把测试作为一个单一的活动捆绑在项目的最后。测试只有在所有其他开发活动完成后才进行,因此被看作是一种 "最终检查",类似于检查出厂的货物。在这种情况下,测试并不被看作是在整个开发过程中进行的活动。
V模型是瀑布模型的延伸 [ISO/IEC 12207])。这个模型的出现对开发过程中的测试方式产生了巨大而持久的影响。每个测试人员和每个开发人员都应该学习V模型,了解它是如何整合测试过程的。即使项目是基于不同的开发模式,这里说明的原则仍然可以应用。
其基本思想是,开发和测试是具有同等价值的相应活动。在图中,它们由 "V "的两个分支来说明:
验证(Verification)包括检查测试对象是否完全和正确地履行了它的规格。换句话说,测试对象(即相应开发阶段的输出)被检查是否按照其规格(相应阶段的输入)"正确 "开发。
确认(Validation)包括检查测试对象在其预期的环境中是否真正可用。换句话说,测试人员检查测试对象是否真正解决了分配给它的问题,以及它是否适合其预期用途。
实际上,每个测试都包括这两个方面,尽管验证的份额随着每个抽象层次的增加而增加。组件测试主要侧重于验证,而验收测试则主要是确认。
V型模式的特点
开发和测试活动分别进行(用左边和右边的分支表示),但对项目的成功同样重要。
有助于直观地了解测试的验证/确认方面。
区分了协作测试的层次,即每个层次都是针对其相应的开发层次进行测试。
尽早测试的原则
V型模型给人的印象是测试在开发过程的后期开始,在实施之后。这是不对的! 在模型的右侧分支的测试级别代表了测试执行的不同阶段。测试准备(计划、分析和设计)必须在左侧分支的相应开发步骤中开始。
迭代开发的基本思想是,开发团队可以利用他们从以前的开发阶段获得的经验,以及现实世界和客户对早期系统版本的反馈,在未来的迭代中改进该产品。这种改进可以采取故障修正或改变、扩展或增加特定功能的形式。所有这些方案的主要目标是一步一步地改进产品,以便越来越准确地满足客户的期望。
增量开发背后的理念是按预先计划的阶段开发产品,每完成一个阶段就提供一个功能更全面的产品版本(增量)。增量的大小可以有很大的不同--例如,从改变一个简单的网页布局到增加一个具有额外功能的完整新模块。增量开发的主要目的是尽量缩短上市时间--即发布一个简单的产品版本(或一个功能的简单版本),以尽快为客户提供一个产品或功能的工作版本。然后,根据客户的反应和愿望,不断地提供进一步的改进。
在实践中,这两种方法论的边界是模糊的,它们通常被称为迭代-增量开发。两者的一个决定性特征是,每个产品的发布都能使你从客户和/或终端用户那里获得定期的、早期的反馈。这减少了开发一个不符合客户期望的系统的风险。
迭代-递增组合模型的例子有:螺旋模型、快速应用开发(RAD)、Rational统一流程(RUP)和进化开发。
所有形式的敏捷软件开发都是迭代-递增的开发模式。最著名的敏捷模型是: 极限编程(XP),看板,以及Scrum。近年来,Scrum已经成为其中最流行的一种,并得到了极大的普及。
创建新的增量/版本的速度因模式不同而不同。非敏捷的迭代增量项目倾向于预见六个月到一年的发布周期,有时甚至更长,相反,敏捷模式试图将发布周期减少到每季度、每月、甚至每周的节奏。
在这里,测试必须适应这样短的发布周期。例如,这意味着每个组件都需要可重复使用的测试用例,这些测试用例可以很容易地在每个新的增量中立即重复。如果不满足这个条件,你就有可能减少系统的可靠性,从增量到增量。
每个增量还需要新的测试用例,涵盖任何额外的功能,这意味着你需要维护和执行的测试用例的数量(在每个版本)随着时间的推移而增加。发布周期越短,所有的测试用例在所有分配的发布时间框架内得到满意的执行仍然很关键,但变得更加困难。因此,在使你的测试适应敏捷开发时,测试自动化是一个重要工具。
一旦你建立了可靠的自动化测试环境,你就可以把它用于每一个新的构建。当组件被修改时,它会被集成到之前的完整构建中,然后再进行一次新的自动化测试运行。任何出现的故障都应该在短期内修复。这样一来,项目总是有一个完全集成和测试的系统在其测试环境中运行。这种方法被称为 "持续集成"(CI)。
这种方法可以用 "持续部署"(CD)来加强: 如果测试运行(在CI期间)是无故障的,经过测试的系统会自动复制到生产环境中,并安装在那里,从而以准备运行的状态部署。
将CI和CD结合起来的结果是叫做 "持续交付 "的过程。只有当你有基本自动化的测试环境,使你能够进行 "持续测试 "时,这些技术才能成功应用。
对开发和测试的计划和可追溯性的要求因环境不同而不同。生命周期模型对特定产品的开发是否合适,也取决于其开发和使用的背景。以下基于项目和产品的因素在决定使用哪种模式时起着作用:
案例研究: VSR-II项目中的混合开发模式
VSR-II项目的目标之一是使其 "尽可能的敏捷",所以DreamCar模块和所有基于浏览器的前端组件和子系统都是在敏捷Scrum环境下开发的。然而,由于它们是安全关键型的,ConnectedCar组件将使用传统的V型模式开发。
原型开发也是项目早期的一个选择,一旦实验阶段完成,你可以在项目的其余部分改用渐进式方法。
开发模型可以而且应该被调整和定制,以便在特定的项目中使用。这种适应过程被称为 "定制"。
定制可以涉及结合测试层次或某些测试活动,并特别组织它们以适应手头的项目。例如,当把现成的商业软件集成到更大的系统中时,集成测试阶段的互操作性测试(例如,当与现有的基础设施或系统集成时)可以由客户而不是供应商进行,验收测试(功能和非功能的操作和客户验收测试)也是如此。更多细节见3.4和3.5节。
然后,量身定制的开发模型包括对所有项目参与者具有约束力的所需活动、时间尺度和目标的看法。任何详细的计划(时间表、人员配置和基础设施分配)都可以利用并建立在定制的开发模型上。
无论你选择哪种生命周期模型,你的定制应该支持良好和有效的测试。你的测试方法应该包括以下属性:
软件系统通常是由许多子系统组成的,而这些子系统又是由多个组件组成的,通常被称为单元或模块。由此产生的系统结构也被称为系统的 "软件结构 "或 "架构"。设计能完美支持系统要求的架构是软件开发过程中的关键部分。
在测试过程中,系统必须在其结构的每个层面上进行检查和测试,从最基本的组件一直到完整的集成系统。与架构的某一层次有关的测试活动被称为测试 "层次",每个测试层次都是测试过程的一个实例。
以下各节将详细说明各个测试层次在不同的测试对象、测试目标、测试技术和责任/角色方面的区别。
组件测试包括系统地检查系统结构中的最低层组件。根据用于创建这些组件的编程语言,这些组件有不同的名称,如 "单元"、"模块 "或(在面向对象编程的情况下)"类"。因此,相应的测试被称为 "模块测试"、"单元测试 "或 "类测试"。
组件和组件测试
无论使用哪种编程语言,所产生的软件构件是 "组件",相应的测试被称为 "组件测试"。
组件的具体要求和组件的设计(即规格)要被参考以形成测试基础。为了设计白盒测试或评估代码覆盖率,你必须分析组件的源代码并将其作为额外的测试基础。然而,为了判断组件对测试用例的反应是否正确,你必须参考设计或需求文档。
如上所述,模块、单元或类是典型的测试对象。然而,像shell脚本、数据库脚本、数据转换和迁移程序、数据库内容和配置文件也都可以成为测试对象。
组件测试通常只测试与系统其他部分隔离的组件。这种隔离的作用是在测试中排除外部的影响。它还简化了测试用例的设计和自动化,因为它们的范围很窄。
组件可以由多个构建块组成。重要的一点是,组件测试只需要检查有关组件的内部功能,而不是与外部组件的交互。后者是集成测试的主题。组件测试对象通常是 "从程序员的硬盘中获得的",这使得这一层次的测试与开发工作紧密相连。因此,组件测试人员需要足够的编程技能来正确地完成他们的工作。
案例研究: 测试calculate_price类
根据其规格,VSR-II DreamCar模块计算车辆价格的方法如下:
我们从清单价格(baseprice)减去经销商折扣(discount)开始。然后再加上特别版的加价(specialprice)和任何额外的附加物的价格(extraprice)。
如果增加了三个或更多不包括在特别版中的额外费用(额外费用),这些额外费用将获得10%的折扣。对于五个或更多的额外费用,折扣增加到15%。
经销商折扣是从清单价格中减去的,而配件折扣只适用于额外的东西。这两种折扣不能同时使用。最后的价格是用以下C++方法计算的:
为了测试这个计算,测试人员通过调用calculate_price()方法和提供适当的测试数据来使用相应的类接口。然后,测试人员记录组件对这个调用的反应--即读取并记录方法调用所返回的值。
这段代码是有错误的:计算≥5的折扣的代码永远无法到达。这个编码错误可以作为例子来解释第五章详述的白盒分析。
要做到这一点,你需要 "测试驱动"。测试驱动是一个独立的程序,它进行所需的接口调用,并记录测试对象的反应(也见第5章)。
对于calculate_price()测试对象,简单的测试驱动可以是这样的:
我们例子中的测试驱动非常简单,例如,可以扩展到记录测试数据和带有时间戳的结果,或者从外部数据表中输入测试数据。
要写测试驱动,你需要编程技巧。你还必须研究和理解测试对象的代码(或者至少是它的接口的代码),以便编程正确调用测试对象的测试驱动。换句话说,你必须掌握相关的编程语言,你需要获得适当的编程工具。这就是为什么组件测试通常是由组件的开发者自己进行的。这样的测试通常被称为 "开发者测试",尽管 "组件测试 "才是真正的意思。开发者测试自己的代码的缺点将在第2.4节讨论。
组件测试经常与调试相混淆。然而,调试涉及消除缺陷,而测试涉及系统地检查系统的故障(见2.1.2节)。
使用单元测试框架(比如pytest、unittest、junit等)大大减少了测试驱动编程的工作量,并在整个项目中创建一致的组件测试架构。使用标准化的测试驱动也使团队中不熟悉各个组件或测试环境的其他成员更容易进行组件测试。这些类型的测试驱动可以通过命令行界面控制,并提供处理测试数据的机制,以及记录和评估测试结果。因为所有的测试数据和日志都是相同的结构,所以有可能在多个(或所有)被测组件中评估结果。
组件测试级别的特点是不仅有测试对象的类型和测试环境,而且有非常具体的测试目标。
组件测试最重要的任务是检查测试对象是否完全正确地实现了其规范中定义的功能(这种测试也被称为 "功能测试 "或 "功能性测试")。在这种情况下,功能等同于测试对象的输入/输出行为。为了检查实现的完整性和正确性,该组件要接受一系列的测试案例,每个案例都涵盖了输入和输出数据的特定组合。
案例研究: 测试VSR-II的价格计算
这种测试输入/输出数据的组合在上例中的测试案例中得到了很好的说明。每个测试用例都输入了特定的价格和特定数量的额外费用。然后测试用例检查测试对象是否计算出正确的总价格。
例如,测试用例#2检查 "五个或更多额外项目的折扣"。当测试用例#2被执行时,测试对象输出一个不正确的总价格。测试用例#2产生了一个故障,表明测试对象没有满足其对这个输入数据组合的指定要求。
组件测试揭示的典型故障是错误的计算或缺失(或选择不当)的程序路径(例如,被忽略或错误解释的特殊情况)。
软件组件必须与多个相邻的组件进行交互和交换数据,不能保证该组件不会被错误地访问和使用(即违背其规范)。在这种情况下,被错误处理的组件不应该简单地停止工作并使系统崩溃,而是应该做出 "合理 "和稳健的反应。因此,测试稳健性是组件测试的另一个重要方面。这个过程与普通的功能测试非常相似,但是用无效的输入数据而不是有效的数据为被测组件服务。这样的测试案例也被称为 "负面测试",并假设组件将产生合适的异常处理作为输出。如果没有内置足够的异常处理,组件可能会产生运行时错误,如除以零或空指针访问,导致系统崩溃。
案例研究: 负面测试
对于我们之前使用的价格计算的例子,负测试将涉及到用负的输入值或错误的数据类型(例如,char而不是int)7进行测试:
// testcase 20
price = calculate_price(-1000.00,0.00,0.00,0,0);
test_ok = test_ok && (ERR_CODE == INVALID_PRICE);
...
// testcase 30
price = calculate_price("abc",0.00,0.00,0,0);
test_ok = test_ok && (ERR_CODE == INVALID_ARGUMENT);
各种有趣的事情出现了:
因为可能的 "坏 "输入值的数量几乎是无限的,设计 "负面测试 "比设计 "正面测试 "容易得多。
为了评估由测试对象产生的异常处理,测试驱动必须被扩展。
测试对象中的异常处理(在我们的例子中评估ERR_CODE)需要额外的功能。在实践中,你经常会发现一半的源代码(有时甚至更多)是用来处理异常的。健壮性是有代价的。
除了功能和健壮性,组件测试还可以用来检查组件的其他属性,这些属性会影响其质量,并且只能在更高的测试级别上使用大量的额外工作来测试(如果有的话)。例如,非功能属性 "效率 "和 "可维护性 "。
效率属性表明一个组件与可用的计算资源的交互是多么的经济。这包括诸如内存使用、处理器使用、或执行功能或算法所需的时间等方面。与其他大多数测试目标不同,测试对象的效率可以使用合适的测试标准进行精确评估,如内存的千字节或以毫秒计算的响应时间。效率测试很少对系统中的所有组件进行测试。它通常只限于那些在需求目录或组件规范中定义了某些效率要求的组件。例如,如果在嵌入式系统中可用的硬件资源有限,或者对于必须保证预定的响应时间限制的实时系统。
可维护性包含了所有影响增强或扩展程序的容易程度(或困难程度)的属性。这里的关键因素是开发人员(团队)需要花费多少精力来掌握现有的程序和它的背景。这对于需要修改他多年前编程的系统的开发者和一个从同事那里接手代码的人来说,同样有效。
需要测试的可维护性的主要方面是代码结构、模块化、代码注释、文档的可理解性和最新性等等。
案例分析: 难以维护的代码
示例的calculate_price()代码包含许多可维护性问题。例如,完全没有代码注释,数字常量没有被声明,而是硬编码的。例如,如果需要修改这样的常量,就不清楚在系统中是否需要修改,以及在哪里需要修改,这就迫使开发者要花大力气去弄清楚。
像可维护性这样的属性当然不能用动态测试来检查(见第五章)。相反,你需要用静态测试和审查会议来分析系统的规范和代码库(见第4.3节)。然而,由于你要检查单个组件的属性,这种分析必须在组件测试的范围内进行。
如前所述,组件测试是高度面向开发的。测试人员通常可以访问源代码,支持组件测试中面向白盒的测试技术。在这里,测试人员可以使用组件的内部结构、方法和变量的现有知识来设计测试用例(见5.2节)。
在测试执行过程中,源代码的可用性也是一个优势,因为你可以使用适当的调试工具(见第7.1.4节)来观察测试过程中变量的行为,看看组件的功能是否正常。调试器还可以让你操作组件的内部状态,所以在测试健壮性时,你可以故意启动异常。
案例研究: 将代码作为测试基础
calculate_price()代码包括以下值得测试的语句:
满足条件(discount > addon_discount)的额外测试案例很容易从代码中推导出来。但是价格计算规范没有包含相关的信息,相应的功能也不是需求的一部分。代码审查可以发现这样的缺陷,使你能够检查代码是否正确,规范是否需要改变,或者是否需要修改代码以适应规范。
然而,在许多实际情况下,组件测试 "只 "作为黑盒测试进行--换句话说,测试用例不是基于组件的内部结构。软件系统通常由成百上千个单独的构件组成,所以分析代码只对选定的组件真正实用。
在集成过程中,单个组件越来越多地被组合成更大的单元。这些集成单元可能已经大到无法彻底检查其代码。组件测试是在单个组件上进行还是在更大的单元(由多个组件组成)上进行,这是一个重要的决定,必须作为集成和测试计划过程的一部分。
"测试优先 "是最先进的组件测试方法(而且也越来越多地在更高的测试级别上)。这个想法是首先设计和自动化你的测试用例,第二步是对实现组件的代码进行编程。这种方法具有很强的迭代性:你用你已经设计好的测试用例测试你的代码,然后你以小的步骤扩展和改进你的产品代码,不断重复,直到代码满足你的测试。这个过程被称为 "测试优先编程",或 "测试驱动开发"(通常缩写为TDD])。如果你使用基础良好的测试设计技术系统地得出你的测试用例(见第5章),这种方法会产生更多的好处--例如,负面的测试也会在你开始编程之前被起草,团队被迫澄清这些案例的预期产品行为。
集成测试是继组件测试之后的又一个层次。集成测试假定交给这个层次的测试对象已经经过了组件测试,并且任何组件内部的缺陷都已经尽可能地被纠正。
开发人员、测试人员和专门的集成团队然后将这些组件组合成更大的单元。这个过程被称为 "集成"。
一旦组装完成,你必须测试集成的组件是否能正确地相互作用。这个过程被称为 "集成测试",其目的是为了找到集成组件之间的接口和互动的故障。
在这个层面上,所有描述软件结构和软件系统设计的文件,特别是接口规范、工作流程和顺序图,以及用例图,都要作为测试基础来参考。
你可能会问自己,既然所有的组件都已经单独测试过了,为什么还要进行集成测试?我们的案例研究说明了必须要解决的各种
案例研究: VSR-II DreamCar模块的集成测试
VSR-II DreamCar模块是由一些基本组件组成的。
这些组件之一是CarConfig类,它负责确保车辆配置(基本型号、特别版、额外的附加物等)是允许的,并负责计算结果的价格。该类包括calculate_price()和check_config()方法。该类使用CarDB类从数据库中读取所需的车型选项和价格数据。
前端使用get_config()读取当前的车辆配置,并将结果呈现给终端用户,以便在图形用户界面上进一步调整。对当前配置的修改使用update_config()返回到后端,然后check_config()检查配置是否被允许,并适当地重新计算价格。
尽管组件测试显示CarConfig和CarDB类中没有故障,但它们之间的交互仍然可能是错误的。例如,你可能会发现check_config()无法处理数据库提供的某些额外内容,或者check_config()请求CarDB从数据库中提取的数据,但却以不合适的格式返回给check_config()。
前端和后端之间的交互也可能有问题。例如,如果前端没有正确显示逻辑上允许的配置,用户就会认为这是BUG。也可能是因为update_config()被不适当地调用--例如,前台没有单独显示每个变化,而只是将完整的重新配置作为数据集发送到update_config(),它可以处理这种数据,但可能因此而执行得太慢。
然而,随着DreamCar模块的集成,VSR-II项目的集成测试才刚刚开始。构成VSR-II其他模块的组件(见第2章,图2-1)也必须被集成,然后模块本身才能被集成到整个系统中: DreamCar必须与ContractBase连接,而ContractBase又与JustInTime(订购)、NoRisk(保险)和EasyFinance(车辆融资)子系统连接。最后的集成步骤之一包括将VSR-II连接到外部的FactoryPPS生产计划系统(见第1章,图1-1)。
正如上面的例子所说明的,组件测试不能保证组件之间的接口是无故障的。这就是集成测试层面对整个测试过程的关键所在。潜在的接口故障必须在那里被发现,并隔离其原因。
上面的例子还表明,在集成和整合测试过程中,还必须涵盖与外部系统环境的接口。当与外部软件(或硬件)系统的接口被测试时,这个过程通常被称为 "大集成测试 "或 "系统集成测试"。
系统集成测试只有在系统测试完成后才能进行。在这种情况下,风险在于开发团队只能测试有关界面的 "他们的一半"。而 "另一半 "是外部开发的,因此随时可能发生意外的变化。即使系统测试通过了,这也不能保证外部接口总是按照预期的那样工作。
这意味着有多个级别的集成测试,涵盖不同规模的测试对象(内部组件或子系统之间的接口,或整个系统与外部系统之间的接口,如网络服务,或硬件与软件之间的接口)。例如,如果业务流程要以由多个系统组成的跨接口工作流的形式进行测试,那么要隔离导致故障的接口或组件就会非常困难。
集成过程将各个组件组装起来,产生更大的单元,理想情况下,每个集成步骤之后都会有集成测试。这样构建的每个模块都可以作为进一步集成到更大单元的基础。因此,集成测试对象可以由反复集成的单元(或子系统)组成。
在实践中,现在的软件系统很少从零开始建立,而是由现有系统(例如,数据库、网络、新硬件等)的扩展、修改或组合而成的。许多系统组件是在公开市场上购买的标准化产品(如DreamCar数据库)。组件测试通常不包括这类现有的或标准化的对象,而集成测试则必须涵盖这些组件以及它们与系统其他部分的交互。
最重要的集成测试对象是连接两个或多个组件的接口。此外,集成测试也可以涵盖配置程序和文件。根据系统的结构,对数据库或其他基础设施组件的访问也可以是(系统)集成测试过程的一部分。
集成测试还需要测试驱动程序为测试对象提供数据,并收集和记录结果。因为测试对象是复合单元,没有与外界的接口,而这些接口不是由它们的组件部分提供的,所以重新使用为单个组件测试创建的测试驱动是有意义的。
如果组件测试阶段组织得很好,你就可以获得所有组件的通用测试驱动,或者至少是一组按照统一架构设计的测试驱动。如果是这样的话,测试人员可以调整并使用这些现有的测试驱动来进行集成测试,而不需要额外的努力。
糟糕的组件测试过程也许只能提供一些合适的驱动,这些驱动有不同的操作结构。这种情况的缺点是,测试团队现在不得不投入大量的时间和精力在项目的后期阶段创建或改进测试环境,从而浪费了宝贵的集成测试时间。
因为通过测试驱动接口的接口调用和数据流量需要被测试,集成测试经常使用 "监视器 "作为额外的诊断工具。监视器是一个程序,它对组件之间的数据移动进行检查,并记录它所看到的情况。商业软件可用于监测标准数据流量,如网络协议,而你将不得不开发定制的监视器,用于项目特定的组件接口。
集成测试的目的显然是为了找到接口故障。如果两个组件的接口格式不匹配,所需的文件丢失,或者开发者编程的组件不符合规定的划分,那么在第一次尝试集成时就已经出现问题了。这样的故障通常会在早期通过失败的编译或构建运行被发现。
难以发现的问题是运行时在组件之间的数据交换(即通信)过程中发生的故障,检测这种故障需要进行动态测试。可以区分以下基本类型的通信故障:
组件传输没有数据、语法错误的数据或错误编码的数据,接收组件无法处理,从而导致异常或崩溃。根本原因是功能组件错误,不兼容的接口格式,或协议错误。
通信是正常的,但由于某个组件的功能故障或对规范的误解,所涉及的组件对传输的数据有不同的解释。
数据传输正确,但在错误的时刻(定时或超时问题)或间隔时间太短(导致吞吐量、容量或负载问题)。
案例研究: VSR-II的集成错误
在VSR-II的集成测试中,可能会出现上述类型的以下故障:
在DreamCar GUI中选择的额外内容没有移交给check_config(),从而产生不正确的价格和订单数据。
在DreamCar模块中,车辆的颜色是由代码表示的(例如,442表示金属蓝)。然而,生产计划系统(FactoryPPS,见第一章,图1-1)对一些代码的解释不同(这里,442表示珍珠效果的红色)。这种差异意味着,从VSR-II的角度来看,正确的订单可能导致错误的产品被制造和交付。
主机系统会确认转移的订单。在某些情况下,检查可交付性需要很长时间,以至于VSR-II认为存在数据传输错误并取消了连接。其结果是,客户无法订购他花了很多时间配置的车辆。
因为故障只发生在软件单元之间的交互过程中,这些类型的故障都不能在组件测试中发现。
除了功能测试外,集成测试也可以涉及非功能测试。这在组件接口的非功能属性被归类为系统相关或有风险的情况下很有用(如性能、负载下的行为或与容量相关的行为)。
有没有可能完全不进行组件测试,而直接用集成测试开始测试过程?是的,这是可能的,而且不幸的是,这也是常见的做法。然而,这种方法有潜在的严重缺陷:
这种测试所揭示的大多数故障都是由单个组件内部的功能故障引起的。换句话说,实际上是在不适合的环境中执行的组件测试,使访问单个组件变得复杂。
因为不容易接触到每个单独的组件,有些故障不会被激起,因此不可能发现。
如果发生了故障或崩溃,就很难或不可能定位到导致故障的组件。
不进行组件测试,只是以故障发现率低和诊断工作增加为代价,节省了精力。将组件测试与集成测试结合起来要有效得多。
为了最大限度地提高测试效率,应该以什么样的顺序集成组件?测试效率是用测试成本(人员、工具等)和有用性(发现故障的数量和严重程度)之间的关系来衡量的,对于特定的测试层次来说。测试经理负责为手头的项目选择和实施最佳的测试和集成策略。
单个组件的完成时间可能相隔数周或数月。项目经理和测试经理不会想等到所有相关的组件都准备好后再进行一次集成。
处理这种情况的一个简单的临时策略是按照组件完成的(随机)顺序来集成。这包括检查新到的组件是否应该与已经存在的组件或子系统集成。如果这个检查是成功的,新的组件可以被集成并进行集成测试。
案例研究: VSR-II项目的集成策略
VSR-II的中央ContractBase模块的工作比原来想象的要复杂,该模块的完成被推迟了几个星期。为了避免浪费时间,项目经理决定开始测试DreamCar和NoRisk模块。
这两个模块没有相互接口,但通过ContractBase交换数据。为了计算适当的保险费,NoRisk需要车辆类型和其他参数。
必须编写桩,作为ContractBase的临时占位符使用。这个桩从DreamCar接收简单的车辆配置,确定车辆的类型代码并将其传递给NoRisk。桩还可以输入其他各种与保险有关的客户信息。然后,NoRisk计算出保费,在窗口中显示出来供检查,并将其记录为测试结果。因此,桩暂时填补了不完整的ContractBase模块留下的空白。
这个例子强调,尽管它可能节省时间,但过早地开始集成测试会增加创建桩的工作量。
测试管理必须选择一种测试策略,优化节省的时间和维护测试环境所增加的工作量之间的关系。
哪种策略是最好的(即最经济和最节省时间),取决于每个项目必须分析的约束条件:
系统结构决定了系统所包含的组件的数量和类型以及它们之间的依赖关系。
项目进度表定义了各个组件的完成时间、集成和测试时间。
整体的测试计划定义了系统的哪些方面要进行彻底的测试,以及在哪个测试级别。
测试经理必须研究这些约束条件,并利用它们来制定适合当前项目的集成策略。因为各个组件的交付时间是关键,在项目计划阶段与项目经理协商,以确保组件的交付顺序和时间能够支持测试,是一个好主意。
测试经理可以将以下基本的集成策略作为规划的指导:
自上而下的集成:
测试从调用其他组件的主要组件开始,但除了操作系统外,自己并不被调用。附属组件被存根所取代。然后,较低系统层的组件逐渐被集成,而上面的(已经测试过的)层则作为测试驱动。
好处是:
因为已经测试过的组件构成了运行时环境的大部分,你只需要基本的测试驱动,或者根本不需要测试驱动。
缺点是:
尚未集成的附属组件必须由桩来代替,这可能涉及到很多额外的工作。
自下而上的集成
测试从不调用任何其他组件的基本组件开始(除了操作系统的功能)。较大的单元是由经过测试的组件逐步建立的,然后再进行集成测试。
临时性的集成
组件按照它们完成的(随机)顺序被集成(见上文)。
骨架集成
骨架框架("骨干")被创建,各个组件被陆续集成到其中。持续集成(CI)(见第3.1节)是这种策略的一个现代版本,其中骨干是由现有的组件组成的,新的测试组件被添加到其中。
纯粹的自上而下和自下而上的集成只能应用于有严格层次结构的系统,这在现实世界中是很少见的。在实践中,大多数项目都依赖于上面详述的策略的自定义混合。
必须避免任何非递增式的整合(也被称为 "大爆炸")。这种方法不涉及真正的战略。团队只是简单地等待,直到所有的组件都准备好了,集成意味着简单地把它们一下子扔到一起。在最坏的情况下,上游组件的测试也被跳过。其缺点是显而易见的:
等待 "大爆炸 "的时间是不小心浪费的测试时间。测试总是涉及到时间压力,所以不要浪费任何一个测试日。
所有的故障都是一次性发生的,很难(或者根本不可能)让系统完全运行。缺陷的定位和纠正是复杂和耗时的。
一旦集成测试完成,下一个就是系统测试。这个层次的测试是检查完整的、集成的系统是否真正满足了其指定的要求。在这里,你可能也会问自己,为什么在成功的组件和集成测试之后,这个步骤是必要的。原因是:
低级别的测试从软件制造商的角度检查技术规范。相反,系统测试是从客户和最终用户的角度来看待系统。系统测试人员检查指定的要求是否已经完全和适当地实现。
许多功能和系统属性来自于系统组件的相互作用,因此只能在整个系统层面进行观察和测试。
案例研究: VSR-II系统测试
对于销售方的利益相关者来说,VSR-II系统最重要的任务是使订购车辆尽可能简单。订购过程几乎使用了系统的所有模块:在DreamCar中进行配置;使用EasyFinance和NoRisk进行融资和保险;通过JustInTime传输订单;在ContractBase中保存文书。只有当所有这些组件都能正常工作时,系统才能真正实现其预期的目的,而系统测试可以验证是否如此。
测试基础包括在系统层面上描述测试对象的所有文件和其他信息。这些可以是系统和软件的要求,规格,风险分析(如果有的话),用户手册,等等。
一旦集成测试完成,你将面对一个完整的、可以运行的系统。系统测试在尽可能类似于系统生产环境的环境中检查完成的系统。所有的硬件、软件、驱动程序、网络、第三方系统和其他将成为其工作环境一部分的组件都需要安装在系统测试环境中,而不是存根和测试驱动程序。
除了检查用户、培训和系统文档外,系统测试还检查配置设置,并应通过提供负载/性能测试结果来支持系统优化(见3.5.2节)。
为了节省时间和金钱,系统测试通常在生产环境本身进行,而不是在单独的系统测试环境中进行。这是个坏主意,有各种原因:
系统测试肯定会暴露出故障!这些故障可能会产生高度的负面影响!这些故障会对客户的生产环境产生非常不利的影响。客户现场的崩溃和数据丢失可能是昂贵的,应该不惜一切代价避免。
测试人员对影响客户生产环境的配置和参数的控制有限。如果你在客户系统的其他部分运行时进行测试,这会巧妙地改变你的结果,使你进行的测试极难重现(见3.6.3节)。
由于它需要复杂的测试环境,系统测试所涉及的工作常常被低估。经验表明,通常在系统测试开始时,只有一半所需的测试和质量保证工作已经完成。
如前所述,系统测试的目标是验证完成的系统是否满足指定的(功能和非功能)要求以及满足的程度(见3.5.1和3.5.2节)。系统测试可以识别由于错误的、不完整的或不一致的实现需求而造成的缺陷和不足。它还应该识别未记录的或被遗忘的需求。
在依赖数据库或其他大量数据的系统中,数据质量是重要因素,可能需要作为系统测试过程的一部分来考虑。数据本身成为 "测试对象",它们需要适当地检查一致性、完整性和最新性。
题外话:系统测试问题
在太多的项目中,对需求的澄清和记录要么是零散的,要么是完全忽视的。这使得测试人员很难知道系统实际上是如何工作的,并且加倍难以可靠地揭示故障。
模糊的需求
在没有需求的地方,任何系统行为都是被允许的,或者系统行为根本无法被评估。用户(或内部/外部客户)当然会对 "他的 "系统的期望有想法。需求确实存在,但只是在参与项目的一些人的头脑中。然后,测试人员被赋予了一项艰巨的任务,即整理关于系统计划行为的所有相关信息。处理这种情况的方法之一是使用探索性测试(见5.3节)。
错过的决定
在收集这些信息的过程中,测试人员会发现各个参与者对需要建立的东西有着非常不同的想法和态度。为了避免这种情况,项目需求需要被写下来,然后由所有相关的参与者同意并批准。
换句话说,除了收集需求,系统测试还必须强制执行澄清和决策过程,这些过程本应在很久以前就完成,而现在却做得太晚。这种信息收集需要时间,而且成本极高,保证会延迟产品的交付。
一些项目失败
如果需求没有被记录下来,开发人员就不会有明确的目标,所产生的系统满足客户隐含需求的概率确实很低。在这种情况下,没有人期望会产生一个可用的产品,系统测试往往只能 "证明 "项目的失败。
减少早期反馈的风险
迭代和敏捷项目也需要清楚地制定和书写需求。同样,总有一些需求是不完整的,不正确的沟通,或者干脆被忽略的风险。然而,在这种情况下,每个迭代都提供了检查给定需求满足情况的机会,从而减少项目失败的风险。
如果需求没有得到充分的满足,你可以利用下一次迭代来改善事情。你最终可能需要比原来计划的更多的迭代来确保达到特定的功能水平。这样一来,就相当于一个产品迟迟不能交付,但可以使用,而不是一个完全失败的项目。
到目前为止,我们所看到的测试类型都是由软件制造商和/或开发团队在软件交付给客户之前进行的。
在系统启动之前(特别是在定制软件的情况下),它要经过额外的 "验收测试"。这种测试是从客户/终端用户的角度进行的,是客户直接参与或实际负责的唯一测试。
验收测试的典型变体是:
用户验收测试
系统操作者的验收
合同和法规验收测试
现场测试(即α和β测试)
验收测试也可以在较低的层次上进行,通常分布在多个测试层次上:
现成的软件可以在集成或安装时进行验收测试
组件的用户友好性可以作为组件测试过程的一部分进行验收测试
新功能的验收可以在系统测试前使用原型来检查。
验收测试的范围是基于风险的,并且是高度可变的。在定制软件的情况下,风险水平很高,因此全面的验收测试是必不可少的。在光谱的另一端,如果你正在安装现成的软件,只需安装软件包并测试几个典型的使用场景就足够了。但是,如果软件包访问了其他系统的接口,那么这些独立系统之间的交互也必须进行测试。
测试基础包括所有从用户角度描述测试对象的文件--例如,用户和系统要求,用例,业务流程,风险分析,使用系统的流程描述,表格,加上报告和系统维护和管理流程的描述。如果测试是基于法律或其他正式法规,通常被称为法规验收测试。
在定制软件的情况下,客户将(与制造商合作)进行合同性验收测试。根据测试结果和开发合同规定的准则是否得到满足,客户将决定是否接受交付的产品。该合同也可以是两个公司内部部门之间的(不太正式的)协议,作为联合项目开发产品。
开发合同中详细说明的验收标准将作为测试标准,所以这些标准必须被清楚明确地定义。如果相关的话,任何法律规定、监管标准或安全条例都是验收标准的一部分。
在实践中,软件制造商通常应该在内部系统测试中使用适当的测试案例检查这些验收标准。然后,验收测试可以通过重复这些系统测试的子集来完成,向客户证明合同要求已经得到满足。
我们的提示
因为制造商有可能误解合同约定的验收标准,所以客户必须设计自己的验收测试案例(或者至少对起草的案例进行严格的审查)。
与在软件制造商的测试环境中进行的系统测试不同,验收测试是在客户自己的验收测试环境中进行。这两种环境之间的差异可能导致无故障的系统测试案例在验收测试中失败。安装和配置程序是验收测试的一部分,通常被称为运行验收测试。验收测试的环境必须尽可能地与系统要使用的生产环境相似。然而,要避免在生产环境中进行验收测试,因为这有可能会破坏关键的生产过程。
你可以使用你用于系统测试的相同方法来得出合适的验收测试和测试标准。如果你正在测试管理性的IT系统,你还需要测试在一个典型的时间段和/或计费期发生的业务案例(例如,每月的计费周期)。
验收测试的另一种是用户验收。如果客户实际上不是最终用户,这种测试就特别重要。
案例研究: 不同的用户群和使用情况
在VSR-II的案例中,客户是汽车制造商,但最终用户是经销商的工作人员和客户,他们在经销商处或网上使用DreamCar模块来配置车辆。公司总部的工作人员也使用该系统--例如,更新价格表。
不同的用户群一般会对新系统有不同的期望。如果用户群拒绝该系统--例如,因为他们认为该系统 "不方便",这可能意味着整个实施被拒绝,即使该系统按照其功能规格运行。因此,针对每个用户群进行验收测试是很重要的。这样的测试通常是由客户组织的,客户可以根据既定的业务流程和用户场景来选择最合适的测试案例。
我们的提示 尽早向终端用户展示样机
如果在验收测试阶段出现了严重的问题,那么除了系统的表面现象外,通常已经来不及改变什么了。为了避免这种灾难,在开发过程中尽早让选定的最终用户评估产品原型是一个好主意。
系统运营商的验收测试确保系统管理员对系统在现有IT环境中的整合方式感到满意。这些测试包括备份和恢复程序、系统关闭后的重新启动、用户管理、以及数据处理和安全的各个方面。
如果一个产品注定要在多个不同的生产环境中运行,对于制造商来说,建立系统测试环境来反映每个终端用户的情况是非常昂贵和不现实的。在这种情况下,制造商将在系统测试之后进行现场测试,旨在识别(如果可能的话,纠正)来自未知或部分未知环境的影响系统的因素。现场测试对于开发商业的、现成的软件产品的制造商特别有用。
在这种情况下,制造商将稳定的、预先发布的软件交付给代表市场或产品典型目标环境的选定客户。
这些客户然后执行制造商规定的测试,或者只是在真实世界的条件下测试软件。然后,他们以评论和缺陷报告的形式向制造商提供反馈,制造商可以利用这些反馈对产品进行适当的调整。
这种测试被称为 "α "或 "β "测试。阿尔法测试是在开发者的测试环境中由角色在开发组织之外的人进行的,而贝塔测试是在客户现场进行的。
现场测试不能取代制造商的内部系统测试(尽管有些制造商认为它应该)。只有在系统测试证明软件足够稳定的情况下,你才应该发布软件进行现场测试。
上面提到的所有验收测试的变体都与迭代和/或敏捷开发过程有关。
迭代和敏捷开发的指导原则之一是尽快收集客户的反馈,这正是验收测试的内容。因此,你应该在每个迭代中包括一些验收测试,也许是通过用户调查,在产品演示中,或者与你的客户/用户一起进行实践测试。因此,验收测试的内容和重点可能会在各个迭代中发生变化。与其说是对最终产品版本的结论性接受,不如说是用反馈来指导下一个版本中可以或必须改进的地方。
你进行的验收测试的类型和范围也取决于你正在进行的迭代的 "类型"。你需要区分产生内部版本的迭代和产生外部生产性使用的版本。例如,测试团队不需要为一个纯粹的内部版本进行合同验收测试。
前面几节详细介绍了软件开发过程中需要涵盖的测试层次。这些测试的重点和目标因级别不同而不同,所以不同类型的测试必须以不同的彻底程度进行。我们对以下基本类型进行区分:
功能测试包括所有的测试技术和方法,用于测试测试对象的可观察的输入/输出行为。功能测试案例是使用5.1节中描述的 "黑箱 "技术建立的。功能要求是计划系统行为的测试基础。
功能要求和功能适用性
功能需求13规定了系统或系统组件的预期行为--换句话说,它们描述了系统(或组件)被设计来完成的 "内容",而实现它们是一个正常运行的系统的基本前提条件。一个组件是否以及如何满足其功能要求被称为其 "功能适用性",这是ISO 25010标准[ISO 25010]中定义的质量特性之一(见第2.2节)。
下面的例子列出了VSR-II系统中价格计算的一些功能要求(也见3.4.1节):
案例研究: VSR-II DreamCar模块的功能要求
R 100:
用户可以从当前目录中选择一个车型进行配置
R 101:
将显示所选模型的可交付的额外内容。客户可以选择其中任何一项添加到配置中。
R 102:
当前配置的价格(型号加额外费用)将根据当前的价格表计算并实时显示。
R 103:
对于当前选定的车辆,将只显示属于可交付配置的额外费用。当前配置的不可交付的额外费用将被灰色显示。
功能测试案例
检查功能属性的测试需要对每个功能需求至少有一个测试用例。验证R102 "计算总价"(见上文)的合适测试可能是这样的:
案例研究: 基于需求的功能测试用例
T 102.1:
选择一个汽车型号;显示根据销售手册的价格
T 102.2:
选择了额外的东西;车辆的价格被这个额外的东西的价格所增加
T 102.3:
取消选择某项额外费用;车辆价格按该额外费用的价格降低
T 102.4:
选择了三个额外的项目;这些项目根据规格进行折扣。
通常情况下,需要一个以上的测试用例来测试一个功能需求。在我们的例子中,需求102包括一系列的价格计算变体,需要由适当的测试用例(102.1-102.4)来覆盖。这些案例可以使用其他黑盒技术进行微调和增加,如等价分割(见5.1.1节)。
重要的是,一旦预定义的测试用例(或测试规范中定义的子集)被运行而没有发现任何故障,功能就被认为是正确实现的。
功能测试发生在每一个测试层面。对于组件和集成测试,测试基础包括组件的技术规范,每个组件的接口(API)的规范,或白盒信息(见3.5.3节)。对于系统和验收测试,测试基础是由功能系统要求组成的(如上面的例子所示)。
如果一个软件系统被设计成自动化或支持特定的业务流程(如上面的例子),用例或基于业务流程的测试技术也是合适的(见5.1.6节)。
案例研究: 测试一个业务流程
VSR-II在销售过程中为经销商提供支持,可能发生的情况如下:
客户选择感兴趣的可用型号
客户查看设备和价格选项,并相应配置车辆
销售人员建议各种融资方案
客户做出决定并下订单
业务流程分析通常是作为需求分析的一部分进行的,它显示了哪些业务流程是相关的,它们发生的频率如何,在什么情况下发生,哪些人、公司和外部系统参与其中,等等。这些信息形成了测试基础,然后用来创建模拟典型业务事件的测试场景。这些测试场景根据这些事件的典型频率和相关性进行优先排序,同时也根据相应流程的风险系数进行排序。
基于需求的测试集中在单个系统功能上(例如,传输订单),而基于业务流程的测试则使用测试序列来检查连续的程序(例如,由车辆配置、同意购买和传输订单组成的销售)。
除了我们的样品VSR-II系统的基本选择、配置、购买功能外,用户接受度也是基于系统的用户友好程度。用户友好性(或可用性)的评判标准是:系统使用起来有多容易,它 "回答 "的速度有多快,以及它所提供的输出有多清晰。换句话说,除了功能标准之外,我们还必须测试和验证系统的非功能属性。
非功能需求描述了系统的功能行为的属性--即一个系统或组件应该如何 "好 "地完成它的功能。它的实现强烈影响着客户/用户的满意度,因此也影响着系统的受欢迎程度。根据[ISO 25010],这种特性包括用户满意度和效率(见2.2节)。从制造商的角度来看,灵活性和可移植性是产品可维护性的重要方面,因为这些有助于降低维护成本。
以下的非功能系统特性应该被涵盖,并通过相应的测试来检查(通常在系统测试过程中):
当受到用户错误、编程错误、硬件故障和类似情况时。测试异常处理和重启/恢复行为。
。
非功能需求往往是不完整的或含糊的措辞。诸如以下的属性: "系统需要易于使用 "或 "快速响应",以其目前的形式是无法测试的。
测试人员应该参与需求文档的审查,并确保所有列出的(非)功能需求的措辞是可衡量的,因此是可测试的。
此外,许多非功能需求表面上是如此明显,以至于没有人想到要提及或指定它们。这种 "假定需求 "15 往往是高度相关的,系统必须拥有相应的隐含属性。
案例研究: 假设的需求与指定的需求
原始的VSR系统被设计为运行在一个著名的市场领导者提供的操作系统上,其外观和感觉与操作系统的风格和惯例基本一致。
VSR-II中新的、现代的用户界面是基于浏览器的。市场部与一家网页设计公司合作,创建了全面的风格指南,规定了VSR-II的界面在整个系统中的外观。
可用性测试使用检查表(见5.3节,基于检查表的测试),以确保每个模块的用户界面符合风格指南中规定的规格。这些测试显示,一些规定的显示和界面元素在移动设备上难以阅读。虽然没有制定明确的 "易读 "要求,但决定修改风格指南的相应部分,使界面的所有元素都能满足良好的可读性这一假设要求。
题外话:用功能测试来测试非功能属性
使用现有的功能测试来验证非功能属性往往是权宜之计。非功能测试通常是黑盒测试,可以被看作是一种绑在功能测试上的 "背包"。这类测试的一个优雅的一般方法是如下:
从功能测试中选择的场景代表了整个系统所提供的功能的横截面。非功能维度必须在相应的测试场景中可见,并在测试运行中被测量。如果测量值低于预定的阈值,测试就被归类为通过。实际上,功能测试方案是确定你要测试的非功能系统特性的测量程序。
基于需求的测试是一种黑箱技术,其中测试用例的设计是基于需求的。它也被称为基于规格的测试,因为它使用软件的外部可观察行为的规格作为其测试基础。这种规范可以采取各种形式--例如,用例或用户故事。相应的测试技术将在第5.1节中描述。这些规范和由此产生的测试用例可以与相关软件元素的功能或非功能特性有关。
基于需求的测试大多发生在系统和验收测试期间。如果组件或集成测试来自技术规范或需求,这些也可以归类为基于需求的测试。
基于结构的测试(结构测试,白盒技术)使用软件的内部结构/架构作为其测试基础。它分析诸如组件内的控制流或程序或菜单结构的调用层次等内容。软件的抽象模型中的结构也可以作为一个起点。目标是在测试过程中尽可能多地覆盖观察到的结构元素。这需要设计足够多的测试用例,这些测试用例也可以基于你所调查的软件元素的功能或非功能属性。
结构测试主要是在组件和集成测试中进行,有时也作为更高的测试级别的附加测试(例如,为菜单结构提供覆盖)。这些技术将在以下文章中详细讨论。
到目前为止,我们只是简单地假设,当软件开发项目通过验收测试并交付产品后就结束了。然而,现实中的情况却截然不同。最初的交付只标志着软件产品生命周期的开始。许多软件产品会被使用数年甚至数十年,并且在其生命周期内通常会被多次修改、扩展或维修。请看我们下面的案例研究部分,了解一个例子:
案例研究: 评估VSR-II的热线票据
VSR-II已经取代了原来的VSR系统,并且已经运行了一段时间。为了找到新系统的潜在弱点,终端用户支持团队分析了系统上线后头几个月收到的查询。这里有一些例子:
有几个经销商一直在一个未经批准的平台上运行系统,使用的是传统的操作系统。系统的浏览器无法显示用户界面的某些元素,系统在访问主机系统时有时会崩溃。
一些客户说,他们发现在旧的VSR系统中选择额外的东西很复杂,特别是在比较各种设备包的价格时。因此,VSR-II使客户能够保存各种配置,并在做出改变后调用这些配置。然而,有些客户希望得到更多的便利,希望能够比较不同的车辆型号加设备包。
因为在对保险模块编程时忽略了相应的计算规则,一些很少要求的保险费率无法计算。类似的问题在VSR中已经知道了。
在极少数情况下,订单确认在等待15分钟后仍未到达工厂电脑。然而,为了避免浪费网络容量,VSR-II最迟会在15分钟后切断连接。这意味着经销商必须重复订单,并在以后单独向客户发送确认。由于对订单确认的不必要的等待,这引起了客户的不满。
问题#1基本上是经销商的问题。然而,制造商可以修改系统,以防止经销商不得不进行昂贵的硬件更新。
问题2总是会出现,不管原来的系统要求有多全面。新的系统带来了新的用户体验,而由此产生的客户偏好只有在系统运行一段时间后才会显现出来。
问题3可能是在系统测试中发现的,尽管测试是一种抽查,并不能保证没有缺陷,而只是有一定的概率发现现有故障。好的测试经理会检查哪些测试是必要的,以发现这个疏忽,并相应地修改测试计划。
问题#4被发现,并使用一个补丁进行了补救。VSR-II不再丢失订单数据,而是自动多次重复订单过程。然而,这仍然不能防止客户在订单确认需要一段时间时的等待。真正的解决方案将涉及修改公司主机上的批处理系统--这个解决方案超出了VSR-II的职权范围,因此在短期内无法补救。
这四个例子代表了随着时间推移而出现的典型问题,即使是在最成熟的系统中:
系统在新的、不可预见的、非计划的条件下被使用
客户提出了新的、未预见到的要求
需要有涵盖罕见(因此被忽视)的特殊情况的功能
问题不断发展,只是零星出现,或在系统运行较长时间后出现。这类问题往往是由外部因素引起的。
每个软件系统在其生命周期内都需要进行修改和调整。这个过程通常被称为 "软件维护"。
软件是不会磨损的。与硬件的维护不同,也与物理工业产品不同,软件维护的目的不是为了保持运行能力或修复使用过程中造成的损坏。软件维护的目的是:
纠正产品中无意中出现的故障
改善产品的质量特性
使产品适应变化了的操作条件(例如,当一个新的操作系统、一个新的数据库或一个新的通信协议被实施时)
相应的测试过程被称为维护测试。
软件产品的变化可以由错误修复引发,也可以由作为 "正常 "维护和持续发展的一部分的有计划的功能修改/扩展引发。
测试新版本
在这两种情况下,结果都是一个新的产品版本。新版本与早期版本基本相同,但对现有功能进行了一些修改,也有一些全新的功能。
测试过程是如何对这些变化做出反应的?每个测试层的所有测试都必须在每个版本中重复进行吗?还是只测试那些直接受变化影响的元素就足够了?下面的章节将讨论这些问题。
在软件维护的情况下(见上文),基本的测试策略(也称为确认测试)包括重复所有在上一版本中发现故障的测试案例。这些测试用例必须通过,以便将相应的故障归类为已纠正。
如果以前的测试用例没有发现的故障已经被修复(例如,因为来自于热线单),你需要起草新的测试用例来验证新发现的故障是否真的已经被纠正。
纠正以前未发现的故障往往会改变附近程序元素的(正确)行为。这可能是故意的,也可能是意外的,这就需要额外的测试用例。这些测试用例要么是修改过的,要么是新的,以验证这些改变是否达到预期效果。你还需要重复现有的测试用例,验证被修改的元素的 "其余部分 "保持不变,仍然正常运行。
有些软件故障会对系统的完整性造成直接威胁,因此需要及时关注。在这种情况下,紧急的 "热修复 "比深思熟虑的长期解决方案更方便。专注于最重要的确认测试有助于快速提供热修复,但你还是需要尽快进行全面测试(如上所述)。
如果项目经理提前计划好维护性发布,并将其纳入整个测试计划,那么维护性测试总是更简单、更成功。在处理遗留系统时,你往往只能获得过时的规范(或者根本没有规范),这使得维护和维护测试更加困难。适当的计划使这方面的测试更容易。
不能把维护作为跳过测试的借口。如果你因为 "反正未来的版本会纠正缺陷 "而跳过测试,你还没有正确理解软件缺陷可能导致的成本和风险。
确认测试本身,或在修改附近的新测试其实是不够的。表面上简单的局部修改可能会在系统的其他部分(通常是遥远的)造成意想不到的、有时是灾难性的后果和副作用。有多少测试用例和哪些类型的测试用例是必要的,以减少这种风险,必须通过对你所做的修改的潜在影响的高度具体的 "影响分析 "来确定。
当软件被修改以适应改变的操作条件时,你需要运行测试以确保系统操作者接受修改后的系统。这是因为非功能系统属性的各个方面(如性能、资源要求或安装前提条件)在客户的环境中可能(在修改后)表现得不同。
如果修改涉及到数据转换或迁移,你还需要测试数据的完整性和真实性。除了这些因素外,测试修改后的系统的总体策略与测试维护后的系统是一样的(见上文)。
除了故障修正所需的维护外,项目管理部门还将计划对系统进行扩展和其他改变,以保持其竞争力并扩大客户群。大多数系统都要进行持续开发--例如,建立产品的改进版本。这样的版本通常与预定的维护工作相协调。例如,这些可能是每季度的维护版本和每年的 "真正 "功能更新。
案例研究: VSR-II发展规划
VSR-II第二版的开发计划包括以下变化:
ConnectedCar通信软件要进行扩展,以确保与最新车型系列中内置的新一代物联网传感器兼容。
第1版中没有及时准备好的各种功能扩展将在第2版中交付。
安装基础将被扩展到包括该公司在世界各地的经销商。这需要对更多的国家进行本地化工作,包括系统菜单和手册的翻译。
变更#1是由外部资源和系统的计划变更导致的。变更#2是从一开始就计划好的功能,但由于时间限制还没有实施。变更#3是一套扩展功能的一部分,是预先计划的市场扩张所需要的。
这些变化都不是因为故障纠正或不可预见的客户要求,而是VSR-II正常的迭代/增量产品开发过程的一部分。
这样的版本开发后的测试遵循两个主要目标:
检查每一个新的或扩展的功能是否按预期运行
检查先前存在的功能是否(无意中)受到损害。
为了实现目标1,你需要开发和执行新的测试案例。目标#2需要适当的回归测试(见下面3.6.3节)。
退役前的测试
当系统要永久退役时,会出现一个有趣的情况。在这里,你需要在退役前检查系统数据是否可以或必须被归档或转移到后续系统。
对程序进行修改后重复测试的过程被称为 "回归测试"。
回归测试利用现有的测试用例来检查所做的修改是否产生了新的故障,是否有无意的副作用。换句话说,其目的是确保修改后的系统中未被改变的部分仍然像修改前那样工作。
最简单的方法是在新版本的程序上执行现有的测试。
为了使现有的测试案例对回归测试有用,它们必须是可重复的。这意味着手动测试用例必须有足够的文件。用于回归测试的测试用例将被定期和经常使用,因此注定要进行测试自动化。回归测试案例的自动化是非常有用的(见第7.2节),因为它确保了精确的可重复性,同时降低了每个测试重复的成本。
哪些现有的测试应该被用来确保成功的回归测试?
因为我们要检查现有的功能是否被(无意中)破坏了,我们基本上需要重新运行所有涵盖这个预先存在的功能的测试。
如果很少(或没有)自动化测试,因此你必须手动执行回归测试,你将不得不选择手动测试中最小的子集。为了选择合适的测试用例,你必须仔细分析测试规范,看看哪些测试用例与哪些原有的功能有关,哪些与新的、修改的功能有关。
如果你有自动化测试用例,最简单的策略是简单地对新产品版本重新执行所有的测试用例:
返回 "通过 "结果的测试用例表明组件/功能没有改变。这可能是因为没有进行所需的改变,或者是因为 "旧 "的测试用例没有足够精确地制定,以涵盖修改后的功能。在这两种情况下,相应的测试用例需要被修改,以确保它们对新功能的反应。
返回 "失败 "结果的测试用例表示修改的功能:
如果这包括那些没有被标记为改变的功能,那么失败是有理由的,因为它表明--与计划相反--相应的功能已经被改变。因为这种无意的修改被 "旧 "测试用例所揭示,所以不需要进一步修改。
对于需要改变的功能,测试用例也需要调整,使其涵盖新的功能。
所有这些的结果是一套回归测试,验证计划中的功能改变。涵盖全新功能的测试用例还不是这个套件的一部分,必须单独开发。
在实践中,运行所有现有测试的完整回归测试通常成本太高,花费时间太多,特别是当(如上所述)涉及手动测试时。
因此,我们正在寻找一些标准,以帮助我们决定哪些遗留的测试用例可以被忽略而不损失太多信息。在测试环境下,这需要在最小化成本和接受商业风险之间做出妥协。以下是选择测试用例的常见策略:
只重复在测试计划中被赋予高优先级的测试。
撇开功能测试的特殊情况。
将测试限制在某些特定的配置上(例如,只测试英语版本,只测试特定的操作系统,以及类似的)。
将测试限制在特定的组件或测试级别。
这里列出的规则主要适用于系统测试。在较低的测试级别,回归测试的标准可以基于设计文档(如类的层次结构)或白盒信息。
软件开发生命周期模型以章节、阶段或迭代的方式构造软件开发过程。两种基本的模型类型是 "顺序的 "和 "迭代/递增的"。
顺序开发模式的特点是开发活动以线性(即顺序)方式进行。
迭代/增量模式产生定期的扩展和/或改进的产品发布,使客户和系统的用户能够及时反馈。这种方法缩短了产品上市的时间,也降低了开发出的产品不能满足客户期望的风险。所有的敏捷开发方法都被归类为迭代/递增式的。
V模型是一个重要的顺序开发模型,它定义了组件、集成、系统和验收测试级别。它区分了验证和确认,并提供了良好的测试实践原则,可以应用于任何开发模式:
越早发现缺陷,纠正它的成本就越低。因此,V型模型要求在每个开发阶段结束时进行验证程序(如审查)。这有助于防止后续缺陷的扩散。
组件测试检查单一的软件组件。集成测试检查这些组件的协作情况。功能和非功能的系统测试从用户的角度检查整个系统。在验收测试中,客户检查产品在合同方面的验收情况,以及用户和操作人员的验收情况。如果系统要安装在多个环境中,现场测试提供了一个额外的机会,通过运行产品的预发布版本来获得系统的经验。
在产品的整个生命周期中,维护和增量开发不断创造出新的软件产品版本。每个新版本都要进行测试,这种回归测试的范围取决于相应的风险分析的结果。