转载自:http://www.ibm.com/developerworks/cn/rational/r-cn-rftappopspeed/

级别: 初级

欧 迎丰, 软件测试工程师, IBM
赵 暇, 资深软件工程师, 项目组负责人, IBM
刘 哲, 软件工程师, IBM

2008 年 5 月 05 日

使用 RFT 能够高效准确自动的对应用软件的操作响应速度进行测试。本文以测试一个普通文档管理软件打开和保存文档的速度为例,介绍了使用 RFT 进行此类测试的基本流程框架和通用的方法,以及通过使用 RFT API 等方法快速准确的查找到 TestObject 及其属性或调用其方法,从而得到准确的软件操作响应速度的过程。

软件操作响应速度测试的利器——Rational Functional Tester

在一个普通的应用软件开发过程中,我们需要了解应用软件执行操作的响应时间,并由此判断和分析软件在运行时带给用户在响应速度上的体验,从而为用户提供更完善更友好的软件产品。

那么如何才能准确的对软件的操作响应时间进行准确的测量以达到我们的目的呢?如果仅仅通过人工手动的方式,凭借掐秒表,算时间的方法,是很难进行准确高效的测量的,而且由于软件版本的 daily build,我们还需要保证软件不同 build 之间性能的延续性,如果都是通过人工测试来完成这样的工作的话,将会耗费大量的人力物力和时间,而且测试的准确性也很难得到保证。

因此,我们不可避免的要通过自动测试方式对软件的性能进行测量。在 IBM Rational 提供的一系列软件自动测试产品中,首先映入我们眼帘的就是 IBM Rational Performance Tester (RPT) ,但是,RPT 是一个为测试 web 应用软件而设计的自动测试平台,它通过模拟并发用户的数量,产生一系列的报告,标识出 web 页面的性能、URL 和事务等信息,从而帮助查明 web 系统的瓶颈。因此它并不能适用于普通的非 web 应用软件的性能测试。

而 IBM Rational Functional Tester (RFT) 却恰恰是用于测试 Java、Web、.NET 等各种应用软件的一个自动化测试工具。通过 RFT,可以访问到应用软件中的各个对象以及对象的方法和属性,并以此为依据产生测试脚本,让应用程序自动运行,并校验程序运行的结果,从而完成自动测试。

在 RFT 中要获得软件的一个操作所花的时间非常简单:

startTime = System.currentTimeMillis();
            ……Operation process……
            endTime = System.currentTimeMillis();
            operationCostTime = endTime – startTime
            

由于调用的是 System.currentTimeMillis() 函数,因此得到的 operationCostTime 也是精确到以毫秒为单位。

那么,下面让我们以一个普通的文档管理软件为例,一起来探讨一下如何使用 RFT 对应用软件进行操作响应方面的自动性能测试吧。

操作响应速度测试的基本流程框架

要通过 RFT 自动测试一个操作的响应时间,首先需要精确定义操作起始以及操作完成的标识。

操作的起始往往都比较容易决定,比如说按下某个按钮,点击某个菜单等等;而判断某个操作的结束却往往需要更仔细的斟酌:

因为操作的结束对于用户来说可能只是某个界面上的改变,但对于程序本身来说,却包含着许多对象的创建或者删除,更多对象状态和属性的改变。那么要在 RFT 中通过程序脚本来判断操作是否结束 , 要使得程序脚本具有可执行性,在程序中就需要明确的对某个对象的某个属性或方法进行判断,才能由此准确的测试出应用软件的响应时间。

因此,操作完成的 RFT 判断标识,往往都需要与软件的开发设计人员进行充分的讨论交流,根据程序中的具体实现流程,分析总结出软件中哪一个对象的创建或显现或是哪一个对象的哪一种状态的改变能够最恰当的标识出操作的结束。

一般的操作响应时间判断流程如下:


图 1 操作响应速度测试程序流程图
图 1 操作响应速度测试程序流程图 

案例一:文档保存——对象属性的改变标识操作结束的测试

测试方案

我们首先来看一下如何测试某个文档管理软件保存一份文档所需的确切时间:

操作的起始我们很容易鉴定,当 Save 这个 Menu 被点击时,我们的测试计时就可以开始了。而具体的反映到 RFT 的 script 代码上,我们首先可以通过 RFT 获得 menu 对应的 TestObject 对象,然后直接调用 click 方法即可:

menu.click(atPath(“File->Save”));
            

然后开始计时:

startTime = System.currentTimeMillis();
            

而保存操作的结束标识呢?从用户的角度来看,当用户点下保存菜单时,鼠标的指针会变成沙漏状,而保存操作完成后,鼠标指针会变回箭头状。可在 RFT 中,想判断鼠标指针的形状非常困难而且鼠标的状态还可能来回的变化,因此我们必须寻求其他的途径。

通过与开发人员的讨论,我们了解到,用户在保存前,首先会对文档进行修改(没有改动过的文档,保存菜单是 disable 的),那么在软件界面下方的状态栏中,有一个用于标识 Modify 状态的 Lable 对象,如果文档的内容发生了改变,则该 Lable 对象的内容 (Text) 会变成“*”,而当保存完成后,软件内部的流程会将该 Lable 对象的内容相应的更新成为“”,表示该文档相对于保存到硬盘上的文档而言,没有发生改变(保存刚刚完成,打开的文档内容当然和保存到硬盘上的是一致的)。因此,我们可以通过 RFT 获取该 Lable 对象及其 Text 属性,并对属性进行判断,当该对象的 Text 属性由“*”变成“”时,即表示保存操作已经完成了。

具体的操作也很简单,我们只要用 RFT 的 Object Map,在软件的界面上抓取到这个 Modification Lable 对象,


图 2 Modification Lable 对象抓取图
图 2 Modification Lable 对象抓取图

并将其添加到 Script 中,


图 3 对象添加到 Script
图 3 对象添加到 Script

还可以在 Script Explorer 中选择 Insert at Cursor 直接生成返回 MLable 对象的代码,


图 4 Insert at Cursor
图 4 Insert at Cursor

进而就可以通过 RFT 提供的 API 获得它的相关属性了。

测试代码

具体的执行代码如下 ( 假设 Modification Lable 对象的名称为 MLable):

TestObject toMLable = MLable(); // 得到 Modification Lable 对象
            menu.click(atPath(“File->Save”));
            startTime = System.currentTimeMillis();
            while(true){
            String MLableTxt = (String) toMLable.getProperty(“Text”);
            if(MLableTxt == “”)
            break;
            }
            endTime = System.currentTimeMillis();
            System.out.println(“The Save Operation’s performance is: ” +
            ( endTime- startTime)+“Milliseconds”);
            

测试的结果及问题

让我们执行一下脚本看看测得的时间是多少,或许会得到一个结果:

“The Save Operation’s performance is: 20943 Milliseconds”
            

这个结果与我们人工掐表测试的结果相差很远——人工掐表测得的结果才一秒多——那问题到底出在什么地方呢?

让我们来分析一下,其实在这个简单的判断循环中,最可能会有问题的只有这一句话:String MLableTxt = (String)toMLable.getProperty(“Text”); 让我们来看看执行这句话需要多少时间:

startTime = System.currentTimeMillis();
            String MLableTxt = (String)toMLable.getProperty(“Text”)
            System.out.println(“The performance of getProperty is:”+(
            System.currentTimeMillis()-startTime) +“Milliseconds”
            );
            

结果是多少呢 ?

 “The performance of getProperty is: 20012 Milliseconds”
            

原来是 toMLable.getProperty(“Text”) 需要这么长的时间,那这样的话,在这条语句的执行过程中,软件的保存操作虽然早就完成了,但是由于 toMLable.getProperty 一直在执行,只有在 toMLable.getProperty 执行结束后,程序才能进入判断流程,继而得知操作已经结束,此时再统计时间,自然就大大的超出了保存操作原本需要的时间了。

ObjectMap 分析

那为什么小小的一句 toMLable.getProperty 需要这么常的时间呢?其实,这都是 Object Map 惹的“祸”。

让我们来探寻一下 toMLable 的来源:

TestObject toMLable = MLable();
            

由于我们先前将 Object Map 中的 TestObject 添加到 Script 中这一操作,在 Script 对应的 Helper 父类中,才对应的创建了 MLable() 方法:

protected GuiTestObject MLable ()
            {
            return new GuiTestObject(
            getMappedTestObject("MLable"));
            }
            

该方法返回的是一个 MappedTestObject,即在 RFT ObjectMap 中,由 ObjectManager 维护的对象。而在 RFT 的相关文档中,我们可以了解到,ObjectManager 是通过某个识别算法将 Map 中的对象与被测试软件中的对象进行一一对应的。而我们通过 MLable() 得到的 toMLable 对象,也只是保存在 ObjectMap 中的对象的引用,而并不是直接的指向被测软件中对应的对象。只有我们在调用 toMLable.getProperty 方法时,ObjectManager 才会去被测软件中根据识别算法重新找寻对应的对象,并返回其相应属性,当被测软件中存在众多的类似对象或多层的容器对象结构时(StatusBar 下有众多的 Lable 对象),这种查找自然就需要一个较长的时间。而且如果我们试着连续调用多次 toMLable.getProperty 方法,toMLable.getProperty 方法的执行时间也不会发生什么改变,每次都需要执行将近 20 秒左右的时间,这也是能够和 RFT 的相关文档说明互相印证的。

因此,我们不得不放弃这种通过 ObjectMap 直接获得被测对象的简单方法,当然,在不考虑响应时间的其他 RFT FVT 测试中,这种方法依然是可以使用的。

获取 TestObject 的直接引用

要能够在足够短的时间内获取 Modification Lable 的 Text 属性,就必须获得直接指向该对象的 TestObject,而在 RFT 的系统功能框架中,我们可以通过调用目标对象的父类对象的 getChildren 或 find 等方法,直接获得指向被测对象的引用,从而快速的取得被测对象的实时属性。

首先,我们可以通过 ObjectMap 获取软件用户界面下部的状态栏 StatusBar 的 TestObject,


图 5 状态栏 StatusBar 的 TestObject
图 5 状态栏 StatusBar 的 TestObject

而且由于我们并不是直接判断 StatusBar 的属性,所以我们通过 ObjectMap 这种方便的方法而即使慢一点也不会有什么影响。

TestObject toStatusBar = StatusBar();
            

我们可以调用 toStatusBar.getChildren() 函数得到状态栏中所有的 Lable 引用,并调用相应的 getProperties() 方法将这些 Lable 的属性全部打印出来,这样我们就能清楚的知道如何能够通过程序代码来找到我们需要轮询判断的 Modification Lable 对象。

TestObject Lables[] = toStatusBar .getChildren();
            for(int i =0;i<Lables.length;i++){
            System.out.println(Lables[i].getProperties);
            }
            

在打印出的一长串属性列表中,我们可以找到 Modification Label 对象的属性,和其他的 Lable 对象相比,我们可以通过 toolTipText 这个属性把它和其他 Lable 区分开来。

int Midx = 0;
            for(int i =0;i<Lables.length;i++){
            if(Lables[i].getProperty(“toolTipText”)!=null &&
            ((String)Lables[i].getProperty(“toolTipText”)).startwith("Modification")){
            Midx = i;
            }
            }
            

这样,Lables[Midx] 就是我们要进行轮询判断的 Modification Lable 对应的 TestObject 了。

我们先来看一下通过 Lables[Midx] 查询其 Text 属性所花的时间:

startTime = System.currentTimeMillis();
            String MLableTxt = (String)Lables[Midx].getProperty(“Text”);
            System.out.println(“The performance of getProperty is:”+(
            System.currentTimeMillis()-startTime) +“Milliseconds”
            );
            

结果是多少呢 ?

“The performance of getProperty is:62 Milliseconds”
            

62 毫秒,这对于我们测量操作结束时机的判断,已经影响不大了。因此我们可以将之前 PVT 循环判断流程中的

String MLableTxt = (String) toMLable.getProperty(“Text”);
            

替换为

String MLableTxt = (String)Lables[Midx].getProperty(“Text”)
            

即可测试得出软件保存文档操作所需的时间。而最终测试出的结果,平均是 1643 毫秒,这与我们人工测试的结果 1.70 秒相比,是非常接近的,也是可相当可信的。


案例二:文档打开——对象的产生标识操作结束的测试

通过上面的描述,我们可以了解到,在使用 RFT 对应用软件的操作响应速度进行测试时,操作结束的标识是某个对象的状态属性发生变化的情况下,该如何合理的编写 Script 的代码。可是在某些情况下,操作完成的标识是某个对象被创建出来呢?

让我们来看一个测试某文档管理软件打开一个文档所需时间的例子。

测试方案

文档的打开,可以分为在用户界面上能够看到文档和文档完全导入两种,即所谓的 Load Show 和 Load Finish。而这两种操作的结束时间,都是我们需要测试的。

我们首先来看一下 Load Show 的情况:

对于打开文档的操作,开始的标识也一样很容易确定,当打开文档的对话框出现,文件被选中,最终打开的按钮被点下时,计时就需要开始了。

而同样通过与开发人员的交流,我们知道,文档被打开后,软件是通过一个 SuperODCControl 对象来显示文档的,因此当这个 SuperODCControl 对象被创建并且显示出来的时候,就可以标识 Load Show 已经完成了。

我们可以通过 ObjectMap 获得 SuperODCControl 这个对象并调用其 exists() 和 isShowing() 方法来判断 LoadShow 操作是否完成。可是正如同我们前面提到的,通过 ObjectMap 获得的对象只是在 Map 中由 ObjectManager 管理的对象,如果调用其方法或者获取其属性,ObjectManager 都需要去被测软件中重新进行识别,找到对应的对象后再执行相应的操作。虽然对于 SuperODCControl 来说,其容器继承包含结构相对简单,ObjectManager 查找起来相对容易些,但是为了能够更准确的测试出软件的响应速度,我们还是希望能够找到直接指向 SuperODCControll 的 TestObject。

与之前类似,我们首先要找到 SuperODCControl 的父类容器对象,然后才能通过 find 之类的方法找到直接指向 SuperODCControl 的 TestObject。继而通过调用其 isShowing() 方法来判断 LoadShow 操作是否完成。

可是不同的是,在 SuperODCControl 对象存在之前,我们需要在循环中不断调用它父类的 find 方法来对 SuperODCControl 进行查找,因此,我们获得的 SuperODCControl 父类的 TestObject 也应该是直接指向被测对象的引用,而不是指向 ObjectMap 中的对象。

因此,我们需要首先通过 ObjectMap 获得 SuperODCControl 父类对象的父类对象,调用其 find 方法,得到直接指向 SuperODCControl 父类对象的引用。


图 6 SuperODCControl 对象
图 6 SuperODCControl 对象
ParentCompsite =
            Grandcomposite().find(atChild("class","org.eclipse.swt.widgets.Composite"));
            

然后在循环中就可以直接调用:

toODCControl =
            ParentCompsite[0].find(atChild(
            "class","com.ibm.productivity.tools.core.SuperODCControl")
            );
            

这里需要注意的是,即使只找到了一个对象,通过 find 返回的也是 TestObject[] 类型。

测试代码

具体的流程如下:

ParentCompsite =
            Grandcomposite().find(atChild("class","org.eclipse.swt.widgets.Composite"));
            Menu().select("File->Open->File...");
            OpenDlg objDialog = new OpenDlg();
            objDialog.FileName.setText(FileName);
            objDialog.Open.click();
            startTime = System.currentTimeMillis();
            while(true){
            toODCControl =
            ParentCompsite[0].find(
            atChild("class","com.ibm.productivity.tools.core.SuperODCControl")
            );
            if(toODCControl.length > 0)
            break;
            }
            while(true){
            if(((GuiTestObject)(toODCControl[0])).isShowing())
            break;
            }
            endTime = System.currentTimeMillis();
            System.out.println(
            “The Load Show Operation’s performance is: ” +
            ( endTime- startTime)+“Milliseconds”
            );
            

让我们试着运行一下 :

exception_name = com.rational.test.ft.ObjectNotFoundException
            exception_message = Looking for [ScrollTestObject(Name: composite, Map: Composite)]
            - no plausible candidate was found.
            

错误分析及解决方案

原来,无论是 SuperODCControl 的父类 ParentCompsite,还是其父类的父类 Grandcomposite,在文档打开之前,都是不存在的。只有打开文档的操作开始后,软件才会一步步创建 compsite 类的实例,最终再创建 SuperODCControl 对象。

那我们要在 objDialog.Open.click() 之前获取 compsite 的 TestObject,自然是得不到的。那 Grandcomposite 的父类呢?它是否 click 之前就已经存在了,我们可以获取它的引用吗?如果不是的话,它的上一层父类是吗?

我们可以在 ObjectMap 中可以看到,软件中有着一系列的容器包容结构,怎样才能找到哪一个 Composite 是文档没有打开时就已经存在的通用容器窗口呢?一层层的去尝试的话显然是不太可行的,我们这里介绍一个小小的技巧:

利用 RFT 抓取 TestObject 的不同方法,我们可以巧妙的知道到底哪一个 Composite 是真正的当文档没有打开时,就存在的窗口父类。

首先,我们只运行文档管理软件,但是不打开任何文档,这时使用 RFT 的手型对象抓取工具,选择文档管理软件的当前空白窗口:


图 7 抓取的当前空白窗口
图 7 抓取的当前空白窗口

选择之后,我们并不是直接选择工具界面下方的 Next,而是选择下拉列表中的 TestObject Brower 方式。


图 8 TestObject Brower
图 8 TestObject Brower

这时,我们可以看到,我们选择的窗口对象的在整个软件中的树形结构。


图 9 窗口对象浏览
图 9 窗口对象浏览

我们只需要记住这个结构即可,然后再将 Select Method 的下拉列表选择成 Drag Hand Select。

接下来,使用我们的文档管理软件打开一个文档,再使用手型工具选择我们打开文档的窗口:


图 10 抓取的文档窗口
图 10 抓取的文档窗口

选择之后,我们同样的再次选择下拉列表中的 TestObject Brower 方式:


图 11 文档窗口对象浏览
图 11 文档窗口对象浏览

得到 SuperODCContrl 对象的树形结构,这时我们再比较一下,打开文档前后两个树形结构的差别,从而找到统一的 Composite 父类。这样,我们可以在 ObjectMap 中添加这个父类的父类 SSashForm,然后在代码中通过 find 方法找到这个统一 Composite 父类的直接引用,之后就可以用这个直接引用的 find 方法来找到 SuperODCControl 对象了。

CommParentComposite =
            SSashForm.find(atChild("class","org.eclipse.swt.widgets.Composite"));
            SuperODCControl =
            CommParentComposite[0] .find(atDescendant
            ("class","com.ibm.productivity.tools.core.SuperODCControl")
            );
            

需要注意的是,由于 SuperODCControl 不是 CommParentComposite 的直接子类对象,因此在使用 find 方法是,参数是 atDescentdant 而不是 atChild。

我们可以测试一下这种方式的效率,得到的结果是只花费了几十个毫秒。

在使用上面的方法后,我们最后测试得到的软件打开文档 Load Show 的时间平均是 901 毫秒,这与我们 Manual 测试的结果是非常类似的。

而对于 LoadFinish 来说,我们则需要判断软件状态栏下方的 TotolPage 状态栏对象是否存在,并且其 Text 内容变化为文档的总页数时,才表示文档全部 load 结束了。这种判断方式与我们之前的保存结束判断方式是一致的,我们可以采取类似的方法来进行测试。而最终的测试结果为 3635 毫秒,与我们的 Manual 测试结果也非常的接近。


总结:

通过上述的介绍。我们大概的了解了使用 RFT 对普通应用软件进行操作响应速度自动测试的基本情况。如何准确的确定应用软件操作的起始和终止的时间,并进行准确适时的判断和测试,以及在编写 RFT 脚本时可能会遇到的一些问题和一些小的技巧等等。但是,在使用 RFT 进行软件性能测试时,还存在着种种的问题,比如说如何进一步减小 RFT 程序的判断时间,减小 RFT 运行本身给软件性能带来的干扰,采用更科学合理的算法对测试结果进行分析而不是简单的算术平均等等。这些问题,我们会在后续的内容中继续讨论。


 

参考资料

学习

获得产品和技术

讨论

作者简介

  欧迎丰,是 IBM 中国软件开发中心的一名软件测试工程师。目前从事应用软件的性能测试工作。曾在《计算机研究与发展》期刊发表文章《一种基于移动 IPv6 的自适应预测切换机制》,并拥有专利《一种卡片操作系统访问外存的方法》。

  赵暇,是 IBM 中国软件开发中心的一名资深软件工程师兼项目组负责人,目前担任 IBM Lotus Symphony 测试项目组负责人。她还曾经担任过 IBM 全球服务部(IGS)的项目组负责人。熟悉 Rational 相关工具及其整合的开发与测试。

  刘哲,是 IBM 中国软件开发中心的一名 IBM Lotus 软件工程师。喜欢 Java,自动化测试和 Web 开发。