首页> 中国专利> 一种面向Java程序的随机测试用例生成方法

一种面向Java程序的随机测试用例生成方法

摘要

本发明公开了一种面向Java程序的随机测试用例生成方法,其步骤为:(1)解析Java程序的类定义,获取对象方法列表;(2)建立对象方法间的依赖关系图,包括方法调用图和属性依赖图;(3)确定待覆盖的目标方法列表;(4)以公有类的实例化为目的生成调用链,维护类对象库;(5)以目标方法覆盖为目的生成调用链,维护调用链库;(6)基于调用链库生成Java程序的测试用例集。本发明可广泛应用于大中型Java软件系统的自动化测试工作,可完全自动化地生成测试用例,能够以较少的测试用例数量达到更高的代码覆盖率,同时能够处理复杂的数据结构,自动生成测试断言;测试用例生成方法简单有效,具有良好的适用性和扩展性。

著录项

  • 公开/公告号CN102736980A

    专利类型发明专利

  • 公开/公告日2012-10-17

    原文格式PDF

  • 申请/专利权人 南京大学;

    申请/专利号CN201210219285.9

  • 发明设计人 顾庆;张孟乐;汤九斌;陈道蓄;

    申请日2012-06-29

  • 分类号G06F11/36;

  • 代理机构江苏圣典律师事务所;

  • 代理人贺翔

  • 地址 210000 江苏省南京市仙林大道163号南京大学计算机系

  • 入库时间 2023-12-18 06:52:28

法律信息

  • 法律状态公告日

    法律状态信息

    法律状态

  • 2014-10-22

    授权

    授权

  • 2012-12-12

    实质审查的生效 IPC(主分类):G06F11/36 申请日:20120629

    实质审查的生效

  • 2012-10-17

    公开

    公开

说明书

 

技术领域

本发明涉及Java软件程序的自动化测试用例生成,特别涉及利用Java语言本身提供的反射机制和虚拟机设施,分析对象方法间的依赖关系,应用组合测试技术,完全自动化地生成测试用例。

背景技术

随机测试是一种有效的能够完全实现自动化的测试用例生成方法。随机测试基于软件程序的输入空间,利用随机算法独立采样测试输入,进而生成测试用例。随机测试的优势包括:测试用例生成步骤简单,能够在较短的时间内生成庞大数量的测试用例;能够利用现有的伪随机数生成算法,基本不需要人工干预;随机算法能够保证测试输入的无偏性,避免人工测试的主观偏见问题;随机算法能够保证测试输入的独立性,在足够数量的测试用例前提下,能够采用统计学方法对软件质量做定量评估。随机测试同时也存在一些劣势,主要包括:对于复杂数据类型难以处理,难以自动识别其所对应的输入空间类型;测试断言难以生成,难以自动确定每个测试用例的预期结果;输入空间过于庞大,为达到有效的代码覆盖率(各种分支条件的覆盖),常需要生成海量的测试用例,冗余的测试用例过多;对于特定的程序上下文,随机测试所生成的测试输入有很大比例是无意义的,导致无效和非法的测试用例。

Java程序是一类面向对象程序。随机测试应用到面向对象程序可以有效提高测试用例生成的多样性和实用性,能够有效降低测试用例生成的成本和时间。在面向对象程序测试中,生成一个测试用例不仅需要考虑测试输入,还需要考虑相关对象所处的状态(由类中属性变量的不同取值决定);对象状态的生成需要通过进一步的类(对象)实例化和对象方法调用来完成,其中类实例化实际上是对类中构造方法的调用;于是对应一个对象方法的调用序列,定义为调用链。因此面向对象程序的随机测试需要随机生成调用链,同时为调用链中的每一个对象方法调用随机生成测试输入。目前应用于Java程序的随机测试方法包括JCrasher、Randoop、Eclat等,这些随机测试方法尚存在无效和冗余测试用例过多,随机生成的调用链过于复杂,缺少合理的系统化指导,测试生成时间过长或者对人工干预的要求较多等问题。

发明内容

本发明的主要目的是针对传统面向对象随机测试方法存在的无效和冗余测试用例过多、代码覆盖率过低等问题,提出一种面向Java程序的随机测试用例生成方法,应用组合测试技术,分析对象方法之间的依赖关系,系统化生成和维护调用链,自动化生成测试用例。

为实现本发明所述目的,本发明采用如下的步骤:

1)解析Java程序的类定义,获取对象方法列表;利用Java语言提供的反射机制,首先从指定目录读取所有的.class文件和.jar文件;再进一步从.jar文件中析取.class文件,获得Java程序中所有可能包含的类的定义;基于每一个类,根据.class文件读取其中的属性变量和对象方法的定义体;最后输出Java程序的对象方法列表;

2)建立对象方法间的依赖关系图,包括方法调用图和属性依赖图,其中构建方法调用图的过程是:基于对象方法的定义体,发掘对象方法之间是否存在调用与被调用,再构建出方法调用图;

3)确定待覆盖的目标方法列表,组成目标覆盖集                                               ;

4)以公有类的实例化为目的生成调用链,维护类对象库;

5)以目标方法覆盖为目的生成调用链,维护调用链库;

6)基于调用链库生成Java程序的测试用例集。

上述步骤2)中构建方法调用图的过程是:基于对象方法的定义体,发掘对象方法之间是否存在调用与被调用,再构建出方法调用图。

上述步骤2)中构建属性依赖图的过程是:首先将对象方法作为属性依赖图的节点;根据每个对象方法的定义体,通过分析其中每一条语句,确定该对象方法的读属性集合和写属性集合;然后对任意两个对象方法和(节点对),如果的读属性集合和的写属性集合之间的交集不为空集,则添加有向边;从另一角度,如果和之间的交集不为空集,则添加有向边;最后处理完所有对象方法的节点对后,输出属性依赖图。

上述步骤3)中待覆盖的目标方法必须满足4个基本条件:

条件1. 在方法调用图中是顶层节点,即没有其他对象方法调用;或者只有“main”方法调用了;

条件2. 不能是构造方法,构造方法与类同名;

条件3. 必须是公有方法,即由访问修饰符“public”定义;

条件4. 必须是实体方法,即必须有定义体,不能由抽象修饰符“abstract”定义、或者在接口“Interface”类中定义;

从Java程序对象方法列表中,筛选满足上述4个条件的对象方法组成目标覆盖集TC

上述步骤4)中以公有类的实例化为目的生成调用链是指以公有“public”构造方法为覆盖目标生成调用链;用二元组表示,其中代表一个调用链,结尾以“New”操作符调用该构造方法,可能涉及多个调用链的串接;代表所创建的对象;对应的类不能是不可以实例化的抽象“abstract”类或者接口“interface”类;利用Java虚拟机设施运行,通过测试断言判定其运行是否正确;如果正确,则将二元组添加入类对象库;否则将添加入调用链库。

上述以公有构造方法为覆盖目标生成调用链的过程是:首先对Java程序中包含的公有类排序,排除其中的接口类和抽象类;排序的依据是排在前面的类在构造方法中尽量不会引用排在后面的类;然后按顺序对每一个公有类,处理其中每一个公有的构造方法:为构造方法的每一个参数按照参数类型设置级别;再根据参数级别的两两组合,为每一个参数级别组合构造调用链;其中两两组合是指对任意两个参数和,其级别的所有可能组合至少出现一次。

上述判定调用链运行是否正确的测试断言基于以下4个判定组成:

判定1. 运行过程中没有抛出异常;

判定2. 对象不能为空,即“assertNotNull(Obj)”;

判定3. 对象等于自身,即“assertTrue(Obj.equals(Obj))”;

判定4. 对象的散列值等于自身的散列值,即“assertTrue(Obj.hashcode()==Obj.hashcode())”。

上述步骤中按照参数类型设置级别时分以下4种场景:

场景1. 基本数据类型参数,分以下4种情况进行处理:

情况1. 整数类型(包括长整型、短整型和字符型),分为4个级别(Level):0、任一个正整数、任一个负整数、最大值(如Integer.MAX_VALUE);

情况2. 浮点类型(包括单精度和双精度),分为4个级别:0、任一个正浮点数、任一个负浮点数、最大值(如Double.MAX_VALUE);

情况3. 布尔类型,分为2个级别:true、false;

情况4. 枚举类型,分为1个级别:任一个枚举值;

场景2. 字符串类型(即String类),分为4个级别:null、空串、任意长度普通串、任意长度包含特殊字符(如\n)的串;

场景3. 对象类型(排除String类以及同基本数据类型对应的类如Integer、Double等),分3种情况处理:

情况1. 普通类,即可以实例化的类;此时根据类所拥有的公有构造方法数量来决定参数的级别数量:若参数有1个以上的公有构造方法,则的级别为:null、分别与不同构造方法对应的级别;若没有公有构造方法,则为2个级别:null、任一个可以返回该类对象的调用链;

情况2. 接口“Interface”类;此时根据实现“Implement”该接口的普通类数量来决定参数的级别数量;每个级别分别对应一个普通类,在具体取参数值时,按情况1随机选择相应普通类的一个级别赋值;

情况3. 抽象“abstract”类;此时根据继承“extends”该抽象类的普通类数量来决定参数的级别数量;每个级别分别对应一个普通类,在具体取参数值时,按情况1随机选择相应普通类的一个级别赋值;

在上述情况2和情况3中,如果实现或继承的类是抽象类,则进一步按情况3确定继承该抽象类的普通类,数量累计入参数的级别数量。

场景4. 数组类型,按照数组的长度划分级别,分为3个级别:0、1、任一个正整数;对于每一个数组元素,再按照其所属的类型随机选择一个级别来赋值。

上述步骤5)中以目标方法覆盖为目的生成调用链的过程是:首先按照属性依赖图对目标覆盖集中的目标方法排序,排序的依据是排在前面的目标方法尽量在属性操作上不依赖于排在后面的目标方法;然后顺序对每一个目标方法,从运行主体、参数列表、以及属性依赖三个层面分别设置级别,再按照层面级别的两两组合,为每一个层面级别组合构造调用链;所构造的调用链以该目标方法的调用为结尾,可能涉及多个调用链的串接;添加入调用链库中;如果该目标方法返回了类对象库中不存在的类的对象,则应用测试断言判定运行正确后,将二元组添加入类对象库中。

上述步骤中对目标方法按照不同层面设置级别分以下三个层面:

(1)运行主体,指运行目标方法的类(static)或对象。如果是静态(static)方法,不考虑运行主体;如果是非静态方法,则运行主体是实例化的对象,分以下两种情况处理:

情况1. 普通类,同处理对象类型参数时的情况1类似,根据该类所拥有的公有构造方法数量划分级别,不考虑Null,级别数量等同于公有构造方法的数量;

情况2. 抽象类,同处理对象类型参数的情况3类似,根据继承该抽象类的普通类数量划分级别,不考虑Null,级别数量等同于继承该类的普通类数量。

(2)参数列表,参数列表的处理与步骤4)中构造方法的参数列表处理方式相同:按照参数类型设置级别,生成所有参数级别的两两组合。

(3)属性依赖,考虑目标方法所读取的所有属性变量,即读属性集合;按中属性变量的个数设置级别;每一个级别对应任一个写入相应属性变量的目标方法,其写属性集合中包含该属性变量。

上述步骤6)中遵循JUnit框架基于调用链库生成Java程序测试用例集的过程是:首先以Java程序中的类为单位创建“TestCase”类,用以组织调用链;对调用链库中的每一个调用链,按所覆盖的构造方法或目标方法创建一个“Test”方法,其中按步骤4)所述加入测试断言;然后按照所从属的类,将“Test”方法添加入对应的“TestCase”类中;最后将所有非空的“TestCase”类添加入“TestSuite”类中,作为最终生成的Java程序的测试用例集。

本发明通过引入了组合测试技术,充分考虑对象方法间因属性变量操作而导致的依赖关系,有效利用Java语言的反射机制和虚拟机设施,系统化生成和维护调用链,自动生成测试断言,高效率生成测试用例集。基于Java软件程序的实验数据表明,同现有面向Java的随机测试方法相比,为达到相同的代码覆盖率,本发明方法需要更少的测试用例数量;而在同样的测试用例生成时间内,本发明能够生成更多的测试用例;组合测试技术的引入为本发明提供了测试用例生成过程何时终止的合理判定,使得测试用例生成过程更具有系统性。运用本发明时,在测试用例生成过程中完全不需要人工干预,缓解和部分解决了现有随机测试方法存在的无效和冗余测试用例过多、代码覆盖率过低等问题;本发明可广泛应用于大中型Java软件系统的自动化测试工作,适用于单元测试、集成测试和系统测试各个阶段;具有良好的适用性和扩展性。

下面结合附图进行详细说明。

附图说明

图1 是面向Java程序的随机测试用例生成方法的结构图;

图2 是解析Java程序的类定义并获取对象方法列表的工作流程图;

图3 是一个Java程序对象方法列表的示例图;

图4 是构建对象方法间属性依赖图的工作流程图;

图5 是确定待覆盖的目标方法列表的工作流程图;

图6 是以公有类实例化为目的生成调用链的工作流程图;

图7 是对象方法调用链的一个示例图;

图8 是以目标方法覆盖为目的生成调用链的工作流程图;

图9 是基于调用链库生成Java程序测试用例集的工作流程图。

具体实施方式

如图1所示,本发明方法包含六个基本步骤:(1)解析Java程序的类定义,获取对象方法列表;(2)建立对象方法间的依赖关系图,包括方法调用图和属性依赖图;(3)确定待覆盖的目标方法列表;(4)以公有类的实例化为目的生成调用链;(5)以目标方法的覆盖为目的生成调用链;(6)基于调用链库生成Java程序的测试用例集。其间需要维护和扩充类对象库和调用链库,以助于生成有效的测试用例。方法的输入是Java程序,可以由包含Java程序的目录、或者整合了Java程序的jar包(.jar文件)表示;输出是测试用例集,采用JUnit测试用例框架描述。

步骤一是解析Java程序的类定义,并获取对象方法列表;过程如图2所示。一个Java程序由若干“类(class)”组成,每个类的定义中包含“属性”和“方法”,分别称之为“属性变量”和“对象方法”;对象方法可分为两个类别:构造方法和一般方法。所有属性变量和对象方法都可定义访问修饰符,分别是“public”、“protected”、及“private”,其中“public”修饰符代表外部可以访问该对象方法(或属性变量),即公有方法(或公有属性),能够作为测试用例的调用入口。属性变量和对象方法还可以定义静态修饰符“static”,代表该属性变量或对象方法从属于“类”,由该“类”所有实例化的(动态)对象共享。

Java语言提供反射(Reflection)机制,能够根据Java程序的二进制代码(.class文件)读取和识别Java程序的类定义信息。利用反射机制,首先从指定目录读取所有的.class文件和.jar文件;再进一步从.jar文件中析取所有.class文件,获得Java程序中所有可能包含的类定义;基于每一个类,读取其中的属性变量和对象方法定义,最后输出Java程序的对象方法列表。

图3是一个Java程序的部分对象方法列表的示意图,其中包含对象方法的名称、参数列表(参数类型列表)和返回值(类型)、对象方法所属的类名、以及对象方法的访问修饰符。类名用目录形式表示,图中“java/lang/String”代表类“java.lang.String”;单个字母表示的参数或返回值类型代表基础类型,如“I”代表整型;字母“L”代表对象类型,紧跟其后为类名;字母“[”代表数组类型,紧跟其后为数组元素所属的类型。

步骤二是建立对象方法间的依赖关系图,包括方法调用图和属性依赖图。其中方法调用图反映对象方法之间的调用关系,方法调用图的构建可以利用现有的技术,在反射机制所获取的对象方法之定义体的基础上发掘对象方法间是否存在调用与被调用。构建属性依赖图的过程如图4所示。属性依赖图定义为一个有向图,其中为节点集,每一个节点代表一个对象方法;为边集,代表对象方法间的属性依赖关系。每个对象方法的定义体确定了对象方法可能读取的属性变量集合(读属性集合)和可能写入的属性变量集合(写属性集合);对于任意两个对象方法和(),如果的读属性集合和的写属性集合之间的交集不为空集,即,则在属性变量操作上依赖于,即存在一条从到的有向边。

如图4所示,首先利用反射机制获取每个对象方法的定义体,通过分析定义体的每一条语句确定该对象方法的读属性集合和写属性集合;然后对任意两个对象方法和,如果读属性集合和写属性集合之间的交集不为空集,则添加有向边;从另一角度,如果和之间的交集不为空集,则添加有向边;最后处理完所有的对象方法对后,输出属性依赖图。

步骤三是确定待覆盖的目标方法列表,组成目标覆盖集;过程如图5所示。对一个对象方法,成为目标方法(即)需要满足以下4个基本条件:

条件1. 在方法调用图中是顶层节点,即没有其他对象方法调用;或者只有“main”方法调用了;

条件2. 不能是构造方法,构造方法与类同名,在图3中由“<init>”标识。

条件3. 必须是公有方法,即由访问修饰符“public”定义;

条件4. 必须是实体方法,即必须有定义体,不能由抽象修饰符“abstract”定义、或者在接口(Interface)类中定义;

按图5,首先从方法调用图中删除所有“main”方法代表的节点,以及其关联的所有边;然后针对每一个顶层节点所对应的对象方法;如果满足条件2、3和4,则将添加入目标覆盖集;最后处理完所有的顶层节点后,输出目标覆盖集。

步骤四是以公有类的实例化为目的生成调用链,同时维护类对象库;过程如图6所示。公有类指由“public”修饰符定义的外部可以访问的类,一般包含由“public”修饰符定义的构造方法;公有类实例化是指以构造方法为覆盖目标生成调用链,用二元组表示,其中代表一个调用链,代表所创建的对象,对应的类必须可以实例化,不能是抽象(abstract)类或者接口(interface)类;图7所示是一个调用链的示例,其中包含先后执行的对象方法;图中以“new”操作符开头代表对构造方法的调用。

同一般的对象方法类似,构造方法可以包含一组参数;每一个参数可以是基本数据类型,也可以是字符串类型、对象类型或者数组类型。对于基本数据类型参数,分以下4种情况进行处理:

情况1. 整数类型(包括长整型、短整型和字符型),可以分为4个级别(Level):0、任一个正整数、任一个负整数、最大值(如Integer.MAX_VALUE);

情况2. 浮点类型(包括单精度和双精度),可以分为4个级别:0、任一个正浮点数、任一个负浮点数、最大值(如Double.MAX_VALUE);

情况3. 布尔类型,分为2个级别:true、false;

情况4. 枚举类型,分为1个级别:任一个枚举值。

对于字符串类型(即String类),可以分为4个级别:null、空串、任意长度普通串、任意长度包含特殊字符(如\n)的串。

对于对象类型(排除String类以及同基本数据类型对应的类如Integer、Double等),分3种情况处理:

情况1. 普通类,即可以直接实例化的类;此时根据类所拥有的公有构造方法数量来决定参数的级别数量;假设参数对应的类拥有3个公有的构造方法,则可以分为4个级别:null、3个分别与不同构造方法对应的级别。如果对应的类没有公有构造方法,则分为2个级别:null、任一个可以返回该类对象的调用链;

情况2. 接口(Interface)类;此时根据实现(Implement)该接口的普通类数量来决定参数的级别数量;假设参数对应的接口类由3个普通类实现,则可以分为3个级别,分别对应一个普通类;每个级别在具体取参数值时,按情况1随机选择相应普通类的一个级别赋值;

情况3. 抽象(abstract)类;此时根据继承(extends)该抽象类的普通类数量来决定参数的级别数量;假设参数对应的抽象类由3个普通类继承,则可以分为3个级别,分别对应一个普通类;每个级别在具体取参数值时,按情况1随机选择相应普通类的一个级别赋值;

在上述情况2和情况3中,如果实现或继承的类是抽象类,则进一步按情况3确定继承该抽象类的普通类,数量累计入参数的级别数量。

对于数组类型,按照数组的长度划分级别,可分为3个级别:0、1、任一个正整数;对于每一个数组元素,再按照其所属的类型随机选择一个级别来赋值或实例化。

确定所有参数可能划分的级别之后,采用两两组合的方法(组合测试技术)生成所有参数的级别组合。两两组合是指对任意两个参数和,其级别的所有可能组合至少出现一次。令一个构造方法(或对象方法)有三个参数、和,其中设置3个级别(),设置2个级别(),也设置2个级别();则按照两两组合,所生成的参数级别组合如表1所示:

表1

No123456

如图6所示,首先对Java程序中包含的公有(public)类排序,排除其中的接口(interface)类和抽象(abstract)类;排序的依据是排在前面的类在构造方法中不会引用排在后面的类;由于类之间引用关系的复杂性,如类的某个构造方法中引用了类对象做参数,而类的某个构造方法又引用了类对象;上述排序允许忽略一些引用关系。然后对每一个公有类,处理其中每一个公有的构造方法:为构造方法的每一个参数按照其类型设置级别;再根据参数级别的两两组合,为每一个参数级别组合构造调用链。所构造的调用链以该构造方法的调用(new操作符+对应类名和参数列表)为结尾,表示创建了该类的一个对象(实例),对应的二元组可能添加入类对象库中。

不难发现,构造方法中若某个参数本身是对象类型,则该参数所设置的级别需要实例化时,需要在类对象库中查询对应的调用链,嵌入到新生成的之中,这相当于调用链扩展或串接。对于复杂的有多个对象类型参数的构造方法(对象方法),常需要串接多个调用链,形成复杂的测试用例。未避免不必要的串接,需要利用Java虚拟机设施确认准备加入类对象库的运行正确,确认运行正确基于以下4个判定:

判定1. 运行过程中没有抛出异常;

判定2. 对象不能为空,即“assertNotNull(Obj)”;

判定3. 对象等于自身,即“assertTrue(Obj.equals(Obj))”;

判定4. 对象的散列值等于自身的散列值,即“assertTrue(Obj.hashcode()==Obj.hashcode())”。

上述4个判定构成测试断言。最后运行不正确的代表可能发现了程序中的缺陷或问题,因此将存在错误的添加入调用链库。

步骤五是以目标方法覆盖为目的生成调用链,同时维护调用链库;过程如图8所示。目标方法满足步骤三中所述的4个基本条件,可以是一个静态(static)方法,也可以是一个非静态方法;非静态方法需要在实例化的对象中运行,而静态方法直接在类中运行。考虑目标方法覆盖需要关注三个层面:运行主体、参数列表、属性依赖,下面分别予以说明。

(1)运行主体,指运行目标方法的类(static)或对象。如果是静态(static)方法,不考虑运行主体;如果是非静态方法,则运行主体是实例化的对象,分以下两种情况处理:

情况1. 普通类,同处理对象类型参数时的情况1类似,根据该类所拥有的公有构造方法数量划分级别(Level),不考虑Null,级别数量等同于公有构造方法的数量;

情况2. 抽象(abstract)类,同处理对象类型参数的情况3类似,根据继承(extends)该抽象类的普通类数量划分级别,不考虑Null,级别数量等同于继承该类的普通类数量。

(2)参数列表,参数列表的处理与步骤四中公有构造方法的参数列表处理方式相同,生成所有参数级别的两两组合。

(3)属性依赖,考虑目标方法所读取的所有属性变量,即读属性集合;按中属性变量的个数设置级别;例如某个目标方法读取了3个属性变量,则设置3个级别,每一个级别对应任一个写入相应属性变量的目标方法(其写属性集合中包含该属性变量)。

确定所有层面的级别设置后,再按照两两组合生成所有的层面级别组合。假设目标方法“SomeClass.func(…)”是一个非静态方法:运行主体层面,类“SomeClass”有2个公有的构造方法,设置2个级别;参数列表层面,令“func(…)”有3个参数,级别设置与表1相同,于是共有6个参数级别组合;属性依赖层面,令“func(…)”的读属性集合包含3个属性变量,设置3个级别。按两两组合要求,共需要18个层面级别组合,如表2所示:

表2

No运行主体(A)参数列表(B)属性依赖(C)1A1B1C12A2B2C23A1B3C34A2B4C15A1B5C26A2B6C37A2B1C28A1B2C19A1B4C310A2B3C111A2B5C312A1B6C213A1B1C314A1B3C215A1B5C116A1B2C317A1B4C218A1B6C1

对每一个层面级别组合生成一个调用链,所构造的调用链以该目标方法的调用为结尾。与步骤四中情况类似,其中涉及调用链的串接:在运行主体层面,对象实例化本身对应一个调用链;在参数列表层面,每一个对象类型参数可能对应一个调用链;在属性依赖层面,所依赖的每一个目标方法对应一个调用链。

如果该目标方法返回一个类对象库中不存在的类的对象,有两种情况:一是该类不是公有(public)类;二是该类没有定义公有的构造方法。则利用Java虚拟机设施按步骤四中所述方法确认运行是否正确,如果运行正确,将二元组添加入类对象库中。

如图8所示,首先按照属性依赖图对目标覆盖集中的目标方法排序,排序的依据是排在前面的目标方法尽量在属性操作上不依赖于排在后面的目标方法;由于对象方法在属性操作上复杂性,上述排序允许忽略部分属性依赖关系。然后对每一个目标方法,从运行主体、参数列表、以及属性依赖层面分别设置级别,再按照层面级别的两两组合,为每一个层面级别组合构造调用链;所构造的调用链以该目标方法的调用为结尾,可能涉及多个调用链的串接。添加入调用链库中。最后如果该目标方法返回了类对象库中不存在的类的对象,则确认运行正确后,将二元组添加入类对象库中。

上述处理过程中需要在类对象库中和调用链库中查询层面级别设置所对应的调用链,如果不存在指定的(对象实例化或者是目标方法覆盖)调用链,则忽略相应的级别组合。

步骤六是基于调用链库生成Java程序的测试用例集;过程如图9所示。测试用例按照JUnit框架组织,以Java程序中的类为单位创建“TestCase”类,用以组织调用链。首先获取调用链库,对库中的每一个调用链,按所覆盖的构造方法或目标方法创建一个“Test”方法,其中按步骤四所述加入测试断言;然后按照所从属的类,将“Test”方法添加入对应的“TestCase”类中;最后将所有非空的“TestCase”类添加入“TestSuite”类中,作为最终生成的Java程序的测试用例集。

本发明方法引入了组合测试技术,充分考虑对象方法间因属性变量操作而导致的依赖关系,有效利用Java程序的反射机制和虚拟机设施,系统化生成和维护调用链,自动生成测试断言,高效率生成测试用例集。我们基于四个不同规模和类型的Java软件程序应用本发明方法生成测试用例,并同Randoop等测试用例生成方法相比较;实验数据表明,同现有面向Java的随机测试方法相比,为达到相同的代码覆盖率,本发明方法需要更少的测试用例数量;而在同样的测试用例生成时间内,本发明方法能够生成更多的测试用例;最后,组合测试技术的引入为本发明方法提供了测试用例生成过程何时终止的合理判定,使得测试用例生成过程更具有系统性。运用本发明方法,在测试用例生成过程中完全不需要人工干预,缓解和部分解决现有随机测试方法存在的无效和冗余测试用例过多、代码覆盖率过低等问题;具有良好的适用性和扩展性。

去获取专利,查看全文>

相似文献

  • 专利
  • 中文文献
  • 外文文献
获取专利

客服邮箱:kefu@zhangqiaokeyan.com

京公网安备:11010802029741号 ICP备案号:京ICP备15016152号-6 六维联合信息科技 (北京) 有限公司©版权所有
  • 客服微信

  • 服务号