一种高效的对象缓存机制在测试框架中的应用
 

2009-05-07 作者:张捷,杨莉,陈昱旻 来源:IBM 

 

如果在 IBM Rational Functional Tester(RFT) 项目中完全使用动态搜索的方式获得对象,那么有可能你将面临严重的性能问题,尤其是当你需要测试的应用中对象层次十分多而复杂时,比如 Microsoft Office 软件。Object Map 是一种选择,可 Object Map 常常不能满足你的要求,比如 ObjectMap 经常需要跟随测试的应用改变而更新,而且有很多对象无法使用 Object Map 获取等等。本文提供一种高效的对象缓存机制,不但可以对已经获取的对象进行有效的缓存,快速的提取,还可以在对象层次复杂时,极大的改善动态搜索对象的性能。

使用 RFT 进行 GUI 测试自动化

自动测试解决了传统手工测试中的很多问题,把测试人员从繁重而重复的测试工作中解脱出来,节省了很多人工时间。RFT 是一种非常有效,使用方便的测试自动化工具,在很多项目中得到应用。用户可以选择使用 RFT 进行录制播放的方法来进行自动化测试,也可以选择编写测试框架,并在框架的基础上编写脚本的方式实现。前者的缺点很明显,就是在测试的应用发生变化时需要重新进行录制,而当变更涉及到很多 case 时,这将带来很大的工作量,这也许比手工测试花费的时间还长。所以除非能避免这个问题,所有的项目都采用后者。在我们的项目中也是如此

Object Map

在 GUI 测试中,RFT 提供了两种获取对象的方法,一种是通过 ObjectMap,由 RFT 对需要测试的对象进行识别,用户可以根据需要选择把哪个对象引入测试脚本,RFT 自动生成 getter 方法供使用者调用。RFT 提供的录制播放功能也是采用 ObjectMap 来实现对象获取。此方法的优点是用户不用关心对象获取的细节。然而缺点与使用录制播放的方式类似,就是当测试的应用界面结构变化时,用户经常需要重新更新 ObjectMap,尽管 RFT 有搜索权重机制,一些微小的变化不会导致更新。而且用户经常会遇到无法使用 ObjectMap 获取的对象。

有很多关于如何使用 ObjectMap 的文章和资源,这里只给出一个简单的例子。

Microsoft word 2007 是一个非常适合做例子的应用。测试步骤共两步:

  1. 打开 word 2007;
  2. 在菜单栏中点击”View”,再点击”Home”。

图 1. “View”和“Home”在 word 2007 的 ObjectMap 结构中的位置

在 RFT 中新建一个脚本叫做”ObjectMap.java”,在自动生成的代码中,这个类继承自“ObjectMapHelper.java”。然后把“View”和“Home”两个对象插入到这个脚本中。RFT 会自动为这两个对象生成 get 方法,并插入到 ObjectMapHelper.java 中。

图2. ObjectMapHelper.java

ObjectMap.java 中的代码非常简洁 :

viewpageTab().click() 在菜单栏中点击”View”,homepageTab().click() 即点击”Home”对象。

图 3. ObjectMap.java

动态搜索

另一种获得对象的方法就是动态搜索,使用动态搜索,就是用户自行使用 RFT 提供的搜索方法,从测试应用结构中的某个节点根据提供某些条件进行搜索获取对象。这个方法自由,灵活,而且能得到一些 ObjectMap 无法获取的对象。在我们的框架中,使用的就是这个方法。但这个方法在测试的应用对象结构复杂时,性能比较差。因为搜索将遍历起始节点下所有的对象层次结构。

这里提供的动态搜索的例子可以完成和使用 ObjectMap 完全相同的功能。不过限于篇幅,只提供了获得”View”按钮的例子。

代码 1. ViewButton.java:

package dynamicsearch;
……
public class ViewButton extends GuiTestObject {
public ViewButton( TestObject testObject ){
super( testObject );
}
public static ViewButton getViewButton(){
//获取根测试对象,从该对象开始查找需要的对象
RootTestObject root = RootTestObject.getRootTestObject();
//通过匹配ViewButton的属性来找到该对象
TestObject to[] = root.find( RationalTestScript.atDescendant(
".class", ".Pagetab", ".name", "View" ) );
return new ViewButton(to[0]);
}
}

代码 2. DynamicSearch.java

package TestScript;
……
//调用getViewButton 方法获取View 按钮,然后点击它
public class DynamicSearch extends DynamicSearchHelper
{

public void testMain(Object[] args)
{
ViewButton viewButton = ViewButton.getViewButton();
viewButton.click();
}
}

高效的对象缓存机制

我们想使用动态搜索,但又想有 ObjectMap 的性能优点,于是我们采用了一种高效的对象缓存机制来对动态搜索获取的对象进行缓存和管理,使动态搜索获取的对象可以做到只需要搜索一次,而不用再每次需要使用的时候都进行搜索,这样就减少了大量的搜索时间,提高了性能。同时,在对对象的管理上,该机制采用了类似 ObjectMap 的层次结构,相当于一种简化的 ObjectMap。

  • 该缓存机制具有与 ObjectMap 类似的层次结构
  • 在这个机制中,只有需要的对象才会被存储
  • 如果应用的变化没有造成缓存中存储的对象层次之间的变化,不需要调整缓存部分的代码。
  • 缓存是全局的,如果对象层次结构发生,对程序的修改将基本被限制在缓存这层代码中,不会造成整个框架和所有测试脚本需要修改的惨状。

该机制的实现并不复杂。

  • 使用被测试应用的顶层窗口对象作为根对象存储。
  • 把隶属于该窗口的对象作为根对象的成员变量存储 .
  • 按照 ObjectMap 的层次顺序存储对象,下层的作为上层的成员变量。没有用到的对象层次可以忽略。
  • 在所需该对象为 Null 的时候,getter() 方法调用动态搜索方法获取对象
  • 当所需对象不为 Null 的时候,getter() 方法直接返回缓存中存储的对象。

这里将使用这种缓存机制实现刚才的例子,如果这个操作只被调用一次,那么缓存机制将不会与普通的动态搜索方法有区别。但如果这是一个被频繁调用的操作,那么缓存机制将体现出它的价值,极大的减少对象获取的时间。

建立一个名为 WordWindow 的对象存储所有 MS Word 2007 窗口的直属子对象。这些子对象也可存储属于他们的子对象。

代码 3. WordWindow.java

package appObject;
……
//WordWindow类包含所有在Word窗口中要操作的子对象及该窗口本身
public class WordWindow {
private GuiTestObject viewButton;
private GuiTestObject homeButton;
public GuiTestObject getViewButton(){
if(viewButton == null ){
RootTestObject root = RootTestObject.getRootTestObject();
//通过匹配ViewButton的属性来找到该对象
TestObject to[] = root.find( RationalTestScript.atDescendant(
".class", ".Pagetab", ".name", "View" ) );
return new GuiTestObject (to[0]);
}
return viewButton;
}
public GuiTestObject getHomeButton(){
if(homeButton == null ){
//通过匹配HomeButton的属性来找到该对象
RootTestObject root = RootTestObject.getRootTestObject();
TestObject to[] = root.find( RationalTestScript.atDescendant(
".class", ".Pagetab", ".name", "Home" ) );
return new GuiTestObject(to[0]);
}
return homeButton;
}
}

CacheVer1.java: 使用缓存机制完成操作。

代码 4. CacheVer1.java

package TestScript;
……
//如果该测试用例需要对View按钮和Home按钮做更多的操作,可以直接从缓存中获取这两个对象,而无需再一次搜索
public class CacheVer1 extends CacheVer1Helper
{
public void testMain(Object[] args)
{
WordWindow word = new WordWindow();
word.getViewButton().click();
word.getHomeButton().click();
}
}

接下来将探讨一下如何在该对象缓存机制的基础上进行改进,再进一步的提高动态搜索的性能呢?我们注意到,动态搜索的原理类似于从某个节点开始,对这个节点以下的所有节点进行遍历,把所有满足条件的对象都返回。一般在动态搜索前,我们会获取测试应用的窗口作为根节点,对所有需要的对象进行搜索,这样的操作十分耗时。现在我们有了一个按照对象层次存储的缓存,就可以利用它来减少搜索的时间。

具体实现如下:

  • 使用被测试应用的顶层窗口对象作为根对象存储。
  • 在 ObjectMap 中分析需要被查找的对象
  • 如果有一组对象具有相同的祖先节点,那么把离它们最近的祖先节点对象存储到 cache 中
  • 当需要获取这组对象中的某一个时,先或者事先存储的祖先节点,然后从这个祖先节点开始进行动态搜索。

这种机制会减少很多动态搜索所涉及到的层次,进一步减少了动态搜索需要的时间。

我们建立一个新的 WordWindowVer2 对象作为缓存对象。我们的示例很简单,只有一个缓存对象。在真实的项目中,根据对象的复杂程度,我们会需要一种设计模式更好的组织更多的缓存对象。

代码 5. WordWindowVer2.java

package appObject;
……
public class WordWindowVer2 extends TestObject {
// 在 Object Map 中标注为蓝色的对象,是 Ribbon 按钮的最近的父节点
private TestObject ribbonButtonGroupprivate GuiTestObject viewButton;
private GuiTestObject homeButton;

public WordWindowVer2( TestObject testObject ){
super( testObject );
}
// 通过匹配窗口标题找到 Word 窗口
public static WordWindowVer2 getWordWindowVer2( String title ){
RootTestObject root = RootTestObject.getRootTestObject();
TestObject to[] = root.find( RationalTestScript.atDescendant(
".processName", "WINWORD.EXE", ".name", title ) );
return new WordWindowVer2(to[0]);
}
// 从 Word 窗口对象中找到 Ribbon 按钮组
private TestObject getRibbonButtonGroup(){
if( ribbonButtonGroup == null ){
TestObject to[] = this.find( RationalTestScript.atDescendant(
".class", ".Pagetablist", ".name", "Ribbon Tabs" ) );
return new TestObject(to[0]);
}
return ribbonButtonGroup;
}
// 从 Ribbon 按钮组中找到 View 按钮
public GuiTestObject getViewButton(){
if( viewButton == null ){
TestObject to[] = getRibbonButtonGroup().find( RationalTestScript.atDescendant(
".class", ".Pagetab", ".name", "View" ) );return new GuiTestObject(to[0]);
}
return viewButton;
}
// 从 Ribbon 按钮组中找到 Home 按钮
public GuiTestObject getHomeButton(){
if( viewButton == null ){
TestObject to[] = getRibbonButtonGroup().find( RationalTestScript.atDescendant(
".class", ".Pagetab", ".name", "Home" ) );return new GuiTestObject(to[0]);
}
return homeButton;
}
}

代码 6. CacheVer2.java

package TestScript;
……
// 获取 Word 窗口,并点击窗口中的 Viewn 按钮和 Home 按钮
public class CacheVer2 extends CacheVer2Helper
{
public void testMain(Object[] args)
{
WordWindowVer2 word = WordWindowVer2.getWordWindowVer2("Document1 - Microsoft Word");
word.getViewButton().click();
word.getHomeButton().click();
}
}

如果我们会用到很多 MS Word 菜单按钮的,这种缓存机制将从按钮 group 对象开始进行搜索按钮对象,搜索的速度会变得非常迅速。同时这种机制支持缓存从不同的 Word 窗口获取按钮对象。

在实际的项目中,根据 IBM framework 框架,对象搜索方法会被包装成一个通用方法,并且获取的对象将在测试框架的 Task 层中使用,而不是直接在脚本中调用。

这种缓存机制节省了大量的对象搜索时间,提高了开发调试以及运行的效率。我们在自动测试脚本的运行中,不会在每次需要获取对象时都要等待到不耐烦地地步。我们也可以在更短的时间内让计算机运行更多的测试脚本。

该机制在测试框架中还起到了隔离对象获取和对象调用的分层目的,在缓存的对象代码中,提供了 getter 方法供上一层代码调用,而将从缓存中还是通过动态搜索获取对象的过程封装在缓存这一层代码中。

需要注意的问题是,当缓存中的对象不再指向一个真实的 GUI 对象时,一定要释放该对象。直接把它置成 Null 即可。否则从缓存中获得的将是一个过期的对象,造成脚本失败。

总结

本文提供了一种高效的缓存机制,可以使得使用动态搜索的 RFT 测试框架性能获得很大改善,并能够很好的管理获得的测试对象。我们的项目在没有使用这种机制前,一直为动态搜索的性能问题所困扰,使用该机制后,不论是脚本调试还是运行,都获得了令人满意的速度。同时在测试框架和测试脚本中,建立了清晰的代码层次,隔离了测试对象的获取和使用,使得框架和脚本代码的编写更加简洁,逻辑更加清楚。


火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。
资源网站: UML软件工程组织