1.引言
设计测试输入和相应的预期输出,是任何测试机构最基本的技术活动之一。测试输入数据和相应的预期输出都被写入测试用例当中。测试用例的集合就是测试集。目前,已存在大量的指南、技术和支撑工具用于生成测试用例。接下来将要介绍大量基于测试生成的指南和技术。
2.软件需求
软件需求是设计测试的基本出发点。在软件开发的初始阶段,软件需求只在一个或几个人的大脑里。通过使用诸如UML用例图、顺序图、状态图等建模元素,可获得严格的需求。更进一步,利用形式化需求规约语言如Z、S、RSML,可将严格的软件需求规范进一步转换为形式化的需求。虽然,完整的形式化需求规范是个有用的文档,但常常还是通过运用适当的建模机制来获取软件需求。UML作为一种高效的建模语言,将多种不同的建模元素集成在一个统一框架下,由这些建模元素来严密地、形式化的定义软件需求。
需求规范的三种表示形式:
(1)非正式的的规范
(2)严格的规范
(3)形式化的规范
当然,有时也可以是这三种形式的混合体。需求规范的形式化越高,则越有机会进行自动化测试。
a)其中软件输入域可以从非正式的和严格的需求规范中提取出来,同时,输入域也是测试设计的依据。非正式的和严格的需求规范主要包括以下测试技术:
(1)等价类划分
(2)边界值分析
(3)因果图分析
(4)判定表
这些技术,都能从通常庞大的软件输入域中选取相当少数量的有效测试用例作为测试集。
b)形式化的需求主要包括以下测试技术:
(1)基于模型的技术
(2)基于规范的技术
(a)、(b)介绍的软件测试技术,都属于黑盒测试范畴。且其中的部分技术在有软件源代码时能得到增强。
3.测试用例的选择问题
设D为软件p的输入域,测试用例选择问题是指:选取测试用例的子集T,以T中各元素为输入执行p,执行过程中将发现p中的缺陷。一般来说,不存在构造这种测试用例集合的算法,但运行启发式方法以及基于模型方法所生成的测试用例集合,还是能够有效地暴露部分特定类型的缺陷。因此,测试用例选择问题的关键在于:如何构造测试用例集合T,且其是D的子集,使得采用集合T能够尽可能多地发现软件p中存在的缺陷。测试用例选择问题之所以困难,主要原因在于软件p的输入域规模大、复杂程度高。
软件输入域是指软件在执行过程中可能接收的全部合法输入的集合。软件的合法输入集合是由软件需求决定的。在许多实际问题中,软件输入域的规模非常庞大,可能包含很多元素,同时也很复杂,这些元素可能又具有多种类型,如整数、字符串、实数、布尔型以及结构。
在一般情况下,软件输入域规模非常之大,这就使得测试人员无法使用全部可能的输入值对被测软件实施穷举测试。所谓穷举测试就是指测试人员逐个地使用输入域中的所有元素对软件实施测试。输入域的复杂性增大了选择测试用例的难度。
在一些情况下,由于输入与时序之间存在约束关系,使得即使描述软件输入域都非常困难这是软件测试过程中无法避免的问题。因此,各种测试用例选择方法应运而生,测试人员运用这些方法从软件输入域中选择一个尽可能小的子集,以便达到测试软件的目的。以下将介绍这些测试用例选择方法,同时也给出每种方法的优点与不足。
4.等价类划分(往往都是几个最常用的测试设计技术之一)
采用等价类划分方法进行测试设计时,要求测试人员将输入域划分为数量尽可能少的若干子域,且在划分子域的过程中,要根据严格的数学定义,要求每个子域两两互不相交。每个子域称为一个等价类。
等价类划分的原则:用同一等价类中的任意输入对软件进行测试,软件都能输出相同的结果。在这样的前提条件下,测试人员只需从划分的每个等价类中选取一个输入作为测试用例,N(N个等价类)个这样的测试用例就构成了对该软件完整的测试用例集。
当然,对同一个输入域进行等价类划分,其结果可能是不唯一的。因此,利用等价类划分方法产生的测试用例集也可能不同。即使两个测试人员划分的等价类相同,他们也可能选取出不同的测试用例集。这样得到的测试用例集的故障检测率取决于测试人员的测试设计经验、对软件需求的熟悉程度、是否获得软件源码以及对源代码的熟悉程度。
4.1)缺陷定位
一个软件的全部输入的集合可以至少分为这样的两个子集:其中一个包含所有正常和合法的输入,用E表示;另一个包含所有异常和非法的输入,用U表示。这两个集合,又可分别进一步划分为若干子集,以便软件针对不同的子集,其运行结果不一样。等价类划分方法就是要从这两个集合或其子集中选择适当的输入作为测试用例,以便发现软件中存在的导致其运行异常的缺陷。
举例说明,假设软件A以一个表示人员年龄的整数作为输入。假设年龄的合法值应该在[1,120]范围内,因此,输入集合可以被划分为正常输入集合E,其取值范围为[1,120];异常输入集合U,其取值范围为除[1,120]外的其余所有整数。更进一步,假设该软件依据需求R1处理所有取值在[1,61]之间的输入,依据R2处理所有取值在[62,120]之间的输入。因此,根据软件的预期行为,可将集合E进一步划分为两个子集。同样,对于非法集合U,软件以一种方式处理所有小于1的输入,而以另一种方式处理所有大于120的输入,进而U也划分为两个子集。这样,软件A就有了两个包含正常输入和两个包含异常输入的4个输入区域 ,等价类划分方法就是通过从这4个输入区域中选择测试用例,力图发现软件A的缺陷。
4.2)关系与等价类划分
在集合论中,关系指的是一个n元组的集合。例如,方法addList计算并返回一个整数列表之和,因此,addList就定义了一个二元关系。该关系中的任何一个二元组都是由一个整数列表与该列表各整数之和组成,如((1,5),6),((-3,14,3),14),((),0)等。addList所对应的关系可以定义如下形式:
addList:L->Z
其中,L是包含所有整数列表的集合,Z是整数集。由前面的例子我们可以认为,每个软件、程序或方法都定义了一个关系。事实上,只要定义域(即输入集合)和值域(即输出集合)定义正确的话,这个结论是正确的。
例如,假设方法addList存在一个缺陷,即当输入的整数列表为空时方法失效。在这种情况下,即使按照需求规范,方法addList定义了上一个关系L->Z,它也不能正常地运行。它实际定义的关系如下:
addList:L->Z ∪ {error}
在划分软件的输入域时,测试人员常常采用下面的关系:
R:I->I
其中,I为输入域,R为I上的关系,R定义了一个等价类,该等价类是I的子集。
4.3)变量的等价类
(1)取值范围:
取值范围可以通过显示和隐式两种方式定义。例如,速度speed的取值范围可显示地定义为[60,90],而面积area的取值范围则是隐式定义的。对于speed,测试人员可以确定取值范围之外的输入值,而对area,虽然也可确定取值范围之外的输入值,但由于被测软件运行的软硬件系统对数据表示的限制,有可能使得测试人员无法输入这些取值范围之外的值。
划分原则:(↓非法输入等价类的代表,↑合法输入等价类的代表)
等价类:一个取值范围内的等价类;两个取值范围外的等价类
约束:speed∈[60,90] 等价类代表:{{50}↓,{75}↑,{92}↓}
area:float;area>=0 {{-1.0}↓,{15.52}↑}
age:int;0 <= age <= 120 {{-1}↓,{56}↑,{132}↓}
letter:char {{J}↑,{3}↓}
(2)字符串:
字符串的划分将使用期其语义信息
划分原则:(∑表示空字符串)
等价类:至少分为一个包含所有合法字符串的类和一个包含所有非法字符串的类。合法性由字符串的长度及其他语义特性所决定
约束:fname:string(人名,且长度不超多10个的非空字符串,并且只能有字母组成) 等价类代表:{{∑}↓,{Sue}↑,{Sue2}↓,{Too Long a name}↓}
vname:string(变量名,非空) {{∑}↓,{shape}↑,{address1}↑,{Long variable}↓}
(3)枚举:
如果软件针对变量的不同取值表现出不同的行为,那么每个取值自身构成一个等价类,布尔变量就是这样的。针对枚举类型,比如对于某些特定的取值范围,有可能无法确定非法测试输入值。例如,布尔型输入变量up只接受true和false这两个合法值,其所有可能的等价类都将只包含合法值。
等价类划分原则:
等价类:每个取值对应一个等价类
约束:auto_color∈{red,blue,green} 等价类代表:{{red}↑,{blue}↑,{green}↑}
up:boolean {{true}↑,{false}↑}
(4)数组:
数组是一组具有相同类型的元素的集合,数组长度及其类型都可作为等价类划分的依据。
等价类划分原则:
等价类:一个包含所有合法数组的等价类,一个空数组的等价类,以及 一个包含所有大于期望长度数组的等价类
约束:Jave array:int[] 等价类代表:{{[]}↓,{[-20,10]}↑,{[-9,0,12,15]}↓}
aName = new int[3]
(5)复合数据类型:
包含两个或 两个以上相互独立的属性的输入数据。例如,Java中的数组,以及C++中的记录或结构,都是复合类型。当对软件的一个组件模块(比如函数或对象)进行测试时,将使用这种输入类型。对这种复合数据类型的输入进行等价类划分时,需要考虑输入数据的每个属性的合法和非法取值。
4.4)一元化分与多元化分
输入域划分方法之一是每次只考虑一个输入变量,这样,每个输入变量形成了对输入域的一个划分,我们称这种划分方式为一元等价类划分,简称一元化分。在这种情况下,针对每个变量的输入域存在一种关系R;程序的输入域就是基于R进行划分的;有多少个变量,就形成多少种划分,每个划分包含两个或两个以上的等价类。
另一种输入域划分方法是将所有输入变量的笛卡尔积视为程序的输入域I,并定义I上的关系R。该方法只产生一个划分,划分包含若干个等价类。我们称这种划分方法为多元等价类划分,简称多元划分。
采用多元划分方法得到的测试用例,往往比采用一元化分方法得到的测试用例更能充分地测试被测软件。但是另一方面,采用多元化分方法产生的等价类数量会随输入变量个数的增加而成指数增长。
4.5)等价类划分的完整过程
(1)确定输入域
(2)等价类划分
(3)组合等价类
(4)确定不可测的等价类:有些输入数据组合在实际测试过程中是无法生成的,包含这种数据的等价类就是不可测等价类。产生不可测等价类的原因有很多。举例来说,假设通过某软件的GUI对其进行测试,即数据只有通过GUI才能输入。GUI界面中只包含了所有有效的输入,不允许无效的输入。软件需求中也有一些约束,致使某些等价类不可测
4.6)GUI设计与等价类
设计测试用例时必须考虑GUI的具体实现。在有些情况下,测试设计受GUI设计所左右。例如,GUI的设计过程可能要求GUI尽可能地只提供输入变量的合法值。当然,需要单独针对这些需求对GUI进行测试。
5.边界值分析
边界值分析是一种有效的测试用例选择方法,可以发现位于等价类边界处的软件缺陷。等价类划分方法从等价类中选取测试用例,而边界值分析法从等价类边界或边界附近选取测试用例。当然,用这两种方法生成的测试用例可能有重叠。
通常在设计测试用例时,同时采用边界值分析和等价类划分两种方法。除了使用等价类划分确定边界值外,还可以利用输入变量之间关系确定边界。一旦输入域确定下来,使用边界值分析生成测试用例的主要步骤如下:
(1)使用一元划分方法划分输入域。此时,有多少个输入变量就形成多少种划分。若采用多元划分法,就只能形成输入域的一种划分。
(2)为每种划分确定边界。也可以利用输入变量之间的特定关系确定边界。
(3)设计测试用例,确保每个边界值至少出现在一个测试输入数据中。
注意:
(1)确定输入域边界值时需要仔细考虑各输入变量之间的关系,进而获得那些在输入、输出变量的等价类中并不明显的边界。
(2)使用单个变量等价类的笛卡尔积所构成的输入域划分,将获得更多的测试用例。
6.类别划分法
类别划分法是一种从软件需求生成测试用例的系统化的方法。类别划分法的本质就是测试人员把软件需求转换为相应的测试规范,其中,测试规范由对应软件输入变量和环境对象的各种类别构成。每个类别被划分为若干个对应于软件输入变量、环境对象状态的一个或多个取值的选项。测试规范中同时也包含了各选项之间的约束关系,以便确保生成合理、有效的测试集。将编写好的测试规范输入测试框架生成器,就能获得相应的测试框架,根据测试框架可生成相应的测试脚本。测试框架就是一个由选项组合的集合,其中一个选项对应于一个类别。测试框架也可看作是一个或多个测试用例的模板,将这些测试用例组合起来就形成一个或多个测试脚本。
该方法同时包含手工和自动完成的步骤。以下是使用该方法的详细步骤:
(1)分析功能规范
在该步骤中,测试人员要确定所有能够独立测试的功能模块。对于大的软件系统而言,功能模块可能对应于可独立测试的各个子系统,而子系统又可以进一步分为可独立测试的子模块。根据具体的测试对象来决定这种分解过程的终止时机。
(2)确定类别
对各被测模块的功能规范进行分析,确定相应的输入,同时,还要确定环境中的对象(如文件)。接着,确定各个参数和环境对象的特征。所谓特征就是一个类别,其中有些特征是明确定义的,而另外一些隐式特征需要通过对功能规范的仔细分析才能得到。
(3)划分类别
测试人员要根据每个类别中各个不同的情况,对功能模块进行测试。一种情况就是一个选项,每个类别将包含一个或多个情况。每个类别都至少可以被划分成两个子集,一个包含所有正确的取值,另一个包含所有错误的取值。
对于部署在网络环境中的软件,需要考虑各种网络失效的情况,还要仔细考虑其他各种情况,如数据库失效等。同时,测试人员需要充分考虑被测软件在实际使用过程中所有可能遇到的有效和无效的情况。
(4)确定约束条件
对某功能模块的测试,包括对该模块各参数、环境对象所有选项的组合的测试。由于输入参数间必须满足某些约束关系,因此有一部分的选项组合是无法实现的。无论如何,在本步骤中将确定各选项之间的约束关系。在步骤6中,测试用例生成器将依据这些约束关系只生成有效的测试框架。
约束关系可表示成属性列表或选择表达式。
属性列表的形式如下所示:
[property P1,P2....]
其中property是关键字,P1,P2等表示各属性的名称。可为每个选项分配一个属性。
选择表达式是属性列表中那些已定义属性的连接,其形式如下:
[if P1]
[if P1 and P2 and....]
属性列表和选择表达式一般放在各选项的后面。当某选项后面的属性列表为[error]时,表示该选项是一个错误状态;如果属性列表为[single],则告知测试人员在步骤6中生成测试框架时,该选项不能与其他参数或环境对象的选项进行组合。
(5)编(重)写测试规范
为每个选项分配了属性列表和选择表达式后,测试人员就可以编写完整的测试规范了。测试规范的编写需要采用具有严格语法的测试规范语言(Test Specification Language,TSL)。
在步骤7中,测试人员对步骤6生成的测试框架进行评价,如果对测试框架不满意或发现存在冗余框架时,则可以多次重复执行步骤5.所谓冗余框架,就是由那些不切实际或在实际使用中不可能发生的选项组合而成的框架。在这种情况下,测试人员将重写测试规范,并在一次执行步骤6,重新生成测试框架。
(6)处理测试规范
步骤5中编写的TSL规范将由一个自动测试框架生成器进行处理,生成若干个测试框架。测试人员对测试框架进行分析,找出框架中那些以相同方式测试被测软件的冗余内容。在这种情况下,测试人员可以重写测试规范或直接剔除出这些冗余内容。
测试框架并不是测试用例。从测试框架能很容易地生成包含特定输入值和期望输出的测试用例。值得注意的是,测试框架还包含环境对象的相关信息,这将有利于在测试执行前对测试运行环境进行适当的配置。
测试框架是由各选项根据约束关系组合而成的,被标记为error或single的选项只能生成一个测试用例,而不是与其他选项进行组合。
(7)评价生成器的输出(如需修改,重新执行步骤5)
在本步骤中,测试人员的主要任务是检查步骤6所生成的各测试框架中是否包含冗余用例以及是否缺少某些用例,进而转入步骤5(重写测试规范),并重新执行步骤6。
(8)生成测试脚本
从测试框架产生的测试用例要被组合成测试脚本。所谓测试脚本就是一组测试用例。通常将环境设置相同的测试用例编为同一组,这样可以有效提高测试驱动器执行测试用例的效率。
类别划分法基本上是综合了等价类划分和边界值分析的系统化方法
编写测试规范时,要求测试小组认真研读软件需求规范、软件设计说明及其他软件文档,仔细观察被测软件。对于大的软件系统,测试规范的编写工作可以分配给测试小组的每一个成员。虽然类别划分法中大部分关键步骤都需要人工完成,但如果能使用处理TSL桂法的工具,将有效提高生成测试用例和编写文档的效率,同时也能减少测试用例中错误。
7.因果图分析
等价类划分和边界值分析选择测试用例这两种技术,其中一个是基于输入域的一元化分的,另一个是基于输入域的多元划分的。
因果图,也称作依赖关系模型,主要用于描述软件输入关系(即“原因”)与软件输出结果(即“结果”)之间的依赖关系。因果图可以直观地表示各种依赖关系。在这里,因果图是输入与输出之间逻辑关系的图形化表现形式,这种逻辑关系也可以表示成布尔表达式。测试人员可以从因果图中选择不同不同的输入组合作为测试用例。生成测试用例时,使用特定的启发式方法可以有效解决测试用例数量的组合爆炸问题。
“原因”是指软件需求中能影响软件输出的任意输入条件。“结果”是指软件对某些输入条件的组合所做出的响应。这里的“结果”,可以是屏幕上显示的一条错误提示信息,也可以是弹出的一个窗口,还可以是数据库的一次更新。但“结果”对软件用户并不总是可见的“输出”,事实上,它可能是软件当中的一个内部测试点,在测试过程中通过检测测试点来判断软件运行的中间结果是否正确。例如,内部测试点可能在函数入口处,用于指示该函数已被激活。
举例来说,需求“仅当DF开关为ON时分发食物”,包含一个原因“DF开关为ON”和一个结果“分发食物”。该需求蕴含着原因“DF开关为ON”与结果“分发食物”之间的关系。当然,其他需求可能还要求有另外的原因才能产生结果“分发食物”。
利用因果图方法生成测试用例的一般过程:
(1)仔细研读软件需求规范,确定哪些是原因,哪些是结果,并为每个原因和结果赋予唯一的标识。注意,某些结果同时又是别的结果的原因。
(2)用因果图描述原因与结果之间的依赖关系。
(3)将因果图转换为一个有限入口的判定表,并简称为判定表。
(4)根据判定表生成测试用例。
7.1)因果图中的基本符号
原因(即输入条件)之间往往存在以下约束关系:
(1)排异约束(E):输入条件中最多只有一个为真
(2)包容约束(I):输入条件中至少有一个为真
(3)要求约束(R):如果有一个条件为真,则另一个条件必为真
(4)唯一约束(O):输入条件有且只有一个为真
同样,结果之间也同样存在着约束关系。因果图方法提供了结果之间的“屏蔽关系(M)”。即同一输入条件不能同时出现两种结果,例如网上购物,可能导入的结果是发货或者未发货(两种结果只出现一种)。
7.2)创建因果图
因果图分析的过程包含两个主要步骤:
(1)通过对软件需求的分析确定哪些是原因,哪些是结果。同时明确原因和结果的因果关系,以及原因之间、结果之间存在的约束关系,为每个原因和结果赋予唯一标识,便于在因果图中引用。
(2)构造因果图,以表达这些从软件需求中提取出的依赖关系。当原因、结果数量较大(通常指多于100个原因,或多于45个结果)时,采用增量方法比较合适。
7.3)从因果图生成判定表
判定表中的每一列表示一个输入值的组合,即一个测试用例。每个条件、结果在判定表中都有相对应的一行。因此,可以将判定表看作是一个N * M的矩阵,其中,N表示条件与结果的个数之和,M表示测试用例数。
判定表中的元素究竟取值为1还是0,依赖于其对应的各条件为真或假。对于某个结果,若取值为1或0,则分别对应于该结果“出现”或“不出现”。下面给出从因果图(CEG)生成判定表(DT)的算法,记为CEGDT。
输入:包含原因C1,C2.......Cp和结果Ef1,Ef2,.......Efq的因果图CEG。
输出:包含N=p+q行和M列的判定表DT,其中,M依赖于因果图中原因、结果之间的关系。
Begin of CEGDT
/* i为一个索引,表示下一个将被处理的结果。
next_dt_col:判定表中的下一个空列。
Vk:由1和0组成的长度为p+q的向量。
Vj:1 <= j <= p,表示原因Cj的状态
Vl:p <= l <= p + q,表示结果Ef(l-p)是否发生
*/
步骤1:将DT初始化为空判定表。
next_dt_col = 1;
步骤2:for i = 1 to q,执行如下步骤。
2.1 选取下一个将被处理的结果。
Let e = Ef1
2.2 求所有导致结果e发生的原因组合。
假设结果e发生。从e开始,对因果图进行回溯,确定导致结果e发生的条件C1,C2,.....Cp的组合。可以使用下面将要讲的启发式方法,以避免测试用例数量发生组合爆炸。同时,确保原因组合能够满足各原因之间的约束关系。
设V1,V2,......,Vmi为可能导致结果e发生的原因组合,因为已假设e发生,即e处于1状态,因此,至少有一个原因组合为真,即mi >= 1。根据结果Ef(l-p)针对Vk中的原因组合是否发生,置Vk(l) = 1或Vk(l) = 0,p < l <= p + q。
2.3 构造判定表
从next_dt_col开始,增加V1,V2,.......,Vmi,作为判定表的后续列。
2.4 修正判定表中下一个可用的列。
Let_next_dt_col = next_dt_col + mi
算法结束时,next_dt_col - 1就是生成的测试用例数。
End of CEGDT
7.4)避免组合爆炸的启发式方法
对因果图进行回溯,可以得到原因的组合,这些组合将某个中间节点或结果设置为1状态或0状态。这种使用“蛮劲”的方式将产生数量极为庞大的原因组合。在最坏的情况下,如果有n个与某个结果e相关,那么导致e为1状态的原因组合最多可达2^n个。
当根据原因组合生成测试用例时,n取值较大时将导致测试用例的数量过大。可以采用较为简单的同“与”(∧)节点、“或”(∨)节点相关的启发式方法,避免这种组合爆炸情况。
当然,启发式方法是建立在这样的假设基础之上的:某些类型的错误比其他类型的错误较少出现。这样,虽然采用启发式方法生成测试用例时很可能会极大地降低生成的测试用例数量,但是也会忽略掉一些原本能够检测出错误的测试用例。因此,在使用启发式方法时需格外小心,并且只有在采用其他方法产生的测试用例数量大到无实际意义时才能使用该方法。
7.5)从判定表生成测试用例
判定表的每一列至少生成一个测试用例。注意,当因果图中的一个条件可以用多个值满足时,一个组合就能生成多个测试用例。例如,考虑下面的原因:
C: x < 99
上面的条件C可以被多个值满足,如x = 1,x =49;同样,也有多个值不满足条件C,如x = 100,x = 999。这样,在从判定表的列生成测试用例时,测试人员可以选择输入变量的不同值。
虽然在确定输入变量值时有多种选择,只要它们满足判定表中的要求就行,但还是建议,所做的选择要使新生成的测试用例尽量与别的方法(如边界值分析)生成的测试用例不一样
8.基于谓词的测试生成