Ⅰ.20 如何创建和访问应用程序的绑定

来源:互联网 发布:js null undefined 编辑:程序博客网 时间:2024/06/05 11:52

这一部分讨论Squish的一个先进并且强大的特性——如何创建提供应用程序绑定的包装器。

自动动态绑定

从Squish4.0开始,已经加强了对Squish绑定的支持。对于Java和基于Qt的应用程序,Squish现在可以动态的创建包装器,它可以自动的为所有的标准库对象提供绑定,这些库对象供AUT使用,也为AUT自己的自定义类提供绑定。用Java的时候,甚至不是自动动态绑定的类可以使用Java的自省功能来访问。这意味着在大多数情况下,没有必要创建自定义绑定。

对于Qt AUT的绑定,Squish将会自动绑定slots和属性(甚至为可安装的信号绑定句柄)。slots是类public slots: 的成员函数;使用Qt的Q_PROPERTY宏声明属性。如果想绑定原始方法,最简单的就是将它们声明为slots。然而,如果不想让方法设为slots,或者想绑定全局函数,可以按照该部分的说明的方法创建自己的绑定。

Squish已经带有其支持的GUI工具包的完整的脚本绑定,如Qt 和 Java AWT/Swing 和 Java SWT。这测试工程师就有机会访问这些工具包提供的所有的widgets、functions、和properties。对于AUT使用的任意标准对象(例如widgets),使用这些脚本绑定,可以检索和这事对象属性、调用对象方法。这使得实现复杂的和稳定的测试脚本成为可能,该测试脚本可以使用验证点验证应用程序的状态和行为的。

在大多数情况下,Squish提供的脚本绑定足够使用了。但是在一些情况下,可能需要访问AUT中定义的对象或者AUT使用的组件中的对象,但是该组件又不在GUI工具包的标准类中。对于Squish 4.0不是问题,因为从这个版本开始,AUT类的绑定是Squish自动创建的。但是对于早期的版本,必须手动实现。

为什么我们要访问自定义AUT组件而非那些AUT内置的GUI工具包呢?因为有时我们想访问附加的自定义属性或者调用自动以组件提供的自定义方法。例如,我们可能想测试一个CAD应用程序,它的自定义Canvas widget是一个key组件。为了提供有综合力的测试,我们想能够获取自定义Canvas来验证测试已经画好的对象是正确的。

为了这个目的,Squish提供了一个非常强大的机制,它可以组装C++代码以及为之创建脚本绑定。设置了绑定之后可以调用函数和访问应用程序中声明的属性,同时又不改变应用程序本身。

这个部分将会给出一个例子,用来展示如何创建绑定使得测试脚本可以访问AUT的API。我们将会一步一步教你做,因此你可以看到如何组装应用程序。

例如,将使用canvas例子,canvas使用了Qt,对于Qt3存放在examples/qt3/canvas下,对于Qt4存放在examples/qt/canvas下。

这个程序是一个非常简单的矢量绘图应用程序。通过点击canvas视图,即插入一个新的矩形。拖动(点击并按下左键移动)可移动矩形。

为了验证canvas是否正确的插入和移动,需要访问它的API,这样我们可以查询它们的大小和位置。

Ⅰ.20.1 组装和包装

为了创建绑定我们必须组装AUT或附加的组件,然后生成一个绑定库(也叫做包装器库)。

首先我们将要组装应用程序。使用squishidl工具可以实现这个功能,这个工具解析C++头文件并产生绑定代码,该绑定代码将应用程序的API透漏给脚本语言。这个工具提供了许多命令行选项。代替手动为每个文件运行squishidl,我们使用frglogic的编译工具(叫’build‘),这个编译工具相当的简化了任务。‘build’使用了一个类JavaScript语言来描述编译规则和目标,是一种便携式的替代品。

编译包装器:

尽管为应用程序你没有使用build,我们还是推荐使用它来生成绑定库。

为了使用build来创建应用程序绑定,在应用程序的源代码所在路径下创建一个名为Buildroot的文件。这个文件必须有单独的一行包含Squish安装路径的绝对路径。例如,如果Squish已经安装在了/usr/local/squish下,Buildroot文件将会包含这样一行:

/usr/local/squish
这将会确保build可以定位到创建一个绑定库所有需要的文件。

所需的源编译

尽管使用Squish的一个二进制版本,还是需要源格式的Squish来创建绑定——特别是src自路径。这是必须的,因为源代码包含文件和信息,这些信息对于解决对外部库的依赖性是有必要的,而这些外部库在编译期间是必须的。

当build运行时,除了寻找知道Squish在哪的Buildroot文件,还要寻找一个名叫Buildsub的文件。这个文件是build工具,等效于make工具的Makefile,它描述了编译的目标,与Buildroot文件一起应该放在AUT的源代码所在相同的路径下。

Ⅰ.20.1.1 组装canvas应用程序

为了给canvas示例生成一个应用程序绑定库(不管是Qt3或者Qt4),我们必须将下面的代码放在Buildsub文件中:

var canvasWrapperHeaders = ["canvas.h"];var vanvasWrapperLib = wrapperLib("canvas",canvasWrapperHeaders, stdIncs, ".");
首先我们定义一个数组列出所有的头文件,我们用squishidl为这些头文件创建封装套。在这种情况下,我们要封装定义在canvas.h文件中的API。

为了创建封装套库,build工具将会调用wrapperLib函数。这个函数的第一个参数必须是用于封装套库的名字。通常这是应用程序的或者组件的不带后缀的名字(即在Windows上不带.exe 或 .dll)。第二个参数是一个数组,它列出了待封装的文件:将第一行创建的数组传给它。第三个参数指定了包含了待用的路径,为此我们使用build工具中预定义的stdIncs数组,该数组包含了所有的标准包含路径。最后一个参数指定了封装套库编译的路径。这里我们只将它设置在当前路径编译。

如果带封装的文件包含其他路径的文件,必须将这些文件所在的路径添加到包含路径中,例如:

var all_includes = stdIncs.concar(["/my/include/path"])
将自己的数据(这里是all_includes)作为第三个参数传给wrapperLib,而不是stdIncs。
既然Buildsub文件有其所需的所有信息,我们可以在包含Buildsub文件的路径先运行build工具。这将会生成绑定套库。

Ⅰ.20.1.1.1 Unix上的符号

有一个潜在的问题。运行时包装器库将要访问应用程序的符号(类,函数等),但是只有导出它们才能使用。

符号是如何导出的根据编译器而定。例如,在Solaris和IRIX上,符号默认自动导出。但是Linux下的gcc不是这样的,它需要添加一个选项 -rdynamic 传给链接。

如何添加 -rdynamic 选项根据所使用的编译系统而定。如果正在使用Qt的qmake作为应用程序的编译工具,可以将下面的一行添加到工程文件中:

linux-g++*:LIBS += -rdynamic
当在32位和64位Linux系统上使用gcc时,这句将会添加-rdynamic选项到链接行。.pro文件也可以安全的用在其他的平台上,因为在其他平台上将会忽略这行。

Ⅰ.20.1.1.2 Windows上的符号
对于Windows,也需要导出符号。本质上当我们创建一个DLL时我们需要对应用程序做同样的操作。我们必须以__declspec(dllexport)属性标识出待导出的每个类。这将会生成一个携带应用程序的导出符号的导入库,我们必须将这些导出符号连接到包装器库中(这最后一个部分是编译工具自动处理的)。

第一个部分是修改应用程序的编译系统来导出符号以及创建导入库。

如果你正在使用qmake来编译应用程序,这些行必须添加到应用程序的工程(.pro)文件中。

wind32:DEFINES += TEST_EXPORT=__declspec(dllexport)unix:DEFINES += TEST_EXPORT=
如果不使用qmake,但是使用nmake直接编译应用程序,将下面的一行传给NMakefile文件中CXXFLAGS变量:

/DTEST_EXPORT=__declspec(dllexport)
r如果也是用Unix的make,你可以将相关的行添加到Makefile的CXXFLAGS变量:

-DTEST_EXPORT=
现在正确的定义了导出宏,但是还需要一步,这样Windows的连接器才会创建所需的携带导出符号的导入库:必须在待导出的代码中使用导出宏。

在canvas示例中,我们在canvas.h中有如下类定义,想为该投文件创建绑定:

class CanvasInterface:public Qt{    //...};class CanvasItem : public CanvasInterface{};class CanvasRect:public CanvasItem{};class CanvasModel : public QObject, public CanvasInterface{};class CanvasView:public QScrollView{};
为了让Windows连接器知道我们想导出这些类的名称符号,必须以导出宏标记它们。一次我们修改文件:添加TEST_EXPORT到类定义中,如下所示:

class TEST_EXPORT CanvasInterface:public Qt{    //...};class TEST_EXPORT <span style="font-family: 'Microsoft YaHei';">CanvasItem : public CanvasInterface</span>{};class TEST_EXPORT <span style="font-family: 'Microsoft YaHei';">CanvasRect:public CanvasItem</span>{};class TEST_EXPORT CanvasModel : public QObject, public CanvasInterface{};class TEST_EXPORT CanvasView:public QScrollView{};
Qt Designer.ui文件:

如果类定义在Qt的.ui XML文件中,在Qt Designer中打开该文件,双击Object Explorer属性表中的name属性。将TEST_EXPORT宏输入到导出宏区域,保存.ui文件。自此,uic工具将会自动添加宏到生成的C++类中。

Windows-specific:

在Windows上,squishidl工具只为类、全局函数以及变量创建了内省和自动化代码,它们都使用TEST_EXPORT宏导出。这是为了避免与生成库中未解决的符号发生冲突。

如果重新编译应用程序,将会创建携带导出符号的到入库叫<appname>.lib。下一步是扩展包装器库的Buildsub文件以导入应用程序的导出符号。为了做到这点我们必须将下面的代码添加到Buildsub文件中:

var importMacro;if(windows)    importMacro = "TEST_EXPORT=__declspec(dllimport)";else    importMacro = "TEST_EXPORT=";setDefines(canvasWrapperLib, [importMacro]);
这里定义了一个TEST_EXPORT宏来导入已导出的符号。同样,将它作为一个canvasWrapper库的编译器行的定义添加进去。

当使用build创建包装库的时候,将会正确的生成绑定库。

Ⅰ.20.1.1.3 额外的依赖

根据应用程序的架构,可能有额外的应用程序或包装库所依赖的库包含进来,应用程序或者包装库依赖这些库。这种依赖性由包装套库继承。因为在链接时解决了一些平台所需要的所有未定义的符号,当链接到包装时有时不得不显式的指定额外库。

当编译包装库时,如果在连接时获取‘unresolved external symbol’(或相似的)错误,这种依赖变得很明显。

这里是一个编译脚本的一段——即,从Buildsub文件中提取出来的——证明如何使用extras属性,该属性用于指定附加的链接标志。基于以上含有附加需求的示例,MyUtils Windows DLL定义了一些包装对象所需的符号:

//create an array property if it doesn't existif (!canvasWrapperLib.extras)    canvasWrapperLib.extras=[];//push a new elemetn onto the end of the arraycanvasWrapperLib.extras.push("MyUtils.lib");
在类Unix系统上,要传递一个类似 -lMyUtils的开关和 -L标志的参数,标志告诉连接器在寻找库时该搜索那个路径。

Ⅰ.20.1.1.4 用于测试的简化的API

有时候,不需要(或不可能)暴露内部API。原因可能各不各样:

因为squishidl的局限性,原始API使用不可能操作的C++特性。比如C++ STL或嵌套的namespaces。

• API使用了不能通过非编译的脚本语言访问的C++模板。

• 测试员将会遇到不可能或难以访问应用程序的内部。

• 测试员希望应用程序开发者让程序有个稳定的界面,界面从正在进行的重构中解耦出来。

对于此类情况,内部API可以隐藏在一个额外层之后,该层是只为测试而创建的。

这里有一个示例界面描述了在特定测试类中的一个函数中使用一个C++模板类:

#ifndef TESTAPI_H#define TESTAPI_H#include "internal.h"class TEST_EXPORT TestAPI{public:    // simple script access to internal C++ template class    static void setNetworkEnabled(bool on){        Component<Network>::self()->setEnabled(on);    }};#endif
一旦应用程序开始了,将会加载额外的包装,使用简单脚本调用就可以使用函数:

TestAPI.setNetworkEnabled(true)
Ⅰ.20.1.1.5 最后注意事项

我们已经看到了有时候需要改变一些编译设置以使应用程序的API可以被测。如果想测试与传递的是相同的编译,可以一直不管这些设置,因为导出应用程序的符号是有害处的。

如果想让一个动态库中的API可测,库中的符号无论如何都将被导出(否则将不可能使用库)。因此对于动态库,,不需要额外的工作来导出符号。

Ⅰ.20.2 使用包装器库创建一个测试

如果包含应用程序绑定的包装器库跟随名称转换 libAUT_NAMEwrapper.so(Unix), libAUT_NAMEwrapper.dylib(Mac OS X) 或AUT_NAMEwrapper.dll(Windows),它定位在一个AUT路径中或者SQUISH_WRAPPER_PATH环境变量中的一个路径中,开启AUT时包装器库自动加载。只包装AUT的API时还是比较方便的。但是如果我们也想包装额外组件的API,通常创建对个包装器库更方便(每个组件创建一个)。

对于Squish加载多个封装器库,它们必须位于一个AUT路径下或者SQUISH_WRAPPER_PATH环境变量中列出的一个路径。为了告诉Squish加载特定的包装器库,只要在测试套件的suite.conf中指定它们的名字即可,并作为WRAPPERS键的值。包装器的名字是Buildsub文件中wrapperLib函数的第一个参数。

另一种,通过测试套件的Settings视图可以指定在Squish IDE中哪个包装器库来加载。所有的变量包装器都列在包装器本分——只勾选你想加载的checkboxes即可。


Squish IDE的Settings 视图展示的包装器

正如截屏所示,默认的包装器——即,相关GUI工具包的没有列进去,因为他们总是自动加载。

Ⅰ.20.2.1  测试案例举例

这里有个示例测试脚本,它往canvas中插入三个矩形。为了验证矩形是否正确插入了,我们使用了函数 CanvasView::canvasModel, CanvasModel::numItems, CanvasModel::item(取一个整数索引)和 CanvasItem::rect。所有的这些函数通过创建的绑定库都可用。

下面是示例脚本的代码。完整的例子测试套件在examples/qt/suite_canvas下(Qt4)。(Qt3下一个非常相似的例子只有Python测试脚本,在examples/qt3/suite_canvas下)。

def checkRectangle(item, x, y, width, height):    test.compare(item.rect().x, x)    test.compare(item.rect().y, y)    test.compare(item.rect().width, width)    test.compare(item.rect().height, height) def main():    snooze(1.5)    sendEvent("QMoveEvent", ":Canvas_QMainWindow", 572, 471, 538, 402)     # insert 3 rectangular items    mouseClick(":Canvas.CanvasView_CanvasView", 90, 56, 1, Qt.LeftButton)    mouseClick(":Canvas.CanvasView_CanvasView", 170, 173, 1, Qt.LeftButton)        mouseClick(":Canvas.CanvasView_CanvasView", 271,96,1,Qt.LeftButton)    snooze(0.5)    # retrieve reference to the canvas model    canvas = waitForObject(":Canvas.CanvasView_CanvasView")    model = canvas.canvasModel()    #check that it contains 3 items    test.compare(model.numItems(), 3)     #for each item, check that it has the correct geometry    checkRectangle(model.item(0), 90, 96, 50, 70)    checkRectangle(model.item(1), 170, 173, 50, 70)    checkRectangle(model.item(2), 271, 96, 50, 70)    # close and exit    sendEvent("QCloseEvent", ":Canvas_QMainWindow")

这里包括了创建应用程序绑定的范围,并且从测试脚本访问它们。使用这个功能,可以实现很强大的、坚固的、复杂的测试。

Ⅰ.20.3 如何探索包装器库

根据Squish的版本,可以研究所有Squish可用的绑定(包装器)。Squish携带自己的包装器库,但是如果已经为AUT的库或一个AUT依赖的库创建了自己的绑定,正如上所述,你也可以探究那些绑定。

Ⅰ.20.3.1 如何打开一个包装器库

Squish为探究包装器提供了一个简单的独立的GUI应用程序。这个应用程序叫wrapperexpl,位于Squish的bin路径下。从命令行运行这个应用程序,——或点击或双击它或在Mac OS X的Finder中选择它,依使用的操作系统而定。

wrapperexpl一启动它就弹出Open Wrapper Libraries 对话框。这个对话框列出了Squish提供的内置的包装器(如,‘squishqtwrapper’)。勾选任意一个你想探索的checkbox。如果想添加自己的包装器库,点击Add...按钮,从文件选择对话框中选择相关的 .so, .dll文件(依操作系统而定),一个一个添加,添加完成后确保勾选了库的checkbox。


The Open Wapper Libraries 对话框

必须确保选择的包装器库是容易辨识的,因为他们的文件名常常包含字串‘wrapper’。

选择Add...之后,库将会被添加到Open Wrapper Libraries对话框中的列表中。


The Open Wrapper Libraries Dialog with a Custom Library

推荐将来使用展示的包装器库列表,点击它们然后点击Remove按钮即可移除任意不感兴趣的库。

点击OK按钮加载所有的已经勾选的库到Wrapper Explorer中。

Ⅰ.20.3.2 如何探究包装器

在Open Wrapper Libraries对话框中选择一个或多个库之后,一个新的窗口代替Wrapper Explorer窗口。这个窗口展示了包装器中的类,使用一个分层树视图来展示他们。


Wrapper Explorer

展开一个类将会展示当前类,以及任意公开的枚举类型、构造函数、属性和可用成员函数。

想要更快的导航,窗口底部有一个文本区域的过滤器。当在这里输入文本时,该文本用于过滤树的条目。如果所有的文本是小写的,过滤器不区分大小写;但是如果文本同时有大写和小写,过滤器区分大小写。如果通过右下角的combobox选择类,过滤器只用于类名——对于快速找到某个类非常有用。对于类成员中的快速导航,将右下角的combobox改成Members即可。

可以将选择出来的项作为文本拷贝到剪贴板,首行缩进用于树。点击可选择项,该选择可以扩展到多个项。例如,在Windows上,可以在点击附加项时按下Ctrl 键选择第二个和其后的子项。另一种是,可以点击一个来选择多个项,然后按下Shift键选择另一个——这将会导致选择两个被点击的项,它俩之间的所有项也将会被选择。也可以点击Edit->Select All 菜单项选取所有的项。

Lazy Discovery

为了效率起见,只在第一次展开一个项时才能发现其子项。这意味着具体展开哪个项将决定选择所有的项生成不同的结果。

0 0