杜庆峰 李珑
摘要:针对测试过程中对子类方法测试不充分的问题,结合面向对象开发技术的继承特性,提出了一种新的测试模型。在父类已被充分测试的前提下,分析子类在父类的基础上新增加的方法、修改继承于父类的方法。对于新增加的方法,按定义的规则生成测试用例;对于修改继承于父类的方法,获取以XML格式存储的父类测试用例集,根据测试模型提出的方法确定可重用的测试用例。这样,子类的测试用例集已确定,执行测试用例集,即完成对子类的测试。通过实例验证,该测试方法是有效的,极大地提高了子类的测试效率问题。
关键词:继承;测试用例集;测试模型;XML格式;复用测试用例
中图分类号:TP311 文献标识码:A 文章编号:1009-3044(2017)07-0236-04
1概述
1.1课题研究背景
从计算机问世以来,程序的编制与测试就同时摆在人们面前,但测试在软件开发中的作用并没有受到应有的重视。但是,就目前软件工程发展的现状而言,软件测试仍然是较为薄弱的一方面。不仅测试理论,已有的测试方法也不能满足当前软件开发的实际需求。
软件测试是保证系统质量的重要手段,软件测试中一般包括单元测试,集成测试,系统测试等阶段。以面向对象技术开发的系统其测试过程也包括这些阶段,但是由于面向对象技术本身的特点,如继承、封装、多态等,使得在对面向对象技术开发的系统进行测试时提出了新的要求,尤其是面向对象的单元测试。在面向对象的单元测试中由于存在继承(可能有多重继承)关系,在将类看成单元的情况下,对类进行单元测试提出了挑战。假设,不考虑类的继承关系而将每个类分别看成一个独立的单元和传统的单元一样单独的进行测试,将耗费大量的时间,同时也不能保证继承了父类的属性及方法在子类中的正确性。
传统的单元测试是针对程序的函数、过程或完成某一特定功能的程序块。面向对象开发的特性使得其单元测试不完全等同于传统的单元测试。尤其是继承特性,虽然在编程阶段使得子类的编写效率有所提高,但在对子类进行测试时所出现的问题是在传统测试中从未遇见的。因此,在对面向对象程序进行单元测试时,除了继承传统的技术之外,还必须研究相适应的新的测试方法。
1.2问题的提出
当子类继承父类时,子类在父类的基础上进行了扩充,即增加方法和修改方法。所以对子类进行测试时,可以在其父类充分测试的基础上,利用父类的测试用例集,分析子类继承父类的关系,进行对子类的测试。
针对继承特性带来的影响,在父类已经进行了充分的测试的前提下,若要对其子类进行测试,应考虑如下问题:
1)子类中添加的方法该如何测试
2)子类中隐式继承父类的方法是否不再重新测试
3)子类中对继承父类的方法进行修改后该如何测试
只有充分覆盖了以上可能出现的三种情况,对子类的测试才能在一定程度上满足要求,确保子类功能的实现,从而保证整个系统的软件质量。
2子类测试模型思想
在问题提出中已经提到对子类的测试是在父类的基础之上,也就是实际上子类的测试分为两步,先分析子类与父类的继承关系,再对子类进行测试。其实这两步就是两个子步骤,下面将对测试过程中用到的一些定义进行说明,其中主要包括类继承关系的定义和测试用例集的定义。
2.1测试过程相關定义
定义1:根基类集合
假设系统中存在一系列类,这一系列类的共同特性是:该类不存在父类,则称具有这一特性的类为系统的根基类。根基类抽象出了系统中某些共同的功能,利用面向对象的继承特性,使其子类根据不同的需求实现具体的细节。记系统的根基类为Pi,系统中所有的根基类只组成一个根基类集合Root={Pi},其中i>0。
定义2:非根基类元素
系统中的类除了根基类,剩下的是非根基类。即非根基类是通过继承根基类或其他非根基类而得到的一系列类。非根基类一定有其对应的父类,可以有其对应的子类。记系统的非根基类为Si。
定义3:方法间调用关系
假设任意一个类Pi,其方法列表PM中有方法mi和mj,并且i≠j。若方法mi在执行过程中用到方法%的执行结果,则称方法mi调用了方法mj,记mi→mj。那么任意一个类Pi的方法间调用关系可用二维数组PMCi表示为:
二维数组元素mij取(0,1)其中的一个值。若mik=0表示mi没有调用mj;若mij=1表示mi调用了mi,即mi→mj。二维数组中的非零元素的个数定义了调用关系的数量。
定义4:XML格式的类结构
XML(Extensible Markup Language),可扩展标记语言,是一种允许用户对自己的标记语言进行定义的源语言,具有快速存储和读取的特性。本文结合XML设计和结构的优势,利用其定义类的结构,定义结构如下:
其中,具体标签含义分别定义如下:
1.
2.
3.
4.
5.
系统中的每一个类都对应一个xml文件,定义该xml文件的命名规则为“类名.xml”。
定义5:类差异脚本文件
假设系统中的两个类A和B,其对应的x打d文件分别为A.xml和Bxml。对比A.xml和B.xml,用差异脚本记录类A在类B基础上的变化,其定义如下:
其中,具体的标签含义定义如下;
1.
2.子标签
3.
4.子标签
5.子标签
类差异脚本文件记录了类A不同于类B之处,定义该文件的命名规则为“A_diff_B.xml”。
定义6:基于xml格式的测试用例库
测试用例(Test Case)是为某个特殊目标而编制的一组测试输入、执行条件以及预期结果,以便测试某个程序路径或核实是否满足某个特定需求。测试用例库(Test Case Library)就是这样一系列测试用例的集合。结合xml轻量级的数据存取特点,利用其存储测试用例,有利于生成测试用例时的存储和执行测试用例时的读取。通用的测试用例结构定义如下;
其中,具体的标签含义定义如下:
1.
2.
3.子标签
4.子标签
5.子标签标识测试输入;
6.子标签
7.子标签
8.子标签
系统所有测试用例存储在一个xml文件中,组成测试用例库,该测试用例库的命名规则为“TC_Library.xml”。
2.2子类测试策略模型
2.2.1测试策略模型思想
根据3.1节中的相關定义,本节给出具体的测试策略。
假设给定系统中的任意一个类Ci,现要对Ci进行测试,基于以上的分析,测试策略的逻辑如下:
步骤一:根据定义1,在集合Roots={Pi}中查询类Ci,若Ci存在于集合Roots中,说明被测类Ci为一个根基类。首先生成类Ci对应的方法间调用关系PMCi和xml格式的类结构Ci.xml文件。
步骤二;由定义可知,Ci.xml结构中
步骤三:根据步骤一,若不存在于集合Roots中,说明被测类Ci为一个非根基类。首先生成类Ci对应的方法间调用关系PMCi和xml格式的类结构Ci.xml文件。分析Ci.xml结构中
步骤四:根据定义7中定义的类差异脚本文件的具体结构,分别可获得子类Ci在父类Cj的基础上新增加的属性、新增加的方法以及修改继承父类的方法。
步骤五;针对类Ci中新增加的每一个方法,为其设计一组测试用例,并按照测试用例模板填充,添加到测试用例库中。针对类Ci中修改的父类方法mj,在测试用例库中找到其对应的测试用例。若父类Cj的测试用例不能全为Ci所用,如图1:
其中集合X表示父类Cj中对该方法设计的测试用例集,集合y表示子类Ci中修改后mj对应的测试用例,则阴影部分为子类Ci可复用父类CI的测试用例集。除去阴影部分,还应为mj设计额外的测试用例;若父类Cj对应的测试用例能全为Ci所用,如图2:
但集合X的测试用例集并不能完全覆盖mi的功能,所以除了复用父类已有的测试用例集,还应为mj设计额外的测试用例集;若父类Cj对应的测试用例全不能为Ci所用,如图3:
集合X与集合y的交集为空,类Ci中修改的方法mj需重新设计测试用例集;若父类Cj对应的测试用例能全为Si所用,如图4;
则mj能完全复用父类的测试用例集,極大了提高了测试效率。
步骤六:分析类Ci对应的方法间调用关系PMCi并根据步骤五的结果,为子类Co设计的测试用例已确定,根据定义8填写测试用例,并添加到测试用例库中。
步骤七;执行测试用例库中针对类Ci的测试用例,最后记录下测试结果并分析测试结果。整个类Ci的测试过程到此结束。
3算法实现
3.1测试策略算法实现
根据3.2节中根据3.1中测试策略模型思想的逻辑,本节将算法予以实现。
对于给定的任意一个类,通过类名在类名.xml文件中获取其对应的xml类结构。根据定义6中对xml格式的定义,分析
在得到的子类_diff_父类.xml文件中,根据定义7中定义的格式,分析
对于子类中修改父类的方法,根据步骤五中的描述,若父类Cj的测试用例不能全为Ci所用,除去父类已有的测试用例部分,还应设计额外的测试用例;若父类Cj对应的测试用例能全为Ci所用,但不能完全覆盖mj的功能,所以除了复用父类已有的测试用例,还应为mj设计额外的测试用例;若父类Cj对应的测试用例全不能为Ci所用,类Ci中修改的方法mj需重新设计测试用例集;若父类Cj对应的测试用例能全为Si所用,则mj能完全复用父类的测试用例集,极大地提高了测试效率。
3.2算法复杂度分析
由2.1中定义1,定义4,定义5,定义6,定义7以及2.2.1中测试策略模型以及3.1中对测试算法的实现,对测试算法存在如下结论:
若被测子类为一级继承根基类,其中,被测子类包含M个属性,N个方法,即M个属性为子类新增加的属性,N为子类新增加的方法和继承父类方法的总和,则对测试策略算法的时间复杂度分析有如下结论;
测试策略算法对被测子类进行操作:获取子类的属性ai和方法mi,并生成对应的child.xml文件;获取子类对应的父类并运用相同的方法生成对应的parent.xml文件,比较两个XML文件,并生成对应的child_diff_parent xml。根据定义2.1中对差异脚本的定义,标签的多少取决于子类不同于父类的程度,标签越多,说明子类在父类的基础上改变的越多。根据步骤五复用父类测试用例的描述,将任何一种可能的情况与其匹配。其算法时间复杂度为T(n)=o(n)(其中n=MM)。
所以测试策略算法的时间复杂度为T(n)=o(n)(其中n=MM)。
4模型算法验证
4.1算法验证环境及数据准备
本文的实验是搭建在Windows7平台的Java环境上,由于本文算法模型主要是对系统中子类的测试,所以选取测试数据时应尽量选取系统中的子类而不是父类。但本文算法执行的前提是父类已充分测试,而该算法同样适用于对父类的测试,所以父类的测试也可以用该算法实现。
由于系统中类内方法之间存在着相互调用的关系,算法模型的可靠性很大程度上和方法之间调用的复杂度相关,因此为了对模型可靠性验证,系统中类的数量及类内方法之间的调用关系复杂度应该由简单到复杂的验证过程。
其次,由于需要验证算法的效率,需要比较在没有运用算法对某一系统中的子类进行测试的时间和运用算法后测试时间的对比。基于以上分析,本文的数据信息如下:
4.2验证结果及结果分析
通过对算法执行与否测试时间的对比,以及算法作用于不同类型的系统的分析,模型能够良好地对系统中的子类进行测试,且具有较高的测试效率。
实验结果的准确性通过对系统测试覆盖率的分析,算法效率通过比较算法执行与否对某一具体类测试的执行时间。
5结论与展望
实验结果表明,本文测试算法对系统中子类的测试有较高的测试效率,提高了单独测试子类的时间和效率,有很大的应用价值。
可以得出以下结论:
1)算法较好地利用了子类与父类的继承关系,实验证明该方法是可行的。
2)对比算法简单,且读取和解析文件速度很快。
3)在分析子类与父类的关系之后,再对子类进行测试的速度明显快于单独测试子类。
对于该测试算法的应用前景,提出以下展望;
1)目前实验数据还不足够,可能存在没有覆盖到的情况,需要不断实验补充。
2)测试覆盖率在一定程度上需要进一步提高。