ITPub博客

首页 > Linux操作系统 > Linux操作系统 > 巧用 Rational Functional Tester 的 IWindow 接口

巧用 Rational Functional Tester 的 IWindow 接口

原创 Linux操作系统 作者:myattitude 时间:2009-04-14 14:23:08 0 删除 编辑

Rational Functional Tester (RFT) 是跨平台的 GUI 自动测试工具,可以自动测试基于 Java, HTML 和 .Net 的应用程序。RFT 的大部分功能是通过对象映射或者动态查找得到的 TestObject 实现。同时,RFT 还提供了一个基于 Win32 API 和 Linux Xlib 的 IWindow 接口,可以用来处理原生控件(指使用系统 API 直接生成的控件)和窗体。当使用 RFT 来测试 Java 或者 Web 应用程序时,可能也需要处理到系统原生的对话框,比如文件对话框或消息对话框。Windows 平台上的原生对话框和其中的控件可以被映射成 Win domain 的 TestObject (*1),但是 Win domain 并不支持 Linux 平台。在需要跨平台测试的情况下,需要使用 IWindow 来处理原生控件。

IWindow 的功能

虽然 IWindow 类只能对原生控件做很基础的操作,但是可以被用来做很多事情。比如,可以对控件点击,输入文字,检查是否显示和是否有焦点。此外,配合 JNI 和 IPC 的 C 语言代码,IWindow 可以实现测试中的所有需求。如何使用 IPC 和 JNI 不在本文的讨论范围中,只会在总结的部分简要提到。

一般来说,我们需要处理 6 种控件: Buttons、Labels、Check Boxes、Radio Buttons、Text Fields、和 Combo Boxes 。下面对每种控件分别讨论:

Button

对于 Button 控件,在 GUI 测试中关心的是上面显示的文字和如何对它进行点击。它们都可以直接通过 IWindow 的方法实现。明确地说,就是分别使用 getText 和 click 方法。

Label

只需要知道上面显示的文字,可以使用 getText 方法得到。

Check Box 和 Radio Button

因为都是属于 Button 类型 , 可以通过点击来选中 Check Box 和 Radio Button 。但通过使用 IWindow 提供的方法,不能知道控件是否被选中(进一步处理请参照后面的“扩展 IWindow 功能”部分)。

Text Field

通常我们需要获得控件上显示的文字和往控件上输入文字。输入文字可以直接使用 RFT 用户熟悉的一些方法,具体就是先调用 IWindow ’ 的 click 方法,然后使用 IScreen 的 inputKeys() 方法来输入文字 (RationalTestScript.getScreen().inputKeys())。得到 Text Field 上显示的文字需要一点技巧:先选中文字,再复制到剪切板里,然后使用 Java 的功能读取剪切板里的文字。下面是具体实现的代码:


例 1: 读取 text field 上显示的文字
public String getText() { 
        IWindow win = this.getControl(); 
        IScreen screen = RationalTestScript.getScreen(); 
        
        //copy text to clipboard 
        win.click(); 
        screen.inputKeys("{HOME}"); 
        screen.inputKeys("+{END}"); 
        screen.inputKeys("^C"); 
        
        //then get string off of clipboard and return it 
        String sReturn = ""; 
        
        Transferable t = Toolkit.getDefaultToolkit() 
                             .getSystemClipboard().getContents(null); 
        
        if (t==null) //nothing on clipboard 
            return ""; 
        
        try { 
            sReturn = t.getTransferData( 
                           DataFlavor.stringFlavor).toString(); 
        } 
        catch (IOException e) 
        { 
            System.err.println( 
                      "getText threw IOException; returning empty string."); 
            System.err.println("Error = " +  e); 
            e.printStackTrace(); 
        } 
        catch (java.awt.datatransfer.UnsupportedFlavorException ufe) 
        { 
            System.err.println("getText threw UnsupportedFlavorException; 
            i.e. data on clipboad is not string data; returning empty string."); 
            System.err.println("Error = " +  ufe); 
            ufe.printStackTrace(); 
        } 
        return sReturn; 
        
    }

Combo Box 和 List Box

Combo Box 是文本框和下拉菜单的一个组合控件,文本框的部分可以按上面的步骤当作 Text Field 来处理。但是通过使用 IWindow 并不能得到下拉菜单的内容,此外,List Box 比 Combo box 更棘手,不能通过输入文字来选择,所以需要一些手段来绕过这些限制。最好的方式是使用键盘来选择 List Box 中的条目。当然,需要知道条目的顺序才能选择正确。下面是一个使用 IWindow 来选择 Combo Box 下拉菜单的例子


例 2: 选择 Combo Box 下拉菜单
public void selectWithKeys(int index) 
    { 
        this.showDropDown(); 
        IScreen screen = RationalTestScript.getScreen(); 
        screen.inputKeys("{HOME}");        
        for (int i = 0; i

因为 IWindow 获取控件信息存在上文提到的一些限制,最好不要用 IWindow 来验证 GUI 的测试点,但可以用它来操作 SUT 到达测试的状态。举例来说,很难利用 IWindow 来验证 Combo Box 的状态,因为只用 IWindow 不容易得到 Combo Box 的所有信息,但是可以使用 IWindow 来设置应用程序的状态。例如,设置浏览器选项或者导出 html 到文件进行验证,等等。

经上所述,得到控件的 IWindow 之后,可以直接对控件进行基本的操作,关键在于如何找到所需的控件。下面两章会分别在 Windows 和 Linux 上做进一步探讨。

通过 IWindow 查找 Windows 窗体和控件

在探讨 Linux 的特殊之处前,先介绍一下如何在 Windows 上查找原生控件。一方面出于教学目的,先理解 IWindow 上的工作原理,有助于理解 Linux 上的基本用法,因为 Linux 上稍微复杂一点。另一方面,如果在 Linux 和 Windows 上测试同一个软件,会希望在两个平台上使用相同的接口来减少维护的工作。(当然,如果只需要测试 Windows 上的原生控件,使用 RFT 的 TestObject 接口会更容易些)

为了使用 IWindow 查找控件,首先需要找到他的父窗体,然后再遍历这个窗体的所有子控件,从中找到匹配条件的。父窗体通常是对话框或者被测程序的主窗体,都可以通过标题找到。例如,可以用下面的方法找到对话框上的控件。


例 3: 通过标题找对话框

public IWindow getDialog(String caption) 
    { 
        IWindow[] wins = RationalTestScript.getTopWindows(); 
        for (int i = 0; i

例 3 中的方法遍历屏幕上所有的顶级窗体,得到每个窗体的标题,当找到匹配标题的时候退出循环 (*2) 。

得到顶级窗体之后,继续搜索它的子控件。有几种属性可以用来定位控件。比如,Button 通常能通过文字找到,因此可以用下面的方式使用指定的文字搜索控件。


例 4: 利用文字查找控件

public IWindow findControl(String caption) 
    { 
        
        //find parent window    
        IWindow parentWindow = getDialog("My Dialog Caption"); 
        if (parentWindow == null) 
            return null; 
        
        //else find control 
        IWindow children[] = parentWindow.getChildren();                
        // if there are no children for this control, then control can't be found 
        if (children.length == 0) 
            return null; 
        
        for (int i = 0; i < children.length; i++) { 
            if (children[i].getText().equals(caption)) 
                return children[i]; 
        } 
        // no match to caption was found 
        return null; 
    }

这个例子遍历顶级窗体的所有子控件,查找给定标题的按钮 (*3) 。搜索文字对某些控件有效,但不是所有控件都有对应的文字。比如 Text Field 就没有对应的文字(IWindow#getText 并不返回 Text Field 上的内容,只能得到对应 window 上的文字属性,是个空字符串)。对于 List Box 也是这样。因此,我们需要其它的方法处理这类控件。一个方法就是使用索引定位这类控件,下面是一个使用索引查找控件的模式。


例 5: 通过索引查找控件

public IWindow findControl(String clazz, int index) 
    { 
        int count = 0; 
        
        //find parent window    
        IWindow parentWindow = getDialog("My Dialog Caption"); 
        if (parentWindow == null) 
            return null; 
        
        //else find control 
        IWindow children[] = parentWindow.getChildren();                
        // if there are no children for this control, then control can't be found 
        if (children.length == 0) 
            return null; 
        
        for (int i = 0; i < children.length; i++) { 
            if (children[i].getWindowClass().equals(clazz)) { 
                count++; 
                if (count == index) 
                    return children[i]; 
            } 
        } 
        // no match to caption was found 
        return null; 
    }

这个模式使用了一个类名和一个表示控件索引的整形数。它遍历所有控件,当找到指定类名的控件时,计数器加一。当计数器与索引值相等时,返回当前的 IWindow 实例,这里注意 Windows 上的标准控件包括 Button, ComboBox, ListBox, 和 Edit 。因为 Button 包括 Check Box 和 Radio Button,所有种类的 Button 必须用同一个类的索引。需要补充的是 Text Fields 属于 Edit 类型。

但使用索引存在一个问题 ,当控件发生移动时(比如研发人员决定在对话框上增加一个 Text Field), 索引值会变化,这样就需要修改测试代码。更可靠的一个方式是使用控件的 ID, 通过 IWindow#getId() 可以得到。控件 ID 是 Windows 赋给每个控件的编号,通常在相应对话框上是唯一的。所以无论控件怎么移动,控件 ID 是不会变的。不幸的是,控件 ID 只在 Windows 平台上有,由于本文的重点在 Linux 上,不详述控件 ID 。

通过 IWindow 查找 Linux 窗体

Linux 平台上面有一些复杂。X window 系统有一个客户机和服务器(Client-Server)结构。客户机(应用程序本身)决定屏幕上应该显示什么,服务器根据客户机提供的数据在屏幕上画出相应内容,并且把用户的输入发送到客户机(现在的 Linux 上面,客户机和服务器是同一台机器)。GUI 程序通过 Xlib 提供的函数来操作客户机和服务器。在 Xlib 这个层次,屏幕上显示的元素是一个个窗体,每个屏幕上的最顶级窗体叫做根窗体 (root window), 它覆盖了整个屏幕。其它的窗体都是根窗体的直接或间接子窗体。应用程序的主窗体和对话框之间没有主从关系。它们都是根窗体 的直接子窗体。由于这个原因,IWindow#getOwner 方法在 Linux 上只返回 null.

控件是客户端上的对象,在服务器端有一个对应的窗体。它把显示的内容画在窗体的区域里,并且响应窗体的事件。客户端对应每个窗体都有一个叫做 Graphic Context (GC) 的数据结构,该结构保存窗体上需要显示的数据。画新窗体或者重画窗体的时候,GC 都需要被发送给服务器。服务器根据 GC 的内容将窗体显示出来,但是并不保存 GC 。服务器仅仅只保存窗体和他们的一些属性在内存中。这些属性通过传递给窗体管理器(window manager)来决定窗体的行为,但不包括窗体上显示的内容。尽管 Xlib 提供一些函数,能够通过窗体的 ID 获取属性,但是我们只能得到关于窗体显示内容有限的一点信息,因为决定窗体显示内容的信息储存在应用程序里,这些数据不仅包括 GC, 还包括定义在更高层 lib 中的控件属性。这就是为什么 RFT 在 Linux 上处理原生控件时有很多局限性。在 Linux 上有效的自动测试工具需要通过 IPC 的方法在 SUT 中开放一些接口,或者,使用 Accessibility 相关的接口,如 Accessibility Tool Kit (ATK),来获得控件类型和文字信息。通过 IWindow 可以得到的信息包括:1,窗体标题 (窗体的 WM_NAME 属性)、2 ,窗体的大小和位置、3,窗体名和类名 (窗体的 WM_CLASS 属性)。

RFT 有几种方可以得到 IWindow (TestWindow 的实例 ) 。下面的部分使用 Firefox 的“ Open File ”对话框为例,来演示如何在 Windows 和 Linux 上用相同的方式使用 IWindow 的函数。


图 1. Firefox 在 Metacity 窗体管理器环境中的文件对话框
Firefox 在 Metacity 窗体管理器环境中的文件对话框

点击查看大图

1. 获得当前活动的窗体

使用 RationalTestScript.getScreen().getActiveWindow() 函数可以直接得到当前活动的窗体。它在 Windows 平台上返回正确的 TestWindow 实例,但在 Linux 上它有可能是窗体管理器创建的包装窗体 (Wrapper Window) 。GNOME 上的默认窗体管理器是 Metacity,会在应用程序窗体创建的时候自动加上一个包装窗体。包装窗体是应用程序窗体的父窗体,除了包括应用程序窗体本身外,还包括标题栏,图标,按钮和边框。getActiveWindow 方法在这种情况下返回包装窗体的 IWindow 。因此我们需要调用 IWindow 的 getChildren 来得到应用程序窗体。GNOME 还有另外一个窗体管理器— Compiz. 它会在桌面特效 (XGL) 开启的时候代替 Metacity 。Compiz 并不需要包装窗体来显示窗体的边框,它使用叫做 window decorator 的程序来给每个应用程序窗体显示边框。getActiveWindow 方法在 Compiz 的环境下可以跟 Windows 平台一样,直接得到应用程序的 IWindow 。

包装窗体有几个特殊的属性可以让我们识别:1,仅有一个子窗体,该子窗体就是应用程序窗体、2,窗体名字是空的、3,WM_CLASS 属性没有设置。例 6 说明了如何通过检查 WM_CLASS 属性和它的子窗体数目来跳过包装窗体。WM_CLASS 属性和 getWindowClasName 方法的关系会在下面一节描述(*4)。


例 6: 按需要跳过包装窗体

public IWindow skipWrapper(IWindow current){ 
        if (current.getWindowClassName().equals("") 
               && current.getChildren().length == 1) 
            current = current.getChildren()[0]; 
        return current; 
    }

2. 同过标题和类名来查找窗体

每个窗体或对话框都有一个属性来存储标题,IWindow 的 getText 方法根据这一属性来获得标题文字。窗体的类名在 Windows 平台上用来描述窗体的类型,比如 "#32770" 代表对话框。X Window 也有一个相似的属性,就是前面提过的 WM_Class. 它包括两个字符串:res_name 和 res_class 。res_name 指的是应用程序名,res_class 代表窗体的类名。getWindowClassName 方法把两个字符串合成一个作为结果返回,比如,图 1 中的文件对话框调用 getWindowClassName 返回 "Firefox-bin Gecko" 。使用 getTopWindows 可以得到系统中的所有顶级窗体(Linux 上不包括最小化的窗体),通过窗体的标题和类名可以定位我们要找的窗体或对话框。例 7 演示如何在 Windows 和 Linux 上获取文件对话框的 IWindow 。


例 7: 得到指定的对话框

public final boolean IS_WINDOWS = 
                         RationalTestScript.getOperatingSystem().isWindows(); 
     public String sCaption = "Open File"; 
     public String sWindowClassName =  IS_WINDOWS? "#32770":"Firefox-bin Gecko"; 

     public IWindow getWindow(){ 
        IWindow[] wins = RationalTestScript.getTopWindows(); 
        IWindow current = null; 
        for (int i = (IS_WINDOWS ? 0 : (wins.length - 1)); 
            (IS_WINDOWS ? i < wins.length : i >= 0); 
            i = i + (IS_WINDOWS ? 1 : -1)) 
        { 
            current = skipWrapper(wins[i]); 
            if (current.getText().matches(sCaption) && 
                     current.getWindowClassName().matches(sWindowClassName)) 
                break; 
        } 
        return current; 
    }

例 7 中,在 Windows 和 Linux 上使用相反的顺序遍历窗体,这跟 getTopWindows 函数在两个平台上返值得顺序有关:Windows 上新打开的窗体在返回的数前面,而 Linux 上新打开的排在后面。使用合适的遍历顺序,一方面,当有两个或多个标题相同的窗体时,可以返回最后打开的那个(通常是我们要测试的),另一方面,可以更快定位到我们需要的窗体。例 7 中还用到了例 6 中定义的函数,在需要的情况下跳过包装窗体。

找到窗体的 IWindow 之后,通常需要做的第一件事是激活该窗体,但是 IWindow#activate 方法在 Linux 上的 Metacity 窗体管理器环境下无效。例 8 演示了通过点击窗体上的一个点来激活该窗体,从而绕过这一限制。getScreenRectangle 方法可以得到包括窗体大小和位置的 Rectangle 对象,它在 Linux 上并不包括标题栏和边框,相信看过前面对窗体管理器的介绍,不难理解原因。为了得到跟 Windows 上一样,包括窗体标题栏和边框的 Rectangle, 在 Metacity 环境下,可以通过 IWindow.getParent().getScreenRectangle() 得到包装窗体的 Rectangle 。但是在 Compiz 环境下没有办法得到,因为标题栏和边框并不是显示在窗体内部。


例 8: 激活一个窗体 :

IWindow current = getWindow(); 
 public void setActive(){ 
        Rectangle r = current.getScreenRectangle(); 
        getScreen().click(atPoint(r.x, r.y)); 
 }

IWindow 还提供了 close 方法,但是只对顶级窗体有效。在 Metacity 环境下会抛出 UnsupportedMethodException 的异常,因为它认为应用程序窗体不是顶级窗体。为了关掉窗体,可以把 IWindow 转换成 TopLevelWindow,然后调用 close 函数。需要注意的是,TopLevelWindow 是 RFT 内部使用的一个类,通常在 SUT 中创建,被 RFT 的 proxy 对象调用。如果在测试脚本中创建它,只有一部分函数可以使用,比如 close, 因为它的很多函数只对自己的进程有效。


例 9: 关闭窗体

IWindow current = getWindow(); 
 new TopLevelWindow(current.getHandle()).close();

TopLevelWindow#setFocus 可以被用来在 Linux 上激活窗体,同样也适用于 Windows 。例 10 是另外一个激活窗体的例子,相比例 8,窗体能在被其它窗体遮挡的时候被激活。

例 10: 使用 TopLevelWindow 在 Linux 和 Windows 上激活窗体

TopLevelWindow(current.getHandle()).setFocus();

一旦在 Linux 上找到控件,同样可以应用上面的方法来操作那个控件,取决于控件的类型。也就是说,可以像本文第一章讲的那样,点击 Button, Check Box, 和 Radio Button, 在 Text Field 上输入和读取文字,和选择 List Box 的条目。但是仅用 IWindow 的函数,在 Linux 上还不能找到控件,需要使用下一章提到的方式来获取控件的类名和相关信息。

扩展 IWindow 的功能

上面提到过 ,IWindow 本身只提供一些基本的接口来操作原生控件,靠这些接口来测试还有很多局限性,但是可以通过一些方式来扩展 RFT,满足测试原生控件的所有需求 。比如前面提到的两点:无法得到 Check Box 或 Radio Button 的状态、不能得到 List Box 的内容。通过使用 JNI 的代码,可以解决这些问题。

同样,这些扩展在 Windows 上更简单和易于理解。Win32 API 提供了一些可以得到 Check Box 或 Radio Button 的状态、 List Box 内容的函数。因此可以写 JNI 调来调用这些 Win32 API 得到需要的这些值。

不幸的是,这些扩展在 Linux 上更复杂一些。原因在于 Linux widget API 只在运行函数的进程中有效。因此,不仅需要提供一些 JNI 接口实现数据列集(marshalling),和跟 SUT 进程通讯的机制,还需要把代码放在 SUT 的进程中去调用合适的 widget API, 并把需要的结果返回出来。

举例来说,对使用 GTK 控件的 SUT,我们需要在 SUT 的进程中使用下面的 C 代码,来得到 Check box 的状态。


例 11: 得到 Check box 的状态

BOOL GtkToggleItemGetStateFromXID (long xid) 
 { 
    GdkWindow *w = NULL; 
    GtkWidget *control = NULL; 
    gboolean bReturn=FALSE; 

    HoldChamixGtkLock(); //lock before calling x 

    w = gdk_window_lookup((Window)xid); 
    if (w!=NULL) 
        gdk_window_get_user_data( w, (gpointer *)&control); 
       //get the GtkWidget * of the control 

    if (control !=NULL && GTK_TOGGLE_BUTTON (control)) 
        bReturn = gtk_toggle_button_get_active (control); 

    ReleaseChamixGtkLock(); 
    
    return bReturn; 
 }

在 SUT 的进程中使用上面的函数得到需要的值后,还需要把信息传递给 RFT 。要实现这一点,需要用 IPC 让 SUT 和 JNI 代码通信。有很多方法可以创建 IPC 传递这样的信息,具体的实现方法不在本文探讨的范围中。

IPC 在 Windows 原生控件的测试方面也十分有用。尽管可以调用 Win32 API 来获取 Windows 标准控件的信息,但是它对定制或非标准的控件并不凑效。以 Lotus Notes 的 "Rich Text Field" 控件为例,它有可能包含 hotspot,OLE 对象,带格式的文本和其他很多富文本项目 , Windows 把整个 Rich Text Field 看作一个大矩形,并不知道如何操作里面的元素,比如 Hotspots 或链接。假如有了 IPC 的机制,可以直接跟 SUT 通信来操作定制的控件。例如,可以在 SUT 中写一小段代码来返回一个 Rectangle, 它代表 Rich Text Field 里面 Hotspot 的大小和位置,当需要点击 Hotspot 的时候,通过 JNI 和 IPC 在 RFT 中调用这段代码来得到这个 Rectangle, 使用 RFT 来点击它代表的点。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/14780914/viewspace-588980/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2008-07-07

  • 博文量
    172
  • 访问量
    332378