软件测试对于每个软件的开发都是必不可少的,它易于实施,又能够有效地提高软件质量,因此一直都被广泛采用,并成为软件工程领域工程实践和理论研究的重要组成内容。一般所说的软件测试主要指编码实现后,对生成的软件程序进行测试,以查找软件中存在的错误,然后进行修复。但更广义地说,软件测试还要包括对软件需求和软件设计阶段形成的相关模型和规格说明进行审查。因此,软件测试并不都是通过动态地运行程序来进行,也包括了静态的检查方法。软件测试的目标和具体方法多种多样。从测试的范围看,有针对软件基本模块的单元测试,有针对一组软件模块进行的集成测试,也有针对整个最终软件的确认测试。从测试的方法分,有把软件作为一个整体,并不去关注软件内部细节的黑盒测试,也有针对软件中具体程序代码结构的白盒测试。此外,除了基本的功能性测试外,还要考虑对软件的许多非功能性需求进行测试,包括性能测试、兼容性测试、用户界面测试、多语言测试、安全性测试、可靠性测试、安装测试等等。传统的软件测试策略和方法主要以结构化程序为目标,在面向对象开发方法被广泛使用后,如何针对面向对象程序结构进行测试也得到进一步的研究,包括对类的测试、类之间交互的测试、类层次结构的测试、以及分布式对象的测试等。
软件测试即简单又困难。简单的原因是,即使不去广泛研究各种各样的软件测试方法,不去深究软件测试的有关理论基础,多数开发人员也能够通过直观的方式采用一些最基本的软件测试方法,并可以找到一些软件中存在的错误,从而体会到软件测试带来的好处。而困难的原因是,如果想要通过测试找到尽可能多的软件错误,或者针对某些特殊的目标(如高可信软件系统)进行软件测试,那么就需要系统地学习软件测试的基本理论和方法;而如果想要满足一定的测试准则(如覆盖率准则),那么可能需要花费大量的资源和时间,即使如此也不一定最终能够满足准则的要求;此外,很多情况下,在测试完成后,如何评价测试的效果和经过测试后的软件质量也是一件困难的事情。
实施有效的软件测试不仅需要掌握测试的相关方法和技术,还要有好的测试工具,并且在工程实践中积累丰富的测试经验也非常重要。本章主要关注工程实践中常用的软件测试方法和策略,并针对当前面向对象技术的广泛采用,描述一些面向对象软件特有的测试方法。另外,还列出一些已被总结出的软件测试经验,并简要描述软件测试过程中涉及到的项目管理活动。
软件测试是软件质量保证的关键步骤,主要对软件需求和设计规格说明、软件代码进行审查,以在软件产品交付之前尽可能发现软件中潜伏的错误。由于不只是针对程序代码,因此软件测试包括了静态审查和动态运行测试。在本章中,除了在介绍需求和设计阶段的测试、以及代码走查外,所提到的软件测试方法均指动态测试,因为这是目前软件开发过程中实施软件测试的主要活动,并且大部分的测试技术和理论针对的是动态测试。
软件测试是一个在软件中寻找错误的过程,当要通过运行程序来判断软件是否存在问题时,需要根据程序的目标输入所需的数据(包括外部激励),这就引入了测试用例的概念。一个测试用例描述了针对某个目标对程序进行测试所采用的一组实际输入、程序执行条件、测试步骤和预期的输出,以核实某个程序或其中的特定路径是否满足特定需求。由于程序输入的范围会非常大,因此会导致一个软件可选的测试用例数目巨大(甚至是无穷的)。这时,需要恰当地设计和选择测试用例集,以在限定的资源和时间内,尽可能地暴露软件中的错误。因此,测试用例集的设计通常被认为是测试中最重要、也是最困难的方面。由于实际测试中使用的测试用例集的输入范围只是程序输入的子集,因此即使软件通过了测试,也无法保证程序一定是正确的。这说明测试本身是不完全的,不能证明程序无错。人们认为,软件测试活动从未间断,只是在软件交付用户使用后,将由用户扮演测试角色而已。
对每个测试用例都需要给出具体描述,表1给出了一个测试用例模版示例。
表1 测试用例模版
当测试用例数量多时,文档化的工作量就比较大。这时,模版内容在实际测试中可以根据需要进行简化,例如把各个测试用例所共有的内容单独列出来(如环境要求),并把所有测试用例用一张表格描述出来。
1.2测试中的信息流程
在进行测试时,需要用到许多配置项作为测试的输入,而测试结果需要进一步进行相应的处理。测试过程中的信息流程如图1所示。
图1测试中的信息流程
图中的软件配置项包括软件需求规格说明、设计规格说明、源代码等,测试配置项包括测试计划、测试工具、测试用例等。这些配置项都应该纳入软件配置管理的范围内。
当测试结果满足需求时,则可以使用其它测试用例继续进行测试,否则就需要对软件进行调试,以找到错误原因并进行修复(具体的调试方法和策略可参加第6章)。也可以在使用一批测试用例测试完成后再统一对发现的错误进行调试。如果需要对软件的可靠性进行评估和预测,则在测试配置项中需要包括软件的操作剖面(使用模型),并以此为依据生成测试用例集进行可靠性测试,然后根据得到的软件失效数据,利用相应的可靠性模型对软件可靠性进行评估和预测。
1.3测试评价和充分性准则
对于测试活动需要定义一些具体的评价指标,一方面可以评价软件测试的效果如何,另一方面也是判断测试是否已经足够的标准,以在适当的时候结束测试。
对于单个测试用例,只需要判断其测试执行的结果是否满足预期的结果,即该测试用例通过或者是未通过。为了反映整个测试活动的效果,以及测试活动是否已经足够充分,一般更需要对使用的所有测试用例的执行结果进行总体评价。常用的一些评价指标和测试充分性准则包括:
l 测试用例中通过和未通过测试的比例分别为多少。这两者之和不一定为100%,因为还可能有一些由于环境条件不能满足造成无法执行的测试用例,这部分也应给出比例。
l 需求(功能)覆盖,即该测试用例集所覆盖的软件需求或功能占需求规格说明中软件需求或功能的比例。
l 模块(过程、函数)覆盖,即运行该测试用例集中的所有用例的过程中,调用过的程序模块(过程、函数)的比例。
l 语句覆盖,即运行该测试用例集中的所有用例的过程中,所执行过的程序语句占所有可执行语句的比例。
l 分支覆盖,即程序中所有的控制结构(例如if语句或while语句)中的逻辑表达式的值在测试中是否既取过true也取过false。在分支覆盖中,逻辑表达式被看作一个整体,不考虑其中的通过逻辑与、或等操作符连接的子表达式。
l 条件覆盖,即程序中所有的控制结构(例如if语句或while语句)中的逻辑表达式中的每个子表达式的值在测试中是否既取过true也取过false。
l 多条件覆盖,即程序中所有的控制结构(例如if语句或while语句)中的逻辑表达式中的每个子表达式取值的组合都被测试用例覆盖到了。
l 条件/分支覆盖,是由条件覆盖和分支覆盖组合而成。
l 路径覆盖,即是否模块中的每条路径都被测试用例覆盖到了。简单说,路径就是从一个模块入口到出口的一个语句序列,而不同路径所包含语句集合不相等。路径具体的定义和使用在本章的白盒测试一节中描述。
l 软件可靠性,即软件在特定时间和特定环境下无故障运行的概率,它由软件可靠性(增长)测试所得到的失效数据经可靠性模型计算得到。
上述一些测试评价和充分性准则具有不同的严格程度,有些指标之间还具有包含关系,例如决策覆盖包含了语句覆盖,路径覆盖又包含了分支覆盖。相对更加严格的准则可能需要更多的测试用例,也需要更多的资源和时间,因此可以根据项目的规模和特点选择合适的评价准则。例如,对于很多航天领域的安全关键软件,要求测试过程满足条件/决策覆盖;规模不大的软件可选择分支覆盖;而对于数十万甚至上百万行代码、并且不是应用于关键领域的软件,可能达到语句覆盖或者功能覆盖就能够满足测试的目标了。
1.3软件测试的重要原则
软件测试不仅与具体的测试技术有关,还与测试人员的思维方式和心理相关。即使是使用相同的测试技术,不同的人员可能会取得截然不同的测试效果。下面这些原则是人们在测试实践活动中总结出来的[4],在进行测试时应该遵循。
(1)在每个测试用例中都必须要定义预期的输出或结果。
(2)应该尽量避免程序的开发人员来测试他自己编写的代码。
(3)应尽量避免程序开发组织测试其自己开发的程序。
(4)应详细检查每次测试的结果。
(5)测试用例中不仅要说明合法有效的输入条件,还应该描述那些不期望的、非法的输入条件。
(6)检查一个程序没有完成希望它作的事情只是测试的一半任务,还应检查程序是否执行了不希望它作的事情。
(7)避免随意舍弃任何测试用例,即使非常简单的测试用例。
(8)不应在事先假设不会发现错误的情况下来制定测试计划。
(9)一个程序模块存在更多错误的可能性与该模块已经发现的错误数量成正比。
(10)测试是一个具有相当创新性和智力挑战的活动。
软件测试的过程中包括了许多要执行的活动,每个活动有其针对性目标、主要技术方法等不同内容,这些活动的次序以及在软件生命周期中实施的时机组成了软件测试的过程。为了搞清楚软件测试中的活动,首先需要从下面几个方面给出描述[2]:
l 谁执行测试?是由软件开发人员来进行测试,还是由一个独立的机构对软件进行测试,或者是两种方式结合起来?
l 将测试哪些内容?是对软件中的每个模块都进行测试,还是只测试风险较高的部分?
l 何时进行测试?测试是一个持续的过程,在每个特定里程碑执行相应的活动,还是在开发结束之后再进行测试?
l 在哪里测试?是在开发环境、模拟环境还是真实运行环境中进行测试?
l 测试达到何种充分程度?如何确定已经进行了足够的测试,或有限的资源如何在测试中进行最佳分配?
测试过程与开发过程是相对应的,并且具有一种反馈关系。图2中传统的V模型描述了软件测试主要活动与软件开发主要活动之间的对应关系,图3描述测试与开发过程之间的反馈。
图2软件开发活动与测试活动的对应关系
V模型主要针对的是软件生命周期瀑布模型,其中单元测试主要是测试软件单元是否满足详细设计中的要求,集成测试主要测试软件单元的接口和集成是否满足概要设计中对软件结构的规格说明,而确认测试则主要测试软件系统是否满足软件需求规格说明中的相关需求定义。尽管瀑布模型已经不能直接适用于现代许多软件的开发过程,但该V模型仍然可以借鉴,例如流行的迭代模型中,开发过程中的每一次迭代是一个小的瀑布模型,其测试活动依然可以和开发活动相对应。
图3测试过程与开发过程之间的反馈
软件测试过程发现错误并反馈给开发过程,根据错误原因对程序进行修复,甚至有可能对软件需求进行变更,或对某些软件设计进行修正。对于开发完成的规格说明、代码等(包括修复后的),作为测试过程的输入。
图4软件测试过程
图2中的V模型只是反映了测试在软件生命周期中的位置,并且仅仅描述了软件测试中单元测试、集成测试、确认测试等几个主要活动。当把静态审查等也作为测试活动考虑,并且与软件测试过程中的管理活动相结合,则需要一个更加细致的测试过程描述。图4给出一个专门针对测试活动的过程,以供参考。在软件项目初期,需要制订软件测试计划,以描述将要进行的软件测试活动、时间、人员、资源、工具环境等方面的内容。软件测试计划的内容将在本章7节“软件测试过程中的项目管理”中讨论。当软件需求规格说明完成后,需要对需求进行测试(审查);而在软件设计规格说明完成后,同样需要对软件设计进行测试(审查)。当发现问题时,及时进行更正,直到通过为止。在软件实现阶段,需要对代码进行单元测试、集成测试,并在软件系统形成后,针对需求进行确认测试,这些都是对软件实现进行测试。针对需求、设计、实现的测试活动将在本章4节“软件测试活动与实施策略”中描述。在对软件实现进行测试发现软件失效后,需要对软件进行调试,以发现问题的原因,并进行修复。对代码修改后,还需要进行回归测试,以确定错误被修复且没有引入新的错误。在所有测试完成后,将对测试过程和结果进行评价,这在本章1.3节“测试评价”中已经讨论。当认为测试已经达到预期的目标时,可以结束测试活动并交付软件产品。
该过程依然没有完全包括测试中一些具体活动,例如评审测试计划、建立测试环境、代码走查等。根据软件特点、所选开发过程模型等方面不同,每个软件项目开发都对软件测试活动有特定的要求,因此在工程实践中应该针对项目来定制软件测试过程。
在测试过程中,每个测试活动只是指出了要做哪些事情以及测试哪些对象,而具体如何进行测试就需要考虑选择合适的测试方法。测试方法有很多,但大部分都可以划归到黑盒测试与白盒测试这两种基本方法之一。黑盒测试是已知产品应该具有的功能或者运行特征,在不考虑程序内部结构的情况下,通过运行测试用例来测试软件运行是否正常。白盒测试则是在了解程序代码的情况下,测试软件的内部运行过程是否按照规格说明中的规定正常执行。对于这些测试方法,如何生成有效的测试用例集是其最主要的目标,即用尽可能少的代价发现尽可能多的错误。软件测试最基本的方法主要针对功能性测试,即判断产品功能是否正确并与预期一致,但由于软件还有许多非功能性需求,因此需要进行针对性的测试,例如性能测试、兼容性测试、可靠性测试、用户界面测试、安装测试等。
在软件测试过程中,根据需要可能会在不同阶段的测试活动中用到不同测试方法,甚至在同一个测试活动中用到多种方法;而同一种测试方法也有可能在多个测试活动中使用。因此,不同的测试方法并不能相互替代,例如黑盒测试和白盒测试,而是应该根据实际需要互为补充。本节将结合实例,对常用的一些黑盒测试方法、白盒测试方法以及非功能性测试方法进行介绍。
黑盒测试主要是用来测试软件系统是否满足功能要求,例如,它可以用来测试软件以下几类错误:
l 不正确或遗漏的功能;
l 界面错误;
l 数据结构或外部数据库访问错误;
l 性能错误;
l 初始化和终止条件错误;等等。
在进行黑盒测试时,不需要清楚软件代码的结构,测试过程中主要的信息包括软件的输入数据、预期运行结果、实际运行结果、用户外部可见的系统状态(例如等待输入、显示对话框)等,测试人员需要根据这些信息来判断测试是否通过。在软件开发中,黑盒测试方法有两个明显的优点[3]:
(1)黑盒测试与软件如何实现无关,因此如果软件实现发生变化,测试用例仍然可以使用;
(2)测试用例的开发可以与软件实现并行进行,因此能够缩短整个软件开发周期。
下面讨论几种常用的黑盒测试方法。
等价分类法的主要思想是把程序的输入数据集合按输入条件划分为若干个等价类,以减少测试用例的数目。一个等价类是具有相同测试目标或暴露相同软件缺陷的测试数据,如果只为每一个等价类设计一个(或几个)测试用例,就可大大减小测试的次数又不丢失发现错误的机会。例如,当程序需要处理一个输入的字符串时,对于输入“abcdefg”和“gfedcba”,处理的过程很可能没有什么区别,并不能发现更多的问题。但如果数据非字母的特殊符号,例如“s3&>.@”,从直观上可虑,就可能与输入纯字母符号相比发现额外的程序错误。这就可以把字母符号和非字母符号分为不同的等价类。当然,等价类的划分可以较粗,也可以再细化,这需要根据软件本身的目标、算法处理过程、测试所能承受的开销与时间等因素来进行调整。
据此可以看到,等价分类法的关键是根据输入数据的类型和程序的功能说明划分等价类。在寻找等价类时,一般考虑把具有相似输入、输出和操作的测试数据分在一个等价类中。下面是划分等价类时常用的一些规则:
(1)如果输入值是一个范围,则可划分出一个有效的等价类(输入值落在此范围内)和两个无效的等价类(大于最大值的输入和小于最小值的输入);
(2)如果输入是一个特定值,则可类似地划分出一个有效等价类和两个无效等价类;
(3)如果输入值是一个集合,则可划分出一个有效等价类(此集合)和一个无效等价类(此集合的补集);
(4)如果输入是一个布尔量,则可划分出一个有效等价类(此布尔量)和一个无效等价类(此布尔量之非)。
当有多个输入变量时,就有可能需要对等价类进行组合。例如,一个软件的输入包括一个整型变量和一个字符串变量。如果整型变量的等价类为正整数、零、负整数,字符串变量的等价类为全字母字符串和非全字母字符串两类,那么通过组合,整个输入的等价类可能就有6个,包括(正整数,全字母)、(零,非全字母)等。需要注意的是,如果为了减少测试用例的数量而过分划分等价类(即等价类数目过少),那么就有可能漏掉潜在的软件缺陷,因此需要根据经验和软件特征适量而行。
根据软件测试的经验表明,在输入数据的范围边界值上非常容易发生错误,因为边界条件本身就是特殊情况,在编程上很可能需要特殊处理,也很容易忽略或出现编程错误。例如,一个整型变量的输入范围是正整数,那么输入分别为1和0时程序会怎么做;一辆汽车的定速巡航系统规定车速到达40公里时才可以使用该功能,那么当速度分别为40公里和39公里时,汽车控制软件会如何处理。边界值分析法的目标就是通过选择特定的测试用例,强迫程序在输入的边界值上执行。边界值分析法可以看作是对等价分类技术的补充,即在一个等价类中不是任选一个元素作为此等价类的代表进行测试,而是选择此等价类边界上的值。此外,采用边界值分析法设计测试用例时,不仅要考虑输入条件,还要考虑测试用例能够覆盖输出状态的边界。
采用边界值分析法设计测试用例与等价分类法有许多相似之处:
(1)如果输入条件指定了由值a和b括起来的一个范围,那么值a、值b和紧挨a、b左右的值应分别作为测试用例;
(2)如果输入条件指定为一组数,那么这组数中最大者、最小者和次大、次小者应作为测试用例;
(3)把上面两条规则应用于输出条件。例如,某程序输出为一张温度压力对照表,此时应设计测试用例正好产生表项所允许的最大和最小值。
(4)如果内部数据结构是有界的(例如,某数组有100个元素),那么应设计测试数据,使之能检查该数据结构的边界。
对于很多软件来说,并不是由用户给了输入之后,就只需要等待输出即可,而是不断和用户发生交互。这时,软件在使用过程中,是否能正确地从一个状态变化到另一个状态,或在某个状态下正确接收和处理用户的输入,也同样需要进行测试。而由于软件内部实现同样是不可见的,因此也属于黑盒测试的范围。例如,对于一个字处理软件,当没有文档打开时,许多菜单项是置灰为不可用的,当打开一个文档后才变为可用;如果打开一个文档后未作修改并关闭,那么不会有任何提示,但如果文档被修改了,那么就会提示是否保存文档;等等。这些都说明软件的正常行为应该与当前的状态有关,需要测试其状态变化不会出错。
为了实施基于状态的测试,应该首先建立描述软件使用的状态转换图。其表示类似于UML状态图,但具体含义不同。软件使用的状态转换图中的状态表示软件当时所处的条件或模式,状态之间的迁移表示软件在前一状态下,当出现某些外部激励(如用户操作、输入、收到某些信号等),则变化到下一个状态中。在进行测试时,通过定义测试用例来判断软件运行过程是否能够按照状态转换图中的路径来进行。但由于软件状态转换图的状态和迁移数目可能会很多,导致路径集合庞大,因此测试时也需要考虑如何减少测试用例数目。例如,可以不测试所有路径,只是保证每个状态能够被到达一次;测试最常用的路径和分支;对最容易出错的地方以及有错误处理的分支进行测试;等等。
还有一些黑盒测试的方法可以作为上述几种方法的补充[6],尽管不一定有系统的理论基础,但在实践中表明非常有效,通常能够发现许多遗漏的错误。这些方法非常直接明了,因此下面仅对这些方法进行简要介绍。
(1)像新手那样进行测试。经常会出现的一种情况是,一个经常使用某个软件的人总是能够正常运行该软件,但如果一个对计算机或对该软件不熟悉的人来进行操作,很快就会出现软件出错、甚至崩溃的情况。这就是因为新手不了解软件的操作,不去遵循任何已有规则,因此会出现一些开发人员和熟练者不会进行的操作,例如毫无逻辑顺序的点击一通菜单和按钮。当测试时,抛开对软件已有的了解,像一个新手那样去使用软件,就很有可能发现一些从未想过的错误。
(2)在已经发现过软件缺陷的地方再进一步进行测试。就像在1.3节中软件测试的原则中所说的,“一个程序模块存在更多错误的可能性与该模块已经发现的错误数量成正比”。软件中某个地方已发现的错误越多,表明这里可能存在的错误越多,那么就应该着重进行测试。在时间和资源允许的情况下再去试试,经常会发现一些意外的结果。
(3)像黑客那样去思考。黑客经常能够发现软件中存在的漏洞,并获取自己所想要的信息或利用漏洞进行破坏。因此,测试人员可以按照黑客的思维方式和手段去尝试寻找软件中存在的漏洞,想想黑客通常希望获取软件中哪些有价值的信息,或者希望对系统进行哪些非授权的操作,并熟悉黑客的攻击手段去尝试,会发现一些软件中的缺陷。
(4)凭借个人的经验、直觉和预感去进行测试。对于软件测试来说,丰富的经验非常重要,它经常能够使测试者直接发现缺陷存在的位置。而丰富的经验也会使测试者具有一种敏锐的直觉和预感,这时不要放过它们,而应该去深入追究,直到发现问题所在。
黑盒测试方法直观、易行,但由于不了解程序内部结构,因此常常带来以下问题:
(1)测试用例之间可能存在严重的冗余;
(2)可能会有未测试的软件漏洞;
(3)难以针对被测的程序来评价和度量测试的效果。
白盒测试方法的典型特征是它们都基于被测程序的源代码,因此支持较为严格的数学分析和精确度量,但也导致测试开销的迅速增加,因此需要根据软件本身的相关特性(如是否安全关键软件、复杂性如何等)来确定是否使用,或者是只针对其中某些关键模块选用适用的白盒测试方法进行测试。下面主要针对白盒测试中的基本路径测试、分支与条件测试、循环测试等举例进行说明,这些方法的详细原理请参见[9]。除此之外,还有其它一些更复杂的白盒测试方法,如数据流测试等,可参见相关参考文献。
基本路径测试是根据程序的控制流确定基本路径集合,然后据此设计出一组测试用例,保证集合中的每条基本路径都在测试中被执行。当每条基本路径都被执行过时,就能保证程序中的每条语句也都被执行过。其过程主要包括:
(1) 建立被测程序的流图;
(2) 根据Cyclomatic复杂性度量方法或McCabe复杂性度量公式计算得到程序中基本路径的数目;
(3) 确定程序的基本路径集合;
(4) 根据基本路径集合设计测试用例集,确保每条基本路径都至少执行一遍;
(5) 执行测试并记录结果。
程序中的分支和循环结构是最容易出现逻辑错误的地方,这样将会使程序沿着在当前条件下本不应该运行的路径执行。分支测试把判断中的条件作为一个整体考虑,在测试时需要分别考虑该条件为真或为假的情况。而当条件是一个复合条件,即由多个条件判断通过逻辑运算(与、或、非等)组合形成,为了更加仔细进行测试,可以对每个组成条件分别取真、假,这样就形成了条件测试。
程序执行过程中可能会反复多次执行循环结构中的语句,如果多执行一次或少执行一次都会造成运行结果错误。循环测试是为了检查循环结构的正确性。当程序是可终止时,循环结构总有一个最大执行次数n,但由于开销限制,一般不可能对循环恰好执行0次、1次、……、n次都分别测试一次。这里考虑简单循环(即不考虑循环嵌套、非结构化循环等情况),当循环次数至多为n时,测试用例设计需要考虑以下方面:
(1) 完全跳过循环;
(2) 仅循环一次;
(3) 循环两次;
(4) 循环m次,m (5) 循环n-1次、n次、n+1次。 这样就可以把循环次数处于范围边界附近和处于范围内部的情况都测试到。 由于白盒测试方法与程序的代码控制结构直接相关,当程序复杂时,其测试开销非常巨大,通常只能在一定的测试充分性准则下进行(见1.3节)。例如,一般应用领域的程序可能达到语句覆盖即可,关键领域的软件测试需要满足多条件覆盖等更加严格的充分性准则。 软件除了要求满足功能需求外,还有许多非功能性的需求,例如性能、兼容性、安全性、可靠性、易用性等也非常重要。因此,有必要在测试过程中对这些需求进行针对性测试。非功能性测试在测试用例集的选择上需要根据测试目标确定,黑盒方法和白盒方法都可以使用,但更多的是采用黑盒测试方法。下面对常用的一些非功能性测试方法进行简要介绍。 性能测试的目标是检查软件系统是否满足在需求说明书中规定的性能或效率,特别是对于嵌入式实时软件、网络通信软件等。通常,对软件性能的检测表现为在特定负载和配置环境下软件能够达到的响应时间、吞吐率、并发操作、存储规模等,这与具体软件用途相关。例如,下面是几个对软件系统性能的要求: l 家庭保安系统当监测到异常情况后,必须在1秒内拨出警报电话; l 网上机票预订系统应能够支持同时有500个用户进行网上订票; l 对于某数据库应用系统,要求支持存储100万个客户的详细信息; l 开发某个网上金融交易系统,要支持每分钟完成200项交易;等等。 可以看到,不同的软件所关注的性能指标也不相同,这些内容应该在软件需求规格说明中进行具体描述。性能测试可以发生在各个测试阶段中,即使是在单元层,一个单独模块的性能也可以使用白盒测试来进行评估,例如一个算法的时空效率等。然而,只有当整个系统的所有成分都集成到一起之后,才能检查一个系统的真正性能。由于性能常常受到硬件的影响,因此软件性能测试经常要求软件与硬件一起进行测试。 对于性能测试,需要根据软件应达到的性能指标来设计测试用例集。性能测试很多情况下难以完全人工实施,例如,在分布式环境下同时向服务器发出500个服务请求,或判断嵌入式系统的响应时间是否在20毫秒之内。因此,需要针对测试开发相应的模拟程序提供激励,或在系统中实现某些监测机制来判断系统的响应。这些方法可以提高测试的自动化程度,同时提高测试的效率和准确性,并降低测试成本。在性能测试中需要的工具或环境支持,也需要在测试用例中进行描述。例如,针对网上机票预订系统,定义下面的测试用例来测试系统能否同时支持500个用户进行网上订票,以及对每个用户请求的平均响应时间是否能够达到要求。 对同一个性能指标进行测试也需要多个测试用例,因为一次测试通过并不意味着软件总能够达到要求。可以改变测试环境、条件、输入、步骤等内容来定义多个测试用例并实施测试。 强度测试有时会被当作性能测试的一部分,它们测试的方式基本类似。但由于强度测试有其独特的目标,因此这里把它单独列出来。性能测试主要还是为了测试软件系统在正常使用时是否能够达到一定的性能指标,而强度测试则是对系统不断施加更大的压力和使用强度,通过确定一个系统的瓶颈或者不能接收的性能点,来获得系统能提供的最大服务能力。例如,测试机票预订系统能否在同时有500个用户使用的情况下正常运行属于性能测试,但如果把测试场景定为上千用户、甚至上万用户同时使用,那么就时一种强度测试了,以判断系统能够承受最大的强度是多少。 强度测试适用于在可变负载系统中运行的软件[4],因为尽管可以预测系统大多数使用下负载的范围,但并不能避免超过预期负载上限的情况发生。例如,一个空中交通管制系统的需要规格说明中要求可以跟踪处理一个区域范围内最多100架飞机的情况,可以通过模拟来进行性能测试。但由于客观上无法避免第101架飞机飞入该区域,甚至可能有110架飞机在区域内,因此需要进行强度测试,以确定系统在这些情况下会如何运行。基于Web的应用程序也是最常接受强度测试的软件之一(例如网上机票预订系统),因为无法保证网络中该系统的用户一定不会超过某个限度。 对于强度测试,可以与性能测试一起进行,但需要对测试用例中定义的测试环境、条件、输入、步骤等内容进行调整,以适应强度测试。例如,对例7中的测试用例,把模拟软件发出的服务请求数目增加到1000个。在强度测试中,系统如果出现崩溃,并不意味着测试没有通过,因为这已经超过需求的范围。但应该尽可能的对系统进行完善,以能够对超强度的情况进行处理,避免系统崩溃或能够尽快恢复运行。 开发完成的软件最终要运行在实际的硬件平台和软件环境中,并可能与其它应用软件之间存在交互,这就需要测试该软件能否正常的在各种硬件和软件环境中正常运行。例如,软件是否能够在各种品牌、不同配置的个人计算机中运行,Windows应用程序能否在Windows 98、2000、NT、XP等不同版本操作系统中正常使用,等等。一般把检查软件在其设计运行和连接的硬件上能否正常工作的测试活动称为配置测试,把检查软件与其它系统软件和应用软件之间能否正常地交互和共享信息称为兼容性测试。 软件所需要的硬件和外设的种类、品牌繁多,例如一个游戏软件除了基本的计算机组成部件外,还可能需要声卡、显卡、网卡、游戏杆的支持。尽管硬件产品需要符合一定的标准,但并不是被所有厂家严格遵循,厂家还可以为了提高竞争力增加设备的功能和性能。因此,软件是否能够在各种硬件环境中正常工作,需要进行一定的测试。由于硬件品牌、型号很多,因此难以完全进行测试,这时一种方法就是等价分类法,需要把可能的配置减少到可控制的范围内容,确定基于哪些硬件进行测试。进行配置测试所采用的一般过程如下: (1)确定所需的硬件类型。例如,软件是否需要打印机、声卡,是否需要调制解调器或网卡通过网络进行联机注册,等等。 (2)确定可用的硬件产品、型号和驱动程序。应该选择那些性能满足软件需要的产品,对于几乎相同的产品可以划分到同一个等价类中。 (3)确定可能的硬件特性、模式和相关选项。例如,游戏软件可以通过显卡设置不同的分辨率和颜色深度,打印机可以是彩色打印或黑白打印。 (4)对可选的硬件配置进行缩减,直至测试可以接受的范围。 (5)明确与硬件配置有关的软件特性。例如,在应用程序中打开或关闭文件的功能就于打印功能无关,而图片的颜色、字体大小则与声卡输出无关,等等。 (6)设计在每种硬件配置中执行的测试用例。为了减少测试用例数目,应该对每种硬件配置主要测试相关的软件特性。 (7)在实际配置环境中执行测试。在发现问题后,需要确定是软件缺陷,还是硬件缺陷。如果是硬件缺陷,可以向厂家报告问题。 (8)反复测试,一直到达到测试目标为止。 应用软件一般都要运行在操作系统上,可能通过数据库访问接口访问多种数据库,或与其它应用软件共享数据与信息(例如,剪切、拷贝的文字和图片可以粘贴到其它软件中)。因此,能否与其它软件兼容对正常使用非常重要。对新开发的软件进行兼容性测试时,需要考虑下面三个方面: (1)软件要求与其它他哪些平台软件(操作系统、数据库系统、Web浏览器等)和应用软件保持兼容。 (2)软件需要使用哪些数据和操作与其它软件交互,例如通过文件或数据库,通过剪切、复制和粘贴操作进行数据交换。 (3)软件之间需要遵循哪些交互标准和规范,例如文件格式、网络通信协议等。 在兼容性测试中,应该根据这些数据和操作来设计测试用例。在测试用例中需要对所用的软件环境、厂商、版本、要测试的问题、输入、步骤等方面进行描述。 软件的安全性越来越受到广泛重视,因为各种不安全因素会造成极大的损失,包括黑客攻击、病毒、后门程序等等。即使是已经经过充分测试并交付使用的软件产品也可能存在很多漏洞,如Windows或IE等软件经常发现安全漏洞,并由开发商提供补丁程序来修复这些漏洞。安全性测试就是设计测试用例来暴露软件安全漏洞的过程,例如设法破坏数据库管理系统的数据安全机制、突破重要领域软件系统的用户访问控制等。 实施安全性测试的人员首先应该熟悉可能发生的各种安全威胁模式,并要了解需要安全保护的软件系统安全特性,例如保密的数据、需要授权的操作等,以进行针对性的测试。安全性测试的主要过程和步骤为: (1)明确潜在的安全隐患,包括容易遭受攻击的内容、可能作为潜在入侵者的人员角色等。 (2)明确潜在的入侵行为及时机,例如事务初始化、系统输入、执行存储和检索操作等时候。 (3)列出每种潜在安全隐患可能遭到的入侵行为及其可能性。 (4)确定风险较高的入侵点,这可通过对包括发生可能性、后果严重程度等因素进行分析得到。 (5)根据风险的顺序,设计测试用例来实施安全性测试。 达到彻底的安全性测试是不切实际的,对于多数软件,只能尽量的降低被入侵成功的可能性,减少被攻击带来的损失。 软件可靠性指软件在特定环境下和给定时间内无故障运行的概率[5],它被认为是软件质量的关键因素。它的基本思想是:定量地刻画产品所期望的用途,根据该信息,将资源集中到最常用/关键的系统功能上并使测试能真实地代表实际运行;定量地评估和预测软件可靠性,使用户在软件开发中准确地跟踪软件可靠性,能够平衡可靠性、开发时间和开销的需求。图5给出了软件可靠性测试的基本过程。 图5软件可靠性测试的基本过程 软件可靠性测试强调对软件系统实际运行环境的刻画,使得软件测试能真实地反映软件系统的可靠性,而对软件实际使用情况的刻画是通过操作剖面定义的。软件系统的操作剖面描述了该软件在实际运行时各操作的发生概率。建立操作剖面是软件可靠性工程的一个重要部分,它可以通过两种方法获得:参考老版本或现有类似系统的实际操作;或者从可行性及需求分析阶段开发的功能定义出发,估计建立操作剖面。经常是两种方法混合使用。一般由测试计划人员建立软件的操作剖面,同时需要系统工程师和设计人员广泛合作,最好在可行性与需求分析阶段也有测试计划人员参加。 在操作剖面的基础上,可以以手动或自动的方式产生测试用例。生成的测试用例一方面要使得其中相关操作的发生比例符合操作剖面中概率的要求,另一方面又要使测试用例数目能够在可靠性测试所需的相关资源和时限的范围内。 在可靠性测试过程中如果发生软件失效,需要记录下来形成失效数据。通常采集的失效数据有两类:失效计数(Failure Count)数据和失效时间间隔(Time Between Failures)数据。失效计数数据指单位时间内检测到的失效次数;失效间隔时间数据指连续两次失效之间的时间间隔。失效数据的基准最好采用执行时间。如果执行时间难以获得,可以用日历时间作为基本近似值。 在得到失效数据之后,选择可靠性模型具体计算出软件的可靠性结果。目前没有一个模型(组合模型)表现出普遍适用于用户的需要,也没有一种评价这些模型假设的通用方法能够预先确定哪个模型对于一个具体的开发工作最佳。这时要仔细考察模型对开发方法及开发环境所做的假设,以确定它是否适用于用户的项目。当可靠性结果满足预期指标,则可进行后续的活动,否则,需要对软件进行完善,继续进行可靠性测试,直至满足要求。 绝大多数软件都要与人发生交互,由用户发出激励、命令、提供输入,并给用户返回某些结果和信息。使用者与软件系统中间进行交互的方式称为用户界面(UI)。用户界面有很多种不同类型和交互方式,例如早期通过命令行方式进行输入和输出,现在更多的是图形化用户界面,可提供菜单、按钮、列表等方式供用户操作软件。为了使用户更好得使用软件,用户界面应该要易于使用且具有较强的实用性,例如: l 是否把常用的按钮或菜单项放在醒目的位置; l 软件的输出是否清晰、有序; l 错误信息是否直观和易于理解; l 软件错误顺序是否符合用户的业务过程; l 界面的风格是否一致;等等。 由此可以看到,软件即使功能正确、性能符合需要,也不一定就能够满足用户的实际使用。因此,用户界面测试也常被称为易用性测试。 设计优秀的用户界面需要对人体工程、心理因素、软件将应用的业务领域等具有深入的理解,需要测试人员从用户的角度去寻找用户界面中存在的问题。一般情况下,实施用户界面测试将从下面几个方面进行考虑[6]: (1)符合标准和规范。在现有的一些常用平台下运行的软件,应该符合该平台的用户界面标准与规范,这些规范一般都是已经被公认并普遍使用的,使得应用软件的使用更容易上手和被接受,例如开发Windows操作系统或Mac平台中的软件,其窗口、菜单、对话框等界面元素和布局就应该尽量安装相应的规范进行设计。 (2)直观性。用户界面应该使用户能够在很短时间内就明白怎么操作,并且能使用户在需要时尽快找到自己所需的东西。直观的界面应该整洁、不拥挤,具有良好的组织和布局,能随时决定放弃操作或回退,没有过多的冗余元素干扰用户的选择,使用步骤简洁且能满足要求,等等。 (3)一致性。用户一般希望一个软件内部以及不同软件之间的相似操作和使用能够一致,这样就可以减少学习的时间以及发生操作失误的情况。例如,打开或保存文件、剪切、复制等操作的组合键和热键应该相同,某些含义相同的术语应该一样,不同对话框的确认、取消按钮位置相同,等等。 (4)灵活性。用户有时喜欢更多的选择,能够在不同的情况下用不同的方式完成某个功能,或者能够轻松从一个操作转到另外一个操作,或以文字、图形等不同方式查看数据,应该在软件复杂性许可的情况下尽量使得操作更加灵活。 (5)舒适性。软件的使用不应该为用户造成障碍和困难,但由于舒适性与人的感觉相关而且难以量化,因此难以准确定义。例如,软件的界面和使用感觉应让用户感觉与自己的角色和工作内容相符,在关键时候应该出现一些提示或警告,性能要达到要求而避免用户由于缓慢而急躁,等等。 (6)正确性。需要测试界面所提供的内容应该与具体的操作和处理结果一致,不会出现界面相关的错误。例如,界面显示的文字正确地反映了实际的操作,保存文件时显示的路径名与存储介质一致,所见即所得的屏幕显示与打印出的结果应该完全一样,等等。 (7)实用性。界面中的内容对于用户使用软件确实都是有用的,能完全反映软件需求,不应为了追求功能数量或满足某些开发人员个人意愿,加入不必要的界面元素。 当今很多软件都是面向世界各个国家发布的,包括操作系统、数据库、文字处理、图像处理等各种类型的软件。这就需要软件能够支持多个国家的语言,有的软件甚至能支持100多种不同的语言和方言。除了语言外,还要考虑到软件必须遵循各个地域的文化、习俗等特征。使软件适应特定地域的语言、方言、习俗、文化等方面的过程被称为本地化(localization)。对软件本地化结果进行测试的过程称为本地化测试。 由于本地化不仅仅是语言翻译过程,因此进行本地化测试时,不只是检查语言翻译的质量如何,还要考虑对包括以下内容在内的很多方面进行测试。 (1)文本扩展。在描述同样内容时,不同的语言字符的长度可能相差很大,这时如果按钮或文本框长度不够,就会无法完整显示,或导致界面布局混乱。因此,一般在设计时都会考虑对这些元素留有余地。还有更严重的情况,由于不同语言字符串长度不同,如果分配的内存空间不够,可能会导致软件崩溃。因此,需要对本地化后文本的扩展结果是否正常进行测试。 (2)字符集。如果用ASCII码表示英语字符,由于只有256种不同字符,就无法表示汉语等很多英语之外的语言,以及很多特殊字符(例如J)。这时,如何表示不同语言的字符成为一个问题,需要在本地化测试中考虑。在如今,当使用Unicode字符集后,由于对所有语言的字符统一编码,使得这种问题较容易解决。 (3)字符计算。当对字符(串)进行某些处理时,不同语言会导致计算过程不同。例如,当对人名进行排序时,英语可以根据字母序进行,而汉语名称根据习惯就可能要按照笔画次序或拼音次序进行,这会导致算法不同。此外,有的语言是从右向左读,有的语言是从左向右读,如何进行内部处理也会不同。这些都需要在本地化测试中考虑。 (4)内容。同样内容的文字、图片、音乐、标识在一个国家不会被注意,但在其它国家可能会引起争议或反感,例如有边界争议的地图、具有象征意义的图形等。在对这些内容进行测试时,需要对本地文化相当熟悉的人加入近来。 (5)数据格式。不同的地区在一些度量单位及格式上使用不同的标准,包括长度、重量、货币、时间等。对这些内容进行本地化时,不能仅仅翻译即可,例如英寸不能直接用厘米替换,而需要对内部的处理进行本地化。这需要对本地这些方面熟悉的人参与测试,并且需要利用原版的软件创建的测试用例来实施本地化测试。 (6)配置和兼容性测试。针对本地化需要进行单独的配置测试和兼容性测试,因为当软件在不同地域使用时,其硬件环境、运行平台都有可能发生变化。例如,键盘布局和输入方法在不同语言中就会有区别,不同语言的软件可能会运行在各自语言的操作系统中,还有不同语言软件之间的互操作和信息共享能否正常处理,等等。 基于Web的软件与传统软件有明显的不同之处,它的使用主要是通过浏览器进行,通过超链接把各个网页联系在一起,满足用户对信息的获取。本书中的网上机票预订系统就是一个典型的基于Web的软件系统。网页中包含了许多需要测试的特性,例如:不同字体、大小、颜色的文字;各种格式的图片与动画;通过点击文字或图片打开的超链接;下拉列表框;用户输入区域;动态变化的文字、图片等内容;用户可定制的网页内容;需要注册和登录的访问控制机制;用户需要能够在各种硬件平台上、使用各种浏览器软件访问网页;等等。因此可以看到,对网页的测试是一项工作量繁重的任务,需要测试大量的目标。 (1)测试文本。与其它类型软件不同,网页上存在大量的文本信息,这时可以把文本当作文档来进行测试,对其中的术语、题目、内容素材、电子邮件地址、电话号码等信息的准确度、时效性方面进行检查,文字的拼写是否有误也要经常进行检查。此外,由于窗口缩放会引起文字段落的改变,因此应测试是否会导致格式混乱。 (2)测试超级链接。要确保每一个超级链接都能够跳转到正确的目标网页,超级链接的标识应该明显,一般其文字会有下划线,颜色也会有区别。鼠标指针经过超级链接时应该会变成手的形状。确定“返回首页”等链接存在以方便用户,要确保网站中不存在无法通过超级链接到达的网页。 (3)测试图片和视频。确定图片和视频是否能够正确载入和显示,对于视频要测试如果客户机没有相应的播放器时会导致何种结果。图片和视频的大小应该合适,否则会导致载入过于缓慢。也要测试当窗口缩放时,图片、视频以及围绕的文字是否会格式混乱。 (4)测试表单。要检查网页中用于输入和选择信息的表单内容完整、格式清晰,测试表单能否正确接受和处理正常的输入、拒绝错误的输入并给出提示信息。 上述内容都可以通过黑盒方式进行测试,如果网页中包含了JavaSpript、ASP、VBScript等处理动态内容的语言,那么还可以使用前面所给的白盒测试方法对代码进行测试。 此外,由于Web软件的用户广泛,客户机环境多样,因此前面讲过的性能测试、强度测试、配置和兼容性测试、安全性测试、用户界面测试、本地化测试、易用性测试、恢复测试等都非常重要。 软件在运行过程中,其可执行文件需要许多库文件、配置文件、数据库等相关资源的支持,有些大型软件所需的文件数目成百上千,为了便于组织,还需分目录存放,因此难以仅仅把可执行文件拷贝到存储介质中即可。这使得需要制作安装程序,只要求用户提供和选择安装路径、将要安装的模块等简单信息,就可以自动完成安装过程。稍复杂的安装程序需要在安装过程中执行写注册表、安装控件、定义数据源等操作,因此还要编写相关的脚本代码。即使开发的应用软件本身符合用户需求,但如果在安装过程中出现问题,依然不能正常执行。这就要求专门针对安装过程进行测试。安装测试需要考虑的因素包括: (1)注册过程的正确性。许多软件为了防止盗版,为每套安装程序提供一个序列号,在安装过程中需要输入,只有输入正确才能继续。需要对该注册过程进行测试,以确保当输入正确序列号时一定能够使得安装继续顺利进行,而当输入序列号不正确时,将要求重试或终止安装过程。 (2)正确反映用户定制。安装过程可以让用户定制许多信息,例如将要安装的路径、用户期望安装的部分模块等,需要检查当用户输入或选择这些定制信息后,安装结果确实能够正确反映用户当初的定制选项。 (3)软件被正确安装。制作安装程序是为了方便用户,但最终结果还是要保证软件安装完成后能够正常使用。因此需要通过检查安装后的目录和注册表、实际运行软件等方式,来确定软件被正确的进行了安装,其运行不会出现因安装不当导致问题。 (4)安装过程无副作用。由于软件安装的过程可能会生成目录、拷贝文件、在操作系统中注册相关信息等,因此有可能会覆盖现有的文件、目录或注册表信息,要保证不会影响其它软件的正常运行,更不能对系统造成不良后果。 (5)易用安装。应对安装界面和安装步骤进行测试,以确定界面简洁明了、信息完整,安装步骤易于用户选择和操作。此外,还应对卸载过程进行测试,以确保软件能够被用户轻松地卸载,文件、目录、快捷方式能被清除干净,占用的系统资源全部释放,并且不会影响其它软件的使用。 (6)升级测试。现在许多软件为正版用户不断提供升级服务,例如增加新的软件特征、弥补软件之前存在的漏洞、升级病毒库等。有些升级需要下载补丁文件后执行安装,更多的是提供在线自动升级功能。需要测试升级过程能够正确更新软件,不会对软件的使用或系统的运行造成不良后果。 即使进行了最严格的测试,也无法保证软件在实际运行过程中不会由于软件缺陷、受到外来攻击、硬件故障等原因而意外崩溃。对于有些应用软件,例如图片浏览软件,重新启动即可;但有很多关键的软件系统,一方面要求能够及时恢复服务能力,另一方面还要能够避免在数据、信息等方面造成损失。例如,网上机票预订系统在用户已经预订航班并正在通过信用卡进行支付时发生故障,那么恢复后该用户是否已订票成功、支付过程是否会给用户或公司造成经济损失都是在测试过程中需要考虑的。可恢复性测试采用人工的干扰使软件系统出错,以检测系统的恢复能力。其目标就是要确保当意外过去后,系统可以恢复到正常的状态,并继续后面用户提供服务。 可恢复性测试需要根据软件特点来人为制造故障。 可以制订一些指标来通过可恢复性测试确定系统是否满足恢复性要求。例如在系统设计中规定软件系统的平均恢复时间(MTTR),那么在可恢复性测试中要判断软件在恢复时是否不会超过该时间上限。对于带有事务处理的软件,例如数据库应用、金融软件,要在测试中考虑事务的回滚等操作是否被正确的执行,能否保持数据的一致性。 上一节讲到白盒测试、黑盒测试以及非功能性测试的相关技术,但只是了解这些具体技术并不能有效地对软件进行测试,开发者需要明白在软件开发的何时、由哪些人员、针对哪些目标来使用这些测试技术。这就需要把软件测试技术与软件开发与测试过程有机的结合起来,测试人员必须知道在过程中的每个阶段应该实施什么样的测试活动,使用哪些具体的测试技术。本节简要描述如何实施图4给出的测试过程所包含的主要测试活动,包括对需求和设计规格说明进行测试、代码走查、单元测试、集成测试、确认测试,以及在软件工业界常用的a和b测试。 在软件需求和设计阶段,由于还没有形成最终的软件代码,因此无法通过运行程序来进行测试。因此,这时测试的主要对象是软件需求规格说明书和软件设计规格说明书,也包括各种软件分析和设计模型。对需求和设计规格说明进行测试的常用方式是通过需求评审和设计评审进行,以确定规格说明书的正确性、无歧义性、一致性、完整性等,查找规格说明书中存在的错误和问题。由于错误越晚发现,造成的损失和改正所需的代价越大,因此需求和设计评审非常重要,人们都不希望不其中存在的错误引入的软件实现中。关于需求评审和设计评审的具体内容在软件需求分析和设计相关章节描述,这里就不再深入讨论了。 除了静态的评审外,对软件需求和设计进行测试还有一种方式就是通过可执行模型进行测试。最简单的是软件开发早期可能会生成具有用户界面的软件原型,这时可以通过对原型进行检查来判断软件的功能、使用方式等是否符合用户的要求。更进一步的方式是给软件设计模型的动态视图定义可执行的语义(例如可执行UML),然后在相应的设计建模环境中允许设计模型,以判断软件设计中是否存在错误。这类似于通过运行程序来测试代码,只不过这里运行的是模型而已。这是一种有效地在早期发现软件设计错误的方法,更多地是在重要安全关键软件地开发过程中使用。这里就不对其进行详细描述,感兴趣的读者可参见相关资料[10]。 尽管多数人可能会认为与人工对代码检查相比,通过计算机来实际运行程序是实施测试并找到更多错误更有效、更节省的方式,但在实际测试过程中,人们却早已发现人工对程序进行阅读和检查对发现软件中存在的错误非常有效。甚至在多数情况下,通过软件测试活动最终找到的错误中,一半以上都是通过人工代码检查发现的。 代码检查是指人们组成一个小组来对程序代码进行阅读和审核,以检查代码中是否存在与软件规格说明不一致的地方,以及是否存在编程错误或不符合编程规范的方面。一般情况下,以三到四人一组较为合适,其中一人可以是程序的编码人员,还应该有一人作为小组的协调组织人员。代码检查通过会议的形式进行,在会议召开前几天,就应该由协调人员把代码清单和设计规格说明分发给小组其他成员,所有成员应该在会议前熟悉这些材料。代码检查的会议时间一般以两小时左右为宜,不应该过长,否则会造成脑力疲劳而降低效率。在检查会议上,由编程人员逐条语句讲解程序的目的和逻辑结构,其他成员提问和判断代码是否有问题。会议中应注重于发现代码中的错误,而不是如何去修改错误。在会议结束后,代码检查小组应该向开发人员提供一个错误清单,以便对程序进行修改。 在代码检查时,向与会人员提供一个常见编码错误列表会提高人们查找问题的效率。由于每个软件中逻辑错误与其功能相关,因此列表中更多的是一些与编程风格相关的通用问题,大致可以包括下面几个部分[4]: l 数据声明错误,例如变量的类型是否合适、变量未进行初始化等; l 数据引用错误,例如数组下标是否可能越界、是否可能引用空指针等; l 运算错误,例如变量赋值类型不一致、除数是否可能为0等; l 逻辑比较错误,例如对不同类型变量进行比较、布尔运算符是否正确等; l 控制流程错误,例如是否存在goto语句、循环是否一定终止等; l 接口错误,例如形参和实参是否一致、类型是否匹配等; l 输入输出错误,例如文件的打开和关闭顺序是否一致、文件的读取是否能正常结束等; l 其他错误。 除了代码检查外,还有一种方式是代码走查。代码走查的实施形式与代码检查类似,不同之处在于要定义测试用例,然后通过人脑来对程序执行测试用例的过程进行推导。由于人脑的限制,该种方式只能针对比较简单的测试用例实施,但通过这个过程,能够使人们进一步理解程序的逻辑结构和控制方式。 单元测试的对象是组成软件的最小单位——在结构化软件中是过程、函数等模块,在面向对象软件中是类(对类进行测试依然要考虑对其成员函数进行测试,详见5节)。单元测试的依据是软件详细设计规格说明书,一般应紧接在编码之后,当源程序编制完成并通过复审和编译检查,便可开始单元测试,以尽早发现代码中的错误,确保不会有软件单元中的错误遗留到后续的开发和集成中。单元测试应该对单元内所有重要的控制路径设计测试用例,以便发现单元内部的错误。由于单元的规模一般较小,因此单元测试多采用白盒测试技术,以能更充分地发现其中的错误。 单元测试的主要任务包括: (1)软件单元接口的测试。接口测试是单元测试的基础,只有在数据能正确流入、流出模块的前提下,其它测试才有意义。 (2)软件单元中局部数据结构的测试。检查局部数据结构是为了保证临时存储在模块内的数据在程序执行过程中完整、正确。 (3)软件单元边界条件的测试。每个软件单元都会对其输入范围有限制,而对边界值处理是最容易出现错误的地方,因此要专门测试。 (4)软件单元中所有独立执行通路的测试。应该对单元中每一条独立执行路径进行测试,其基本任务是保证模块中每条语句至少执行一次。此时基本路径测试和循环测试是最常用且最有效的测试技术。 (5)软件单元中各条错误处理通路的测试。一个好的设计应能预见各种出错条件,并预设各种错误处理通路,错误处理通路同样需要认真测试。 单元测试是需要为被测单元开发测试驱动模块(driver)和桩模块(stub)。驱动模块接收测试数据并将这些数据传递到被测试单元,并输出相关结果;桩模块用于替代那些将会由被测单元调用的模块,其接口应该与对应的真实模块完全一致,但内部只做少量数据处理。提高内聚度可简化单元测试,如果每个单元只完成一个功能,所需测试用例数目将显著减少,单元中的错误也更容易发现。单元测试的示例可参见例4对Schedule类中的addCourseOfferings方法的测试。 时常有这样情况发生,每个模块经过单元测试都能单独工作,但这些模块集成在一起之后却不能正常工作。因此,需要按软件设计要求把各个模块组装在一起之后进行集成测试以便发现与接口有关的各种错误。集成测试一般在被集成的软件模块都通过单元测试之后进行,其主要依据是软件设计规格说明中关于软件结构的描述。在集成测试中,可以黑盒测试技术为主,但由于与软件结构有关,白盒测试技术也有可能会用到。 在集成测试中,非常重要的一点是如何把模块集成起来。最直接的方式是非增量式集成,即把所有模块按设计要求一次全部组装起来,然后进行整体测试。但这种方法容易出现混乱,因为测试时可能发现一大堆错误,为每个错误定位和纠正非常困难,并且在改正一个错误的同时又可能引入新的错误,新旧错误混杂,更难断定出错的原因和位置。因此,一般推荐使用增量式集成方法,即把模块一个一个的集成起来,逐步扩大测试的范围,这样易于定位和纠正错误,并能够较为彻底地对接口进行测试。增量式集成方法可分为自顶向下和自底向上两种方式。 自顶向下集成测试的过程如下: (1)以软件主控模块作为测试驱动模块,把对主控模块进行单元测试时引入的所有桩模块用实际模块替代; (2)依据所选的集成策略(深度优先或广度优先),每次只替代一个桩模块; (3)每集成一个模块立即测试一遍; (4)只有每组测试完成后,才着手替换下一个桩模块; (5)为避免修改引入新错误,须不断进行回归测试(即全部或部分地重复已做过的测试)。 (6)从步骤(2)开始循环执行上述步骤,直至整个程序结构集成完毕。 自底向上集成测试的过程如下: (1)把低层模块集成起来形成实现某个子功能的模块群(cluster); (2)开发一个测试用驱动模块,控制测试数据的输入和测试结果的输出; (3)对每个模块群进行测试; (4)删除测试使用的驱动模块,用实际开发的高层模块代替并形成能完成更大功能的新模块群; (5)从步骤(1)开始循环执行上述各步骤,直至整个程序集成完毕。 通过集成测试之后,软件已完全组装起来,接口方面的错误也已基本排除,这时就可以开始实施确认测试。确认测试的主要目的是检查软件能否按用户的要求进行工作,即是否满足软件需求规格说明书中的确认标准。 软件确认测试主要是通过一系列黑盒测试进行,其测试用例的设计应主要围绕软件与需求是否一致,是否满足用户提出的所有功能和性能,文档资料是否完整、准确,人机界面和其它方面(例如性能、可移植性、兼容性、错复能力等)是否令用户满意。确认测试的结果有两种可能,一种是功能和性能指标满足软件需求说明的要求,用户可以接受;另一种是软件不满足软件需求说明的要求,用户无法接受。当软件项目进行到这个阶段才发现的严重错误和偏差一般很难在预定的时间内改正,因此必须与用户协商,寻求一个妥善解决问题的办法。 在确认测试完成后,由于软件必须要允许在相应的硬件环境中,因此应该与实际运行时必需的其它组成部分集成起来进行系统测试,以验证软件、硬件、网络等各部件能够在一起正常地完成工作。系统测试已不仅是软件工程需要讨论的范围,这里就不详述了。需要注意的是,3节中所述的非功能性测试主要都在确认测试和系统测试中实施。 在市场化软件开发活动中,a测试和b测试很常见。前面提到的测试活动主要由软件开发人员进行,但这仍然不够,因为用户对软件的实际使用存在很多不可预见的地方。例如,用户可能错误地理解命令,或提供一些奇怪的数据组合,也可能对设计者自认为明了的输出信息迷惑不解等。因此,软件是否真正满足最终用户的要求应由用户进行一系列的测试。 α测试是指软件开发公司组织内部人员模拟各类用户行为对即将面市的软件产品(称为α版本、内部测试版)进行测试,试图发现错误并修正。α测试的关键在于尽可能逼真地模拟实际运行环境和用户对软件产品的操作,并尽最大努力涵盖所有可能的用户操作方式。α测试可以从软件产品编码完成后开始,或在子系统测试完成之后开始,也可以在确认测试过程中产品达到一定的稳定和可靠程度之后再开始。 根据α测试结果进行修改后的软件产品称为β版本(也称为外部测试版)。紧随其后的β测试是指软件开发公司组织各方面的典型用户在日常工作中实际使用β版本,或为对外进行宣传而将β版本免费赠送给典型用户(很多情况下,β版本可以通过Internet免费下载,也可以向软件公司索取),并要求用户报告异常情况、提出批评意见。然后软件开发公司再对β版本进行改错和完善。因此,β测试是在与开发者无法控制的环境下进行的软件现场应用。在β测试中,由用户记下遇到的问题,包括真实的以及主观认定的,定期向开发者报告。 随着面向对象软件开发方法的广泛使用,如何对面向对象软件进行测试成为一个倍受关注的问题。与传统结构化方法相比,面向对象软件和开发方法具备很多与众不同的特性,这些在对其进行测试例均需要进行特别的考虑,例如: l 面向对象软件的构成基础与传统结构化程序设计有所区别; l 面向对象软件中数据和方法的隐藏与封装; l 面向对象编程中存在继承、多态等多种机制; l 面向对象开发过程和分析、设计的重点有所不同,并且关注于对象的交互与集成; l 多视点的分析和设计模型、以及与代码之间存在的映射,可以尽早测试成为可能;等等。 因此,尽管前面讲述的软件测试技术对于面向对象软件同样是适用的(例如白盒测试、黑盒测试、非功能性测试、各种测试活动等),但仍需要专门针对面向对象软件的不同特征研究其相应的测试方法,以更好的保证面向对象软件的质量。 类是面向对象软件的基本组成单位,因此需要测试类的实现是否符合预期的设计目标。由于类在软件运行时必须实例化后才能执行,因此对类进行测试需要开发测试驱动程序来创建类的实例,并为实例创造适当的环境,以便运行具有特定目标的测试用例。测试驱动程序根据测试用例的定义向类的实例发送一个或多个消息(调用类的方法),然后根据响应值、实例对象属性(状态)发生的变化等内容来判断测试用例的执行结果。根据程序语言的存储分配机制,在测试执行完后,测试驱动程序应该删除其创建的对象实例。 对类中每个方法的测试,可以把方法类似看作传统结构化程序中的函数或过程单元,并借鉴3节中所讲的黑盒测试或白盒测试方法来生成测试用例的输入,但其测试过程需要适应于面向对象软件中类的特点,并通过消息传递的方式进行。类方法测试的过程大致如下[8]: Step1. 设置测试消息的参数、全局变量为希望的取值; Step2. 设置被测对象为希望的状态(设置属性值); Step3. 从测试驱动程序向被测对象实例发送测试消息(调用相应的被测方法); Step4. 测试检查: (1)把该被测方法返回的值与期望的值进行比较,若不同,记录未通过; (2)对被测对象的状态和期望的状态进行比较,若不同,记录未通过; (3)捕获所有异常,假如是期望的异常,确定它是否正确。假如无异常或有一个不正确的异常,记录未通过; (4)假如必要,对全局变量的结果与期望的值进行比较,若不同,记录未通过; Step5. 假如发生任何未通过,则诊断和修正该问题。 测试驱动程序的主要目的是运行可执行的测试用例并记录运行的结构,实现良好的测试驱动程序非常重要。测试驱动程序的设计应该相对简单,并要易于维护[8]。对于面对对象软件,驱动程序可通过下面几种方式进行编写: (1)实现一个main( )函数作为测试驱动(例如C++)程序,使得测试程序可以编译、运行。在main( )函数中执行测试用例,对类进行实例化,给类发送相应的测试消息,并输出测试执行的结果。 (2)在被测类中专门实现某些成员函数来测试相应的方法,通过调用这些测试函数来对类中的方法进行测试。 (3)单独实现一个测试类,该测试类中的成员函数都是对被测类进行测试,每个函数对应一个测试用例的实现。这种方法的好处在于既具有灵活性又易于维护,并且有利于测试用例的组织和管理。 对于面向对象软件中存在的多态,也应该在测试时进行专门的考虑。当客户对象与被调用方法进行动态绑定时,应该在设计测试用例时对所有可能的动态绑定形式都进行测试。 面向对象软件的运行主要通过各对象之间的交互和消息传递来完成的。即使单个类中的每个方法在独立测试时是正确的,但当多个类的实例通过交互完成某个任务时,可能存在大量的消息传递,交互过程也是非常复杂的。如果交互过程不正确,那么对于整个软件来说依然无法满足需求,因此对类的交互进行测试非常重要。 在交互测试中,测试驱动程序需要对参与交互的多个类进行实例化,并向相应的对象发送启动交互的消息或交互所需的消息序列。在判断交互测试的结果时,一方面要看交互完成后的输出,另一方面还要看各个对象在参与交互之后所处的状态是否正确。 不同的对象对接收的消息顺序也会有不同的要求,大致可以分为以下几种类型: l 对可接收的消息顺序不加任何限制。例如,对一个时间类CTime的实例,对分别增加或减少年、月、日、小时、分、秒等消息的到来没有特别要求,收到什么消息就直接进行相应得处理。 l 对可接收的消息顺序有限制,但对消息中参数的内容无限制。例如,一个交通灯类CTrafficLight的实例,接收的消息序列必须满足红、黄、绿、黄、红的顺序,而每个消息中设置灯亮时长的参数值可以是任意正数。 l 可接收的消息顺序与对象当前所处于的状态有关。例如,一个堆栈类CStack的实例,当其本身状态为空时,只能接收进栈消息;当处于栈满状态时,只能接收出栈消息;当处于其他状态时,两种消息都可以接收。 l 可接收的消息顺序和内容均与对象当前状态有关。例如,对一个帐户类CAccount的实例,在帐户余额为0时,只能接收存款消息;接收的取款消息中的金额必须要小于等于帐户余额;等等。 针对不同类型的类,在设计测试用例时,应该根据其要求使得测试用例能够检查该类的实例是否可以正确接收正常的消息序列,以及是否能够恰当处理不正确的消息序列。 类之间的继承是面向对象软件开发中的一个重要手段。由于父类和子类之间的行为密切相关又相互区别,因此在对继承进行测试时需要特别的考虑,以提高测试的效果。在面向对象软件设计时,继承关系的发现可以通过两种方式:寻找已有类的特殊化以确定其子类;确定已有某些类的共性以定义其父类。根据这个过程,对继承进行测试时,可以先测试父类,然后测试子类;也可以先测试子类,然后测试父类。但由于子类继承了父类的许多属性和方法,因此很直观的认识是如果先测试父类,那么在测试子类时可以重用很多已有的测试结果,包括测试用例、测试驱动程序等。因此,在这里主要考虑先父类、后子类的测试方式。 对于子类中那些从父类直接继承而来、未作任何改动的操作,很容易被测试人员忽略,认为不必再进行测试了。在很多情况下确实是这样的,但是由于子类实例化后的对象所运行的环境很可能与父类的对象不同,因此为了能够确保这些操作在子类对象的环境中能够正确运行,依然应该进行测试。这时可以重用父类的那些测试用例和测试驱动程序,或根据子类运行的需要和假设对它们进行相应的针对性修改。 之所以要用到继承,是因为子类中存在相对于父类而言新增加的属性和行为、或对父类的操作进行了修改或进一步实现。这些与父类的不同之处主要表现为以下几种: l 子类中增加了父类中没有的新方法。 l 通过重载改变了父类中的方法,或实现了父类中的接口(虚函数)。 l 子类中增加了新的变量和属性来实现更多的状态。 对上面这几种情况,可以使用类测试和交互测试中讲到的方法来进行测试,设计新的测试用例。对于增加的实例变量和状态,可以使用基于状态模型的测试方法来生成测试用例。 对软件进行测试时,很重要的一个目标就是确定软件是否满足需求和设计规范。当使用UML进行面向对象需求和设计建模时,就需要测试最终的软件是否满足UML模型所描述的软件规范。为此,应该基于UML模型来生成相应的测试用例。UML模型包括多种视图,为了测试软件是否满足视图所描述的规范,基于不同视图生成测试用例的方式也不相同。下面简要描述如何基于UML中的主要几种视图来生成测试用例。 (1)用例图。应该对每一个用例、外部执行者与相联用例的交互都生成测试用例;对用例之间的扩展关系和使用关系,应该生成适当的测试用例使得在被测软件的执行过程中实现这种扩展关系或使用关系。 (2)类图。基于类图进行测试,主要考虑测试用例如何反映类图中类之间存在的各种关系。对于类图中的两个类A和B,如果A和B之间存在关联关系,就应该生成相应的测试用例使软件执行时分别生成A和B的实例,并能够实现该关联关系;如果A和B之间存在聚集或组合关系,就应该在测试中生成A的实例并包含B的实例;如果A和B之间存在依赖关系,就应该在测试中使A的实例去使用B所提供的服务;如果A和B之间存在继承关系,可参见上一节中所讲到的对继承关系如何进行测试。 (3)顺序图和协作图。应该按照顺序图或协作图中所描述的场景进行测试,开发测试用例能够使软件的运行过程实现图中对象之间的交互过程,包括图中存在的对象之间的消息传递以及各个消息之间的顺序;对于同步消息和异步消息,也应该区别考虑,生成测试用例以实现对象之间真正的同步或异步交互。 (4)状态图。UML状态图描述了对象在生命周期中针对外部激励所进行的状态变化,为对象的行为提供了一种约束。因此,在根据状态图进行测试时,主要考虑是否存在对象接受了不正确的事件序列、执行了不正确的动作、产生了不正确的响应、或发生了不正确的状态变化等控制错误。在基于状态图设计测试用例时,应促使在软件运行中,对象能够到达所有的状态、执行了所有的迁移、或走过了所有迁移路径。例如,在状态图中存在一条从状态W到V的迁移,迁移激发事件为E,那么在测试时就可以考虑设置对象状态为W,并向对象发送事件E,然后检查对象是否正确执行了该迁移,并到达了状态V。 (5)活动图。活动图描述的范围很广,大到整个业务过程,小到具体操作或算法的步骤流程,在根据活动图设计测试用例时,应该满足活动图中描述的活动及其顺序。例如动作a和动作b,如果a在b之前,那么测试用例就应该能够促使两个动作均执行,并判断是否a在b之前执行。对于活动图中存在的活动之间的同步也应该在设计测试用例时考虑到。 (6)构件图和配置图。检查软件实现后的构件是否按照构件图中描述的那样进行组织和关联(包括源代码、二进制代码、第三方构件等);按照配置图所描述的物理环境来搭建测试运行环境(包括节点及其之间的连接),并把构件部署到相应的节点上来运行测试。 除了需要深入的了解测试技术外,丰富的实践经验对于良好的软件测试实施也非常重要。经验需要测试人员通过实际的项目积累获得,在本节将列出部分已被总结出的测试经验供参考,更多内容可参考文献[7]。这些经验并不是仅仅与测试技术相关,而是涵盖了实施软件测试活动时需要考虑的各个方面。 (1) 对于测试人员,应该尽快找到软件中可能存在的错误。因此,在测试时,一般可遵循下面的测试顺序: l 先测试软件中经过变更的部分,然后测试没有变化的部分; l 先测试软件核心功能,然后测试辅助或次要功能; l 先测试软件能力,然后测试安全性、可靠性等方面; l 先测试常用操作,然后测试少见操作; l 先测试常见安全威胁,然后测试罕见威胁; l 先测试对软件影响大的问题,然后测试影响小的问题。 (2) 测试人员不应该以确认软件能够正确运行作为目标,因为这本身就是无法确定的,并且会使测试员在思想上出现偏差。测试人员要关注软件失效,其目标应该是发现软件中存在的问题,这样才会竭尽全力去寻找问题。 (3) 测试人员不应该承担软件是否可以通过验收并进行发布的职责,否则会承担很大的压力而影响测试工作本身的目标。应该由对质量、进度和成本等项目各方面都了解且能对项目进行控制的人员或小组承担。 (4) 测试员不应该有“事不关己”的想法,例如在测试产品是否满足规格说明时,当出现规格说明之外的问题就不去关心。由于规格说明在很多情况下并不是面面俱到的,而软件最后使用时,用户并不去关系需求规格或设计规格中是如何描述的,软件的任何问题都是质量问题。因此,测试人员应该尽可能的寻找并报告所有问题。 (5) 测试人员应该熟悉建模技术,因为在设计测试时,所有的场景、清单或图表、以及关于用户及其使用方式等都是一种模型。如果模型有缺陷,那么测试也会存在不足。因此,测试人员对建模技术越精通,就越能更好的进行测试。 (6) 多数情况下,测试所要依据的需求文档都是不完整、不准确的,这时需要通过其它途径来得到测试所需的信息,包括与软件质量相关的人员进行交流或召开会议、从已知的项目和产品其它信息推导出什么需求最重要等。 (7) 测试时除了经过用户确认的、文档化的规格说明外,还应该使用各种隐含的需求信息源,例如其它竞争对手的相关产品、同一产品的老版本、有关报道和杂志文章、图形用户界面风格指南、操作系统对兼容性的要求、测试人员个人经验等。如果产品与这些隐含需求冲突,测试人员应该在测试报告中详细说明。 (8) 当被测的软件产品过于复杂时,人的脑力可能无法马上能够理解。这时可以试着分阶段间歇进行,这样可以逐步开始明白产品的模式,并在脑中形成更系统、更具体的测试策略。 (9) 测试人员在感到有些困惑时,应该有勇气提出问题,因为这种困惑有可能预示着存在各种各样的问题,例如时产品本身定义有缺陷、规格说明未描述清楚、开发人员在编程中发生遗漏或误解的情况等。 (10) 在测试用例定义中,尽管有对测试步骤和过程的描述,但测试人员并不能一味的完全遵循,需要根据自己对产品的理解来进行调整,因为一方面测试用例的编制不一定能真正准确地反映产品使用,另一方面测试用例不一定能够覆盖所有需要测试的方面。 (11) 测试中发现错误后要记录到报告中,由于该报告具有重要作用,因此应该丰富每个错误报告的信息,提高报告内容的可理解性。良好的错误报告会为测试人员带来好的声誉,而差的错误报告将可能为程序员带来许多额外的工作。 (12) 应该及时的报告测试过程中发现的错误,因为经过一段时间(甚至仅1-2天),测试人员可能会遗忘某些错误发生的细节。此外,当错误未被报告时,开发人员和管理者会认为正常,这时小的错误可能会扩散成为大的错误,会导致改正错误的成本变的更高。 (13) 当发现软件失效时,测试人员看到的是错误的行为,而不是实际的代码内部错误。该内部错误可能会很严重,因此应该接着执行一些后续测试,以发现更多的行为错误和发生条件,这样会为错误的更正提供更多的信息,也会使得错误报告内容更加充分。 (14) 应该提高测试的自动化程度,不要把在测试中节省成本作为开发中的一个重点考虑内容,因为投入一定的时间、精力、人员和资金来提高测试效率,将会更迅速的发现软件中存在的问题,加快软件的修改,从而最终加快软件的开发进度。 (15) 在开发过程中应该尽早地考虑启动测试自动化活动,因为在开发早期(设计阶段及之前),更容易提高软件的可测试性。而当测试方式已成型后,很难把各种资源转向自动化,测试人员也会有抵触情绪。但应该清楚的是,并不是所有的方面都可以自动化,应该尽早建立自动化测试的规划和基础设施。 (16) 应该充分利用测试文档模版,但避免其缺点,例如测试计划、测试用例、错误报告等模版。文档模版可以促进人员之间的沟通,并使新手更快的进入状态。但模版使得文档看起来符合要求、实际上不够具体的情况增加,这样测试人员可能并没有理解很多内容,但可以形成符合要求的结果。因此使用文档模版时,也要注意文档内容的质量。 (17) 测试人员在测试报告中应该展示自己的能力和品质。例如,在报告中应该干脆地报告问题,要将自己的判断建立在对软件行为的实际观察之上,不要假装知道实际自己不了解的内容,不要夸张错误,如果失效无法重现,应该展示自己为了重现失效所作的各种尝试和努力,等等。 (18) 应该在测试工作将不会获得明显收益或测试结果将会被忽略的情况下,应该拒绝测试某个软件版本。例如,某个版本应该具有某些关键功能,但却没有加入,进一步测试将是浪费时间;以前正常的重要功能在新版本中不能正常使用了,明显在实现中出现了错误;一个新的版本将会马上完成,对现在版本的测试很可能不起作用;等等。 (19) 如果不管对软件进行了多少次修改,总是会出现同样的错误,甚至错误越来越多,那么可能就是软件设计的有问题,或者是软件代码的结构太差。这时应该根据错误报告、修改次数、引入新错误的统计数据来提出停止测试的建议,并要求软件进行重新设计或编码。 (20) 对测试人员的所负责的测试对象进行调换,不要让他们自始至终都是对某个项目的同一组功能或特征进行测试,这样将会使测试人员感到厌烦,也将可能使得每个测试人员过于专门化,导致经验不够充分或当某人离开时留下难以补充的缺漏。此外,轮换测试对象会使发现更多软件错误的可能性变大。 (21) 可以尝试结对测试。测试本身也是一种启发式的过程,当一个测试人员需要向另一个说明自己的想法时,通过提问和讨论可以发现更多的问题。 (22) 对于测试新手,在安排其测试新开发的软件之前,可以让其重新测试以往的旧软件,以对其进行培训并增加测试经验。不要派新手参加几乎已经完成的项目,应该让他们从项目开始就参与到测试工作中,使他们能深入了解测试的整个过程,逐步进入状态。 (23) 尽可能使测试团队拥有不同背景的测试人员,因为测试的对象和具体任务牵扯到各个方面,之间可以优势互补。 软件测试是一项技术性和艺术性很强的工作,在现代的软件开发过程中同时也占有非常重要的地位。加强对测试活动及其人员的管理,将对实施有效的测试、提高最终软件质量起到关键的作用。 软件测试计划的目标是声明测试活动的范围、方法、资源和进度,明确被测试的配置项、被测特征、要执行的测试任务、每项任务的负责人、以及与测试计划相关的风险。在IEEE标准829-1998“软件测试文档”和国标GB9386-88“计算机软件测试文件编制规范”中,规定软件测试计划应该包括以下部分: 软件测试的目标是发现软件缺陷,在发现之后需要进行确认,需要提交给管理者和开发人员,然后对软件缺陷进行修复,并且要进行回归测试以确定缺陷已经被剔除。在整个过程中,软件缺陷经历了很多步骤,为了避免出现在其中某个环节出问题,需要对发现的缺陷以良好的方式进行报告和跟踪,这在发现大量软件缺陷时尤其必要。因此,对缺陷进行报告时应该以良好的格式进行,这对于使用工具进行管理以及提高管理自动化程度非常有益。下面给出一个缺陷报告的格式模版供参考[6]。 很多情况下,测试计划并不能被严格的执行,它与软件开发的进度密切相关,经常伴随着项目的延期、测试阶段时间的压缩。如此一来,测试进度的更新是经常发生的事情。所以需要注意的一点是,在测试计划中对进度估算完毕后,排定时最好不要以具体的时间点到时间点的方式,而是采用工作量和工期天数来表示,这样在开发阶段影响了测试计划后,不需要频繁和大幅度的调整。另外,当测试时间被压缩时,如果测试计划制定的详细,包括了各项测试需求和它们的优先级,那么此时可以利用测试需求的优先级,跟项目经理协商,调整测试范围和测试需求,在短的时间内优先测试重要的功能点,而一些不常用的和级别低的测试需求可以转移到用户现场或者后期进行。 对于软件项目计划的更改或者需求、设计的更改,项目组一定要注意及时通知测试人员。测试人员需要及时调整测试计划和测试用例,其中尤属测试用例的调整工作量较大,这种情况的频繁发生要求制定测试用例时,需要保证测试用例的条理性和通用性。另外,应该对测试的更新管理进行规定,明确更新周期和暂停测试的原则。例如,小版本的产品更新不能大于每天三次,一个相对大的版本不能每周大于1次,规定紧急发布产品仅限于何种类型的修改或变更,由谁负责统一维护和同步更新测试环境。测试暂停可以表现在产品错误发布或者服务器数据更新等情况,有利于测试管理者有效安排测试资源的合理运用。 由于测试本身无法找到所有软件缺陷,因此对软件测试的有效性进行度量应该受到关注。对测试进行度量的目标主要有两个:评价软件测试的过程和结果;评价测试人员的工作。对测试进行度量和评价也需要收集大量的数据,其主要过程为: 上面过程最后一步对测试进行评价可以使用多种定量化的度量指标来反映测试的不同侧面及其效果,例如: l 用户参与测试的比例; l 测试的软件单元比例; l 测试的路径(分支、语句)覆盖率; l 测试费用; l 发现的缺陷数目; l 不同严重级别缺陷所占的比例; l 缺陷的修复比例; l 不同测试活动中发现缺陷的比例(例如单元测试、集成测试、确认测试); l 不同测试目标发现缺陷的比例(例如性能测试、兼容性测试、本地化测试等); l 缺陷数目与测试日期之间的曲线;等等。 通过对测试进行度量,一方面评价本项目测试过程,确定是否可以结束测试工作,此外还可以总结测试的不足之处,为开发组织今后的软件项目测试工作提供改进建议。 最能让人感到快乐的事,莫过于经过一番努力后,所有东西正慢慢变成你想要的样子! 最后: 可以关注公众号:伤心的辣条 ! 进去有许多资料共享!资料都是面试时面试官必问的知识点,也包括了很多测试行业常见知识,其中包括了有基础知识、Linux必备、Shell、互联网程序原理、Mysql数据库、抓包工具专题、接口测试工具、测试进阶-Python编程、Web自动化测试、APP自动化测试、接口自动化测试、测试高级持续集成、测试架构开发测试框架、性能测试、安全测试等。 如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一键三连哦! 转行面试,跳槽面试,软件测试人员都必须知道的这几种面试技巧! 面试经:一线城市搬砖!又面软件测试岗,5000就知足了… 面试官:工作三年,还来面初级测试?恐怕你的软件测试工程师的头衔要加双引号… 什么样的人适合从事软件测试工作? 那个准点下班的人,比我先升职了… 测试岗反复跳槽,跳着跳着就跳没了…3.3 非功能性测试
3.3.1性能测试
3.3.2强度测试
3.3.3配置和兼容性测试
3.3.4安全性测试
3.3.5可靠性测试
3.3.6用户界面测试
3.3.7本地化测试
3.3.8 Web测试
3.3.9安装测试
3.3.10可恢复性测试
4 软件测试活动与实施策略
4.1 需求和设计阶段的测试
4.2 代码检查
4.3 单元测试
4.4 集成测试
4.5 确认测试和系统测试
4.6 a和b测试
5 面向对象软件测试方法
5.1 类的测试
5.2 交互测试
5.3 继承的测试
5.4 基于UML的测试
6 软件测试的有关经验
7 软件测试过程中的项目管理
1 制定软件测试计划
2 软件缺陷的报告与跟踪
3 软件测试的进度管理
4 软件测试的度量
好文推荐