发布新日志

  • (转载)RFT-Manual Verification Point

    2008-12-19 15:15:07

    Rational Functional Tester(以下简称RFT)是一款强大易用的自动化功能测试工具。在使用RFT进行功能测试的过程中,测试结果的验证往往是通过插入验证点(Verification Point)来完成的。但是RFT的验证点只能验证有限的数据类型,而在实际应用中,对用户自定义类型的验证存在着较大的需求。本文对验证点的验证执行过程进行剖析,并介绍如何通过自定义ValueManager来实现对用户自定义类型对象的验证。

    1验证点简介

    1.1 验证点的类型

    Rational Functional Tester是Rational最新推出的自动化功能测试工具。RFT具有数据驱动(Data-Driven)测试、scrīptAssure等特性,因而受到广大功能测试人员的青睐。在RFT中,验证点是脚本(scrīpt)中非常重要的组成部分,它完成对被测试程序生成的实际数据和期望数据的比较,并将比较结果写入日志。一般情况下,测试的结果是通过对验证点的执行而得到的。

    RFT提供了多种形式的验证点,包括:

    静态验证点(Static Verification Point):静态验证点是在录制(Record)RFT脚本的过程中通过向导插入的验证点,它在脚本回放(Playback)的过程中自动被验证。

    手动验证点(Manual Verification Point):如果验证点所要验证的内容是由脚本开发人员在脚本中所提供的,则需要建立手动验证点对其进行验证。例如待验证数据来自外部数据源的情况,脚本开发人员需将数据读取后以参数的形式显式传给验证点。

    动态验证点(Dynamic Verification Point):动态验证点是在脚本首次回放时建立的。验证点一旦建立,其行为就和静态验证点相同了。

    如果以录制-回放(Record-Playback)模式使用RFT进行图形界面(GUI)的自动化回归测试(Regression Test),较常用的是静态验证点。而由于RFT的数据驱动测试特性以及与其他RUP工具的良好集成,使之也是非图形化界面的功能测试的首选工具之一。在这些测试用例中,存在着大量的用户自定义类型对象,这些被测试对象并不能在录制过程中被插入对象映射表(ObjectMap)中,也就是不能使用静态验证点来进行验证,这就需要我们使用手动验证点来比较它们。

    1.2 验证点执行过程

    在RFT中,手动验证点有两种声明形式:

    • IFtVerificationPoint vpManual (java.lang.String vpName, java.lang.Object actual)

      该声明接受两个参数,第一个参数为验证点的名称,第二个参数为被测试对象。可以通过如下方式在脚本中调用该方法:

      vpManual("VP1", "The object under test").performTest();
      

      这条语句的作用就是判断被测试对象和基准线(Baseline)是否一致。这里所说的基准线就是期望数据,它以XML格式被存储在磁盘上,后缀名rftvp。

      当回放脚本时,如果基准线所对应的文件已经在磁盘上存在,则RFT会比较被测试对象和基准线中的数据是否一致,如果一致,则测试结果为成功(Pass),否则为失败(Failed);如果基准线尚不存在,则会以当前被测试对象作为基准线存入磁盘。

    • IFtVerificationPoint vpManual (java.lang.String vpName, java.lang.Object expected, java.lang.Object actual)

      手动验证点的另一种声明接受三个参数,第一个参数为验证点的名称,第二个参数则为期望数据,第三个参数为实际数据,也就是被测试对象。可以通过如下方式在脚本中调用该方法:

      vpManual("VP1", "Expected object", "The object under test").performTest();
      

      当脚本回放时,RFT直接比较期望数据和实际数据,并将比较的结果被写入日志。总的来说,验证点的执行过程如图1所示:


      图1 验证点执行流程图
      图1 验证点执行流程图

    不难看出,在执行验证点的过程中,涉及到了将数据写入磁盘以及从磁盘中恢复数据的操作。将这些数据写入磁盘便于测试人员使用工具查看和编辑。但是这也带来了一个问题,对于开发者自定义类型的对象,哪些属性需要被写入磁盘,这些属性按照什么顺序写入都是类型相关的,需要脚本开发者自行定义。更重要的,如何比较自定义类型也是RFT所不能确定的,换句话说,脚本开发者需要告知RFT对象一致的标准。以上这两个任务都需要通过创建ValueManager来完成。

    ValueManager可以用类特定(Class-specific)的形式比较和持久化用户自定义的对象。它是IManageValueClass接口的实现。只有拥有自己的ValueManager的类型才能作为参数传递给vpManual。这些类型被称为基于Value-class的类型。缺省的,只有基本数据类型,String,Vector等少数类型才是基于Value-class的类型那么,如何才能实现我们自己的ValueManager呢?在下面的章节中,我们将通过一个实例来帮助您建立自定义类型的ValueManager。





    回页首


    2. 实例分析--如何实现ValueManager

    2.1需求的提出

    假设我们需要验证一个计算图形重心的算法。该算法的输出结果为一个自定义类型MyPoint,它表示的一个平面上的点。它具有两个属性x,y,分别表示其横纵座标。MyPoint的实现如下,除了构造函数,它还提供了x和y的getter和setter方法。


    public class MyPoint {
    	
    	int x;
    	int y;
    	
    	public MyPoint(int x, int y) {
    		this.x = x;
    		this.y = y;
    	}
    	public int getX() {
    		return x;
    	}
    	public void setX(int x) {
    		this.x = x;
    	}
    	public int getY() {
    		return y;
    	}
    	public void setY(int y) {
    		this.y = y;
    	}
    }
    

    我们的测试人员需要判断该算法得到的重心点是否正确,可以使用了如下脚本:


    MyPoint point = getCenterOfGravity(polygon);
    vpManual("VP_CG", point).performTest();
    

    上面的语句将在第一次回放脚本时将得到的重心坐标写入磁盘;测试人员可以使用验证点编辑器查看得到的基准线是否正确;在此后的回放中(通常是回归测试中),上述代码的工作就是比较当次回归测试得到的重心对象与基准线是否一致。

    但是,如果直接调用该语句,RFT就会抛出异常(Unsupported type, value class required),说明MyPoint不是基于Value-class的类型。要使MyPoint成为Value-class,必须为其实现相应的ValueManager,并部署到RFT中。

    2.2其他验证自定义类型对象的方法

    在介绍如何实现ValueManager之前,首先让我们来看一下其他的解决办法。

    第一种方法,我们可以逐一比较自定义类型的属性来验证被测试对象是否符合要求。如下例所示:


    MyPoint point = getCenterOfGravity(polygon);
    vpManual("VP_CG1", new Integer(point.getX())).performTest();
    vpManual("VP_CG2", new Integer(point.getY())).performTest();
    

    使用logTestResult方法也可以达到同样的目的:


    MyPoint point = getCenterOfGravity(polygon);
    boolean flag = (point.getX() == 6 && point.getY()==8);
    logTestResult("This is not a VP", flag);
    

    这两种方式都有其局限性,第一种方式会使验证点的数量增加,特别是当自定义类的属性很多的时候;并且该方法也不利于重用,如果多个脚本都需要验证MyPoint,则脚本开发的工作会大大增加;第二种方式虽然不增加验证点数目,但是由于logTestResult只记录比较结果,使日志中的信息不足。特别是在测试用例失败的时候,这不利于测试人员定位问题所在。

    因而,为自定义类型实现ValueManager较以上两种方法更好。


    图3 使用ValueManager则可以通过验证点编辑器查看和编辑验证点数据,利于问题定位
    图3 使用ValueManager则可以通过验证点编辑器查看和编辑验证点数据,利于问题定位

    图4 使用logTestResult方法只能记录结果,不能记录数据,信息不足
    图4 使用logTestResult方法只能记录结果,不能记录数据,信息不足

    2.3创建ValueManager

    现在,我们准备创建MyPoint的ValueManager--我们命名为MyPointValue了,如上文所述,它是IManageValueClass接口的一个实现,该接口有7个方法:persistIn, persistInNamed, persistOut, compare, getCanonicalName, getClassName和createValue。这些接口分别完成哪些事情呢?

    我们再来看看,执行验证点的过程中ValueManager是怎么工作的。如图1所示,验证点执行过程中必须的步骤可以归为三类:读取数据--从基准线中读取数据;写入数据--将期望数据写入日志,将实际数据写入日志,写入基准线;比较--比较期望数据和实际数据。以上三类动作都需要ValueManager的参与。下面以MyPoint为例:

    如图5,在从基准线中读取数据的过程中,如果脚本发现基准线存在,RFT会读取基准线数据。如果RFT在注册了的ValueManager表中找到了相应的ValueManager--对MyPoint来说,就是找到了MyPointValue--就会创建该ValueManager的一个实例,然后调用该实例的persistIn方法。persistIn方法包含了如何读取MyPoint内容的逻辑,其返回值就是一个MyPoint对象,这样就将存储在磁盘上XML格式的MyPoint对象恢复出来,供RFT继续使用了。


    图5 从基准线中读取数据
    图5 从基准线中读取数据

    如图6,在写入数据的过程中,无论是写入期望数据,写入实际数据还是写基准线,都是通过persistOut方法完成的。同样的,RFT首先找到MyPoint的ValueManger,将其实例化,然后调用MyPointValue的persistOut方法将其写入磁盘。


    图6 写入基准线
    图6 写入基准线

    如图7,在比较两个MyPoint对象的时候,RFT还是先找到MyPoint的ValueManger,将其实例化,然后调用MyPointValue的compare方法,得到一个分值,根据分值和用户设置判断期望数据和实际数据是否一致。


    图7 比较期望数据与实际数据
    图7 比较期望数据与实际数据

    理解了IManageValueClass接口各方法的作用,我们就可以来实现这个ValueManager--MyPointValue了。如上文所述,有7个方法需要实现。其中最关键的是4个方法:

    • 持久化输出方法

      public void persistOut(Object obj, IPersistOut persistout,
        IAuxiliaryDataManager auxdatamanager)
      			

      该方法通过persistout将对象obj的属性写入到磁盘上。obj就是要写入的对象,persistout是负责写操作的接口,由RFT传入,auxdatamanager是用于命名相关文件的接口,由RFT传入,通常不是使用到这个参数。

      以MyPoint为例,下面的代码段将MyPoint的属性x,y依次记录到磁盘上:

      public void persistOut(Object obj, IPersistOut persistout,
      			IAuxiliaryDataManager auxdatamanager) {
      		if (obj instanceof MyPoint) {
      			MyPoint point = (MyPoint)obj;
      			// persistout是负责写的接口,
      //write方法接受的第一个参数是要写入的属性的名称
      //第二个参数是要写入的属性值
      			persistout.write("X", point.getX());
      			persistout.write("Y", point.getY());
      		}
      	}
      	

      对于测试开发人员来说,这里写入了哪些属性,将来哪些属性才能够被恢复出来。

    • 持久化恢复方法

      RFT提供两个方法读入持久化了的对象。一种接受IPersistIn类型的参数,另一种接受IPersistInNamed类型的参数。二者的不同在于,前者是根据对象属性的存储顺序进行读取的,而后者是按照对象的属性名称进行读取。以MyPointValue为例, 方法一的实现如下:

      public Object persistIn(IPersistIn persistin, IAuxiliaryDataManager auxdatamanager) {
      		// 在persistOut方法中,是按照x,y的先后顺序写入磁盘的,
      // 那么在该方法中就需要按照x,y的顺序将其读取出来read的参数就是写入磁盘的序号。
      		int x = ((Integer)persistin.read(0)).intValue();
      		int y = ((Integer)persistin.read(1)).intValue();
      		return new MyPoint(x, y);
      	}
      

      方法二的实现如下:

      public Object persistIn(IPersistInNamed persistinnamed, IAuxiliaryDataManager auxdatamanager) {
      	// 该方法根据persistOut时写入的属性名称读取属性,read的参数是属性名称
      		int x = ((Integer)persistinnamed.read("X")).intValue();
      		int y = ((Integer)persistinnamed.read("Y")).intValue();
      		return new MyPoint(x, y);
      	}
      

    • 比较方法

      public int compare(Object obj1, Object obj2, ICompareValueClass comparedvalueclass)
      

      compare方法用于确定两个对象一致与否,其前两个参数为要比较的对象,返回值则为一个0-100之间的整数,返回值越大则被比较的对象越相似。

      以MyPointValue为例,其比较原则是,如果二者相等则返回100(表示不同),否则返回0(表示相同)。

      public int compare(Object obj1, Object obj2, ICompareValueClass comparedvalueclass) {
      		if ((obj1 instanceof MyPoint) && (obj2 instanceof MyPoint)) {
      			MyPoint point1 = (MyPoint)obj1;
      			MyPoint point2 = (MyPoint)obj2;
      return (point1.getX()==point2.getX() && point1.getY()==point2.getY()) ? 100 : 0;
      		} else {
      			return 0;
      		}
      	}
      

    • 其他方法

      除上述四个方法,还有3个方法也需要实现,分别是

      public String getCanonicalName() { 
      		return "MyPoint";
      	}
      	

      上面的方法返回该Value-class的平台无关的规范名称

      public String getClassName() {
      		return "com.rational.ft.sample.MyPoint";
      	}
      	

      上面的方法返回ValueManager支持的Value-class的名称

      public Object createValue(Object sourceToCopy) {
      		return null;
      	}
      

      上面的方法返回被测试对象的一个拷贝,可以返回null,较少会被用到。

    2.4 部署ValueManager

    上面的7个方法实现了,MyPoint的ValueManager也就完成了。要在工程中使用ValueManger,还必须将ValueManager部署到RFT中。这也是注册ValueManger的过程。

    首先我们需要将ValueManager的实现导出为jar文件,例如vmsample.jar。

    第二步,我们还需要创建RFT自定义文件。RFT自定义文件是后缀名为RFTCUST的XML文件,用于定义开发者扩展的proxy,valuemanger等。RFT自定义文件内容如下,不难看出,其中ComponentModel元素中Obj一段是用于定义我们所创建的ValueManger的。ValueClass是要被验证的自定义类型;Manager是该自定义类型的ValueManger。


    <ConfigFile L=".ConfigFile">
    	<Section L=".ConfigFileSection">
    		<Name>valueManagers</Name>
    		<Val L=".ValueManagerManager">
    			<ComponentModel L=".ComponentModel">
    				<Name>Java</Name>
    				<Obj L=".ValueManager">
    					<Id>.MyPoint</Id>
    					<ValueClass>com.rational.ft.sample.MyPoint</ValueClass>
    					<Manager>com.rational.ft.sample.valuemanager.MyPointValue</Manager>
    				</Obj>
    			</ComponentModel>
    		</Val>
    	</Section>
    </ConfigFile>
    

    将.jar文件和RFTCUST建立好后,把这两个文件都放到C:\Documents and Settings\All Users\Application Data\ibm\RFT\customization目录下,ValueManager即被部署到RFT上了。(有可能需要重新启动计算机)





    回页首


    结论

    验证点是脚本的重要组成部分。对自定义类型的验证又是测试中所不可避免的。通过开发ValueManager来扩展RFT对自定义类型验证的支持,较之其它方法可重用性好,并使信息能够在日志中一目了然,而且便于修改期望数据。这一特性使RFT的应用更加自由。

  • (转载)利用 Rational Functional Tester 实现 ITCL (或者 IBM) 框架

    2008-12-19 10:28:34

    框架

    IBM 框架以前被称作为 ITCL 框架,由质量软件工程(Quality Software Engineering) 和 IBM 中有经验的自动化团队合作开发而成的。这个框架由三层架构组成,架构的实现贯穿了应用对象、任务和测试用例包(IBM 包)。

    潜在于应用对象、任务和测试用例包之下的基本原理是:

    • 层次化的体系架构
    • 将“做什么”与“如何做”分离开来
    • 代码重用
    • 一致和清晰的组织结构
    • 快速增强的能力
    • 迅速的调试
    • 有效地组织文件
    • 启用协作
    • 学习他人

    下面是对应用对象、任务和测试用例的解释说明:

    • 应用对象:储存有关你的应用程序中的GUI元素信息。同时在这里也可以编写你的Getter 方法,这些 Getter 方法可以返回对象,使 调用者能够对这些GUI元素进行查询和操作。一般情况下,这些方法在Task层中进行调用。
    • 任务:在这里你将编写可重用的方法,这些方法在你的应用程序中执行通用功能。同时在这里,你将编写可以处理和查询复杂的特定应用程序控件的方法。在任务中的方法可以被测试用例调用。
    • 测试用例:导航一个应用程序,验证其状态,并记录其结果的方法。




    回页首


    实施方法论

    在本章节中概述的方法论详细说明了实施IBM框架的5个步骤。

    步骤1. 首先,在你的本地驱动器上创建一个新的项目。这个项目中有一个你可以储存、维护、编译和运行你的自动化代码的储存库。在Functional Tester中,选择File > New > Functional Test Project。给这个项目命名,并点击Finish

    步骤2. 将 IBM 的包― 它包括将在自动化脚本中广泛使用的工具类 ― 导入到你的项目中。尽管这可能只是简单地将路径关联到ibm.jar文件,但是将 IBM 的包导入到你的项目中就可以使你更容易地检查这个包的内容,然后在调试的时候进入到包中。这个IBM的包可以在这篇文章末尾的下载部分中进行下载。

    导入 ibm.jar 包

    1. 在 IBM Rational Functional Tester中,进入屏幕左边的Projects视窗,点击在步骤1中创建的项目
    2. 选择 File > Import。选择 Zip file 然后点击Next。使用Browse按钮在你储存这个文件的指定位置上查找ibm.jar 或者ibm.zip 文件。
    3. 保留所有默认设置并点击 Finish
    4. 你现在应该可以在Functional Test Projects视窗中扩展你项目的名称。你可以在它下面看到一个名为ibm的文件夹。

    步骤3. 创建一个名为AppObject的包

    1. 在IBM Rational Functional Tester中,进入屏幕左边的Projects视窗,在步骤1中创建的项目上点击。
    2. 选择File > New > New Test Folder
    3. 给文件夹命名为AppObject
    4. 点击Finish
    5. 重复1-4的步骤,创建TasksTestCase 文件夹

    什么是 AppObject 包?

    在这个包中你必须映射所有被测试应用的对象。一个最常见的建议是为每一个屏幕准备单独的脚本,以此确保更好的对象以及分类的的重用和组织。比如,创建一个名为login的脚本,它将使所有的对象跟login 界面保持相关。你同样可以创建其它与 sentinbox 界面等等相关的脚本。

    使用 AppObject 文件夹进行工作

    1. 在 AppObject 包中建立一个空脚本
    2. 选择 AppObject 包,点击右键并选择 Add Empty scrīpt,如图1所示。

    图1. 添加一个空脚本
    添加一个空脚本
    1. 将其命名为Login,并点击Finish
    2. 从脚本资源管理器中双击Private Object Map
    3. 确保 mail.yahoo.com site 网站(或者被测试应用)是打开的。
    4. 从 Private Object 图中,点击Test Object > Insert Object(s),如图2所示。

    图2. 插入一个测试对象
    插入一个测试对象
    1. 将指针图标工具从下面的对话框拖到你想映射的目标位置,如图3和图4所示。

    图3. 通过拖拽选择一个对象
    通过拖拽选择一个对象

    图4. 选择的对象
    选择的对象
    1. 点击Finish
    2. 你的Private Object Map窗口应该看起来如图5所示。

    图5. 已完成的 Private Object Map
    已完成的 Private Object Map
    1. 选择你最近添加的对象,点击右键并选择 Add to scrīpt AppObject.Login
    2. 对所有你想要添加到这个脚本的对象重复6-10的步骤。
    3. 点击File > Save并关闭它。你的脚本资源管理器应该看起来如图6所示。

    图6. Login的脚本资源管理器
    Login 的脚本资源管理器
    1. 类似地,你可以尽可能创建更多的脚本到你的项目中,并添加相关的对象。

    自动产生 AppObjects 代码

    一旦你将你的对象添加到你的对象地图中,就可以通过编写几行代码自动产生你的getter 方法。这些getter 方法将帮助你访问跨越或在项目中被添加的脚本中的对象。你将在AppObject文件夹中创建一个空的脚本,编写如列表1中所示的代码来自动产生getter方法

    1. 在AppObject文件夹中创建一个名叫getter 的空脚本文件。
    2. 创建列表1中的代码。

    列表1.自动生getter的代码
                    
    //IMPORT THESE 2 STATEMENTS AT THE TOP OF scrīpt
    import java.util.Vector; 
    import ibm.tools.ClassGenerator;
    //WRITE DOWN  THE BELOW CODE IN TESTMAIN FUNCTION
    public void testMain(Object[] args) 
    	{
    		Vector v = new Vector();
    		v.addElement (new AppObject.Login ());
    	//ADD ELEMENT FOR ALL THE scrīptS YOU HAVE IN YOUR AppObject
    		new ClassGenerator().updatescrīpts(v);
    	}				
    				

    1. 你的Getter脚本内容看起来应该如图7所示。

    图7. Getter 脚本
    Getter 脚本
    1. 选择scrīpt > Run来运行这个脚本。
    2. 运行一次以后,当你点击你的Login脚本时,它将查询你是否要装载这些变更。
    3. 点击Yes,你应该可以看到你的Login脚本已经有了Getter功能,如图8所示。

    图8. 用 Getter 脚本登陆系统的功能
    用 Getter 脚本登陆系统的功能

    步骤4. 创建任务

    任务是你可以编写最大限度重用和最复杂代码的位置。

    1. 首先在Tasks文件夹中创建一个脚本(跟前面的AppObject Folder操作一样)
    2. 选择Tasks文件夹,右键点击它并选择Add Empty scrīpt
    3. 指定详细的名称并点击Finish
    4. 将列表2中的代码插入到脚本中。

    列表2. 登陆任务
                    
    //DECLARE THE OBJECT OF THE scrīptS EXIST IN APPOBJECT 
    public AppObject.Login lgn = new AppObject.Login();
    ….
    ….
    public AppObject.Login lgn = new AppObject.Login();
    	public void AssignLoginInfo()
    	{
    		lgn.getText_login().setText("abc");
    		lgn.getText_passwd().setText("New1");
    	}
    
    public void testMain(Object[] args) 
    {
    }							
    				

    1. 你的脚本内容应当看起来如图9所示。

    图9. 修正脚本内容
    修正脚本内容
    1. 你可以在同一个脚本或者新的脚本中根据每个应用程序的需要随意增加各种功能。

    接下来,你将使这些功能实现自动化

    步骤5. 创建测试用例

    在测试用例中,你可以编写现有的步骤来完成这些行为,这将继承一些 Tasks 和 Appobject的属性特征。

    1. 首先在Tasks文件夹中创建一个脚本(跟前面的AppObject Folder操作一样)
    2. 选择Tasks文件夹,右键点击它并选择Add Empty scrīpt
    3. 指定详细的名称并点击Finish
    4. 将列表3中的代码插入到脚本中。

    列表3. 测试用例
                    
    //DECLARE THE OBJECT OF THE scrīptS EXIST IN TASKS 
    //OBJECT CREATION OF TASKS LOGINTASK scrīpt
    	public Tasks.LoginTask lt = new Tasks.LoginTask();
    ….
    ….
    public void testMain(Object[] args) 
    {
    	//INVOKING THE BROWSER
    	startBrowser("mail.yahoo.com");
    	//ASSIGNED THE USER NAME AND LOGIN INFO
    	lt.AssignLoginInfo();
    	//CLICKED ON LOGIN/SUBMIT BUTTON
    	lt.lgn.getButton_signInsubmit().click();
    	//FURTHER ACTION CAN BE WRITTEN ACCORDIUNLGY		
    }
    

    1. 你的脚本内容看起来应当如图10所示

    图10. 最后的脚本
    最后的脚本
    1. 现在运行这个脚本,打开浏览器,输入用户名和密码,然后登陆到Yahoo帐户
Open Toolbar