JUTA一个Java自动化单元测试工具严俊.docx
- 文档编号:27509917
- 上传时间:2023-07-02
- 格式:DOCX
- 页数:29
- 大小:33.82KB
JUTA一个Java自动化单元测试工具严俊.docx
《JUTA一个Java自动化单元测试工具严俊.docx》由会员分享,可在线阅读,更多相关《JUTA一个Java自动化单元测试工具严俊.docx(29页珍藏版)》请在冰豆网上搜索。
JUTA一个Java自动化单元测试工具严俊
JUTA:
一个Java自动化单元测试工具
严 俊1 郭 涛2 阮 辉1 玄跻峰3
1(中国科学院软件研究所 北京 100190)
2(中国信息安全测评中心 北京 100085)
3(大连理工大学数学科学学院 辽宁大连 116024)
(junyan@acm.org)
JUTA:
AnAutomatedUnitTestingFrameworkforJava
YanJun1,GuoTao2,RuanHui1,andXuanJifeng3
1(InstituteofSoftware,ChineseAcademyofSciences,Beijing100190)
2(ChinaInformationTechnologySecurityEvaluationCenter,Beijing100085)
3(SchoolofMathematicalSciences,DalianUniversityofTechnology,Dalian,Liaoning116024)
Abstract Testingisveryimportantandtimeconsuminginthedevelopmentofhigh-qualitysoftware
systems.ThispaperproposesanautomatictestingtoolJUTAforunittestingofJavaprograms.The
approachisbasedonsharpanalysisoftheprograms.JUTAfirstlyemploystheJavaoptimization
frameworkSoottoparseasingleJavamethodintobytecodeandtranslatesitintoacontrolflowgraph
(CFG).Itthenperformsdepth-firstorbreadth-firstsearchontheCFGtoextractpathsfromit.Some
techniquessuchaspathlengthrestrictionareusedtopreventpathnumberexplosion.FinallyJUTA
analyzesthepathsbasedonthecombinationofsymbolicexecutionandconstraintsolving.Thegoalof
pathanalysisliesintwofolds.Itcangenerateasetoftestcasessatisfyingthetestcriterionsuchas
statementcoverage.Thetestsettypicallyhassmallnumberoftestcasesthatareallexecutable.In
additiontotestgenerationfordynamictesting,itcanalsobeusedinstatictesting.JUTAcanreveal
certainkindsoferrorsfromthesourcecodeautomaticallyiftheuserprovidesproperassertionsto
describetheerrors.Theexperimentalresultsshowthatthistoolisefficientforbothdynamicand
statictesting.
Keywords Javaunittesting;dynamictesting;statictesting;programanalysis;symbolicexecution
摘 要 描述了一个Java自动化的单元测试工具JUTA.JUTA首先调用工具Soot解析单个Java方法
的源码,并将源码解析成一个控制流图.在此基础上,采用符号执行的方法分析控制流图上的路径.工具
能够自动地产生满足覆盖率标准的程序的测试用例.这种方法产生的所有测试用例都是可执行的,并且
一般来说具有较小的测试用例数.如果用户能够合理地给出描述程序错误的断言,框架JUTA能够自
动地检查源码中部分特定类型的错误.实验结果表明工具对Java单元代码的动态测试和静态测试均能
在可接受的时间内给出有效的结果.
关键词 Java单元测试;动态测试;静态测试;程序分析;符号执行
中图法分类号 TP311
0 引 言
软件质量历来就是学术界和工业界共同关心的
问题,因为软件质量问题造成巨大经济损失事件不
胜枚举.当前,随着信息技术的迅速发展,特别是互
联网技术的广泛应用,Java技术以其通用性、平台
移植性、安全性等特征,吸引了众多软件开发者,在
各个重要的行业部门得到了广泛的应用.因此,如何
提高软件质量,尤其是提高应用广泛的Java程序的
可靠性,长期以来一直是一个非常重要的研究课题.
软件测试是提高软件质量的重要手段.优秀的
软件开发机构把40%的工作量花在软件测试上,软
件测试费用则占到了软件开发总费用的30%~
50%,而对于一些要求高可靠性、高安全性的软件,
测试费用已经抬高到了整个软件项目开发所需费用
的3~5倍.
单元测试是软件测试的早期阶段,这种测试方
法主要测试程序的单元函数或者类中的方法,其主
要原理是对源代码中的控制结构和处理过程等进行
分析,检查程序内部处理是否正确,包括语句结构、
分支和循环结构等.由于单元测试需要分析源代码,
所以测试的代价往往比较高.目前,在工业界,单元
测试大多仍然是靠人工完成的,如手工设计测试用
例、人工审阅代码等,不但十分繁琐,而且测试质量
与测试人员的主观经验紧密相关.随着计算机软硬
件规模的不断发展,以人工为主的测试方法已经无
法满足测试的需求.目前,采用计算机辅助的自动化
软件测试技术成为软件工程中一个活跃的研究
领域.
本文介绍一个Java自动化的单元测试工具
JUTA(anunittestingandanalyzingtoolforJava).
与Junit等传统测试框架不同,JUTA提供了一系
列的精度和自动化程度较高的技术来生成测试用例
以及检查代码中的错误,以提高测试的自动化程度
和效率.
1 预备知识
本节将简要介绍JUTA采用的控制流和数据
流分析技术.
1.1 控制流图
形式化地说,控制流图(controlflowgraph,
CFG)是一个有向图G=N,E,s,f,其中N代表
节点的集合,E代表边的集合,s和f分别表示控制
流图的起点和终点.图中每个点n∈N代表程序中
的一系列顺序运算(即这些语句在程序执行过程中
要么全部都执行,要么全部都不执行),或者说是一
个基本块(basicblock);而每条边,e=(ni,nj)∈E,
代表从节点ni到nj的一个转移.用流图作为程序结
构的模型时,程序的运行过程可以用流图中的路径
来刻画.控制流图中的一条执行路径p代表一个执
行序列p=n1,n2,…,nm,其中m是路径的长度,
(ni,ni+1)∈E(1≤i 须从起点s开始.我们称n1=s的这条路径为部分 路径(partialpath).如果n1=s并且nm=f,我们称 p是程序的一条完整路径(completepath).在下文 中,如无特殊说明,我们所说的程序路径都是完整 路径. 例1.图1为一段简单的代码及其控制流图.函 数foo根据输入i和j的值计算参数good的值并 返回.从控制流图上易知程序有3条从起点S到终 点F的完整路径: p1=S,S1,S2,S3,S4,F,p2= S,S1,S2,S3,F,p3=S,S1,F. publicintfoo(inti,intj){ *S* intgood=TRUE; *S1* if((i>2)&&(j>3)){ *S2* j--; *S3* if(i+2*j<5) *S4* good=FALSE; } *F*returngood; } Fig.1 Apieceofexamplecode. 图1 一段示例代码 对于基于控制流图的结构测试来说,主要的测 试方法就是找到一系列的可行测试路径,这些路径 的输入数据就是测试用例.例如在例1中,{i=3, j=4}就是对应于路径p2的一个测试用例.测试人 员通过执行这些测试用例来检查程序的错误.显然, 由于程序代码的复杂性,通过枚举所有的测试路径 来测试程序是不现实的.为了减少测试的代价,人们 定义了一系列的测试覆盖准则(coveragecriterion), 作为测试的标准来限制测试集的大小[1].最简单的 控制流覆盖准是语句覆盖(statementcoverage,即 在软件测试过程中,只有当程序中的所有语句都得 到了运行,才能称该测试是充分的)和分支覆盖 (branchcoverage,即要求在软件测试过程中,所有 1841严 俊等: JUTA: 一个Java自动化单元测试工具 的控制转移都得到了覆盖).由于在控制流图模型 中,语句与节点相对应,语句覆盖意味着测试路径覆 盖所有的节点.同样地,分支覆盖就是所有的测试路 径覆盖了控制流图中所有的边. 1.2 基于路径的程序测试 程序中的一次完整的执行过程对应控制流图中 的一条完整路径.但是,并不是所有的完整路径都代 表程序中的一次运行过程.这是因为可能不存在输 入数据使得程序按照该路径执行.这样的完整路径 称为不可行路径(infeasiblepath),否则称为可行的 (feasible)[2].例如,在例1中,路径p1执行的语句 如图2所示(符号“@”后的表达式表示程序执行该 逻辑判断为真的分支).易知,程序执行到第2个判 断语句时,表达式(i+2×j<5)不可能满足,程序控 制流图中的分支S3-S4不可能被执行到.那么,路 径p1是一条不可行的路径. good=TRUE; @((i>2)&&(j>3)); j=j-1; @(i+2×j<5); good=FALSE; Fig.2 Pathp1. 图2 路径p1 由于控制流测试准则都是基于控制流图中的测 试路径来定义,很自然地,我们可以采用如下方法来 生成测试用例: 首先从控制流图中找到一组路径满 足覆盖准则,然后从每条路径中找出各变量的一组 初始值使得程序能够按照路径执行,这些初值就构 成了测试用例集合.一般来说,一个自动测试用例生 成(automatedtestcasegeneration)系统主要可以 分成3个部分[3]: 1.程序分析器(programanalyzer),其主要工 作是把程序转化成容易处理的中间模型(一般就是 我们前面提到过的控制流图); 2.路径选择器(pathselector),如何选择路径 满足我们的测试需求(比如覆盖准则); 3.测试数据生成器(testdatagenerator),如何 从测试路径中得到我们需要的测试数据. 在实际开发中,程序分析器也被称为前端 (frontend),后两部分合起来被称为后端(backend). 这样的自动测试生成的系统主要用于程序的动 态测试,即采用测试用例测试被测程序.与动态测试 对应的静态测试方法,其框架与测试生成大体相同. 主要的不同指出在于其输出并不是测试用例,而是 直接报告程序是否包含某类错误,或者给出能检出 某类错误的具体的测试数据. 由于程序测试的需求众多,实际的测试系统尤 其是商用系统,可能还包括一些其他的辅助模块,比 如测试脚本的自动生成器等. 1.3 路径分析方法 不可行路径给自动化测试带来了一个难题,我 们不可能简单地通过从程序控制流图的信息中抽取 路径来选择测试用例,在测试用例选择时必须分析 数据流的信息.为了精确地分析控制流图中一条完整 的路径,我们可以采用符号执行(symbolicexecution)[4] 的方法.所谓符号执行,相对于普通的程序执行来 说,就是程序并不真正用数值来替换程序的变量,而 是用一组关于初始值的表达是来表示程序中出现的 项.用符号执行加约束求解进行程序分析的基本思 想是: 采用Hoare逻辑可以将程序路径表示成{P} Q{R},其中P是程序的前置条件(pre-condition.执 行程序前需要满足的条件,或者称为前断言),R是 程序的后置条件(post-condition,程序执行后需要 满足的条件,或者称为后断言).假定在程序的符号 执行过程中由{P}Q可以推导出约束条件c1∧c2∧… ∧cn,则应该有c1∧c2∧…∧cn※R.在一般的测试 中,如果后断言永真,那么可以对约束条件c1∧c2∧… ∧cn求解,可以得到变量的初值,从而得到测试用 例.另一方面,我们可以对约束c1∧c2∧…∧cn∧瓙 R求解,如果有一组解满足这一约束,说明存在一组 输入使运行程序的结果和规范不符.如果程序的规 范正确,则程序中必定包含错误. 对于不包含函数调用代码来说,程序的语句 可以分成3类: 1)动作(action),也就是赋值语句; 2)判断语句(predicate),也就是一个逻辑表达式; 3)其他不影响变量的表达式,如控制台打印语句 System.out.println.我们用一个具体的例子说明符 号执行的原理.考虑图2的路径p1,如果i和j的初 值为i0和j0,我们可以自顶向下依次执行这段程 序,用程序的赋值语句替换判断语句,最后得到一个 只含有判断语句的约束集合{i0+2×(j0-1)<5; (i0>2)&&(j0>3)}.这个集合就是程序初值的 约束.由于这个约束集是不可满足的,所以路径p1 是不可行的.关于符号执行的具体细节请参考文献 [2,5-6]. 符号执行的好处是能够精确地分析程序的行 为,但它的缺点也是显而易见的.由于符号执行方法 过于依赖源代码,并且需要消耗较大的计算资源,因 而无法精确分析带有很多模块调用(特别是当这些 子模块代码不可见的时候)的程序.同时,程序需要 能够用约束精确模拟复杂程序结构,比如指针和数 组的执行,这是一件很困难的事情.最后,为了保证 分析的精确性,符号执行需要有一个能力非常强的 完备解法器(completesolver,就是说,只要约束有 解,解法器必然能够给出一组解).针对不同的程序, 这个解法器可能需要处理位操作等复杂的约束.这 一点也限制了符号执行方法的使用.工具JUTA中 采用较为简单的方式在工具的处理能力和精确度之 前作了一个平衡.我们将程序中的表达式限制为布 尔逻辑和线性数值约束两种类型,并且将程序的外 部调用抽象成简单的赋值语句和后置断言.具体内 容见本文第2节. 2 工具JUTA的设计与实现 本节将详细介绍JUTA工具的流程以及工具 的模块组成,同时对每一个模块给出其功能以及简 要的描述,主要目的是为工具提供一个整体的框架 描述. 2.1 工具流程 为了便于理解源程序需要对程序的源代码进 行一定的处理.首先要做的就是编译分析,包括语 法语义错误的检查、建立中间表示(intermediate representation,IR)并生成控制流图等.同时为了以 后的分析,我们还需要识别出源代码中的循环,并对 循环进行处理,建立一棵循环控制树.这部分通常成 为前端处理.有了前端处理的程序结构和数据信息, 我们就可以通过算法遍历控制流图,得到一系列的 路径,并通过符号执行和约束求解来分析这些路径, 对其可行性进行判断,对于可行的路径为其生成测 试数据,最后收集这些测试数据就得到了满足某些 性质的目标测试数据集.因而,从整体上工具分为两 个部分: 前端处理和后端分析,其整体结构如图3 所示: Fig.3 FrameworkofJUTA. 图3 JUTA的框架 目前,工具JUTA实现了两方面的功能: 静态 测试与动态测试.本文1.2节曾指出,这两种测试方 法可以公用相同的前端,主要区别在于工具的后端. 接下来我们将按照功能分别介绍工具的各模块. 2.2 工具输入 工具输入包含两部分.首先是被测的Java源 码.源码中包含被测Java代码的单个方法.由于工 具JUTA的主要功能是完成单元测试,所以,工具 将丢弃方法中的外部调用.用户在测试时,需要手工 将重要的外部调用替换成赋值语句或者约束表达 式.例如,对于语句 y=foo(x), 假定外部调用foo将y的值改变为foox,并且这个 值的范围是[lb,ub].那么可以将这行代码替换为: y=foox; assert(lb<=foox&&foox<=ub); 另一部分的输入是一些描述性质的断言以及断 言的插入位置.具体请参见本文2.4.2节. 2.3 前端处理 前端分析整体上相当于一个编译器的前端,功 能上相当于一个不产生目标代码的编译器.在这一 部分我们借用的是Java分析优化工具框架Soot[7] 的编译分析功能. Soot是一个功能很强大的分析框架,融合了编 译前端以及分析器的各种功能.工具JUTA的前端 在Soot框架下扩展其功能,产生后端分析所需要的 中间结果.在JUTA中,我们主要使用了Soot的语 法语义检查、中间表示、循环控制分析以及控制流图 生成等模块.接下来简要介绍这几个模块的功能. 2.3.1 语法语义检查模块 该模块主要完成源代码语法语义的检查,确保 给定的输入程序是没有语法语义错误的,同时建立 一棵等价于源程序的抽象语法树. 2.3.2 中间代码生成模块 这部分通过遍历抽象语法树,对各种语句进行 处理,产生中间表示,便于以后的分析和处理.Soot 提供了4种中间表示形式: Baf,Jimple,Shimple和 Grimp.工具JUTA使用了Soot的Jimple中间表 示.Jimple是一种简单的带类型的3地址表示形式. 例如图4为Java源码与对应的Jimple代码. 图4(a)所示的一段简单的Java代码,经过 Soot翻译后得到图4(b)所示: publicstaticintfunc(intx){ inty=-1; if(x>0) y=1; else y=2; returny; } publicstaticintfunc(int){ intx,y,temp$0,temp$1; x: =@parameter0: int; y=-1; ifx>0gotolabel0; gotolabel1; label0: nop; temp$0=1; y=temp$0; gotolabel2; label1: nop; temp$1=2; y=temp$1; label2: nop; returny; } (a)(b) Fig.4 JavasourcecodeandthecorrespondingJimple code.(a)Javasourceand(b)Jimplecode. 图4 Java源码与对应的Jimple代码.(a)Java源码; (b)Jimple代码 2.3.3 控制流图建立模块 本模块通过遍历中间表示的列表,将中间表示 序列划分为基本块,控制流图即是以基本块为节点 的有向图,描述了源代码中的控制流.工具JUTA 使用Soot中的BriefBlockGraph,只为源代码中基 本的控制流提供支持,目前无法处理异常(exception). 2.3.4 循环控制分析模块 这一部分主要是分析控制流中的循环,Soot中 将每一个循环抽象成一个对象,每一个循环都存储 自己的循环头部以及循环体.一般来说,结构化的程 序中的循环分成3类,孤立循环(singleloop)、嵌套 循环(nestingloop)以及串联的循环(concatenated loop)[8].我们可以建立一个抽象的循环控制森林, 森林中每一个节点是一个表示循环的对象,用来描 述循环之间的关系.循环A是循环B的子节点,当 且仅当循环B内嵌在循环A之中.当被测程序中不 包含串联循环时,循环控制森林退化为循环控制树. 2.4 后端分析 本节将介绍工具的后端.考虑到动静态测试的 不同之处,我们将分别介绍动态和静态测试的功能 模块,之后对两者进行一个比较. 2.4.1 动态测试后端 动态测试的目标是生成一组满足覆盖率的测试 数据.所以,动态测试的后端需要分析前端处理后得 到的数据信息(包括中间表示、控制流图以及循环控 制树),根据路径产生策略以及覆盖准则产生满足要 求的路径,并且通过分析路径的可行性得到测试数 据.这一部分有以下4个模块: 路径生成、路径转换、 路径分析以及路径选择. 1)路径生成模块 这一模块将分析由前端处理得到的控制流图, 并抽取出一系列的路径.这些路径可以用基本块的 编号序列表示.路径的产生过程是通过遍历控制流 图来得到的.由于程序中可能有循环,那么,从图中 可能抽取出无限条路径.为了产生有限条测试路径, 我们可以限制循环次数或者限制路径的长度(路径 表示中基本块的个数).在工具JUTA中可以通过 参数来选择使用的路径限制策略. 路径产生是一个遍历控制流图各顶点的过程, 在图的遍历算法中有两种常见的策略: 深度优先遍 历(DFS)和宽度优先遍历(BFS).当采用BFS算法 时,是按照路径长度由短到长的次序生成路径,所以 在队列中需要存储最多两层的中间节点,当路径长 度限制比较大或者循环次数限制值比较大时,队列 中需要存储很多的节点.在使用DFS算法时,由于 优先考虑循环的退出节点,在栈中存储的元素数目 比较少,但是生成的最终路径不是按照由短到长的 顺序.而且很多路径都是由几个相同的基本块所组 成.由上述的比较可知,使用DFS占用较少的系统资 源,但是在处理过程中,尤其是在CFG包含环时容易 陷入一个分支无法跳出,无法快速达到覆盖率指标, 比较适用于静态测试;使得BFS能够较快达到覆盖 率,但是代价相对较高,适用于在测试覆盖准则指导 下的测试生成.工具JUTA的路径生成模块实现了这 两种策略,用户也可以用参数指定所使用的遍历策 略.在默认情况下使用的是深度优先. 2)路径转换模块 前面1.3节中介绍过,对于不包含函数调用代 码来说,程序的语句可以分成3类.而我们主要关注 其中两类语句,即赋值和判断.路径转换模块将采用 基本块的编号序列表示的路径,根据基本块对应的 Jimple中间表示,翻译成一些简单的赋值和判断语 句,方便下一步的路径分析. 3)路径分析模块 这一部分主要是用来对产生的路径进行分析. 张健等人在文献[2,5-6]中提出了基于符号执行以 及约束求解的路径
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- JUTA 一个 Java 自动化 单元测试 工具