【IT168 技术文档】
软件测试是保证软件质量的重要手段。软件自动化测试对于提高测试效率与测试质量起着重要作用,但由于被测系统的复杂性,使得软件自动化测试变得难以实施。本文就是基于RFT的三层测试框架的基础上,提出了脚本修复技术,提高脚本的使用率。
1 基于RFT三层测试框架
由于GUI软件测试自身多变的特点,目前在软件测试脚本生成技术上主要采用分层的概念,尽可能低地降低界面变化对测试脚本的影响。本文就是基于RFT的三层测试框架的基础上,提出了脚本修复技术,提高脚本的使用率。下图就是典型的三层测试框架结构。

图1. 三层测试框架结构
1.1 对象的表示
在描述GUI对象的表示时,本文将使用Rational Functional Tester来获得被测程序的所有对象。图描述了Rational Functional Tester对捕捉到的所有被测对象的组织。点击MyNotePad程序中About菜单中AboutMyNotePad菜单项时弹出的About Dialog对话框。图描述了Rational Functional Tester中MyNotePad程序中的对象组织结构。
可以看到,MyNotePad被组织成了一棵根节点是“MyNotePad”的树,它有3个子节点,分别为About Dialog对话框、MenuBar和TextArea。About Dialog对话框有2个儿子节点,分别为OK按钮和一个label存有about信息。MenuBar有3个子节点,分别为File、Edit和About。TextArea没有子节点。
可以看到,Rational Functional Tester将AboutDialog对话框作为根节点MyNotePad的直接子节点。事实上,Rational Functional Tester会将所有的弹出窗口存为根节点的直接子节点。使用这种组织结构,不可能从图中了解到弹出窗口和其它GUI组件之间的关系。比如,AboutDialog对话框应该在什么状况下弹出?对于这种问题无从得知,所以无法完整的表示GUI上对象之间的关系。为了解决这个问题,本文调整了被测程序的GUI对象组织结构,形成了一颗新的树形结构的图,称之为对象树。如图所示。这样的树形结构可以很容易的看出各个组件之间的依赖关系。

图2. 树形结构

图3. 各个组件之间的依赖关系
对象层在这一层中,要将元素名与测试内部对象名独立出来。界面对象映射脚本实现界面对象逻辑名与被测试软件真实界面对象之间的映射关联。界面对象映射脚本是自动化测试的关键,它可以使测试工程师和自动化测试脚本开发工程师进行工作分工,实现测试开发与软件开发的同步。在软件需求确定后,测试工程师就可以开发测试脚本;而自动化工程师根据与测试工程师约定的界面对象的逻辑名来编写测试脚本。最后,测试工程师开发的测试脚本与自动化工程师开发的测试脚本通过界面对象映射脚本关联成一个有机的自动化测试脚本集。
事件层事件层主要实施一些常用的测试操作,如文本内容录入、菜单选中、列表框内容选择、按钮单击、预期输出结果验证等。它是和对象相互对应的,每一个对象都应该有自己的事件集合。对常用的软件使用操作,可以针对每个类型控件设计一个通用脚本,控件识别 ID 和相关联操作(单击、输入、选中等)作为该脚本的输入参数。对测试预期结果验证也可以使用类似的方法设计针对特定控件的验证脚本,不同之处在于其输入参数是验证的属性和验证结果值。由于所有软件的使用和测试都是这些基本操作的组合,因而在不同的项目之间,重用脚本是一致的。不同的是测试所使用的测试数据从不同的数据文件内读取,因此,这些脚本可以在不同的项目中实施共享,实现一次编写,多处共享,减少脚本的数量,从而降低脚本的维护工作量。同时要将数据分离出来,数据可以来自文件或者自动生成。因此要在事件层中为数据的输入和输出提供接口。
脚本层测试脚本实施对特定功能点和业务功能的测试。是一种针对特定的、被测试软件项目的脚本。其主要功能包括:从测试数据文件内读取测试数据。调用重用脚本和对象映射脚本将这些数据输入到被测试软件的特定对象中。并验证测试结果与预期输出的一致性。记录 Log 和 Bug 现象。它是由重用脚本依据一定的业务流程和特定操作流程组成。
2 GUI空间依赖关系的引入
在同一GUI界面的控件之间或不同界面的控件之间,都有可能存在一些相互关联,相互依赖的关系,称为GUI控件依赖关系。依赖关系在具体表现形式为:一个控件的执行是另一控件执行的结果或者导致了另一控件的执行。例如,本来菜单项是不可操作的,对界面上某一个GUI控件操作后,激活了菜单项。
控件之间的关联关系是通过用户和控件的交互或控件之间的消息而激发的。而想要从GUI软件界面中直接获得控件之间的依赖关系是不实际的,并且很难保证正确性和完整性。因此,在实现时往往需要根据系统的需求和软件设计说明书,人工进行调整。为了方便确定控件之间的关联关系,根据各个控件的类型特点,大致可把控件分为下面两类:
- 行为控件 如界面上的菜单,按钮等控件,用户与之交互会触发某些事件。
- 信息控件 如文本框,编辑框等控件,它们可以接受用户的输入数据或根据某些控件条件输出数据。
一般来说,行为控件的执行和一个或多个信息控件相依赖,信息控件为行为控件的执行提供输入或输出数据。
2.1 事件依赖关系图
事件依赖关系序列图可以表示控件之间的依赖关系。它由有限个(e1,e2...en)事件序列组成,每一个节点都表示一个对象控件对应的事件,有向边则表示了程序执行时界面控件之间的执行时序和之间的依赖关系。
事件依赖关系序列图其实就是以图形符号的方式形式化的来表示界面控件之间的依赖关系的有向图。其中用
来表示一个事件,其属性包括对象元素名称,输入格式,输入参数,前驱顶点,后继顶点等;有向边可用“
”表示,表示系统中控件执行的时序和事件路径;依赖关系可用“
”表示,表示同一界面事件之间的依赖关系;用“
”表示事件e1到事件en省略了若干事件。关联图中如果一个顶点没有前驱顶点,则叫做起点;如果一个顶点没有后继顶点,则叫终点。一个完整的事件依赖关系图,必须能够从起点走到终点,表明了该事件序列是合法的,可达的。
下图所示为一个简单的事件依赖关系序列图。其中e1为起点,en为终点,e2事件制约着e3事件,e3事件依赖着e2事件。
![]()
图4. 一个简单的事件依赖关系序列图
在事件依赖关系序列图中,依赖关系是事件序列能否正常运行的关键。如果能保证在有依赖关系的事件ei出现之前,其依赖事件ej已经出现,这样就可以保证ei的正确运行。通过依赖事件序列生成算法,可以找出ei的所有依赖事件ej,并将ej写入到的ei',ei'为对象i的默认操作,该操作包含了对象i的所有依赖事件。如图5所示用ei'保证了在调用对象i之前,所有依赖事件已经出现。对于ei其依赖事件有两类来源:
- 父节点所有子节点都依赖其父节点,只有父节点的对象处于活动状态,子节点的事件才能被调用。
- 事先指定好的依赖节点测试人员往往需要根据系统的需求和软件设计说明书,人工进行调整,在对象树中将控件A所依赖的控件节点信息,写入到该控件A的依赖节点列表中。

图5. 依赖事件关系图
设集合I保存测试对象Oi的所有依赖节点,依赖事件生成算法的步骤如下:
算法输入:对象树状信息文件
算法输出:生成有依赖关系对象的依赖事件
步骤1:输入对象树状信息文件;
步骤2:遍历对象树中所有节点;
步骤3:从对象集合中找到对象Oi,并将其标识为已访问,其父节点为Oj,其依赖节点为Ok,I=I∪{Oj,Ok};
步骤4:访问集合I,如果集合为空,则转入步骤9;
步骤5:依次访问集合I中的对象,并重复步骤3;
步骤6:当集合I不再变化时,则找到对象Oi所有依赖节点;
步骤7:访问集合I中对象,获得层级码,和该对象的默认事件;
步骤8:将默认事件按照层级码的方式组织起来,生成ei';
步骤9:如果对象树中仍有未访问节点,清空集合I,转到步骤3;
步骤10:至此,遍历结束,生成所有有依赖关系对象的依赖事件ej',算法结束。
3 脚本修复技术的提出
脚本修复技术是修复非法的事件序列,使得这些脚本可以在修改过的GUI上执行。表1中将GUI的变化主要是由3类构成的,对象的增加(例如增加了新的按钮),对象的删除(原有按钮被删除),对象的修改(原先的按钮变成了菜单选项)[30]。如果将这三类分析清楚,那么脚本修复功能就能够实现。除此之外,还有考虑依赖关系,因为对象如果存在被依赖关系,那么它的改变势必会对其产生依赖的对象产生一定的影响,因此可以将GUI的变化分为六类。下面分别介绍这六种状况。
表1 GUI的六类变化
对象变化类型 是否被依赖
增加对象 独立
增加对象 存在被依赖关系
删除对象 独立
删除对象 存在被依赖关系
修改对象 独立
修改对象 存在被依赖关系
3.1 增加对象
(1) 该对象不存在被依赖关系
存在事件序列(e1,e2...en)。新增对象i,如果在该序列中不存在对对象i的依赖事件,那么关于i的事件ei可以不放入该队列中。如图6中“事件序列图1”所示,因此原事件序列可以不用改变。

图6. 事件序列图1
(2)该对象存在依赖关系
存在事件序列(e1,e2...en)。如果在该序列中存在对对象i的依赖事件,即ei是某个事件运行的前提条件。那么关于i的事件ei要放入该队列中。将ei放入依赖事件之前,这样可以保证原理的事件序列可以正常运行。因为对象树被更新,所有对象的事件集合也会被更新。因此在事件序列中只需要将原来ex(依赖于ei),更换为ex'(ex'为默认操作,包含了对i的操作)。如图7中“事件序列图2”所示,ex'替代了ex加入到新的事件序列中。

图7. 事件序列图2
可见对于新增对象,只要原有事件序列没有产生对其的依赖,原有事件序列依然可以正常运行。
3.2 删除对象
(1) 该对象不存在被依赖关系
存在事件序列(e1,e2...ei...en)。如果在该序列中不存在对对象i的依赖事件,那么关于i的事件ei可以直接从该队列中删除。如图8中“事件序列图3”所示,因此原事件序列只用删除ei事件。

图8. 事件序列图3
(2) 该对象存在被依赖关系
存在事件序列(e1,e2...ei...en)。如果在该序列中存在对对象i的依赖事件,那么除了关于i的事件ei要被删除之外,同时要将ex更改为ex'(ex'为默认操作,在默认操作中也删除了对i的依赖事件)。如图9中“事件序列图4”所示,新的事件序列中原有ei被删除。

图9. 事件序列图4
对于删除对象来说,判断该对象的依赖关系是修复事件序列的前提。而这些都可以通过更新对象层和事件层来完成。
3.3 修改对象
(1) 该对象不存在被依赖关系
存在事件序列(e1,e2...ei...en)。如果在该序列中不存在对对象i的依赖事件,那么原事件序列不要更改,只需要更新ei,因为对象树被更新,因此可以使用事件编辑器对ei进行更新。如图10“事件序列图5”所示,原事件序列没有改变。

图10. 事件序列图5
(2) 该对象存在被依赖关系
同图5所示,利用事件编辑器更新事件集合,原有事件序列不需要改变。
对于修改对象,其实可以看成是先删除对象,在新增一个对象。它的处理方式相对简单。
在脚本修复技术中,所有的事件序列的处理,都是要有对象和事件作为支持的,同时判断对象之间的依赖关系也是尤为重要的技术环节。所以在对脚本修复之间,需要动态的更新对象层与事件层的相关信息。
测试脚本修复思想产生的最初考虑就是以现有测试脚本为基础,尽可能使更多的原脚本可以稍加修改就可以用于频繁的回归测试过程。所以它完全可以作为一个现有方法的扩展,在计算能力足够强大或者时间要求相对宽松的条件下为了满足测试覆盖标准的要求(考虑到某一个或者某几个事件的重要性或者排除它们受到影响的可能,需要这些事件必须出现在回归测试集的脚本当中,就必须对该方法进行扩展)而进行应用。
4 脚本修复技术的实现
脚本修复的模块设计如下图所示,待测系统1为旧系统,待测系统2为新系统。当输入两个对象集合之后,通过对比,获得发生变更的对象3;同时更新对象3的事件操作,并找出涉及到该对象3的测试脚本;最后对脚本进行修复,生成可重用的测试脚本。

图11. 解析文件生成代码的流程图
脚本修复器的实现要考虑的情况比较多,也比较复杂。因此对于脚本修复的实现主要从三个大类进行实现,即新增对象,删除对象,修改对象:
1)对新增节点的处理流程如图所示:

图12. 新增节点处理流程图
具体算法实现如下:
// 更新脚本,参数为脚本名称,新增对象列表
Public boolean UpdateScript(String Testcasename,Element newlist)
{
int size = getaddedNode.size();// 节点个数
int i;
String nodename;
String depend;// 依赖节点名称
Boolean update = false;// 更新标记位
for(i=0;i<size;i++)// 遍历节点
{
if(depend.equalsIgnoreCase(“true”))// 存在依赖关系
{
if(operations.findefunction(nodename))// 找到该节点
{
String default = nodename + “default”;// 默认函数名称
operations.instead(nodename,default);// 用默认函数代替原函数
update = true;
}
else
continue;
}
Return update;// 返回 true 或者 false
}
5 脚本修复技术的应用
在RFT中3层框架的表示,appObjects代表对象层,managers代表事件层和对象层一一对应,最后是testcases脚本层。此外还有一些辅助的类用来进行其他操作。

图13. RFT中3层框架
5.1 对象层(appobject)
这里以一个简单的对话框来描述对象的树结构:对象捕获之后对象的层次结构。

图14. 对象捕获之后对象的层次结构
在RFT中的GUI树形图

图15. 在RFT中的GUI树形图
GUI元素对象化:
所有被识别到的GUI被测对象都被记录在Rational Functional Tester的测试对象映射文件中。Rational Functional Tester的测试对象映像文件是一个后缀为rftxmap的XML文件。通过对XML的解析,直接返回GUI对象。为下一步生成相关事件做准备。
public WButton getButtonOK()
{
return new WButton(new TestObject(getMappedTestObject(“OK”));// 将元素对象化
}
RFT中的显示

图16. RFT中的显示
5.2 事件层(managers)
基本事件的表示:
public void clickButtonOK()
{
addDBDlg.getButtonOK().click();
}
public void DoubleclickButtonOK()
{
addDBDlg.getButtonOK().doubleClick();
}
default事件的生成:
public void ClickBTNOKDefault()
{
setTextIPAddressDefault();// 必填字段 1
setTextDBNameDefault();// 必填字段 2
setTextUserNameDefault();// 必填字段 3
setTextPasswordDefault();// 必填字段 4
clickButtonOK();// 保证所有的依赖关系都被调用,Button 此时可以被调用。
}
5.3 脚本层(testcase)
脚本层就是将事件进行拼装。可以安装事先指定的覆盖率自动生成。
public void testcase1(Object[] args)
{
DBServerInfoTableViewMgr dbServerInfoViewMgr = new DBServerInfoTableViewMgr();
// 调用事 件层对象
AddDBServerDlgMgr addDBServerDlgMgr = new AddDBServerDlgMgr();
dbServerInfoViewMgr.clickToolBar("addDBServer");
addDBServerDlgMgr.setTextIPAddress();
addDBServerDlgMgr.setTextDBName();
addDBServerDlgMgr.setTextPassword();
addDBServerDlgMgr.setTextComments();
ClickButtonOK();
// 安装覆盖率自动 生成脚本,也可以测试者手动添加。
}
5.4 脚本修复(update)
在进行用例修复的时候,通常对于删除对象最难处理。因此这里选择删除的对象进行处理。

图17. 选择删除的对象进行处理
如图所示查找内容的输入栏被删除,其对象名称为TextFieldFind。通过用例修复器对现有的用例集合进行修复:
(1)自动更新事件层,删除TextFieldFind的所有事件 , 同时更新所有的default操作,保证default操作一定可达;
(2)查找到涉及到TextFieldFind的测试脚本如Testcase3;
(3)TextFieldFind 对象变化类型为删除,ButtonSearchNext、ButtonInstead、ButtonInsteadAll对其有依赖关系;
(4)将存在有依赖关系的步骤,替换成默认操作,保证其可达性。
原测试脚本如下所示:
public void testMain(Object[] args)
{
MenuEditMgr menueditMgr = new MenuEditMgr();
SearchDlgMgr searchdlgMgr = new SearchDlgMgr();
menueditMgr.ClickMenubar("Edit");
menueditMgr.ClickMenubar("Edit->Paste");
menueditMgr.ClickMenubar("Edit");
menueditMgr. ClickMenubar("Edit->Instead");
searchdlgMgr.SetTextFieldFind("abc");// 事件被删除
searchdlgMgr.ClickBTNSearchNext();// 有依赖关系更新为 BTNSearchNextDefault()
searchdlgMgr.ClickBTNInstead();// 有依赖关系更新为 BTNInsteadDefault()
searchdlgMgr.SetTextFieldFind("123");// 事件被删除
searchdlgMgr.ClickBTNInsteadALL();// 有依赖关系更新为 BTNInsteadALLDefault()
searchdlgMgr.ClickBTNCancel();// 没有依赖关系不做改变
}
经过修复的测试脚本覆盖了旧脚本,执行这个脚本就可以完成回归测试,代码如下所示:
public void testMain(Object[] args)
{
MenuEditMgr menueditMgr = new MenuEditMgr();
SearchDlgMgr searchdlgMgr = new SearchDlgMgr();
menueditMgr.ClickMenubar("Edit");
menueditMgr.ClickMenubar("Edit->Paste");
menueditMgr.ClickMenubar("Edit");
menueditMgr.ClickMenubar("Edit->Instead");
searchdlgMgr.BTNSearchNextDefault();
searchdlgMgr.BTNInsteadDefault();
searchdlgMgr.BTNInsteadALLDefault();
searchdlgMgr.ClickBTNCancel();
}
总结
一个完整的回归测试脚本集由修复的测试脚本和新生成的测试脚本两部分组成。使用测试脚本修复技术对已有测试脚本进行修复,提高了脚本的重用性。
最后结合一个实际的待测程序,进一步对脚本修复中技术细节进行研究与验证。最终达到了高效进行GUI软件测试、降低测试成本、提高GUI软件质量的目的。