级别: 中级 于 雷, 软件工程师, IBM 魏 鑫焱, 软件工程师, IBM 李 伟, 软件工程师, IBM
2009 年 4 月 17 日 本文将首先从 IBM Rational Functional Tester(RFT)的扩展机制和标准接口开始介绍,分析它的优缺点,进而针对它的一些缺陷,提出扩展和调用的方法实践。然后将着重介绍基于 Eclipse 实现 RFT 扩展验证工具包 MVPL 的一些主要功能和相关实现,并且将一些最佳实践和方法介绍给大家。并辅以一个具体实例来说明问题。
Rational Functional Tester(RFT)的现有验证机制
RFT 机制
RFT 本身通过对 Eclipse 平台中的对象的获取和封装,对外暴露一些 API 供用户进行调用。机制如下图所示。可以看出,用户可以通过调用 RFT 的相关对象 API 来获得对象信息,RFT 中会有很多的 Proxy,每一个 Proxy 会通过与后端的被测软件 (SUT—Software Under Test) 相交互,来处理一定的对象操作,也就是说,这些 Proxy 可以驱动被测软件从而操作这些软件上的对象。
图 1. RFT 与 SUT 的交互机制
验证点
验证点是自动测试化脚本的重要组成部分,自动化测试就是通过对验证点的添加并实现具体的测试逻辑来完成对被测试软件功能的验证。RFT 提供强大丰富的验证机制来对用户提供关于验证点的支持。我们可以通过 RFT 的图形化界面去添加、删除或修改一个验证点,也可以通过调用它的验证点相关 API 来自己写代码实现验证点的功能。(如下图)
图 2. 添加验证点
图 3. 添加验证点 Wizard
图 4. 删除验证点
图 5. 修改验证点
优缺点
从上面的介绍,我们可以看出,RFT 的验证结构的功能比较强大,通过对 TestObject 的获取和使用,可以对 Eclipse 中的很多的 SWT/JFace 图形对象提供支持,而且也可以支持很多的验证种类;其次,它直观、易于理解:RFT 的接口结构是按照一般的代码逻辑设计的,所以,很易于大家理解和调用。
但是,它还是存在着一些缺陷。第一,它在使用上非常繁琐:RFT 只是提供了一个可供调用的验证平台和环境,对应到特定的对象验证就会比较繁琐;同时接口偏多,用户通常都需要根据自己的需求来实现自己具体的测试逻辑;第二,由于 RFT 没有区分各个具体对象类型的差异,造成验证形式比较单一,从而不利于用户进行扩展操作;第三,可重用性低,基本上每个验证点都需要用户去重新定制实现。
实例
下面通过一个简单的具体实例,来说明一下使用 RFT 自带的验证库的方法及其缺陷。
-
实例内容
在 Eclipse 的应用中验证 Project Tree 的某一个项目下的项目结构,如下图所示,假定我们要验证 Project 2 的整个项目的树型结构信息。
图 6. 实例项目树
-
使用 RFT 来做验证点的验证
在 RFT 中,有两种方法可以来验证:
-
通过 RFT 的验证点界面步骤向导来操作,详细步骤请见第一节。
通过向导创建完之后,我们会发现 RFT 会生成这样一行代码:
enhancedTree().performTest( EnhancedTree_selectedVP() );
|
通过进一步代码深入,我们会发现它的父类里实现的代码是这样的:
protected IFtVerificationPoint EnhancedTree_selectedVP()
{
return vp("EnhancedTree_selected");
}
|
再次代码深入,我们可以发现具体的实现代码是在 RationalTestScript 类中进行的,所以,这对于用户来说,是无法操作的。而我们这个示例的验证 RFT 是默认无法支持的,所以,这个方法对我们的这个验证请求就失效了。
-
用户自己手工编写验证代码
用户编写验证代码,可以通过直接在脚本类中添加验证方法,然后在脚本中调用来实现。改变后的脚本代码如下:
public void testMain(Object[] args)
{
this.verifyTree(enhancedTree());
}
private boolean verifyTree(TestObject to) {
// Add verify code here
return false;
}
|
在 // Add verify code here 部分需要用具体的验证代码替换,在这里,一个显而易见的问题就存在,那就是用户需要对每一个验证点都手工编写很长一段特定的验证代码,这是费需要很多功夫的。
如何扩展 RFT 的 API 来实现验证点功能
在 RFT 的体系结构中,有几个非常重要的概念,它们是 RFT 机制的基础。我们可以通过对它们的调用来实现验证功能。
-
RationalTestScript:RationalTestScript 是所有测试化脚本的基类,也就是说,所有的测试化脚本都需要集成 RationalTestScript 来实现自己具体的脚本测试逻辑。
-
TestObject:TestObject 是 RFT 关于被测试软件中的图形对象进行封装和交互的核心组成部分,每一个被测软件上的对象都可以映射为 RFT 上的一个 TestObject 对象。
-
Log:RFT 的验证方法通过生成的日志对象来展示,所以日志对验证来说,也是非常重要的一部分。
-
验证点相关 API 和 ItestData 结构:RFT 提供验证点的相关 API 来提供对验证点的支持;ItestData 类用来封装测试对象的测试数据,可以大大地方便我们来获取测试数据。
继承和调用 RationalTestScript
RationalTestScript 类是所有用户测试脚本的基类,它的层次结构如下图所示。RationalTestScript 提供很多方法,这些方法都可以在任何一个测试脚本中使用,同时它也提供了一些方法的基本实现,你可以直接使用它们,也可以重载他们来获得自己的实现方式。
图 7. RationalTestScript 的层次结构图
RationalTestScript 类中,最重要的方法就是 runMain(), 它是所有 RFT 的脚本的启动方法,在所有的脚本中,我们都需要重载它来获得我们特定脚本的执行方式。同时,它也通过 callScript() 方法来支持在某个脚本中对其他脚本的调用。
RationalTestScript 支持很多种应用的启动方法,run() 可以用来执行一个命令;runJava() 方法支持运行 Java 程序;startApp() 支持启动某一应用程序;startBrowser() 可以用来启动浏览器程序;shellExecute() 则可以支持脚本的执行。
同时,RationalTestScript 还可以获得很多对我们脚本执行非常有用的环境信息,比如说,获得 Java 程序中的 Java 虚拟机的信息—getJVMs();Web 程序中的浏览器位置和类型—getBrowsers();获得当前操作系统的类型—getOperatingSystem() 以及环境参数—getenv() 等等。
使用 Log
RFT 默认支持 HTML 和 Text 格式的日志格式,如果你还需要更加强大或定制的日志功能,你也可以通过调用日志的相关 API 来扩展自己的日志格式,设置自己的日志元素和展示方式。日志是以步骤为单位记录的,按照脚本操作的顺序进行组织的。
RationalTestScript 类中有很多方法来控制结果日志的生成:
-
logTestResult(); 将验证点方法生成的结果输出到日志文件中
-
logInfo(); 将步骤信息输出到日志中
-
logError(); 将程序执行中产生的错误信息打印到日志中
-
logException(); 将程序执行中产生的异常信息打印到日志中
-
logWarning(); 打出警告信息
TestObject 的使用
TestObject 类是所有测试对象的基础类,RFT 主要就是通过这个类来与被测软件 / 应用进行交互,它的类(接口)层次结构如下图所示。可以看到,RFT 又将 TestObject 分为很多具体的类型,我们可以将一个被测对象构建成某一种类型的实例,或者可以构建自定义的 TestObject 类型。
图 8. TestObject 层次结构图
RFT 中有两种对象的获取机制。
图 9. 静态对象获取
TestObject to = new TestObject(getMappedTestObject(objectName));
|
使用静态方法获取的对象都存储在 RFT 的 ObjectMap 里,每一个存储在 ObjectMap 里的 TestObject 对象都有一些静态属性,在以后的对象匹配中,RFT 就会对这些静态属性与被测软件上的对象进行比较,从而获得对象。ObjectMap 支持很多操作,包括动态加入对象,删除对象,查找对象等等,我们也可以通过调用 ObjectMap 的 API 来定制一个自己的 ObjectMap 对象。
动态方法是指通过 RFT 提供的 find() 方法来获得对象,RFT 的执行引擎会通过用户的输入属性参数在被测软件上查找当前的所有对象来进行匹配。
TestObject to = RootTestObject.getDomain().find(subItem);
|
动态获取对象的方法将不会使用 ObjectMap 机制,而是采用属性匹配的方法。用户在编写脚本的时候,处理对象都会调用 RFT 的查找方法 API,参数是采用一定的属性值描述特定的对象,以后的每一次脚本执行,RFT 的查找 API 就会根据用户指定的属性值对界面上的所有对象进行查找和匹配,找到最合适的对象并对其进行相关操作。
通过对 TestObject 的相应方法的调用,可以实现对特定对象的调用。通用的 TestObject 对象获取与验证 API 如下:
-
TestObject to; 创建 TestObject 对象
-
to.exists(); 验证一个对象存在与否
-
to.getChildren(); 获得一个对象的孩子对象
-
to.getProperty(property); to.getProperties(); 获取对象的属性
-
to.invoke(methodName); 调用对象的某些方法
ITestData 层次结构
ITestData 是 RFT 提供的验证结构的顶层接口,它的作用是用来封装测试数据的(层次结构如下图所示)。可以看出,RFT 通过使用许多具体的 TestData 类型来继承它,每个类可以支持一种特定的图形对象类型,我们可以利用它以及它的这些子类型来获得 TestObject 的相关测试数据。
图 10. ITestData 的接口 / 类层次结构图
使用验证点 API
RFT 中有专门提供验证功能的代码包 (Java package) -- com.rational.test.ft.vp,其中有很多有用关于验证方面的类和方法,我们都利用他们来扩展。
- 通过
com.rational.test.ft.vp.VpUtil 类来获得测试对象上的具体数据。
- 通过
com.rational.test.ft.vp.IFtVerificationPoint 来执行验证点操作
-
performTest() -- 执行验证操作
-
compare() –- 执行对比操作
-
compareAndLog() -- 执行对比操作,并将结果写入日志中
MVPL 的功能与实现
功能
基于以上分析,我们可以看出,由于 RFT 的验证 API 的使用繁琐、可重用性低等缺陷,用户通常情况下都需要构建自己的验证库,这会给用户带来很多的不便。MVPL 就是在 RFT 基础之上构建的一个适用于 Eclipse 平台应用的验证库,它在继承了 RFT 验证库的功能强大且易于理解等优点的同时,通过扩展 RFT 的相关接口,对每种类型的图形对象进行特定支持,克服了 RFT 验证库的缺点。用户可以通过直接调用 MVPL 的 API 来实现自己的验证需求,而不需要再去自行编写。它将代码组织成以 Eclipse 的 SWT/JFace 的图形对象分类的结构形式。将 RFT 从 SUT 上获得的 TestObject 对象封装,转化成和 Eclipse 的对象相对应的结构。这种结构易于理解,并使用户可以很方便的使用。MVPL 使得用户可以方便快捷在其上根据自己的验证需求构建自己的具体验证点实现。同时,MVPL 对 RFT 的日志功能进行了增强,提供一些定制化的日志生成和展现的方法,使得日志可以更加直观和有效的展现给用户。
实现
MVPL 的代码结构分为图形组件(widgets)包和通用工具(utilities)包。图形组件(widgets)包将 Eclipse 的各个图形对象按类型分类进行封装,并提供方法供用户进行调用,每种类型都有自己特定的属性和方法,同时它们也有共同的一些方法,这些方法将在基类中进行定义和实现;通用工具(utilities)包主要提供一些用户经常用到的测试和验证方法,其中包括日志的生成和相关显示、图形的生成和比对方法、文件以及环境的相关获取和处理方法,以及对 CVS 的支持。
MVPL 的图形组件包按照 SWT/JFace 的对象的规则进行分类,其中包括表格(树)、树、(下拉)菜单、列表(框)、按钮、复选框、工具栏、标签夹等对象,他们都继承自同一个基类—MVPLObject。
如果被测软件(SUT)的相关对象是属于某一种特定类型的,那就可以把它映射到 MVPL 中的一个对象。通常情况下,可以使用通过带有 TestObject 对象的参数的构造函数来创建一个 MVPL 中的图形对象:
MVPLObject mObject = new MVPLObject(testObject);
|
通过这样的转换,我们就可以将 RFT 获取的对象映射到 MVPL 库中的对象来,从而可以使用 MVPL 中的对象方法和属性。
最佳实践 1 -- 对象的抽象和封装
在使用 RFT 对 Eclipse 平台应用的测试过程中,将每个界面上的 SWT/JFace 对象都抽象并映射到我们的测试对象结构中来,是非常必要的,这样既可以使我们的代码逻辑上更直观和清晰,也可以让我们的测试功能更加强大而定制化。MVPL 通过对 RFT 的 TestObject 对象按照具体的类型进行分类,将 Eclipse 的 SWT/JFace 对象分别映射到 MVPL 的对象类型。
下面详细介绍一下 MVPL 中的几个重要的图形对象:
图 11. MVPL 的 widget 层次结构图
-
表格(表格树)(MVPLTable/MVPLTableTree):
表格(表格树)(注:英文是 Table 和 TableTree,两者是不同的对象,却十分相似)是 Eclipse 平台中最常用的图形对象。在对 Eclipse 平台进行测试的过程中,对于这种类型的验证数目是非常多的,验证种类主要包括对整个表格的验证、表格某行(或列)的验证、表格某个单元格内容的验证等等。
图 12. Eclipse 表格
public class MVPLTable(Tree) extends StatelessGuiSubitemTestObject implements IMVPLWidget,
IMVPLMenuGenerator
|
首先,应该根据传入的 TestObject 对象构造一个 MVPLTable/MVPLTableTree 类的对象:
MVPLTable(Tree) table(Tree) = new MVPLTable(Tree)(testObject);
|
构造完对象后,我们可以利用继承过来的 getTestData() 方法来获得信息。MVPLTable/MVPLTableTree 获得这个表格数据信息的代码如下:
private final ITestDataTable tableData = (ITestDataTable) getTestData(“contents”);
|
此时,通过得到的 tableData 对象,可以进一步得到表格、行(或列)和单元格的相关数据信息。
public Object getCell(int row, int col) {
int columnNum = tableData.getColumnCount();
int rowNum = tableData.getRowCount();
return tableData.getCell(row, col);
}
|
如上代码所示,getCell() 方法的功能是获得某个单元格的数据内容。
public String[] getColumnHeaders() {
int columnNum = tableData.getColumnCount();
String[] columns = new String[columnNum];
for (int i=0; i<columnNum; i++) {
columns[i] = (String)tableData.getColumnHeader(i);
}
return columns;
}
|
getColumnHeaders() 方法可以用来获得表格的列标题信息。
-
树(MVPLTree):
图 13. Eclipse 树
树是 Eclipse 图形对象中很重要的一个对象类型,它所代表的是一种层次化的结构。树都有一个根节点,从根节点开始一层一层向下展开,一直到最终的各个叶子节点。对树的验证主要包括对某个或某些节点的信息验证、对树(或其子树)的整个层次结构的验证、验证树的节点是否是展开的等功能;同时也可以将树的结构信息导出到文件里,用来提供验证功能或做备份处理。
MVPLTree 类继承 GuiSubitemTestObject 并实现 IMVPLMenuGenerator 接口。
public class MVPLTree extends GuiSubitemTestObject implements IMVPLWidget,
IMVPLMenuGenerator
|
验证树的某个节点是否存在的方法,输入参数是这个节点在树中的路径。
public boolean isItemExisted(String itemPath) {
try{
TestObject item = (TestObject) super.getSubitem(SubitemFactory.atPath(itemPath));
if(item == null)
return false;
else {
item.unregister();
return true;
}
}
catch(SubitemNotFoundException ex) {
return false;
}
}
|
验证某一个节点是否是展开状态。
public boolean isExpanded(TestObject item) {
return ((Boolean) item.invoke("getExpanded")).booleanValue();
}
|
-
(上下文)菜单(MVPLMenu/MVPLContextMenu):
菜单是指在 Eclipse 平台中菜单栏中的存在的固定功能选项,每一个菜单项都对应着一项固定的功能。
public class MVPLMenu extends GuiSubitemTestObject implements IMVPLMenuGenerator,
IMVPLWidget
|
图 14. Eclipse 菜单
上下文菜单则指一般是由用户右击某一个图形对象时触发产生,根据对象的不同,会动态地产生不同的上下文菜单对象。
public class MVPLContextMenu extends MVPLMenu implements IMVPLWidget
|
图 15. Eclipse 上下文菜单
在代码实现逻辑中,我们将 ContextMenu 实现为 Menu 类的一个子类,它们中的大部分属性和行为是一致的,但是,ContextMenu 对象需要用户对某个图形对象进行某种触发操作后才可以获得。
public TestObject getMenuItem(String mPath) {
StringTokenizer st = new StringTokenizer(mPath, PATH_DELIMITED);
TestObject parent = this;
while(st.hasMoreTokens()) {
String itemName = st.nextToken();
TestObject[] items = parent.getChildren();
boolean found = false;
for(int i=0; i< items.length; i++) {
String name = (String)items[i].invoke("getText");
if (name.trim().equals(itemName)) {
parent = items[i];
found = true;
break;
} else {
items[i].unregister();
}
}
if(!found) {
return null;
}
}
return parent;
}
|
-
列表 / 列表框(MVPLList/MVPLCombo):
列表 / 列表框是 Eclipse 界面设计中非常经常使用到的一类图形对象,作用是给用户提供关于某一问题的一些可选项供,用户可以从这些可选项中选择自己合适的选项。
public class MVPLCombo/MVPLList extends GuiTestObject implements IMVPLWidget
|
图 16. Eclipse 列表框
验证列表 / 列表框中是否存在某一选项的方法:
public boolean isItemExisted(String item) {
String[] items = (String[]) invoke("getItems");
for (int i=0; i<items.length; i++) {
if (items[i].equals(item)) {
return true;
}
}
return false;
}
|
依次验证列表 / 列表框中所有的选项:
public boolean verifyItemInOrder(String[] expected) {
String[] items = (String[]) invoke("getItems");
if (expected.length != items.length) {
return false;
}
else
for (int i = 0; i < items.length; i++) {
if (!items[i].equals(expected[i])) {
return false;
}
}
return true;
}
|
-
标签夹(MVPLTabFolder)
标签夹在 Eclipse 平台中应用十分广泛。标签的使用可以丰富 Eclipse 界面的显示功能。在 MVPL 中,我们实现了 MVPLTabFolder 来提供对 Eclipse 标签夹的支持。
public class MVPLTabFolder extends GuiSubitemTestObject implements IMVPLWidget
|
图 17. Eclipse 标签夹
获得标签夹中的所有标签名的方法:
public String[] getItemNames() {
TestObject[] TabItems = (TestObject[])invoke("getItems");
String[] tabs = new String[TabItems.length];
for(int i=0; i<TabItems.length; i++) {
tabs[i]=(String)TabItems[i].getProperty("text");
TabItems[i].unregister();
}
return tabs;
}
|
获得当前活动的标签名:
public String getSelectedItemName() {
TestObject selItem = (TestObject)invoke("getSelection");
return (String)selItem.getProperty("text");
}
|
-
接口 MenuGenerator
MenuGenerator 接口是用于处理触发 ContextMenu 对象的,每一个可以触发 ContextMenu 的图形对象都需要去实现这个接口并实现其定义的 getMenu() 方法,输入参数可以是任意类型的对象,方法会输出一个 ContextMenu 对象。
public interface IMVPLMenuGenerator extends IMVPLWidget {
MVPLMenu getMenu(Object arg);
}
|
最佳实践 2 – MenuGenerator 的使用
由于 ContextMenu 的结构和触发机制的特殊性,使得对于它的验证和操作需要有别于其他的对象。MVPL 将可以触发上下文菜单的对象抽象为 MenuGenerator 接口,在 MenuGenerator 接口中定义一些公用的接口方法,所以实现 MenuGenerator 的对象类型都需要具体地来实现这些方法。
MVPL 的通用工具包 (utilities) 主要包含如下功能:
-
MVPLLog 类:通过扩展 RFT 的日志格式内容,我们可以将自定义的元素加入到 Log 显示中去,从而可以使测试人员从日志中可以得到更加丰富和有用的信息。
-
MVPLImages 类:其中包括几个有用的图片处理方法:
compareImages() 用来比较两个图片是否相同,combineImage() 用来将两张图片进行合并,saveClipToImage() 可以将系统剪切板的内容粘贴到图片上。
-
MVPLCVSUtils 类:MVPL 通过 MVPLCVSUtils 类来提供对 CVS 相关操作的支持。
-
其他功能:MVPL 提供其他功能,提供对诸如系统和环境参数的获取和操作、文件的相关操作、XML 的相关操作等的支持。
最佳实践 3 -- 日志的封装和扩展
扩展 RFT 的基本日志 API 来获得我们想要的日志格式和结果,是很多测试化脚本的一项重要需求。MVPL 使用 MVPLLog 类来使用户自定义的元素加入到 RFT 的日志中,这可以大大地提高测试化脚本执行结果的易用性,并使得问题的定位更加方便。
注:MVPL Lib 库还有许多重要的功能,在这里不能一一介绍,详细信息可以参见它的 Javadoc,同时,MVPL Lib 库允许用户通过自己实现库中的某些类的子类来扩展 Lib 库的功能。
实例(续)
我们可以使用第一章的实例,同样的验证需求,使用 MVPL Lib 库来处理验证请求,验证过程就变得非常简单。只需要写如下的代码问题就可以得到解决:
MVPLTree tree = new MVPLTree(enhancedTree());
boolean verified = tree.vefiryTreeStructure();
|
通过上面的实例对比,可以看出,使用 MVPL Lib 库来进行验证点的验证,相比于 RFT 自带的验证方法,可以节省很多的工作,而且,非常易于使用。
MVPL Lib 库自身提供了很多很强大的验证方法来满足用户需要,同时,它也有很强的可扩展性,用户可以通过扩展库的 API 来满足特定的验证需求。
总结
本文介绍了一个基于 RFT 的 Eclipse 平台扩展验证工具包—MVPL。首先介绍了 RFT 的基本验证结构并分析了它的优缺点,然后接着介绍了扩展 RFT 的验证机制的一些方法,介绍了 TestObject、RationalTestScript 和 ITestData 等核心类 / 接口,之后,详细的介绍了 MVPL,包括它的功能介绍,具体设计与实现,以及总结出来的最佳实践。在其中,通过一个辅助的具体实例来进行具体说明。在本文中,除了使用文字、图片和表格以外,我们还采用了大量的示例代码来辅助大家的理解。希望可以使读者在使用 RFT 进行自动化测试实施的过程中可以调用 MVPL 进行验证点的实现来简化验证点的工作量,或者方便地构建出自己的验证库。
参考资料 学习
获得产品和技术
讨论
作者简介  | |  | 于雷,来自于 IBM CDL 的 WebSphere BPM Tooling 组,主要从事 Business Space 自动化测试相关工作。 |
 | |  | 魏鑫焱,来自 IBM 中国软件开发中心 WebSphere BPM Tooling 组。一直关注自动化测试领域,主要负责 WebSphere Business Modeler 自动化测试。 |
 | |  | 李伟,来自 IBM CDL WebSphere BPM Tooling 组,从事 Websphere Integration Developer 产品的自动化测试。 |
对本文的评价
|