测试框架设计的几点原则
一个好的测试框架需要具备哪些元素呢?虽然对不同的项目而言,答案可能有所不同。但总的来说,一个好的测试框架通常具有以下的共同特点:
除了以上所述的几点外,一个好的框架还应该提供相应的通用服务,以使得脚本开发者可以很容易而且快速地基于它来开发脚本。比如象错误处理,本地化版本支持,日志服务等。
回页首
定义良好的层次结构
如何定义一个良好的层次结构,是我们在构建测试框架首先需要考虑的问题。通常我们会把最基本的一些原子操作放在最底层,而在较上层封装这些操作,并开放相应的接口供最终的脚本开发者进行调用。这样往往会使得 Case 更加简洁易读。
根据面向对象基本理论,我们首先要定义一些基础的控件类,用来代表那些需要在测试中进行操作的基本界面元素,象按钮,输入框等。如果你使用 RFT 测试 Java 或 Web 应用,那么你可以直接使用 IBM Package,在这个包里封装了所有的在 Java 和 Web 这两个 Domain 下的基本控件。通过在你的脚本中调用这些类,你就可以很方便地实现对被测应用的操纵。但是,我们所测试的应用是基于 Windows .Net 的,在这个包里没有对应的控件类可以利用,因此,第一步,我们要开发自己的控件类。
其实,我们也可以不定义基础类,而直接使用 GUITestObject 来操纵每一个界面对象,但这样做的代价是显而易见的,会有很多重复且不易读的代码充斥在我们的脚本中。这显然不是我们所想要的。好在虽然各种控件的种类繁 多,但对于每一个控件而言,需要封装的主要是一些我们在测试中经常需要调用的简单方法,比如说 Click(),或 SetText(),因此工作量并不太大。
在实现了这一层之后,理论上说,我们可以直接在 Case 脚本里调用这些方法来实现对测试对象的操作。但这样做也有一个问题是,如果以后你对这些方法的名称进行了修改,或者因为程序实现的改变,原来的 A 类控件被 B 类代替,这时,势必会对所有已经编写好的 Case 脚本造成很大的影响,带来很大的代码维护量。因此为了隔离下层代码对上层 Case 的影响,我们在其中又加入了新的一层。最后的层次结构如下:
以下是对每一层的具体解释:
在描述完整个框架的大致结构后,我们来看看这种结构的优点:
采用这样的框架,只开放相应的接口供最终的脚本开发者进行调用,会使 Case 更加简洁易读。在 GUI 界面发生变化时,也不需要对 TestCase 做任何修改,大大提高了程序的可维护性和可扩展性。
在 RFT 里的具体实现
在定义完整个测试架构以后,接下来是在测试工具中用代码加以实现。在本项目中,我们使用的是 RFT,现在,让我们来看看在 RFT 里的具体实现。
图 2. 在 RFT 里的实现
从上图可以看到,我们使用 3 个 Project 来对应 3 个不同的层,而不是象通常的项目一样,将所有的代码放在一起,层次结构通过不同的包结构来体现。这样做的主要原因是让各层之间更加独立,而且更易于管理。 另一个好处是,多个 Project 的设计可以让复制和共享更加灵活。例如,当其他 Team 也想要利用 NetWidgets 时,只需要简单地将对应的 Project 共享给他们即可;而另一个 Team 如果需要某一个 Suite 来验证某项具体功能,则可以将 3 层所对应的 Project 都共享给他。
Dialogs 和 Wizards 目录中定义了所有与具体被测应用相关的方法,而位于 Suite Project 中的 Test Case 则调用他们来进行测试。在这里我们列出一些代码以便大家能够更清楚地了解它们之间的调用关系。
这是一段摘自 LoginDlg.java 的代码(Dialog):
图 3. LoginDlg.java 代码片断(Dialog)
这是一段摘自 Login.java 的代码(Wizard):
图 4. Login.java 代码片断 (wizard)
从上面的代码可以看出,在 Dialog 对象中,所定义的方法都是对该 Dialog 中控件的基本操作方法,象点击一个按钮,在编辑框里输入字串等。但在 Wizard 中,则通过将这些 Dialog 中的基本操作串联起来完成一个简单任务,如登录。相类似的功能则放入同一个 Wizard。
框架中的其他公用服务
通过在框架中提供一些公用服务,可以使得该框架更加易于使用,并且功能强大。在前面的图 2 中,可以看到在 Utils 目录下有 3 个 Class。它们分别提供不同的功能,让我们做一下简单的介绍。
对于自动化测试而言,有一个很重要的环节是用一种统一的方式来记录测试的结果,从而可以方便地进行统计或生成报表。所以第一个要提到的是 TestResult。
TestResult
这个 Class 用来记录每个 Case 的名称和执行结果。它读取一个 Xml 模板文件,这个文件定义了哪些内容需要在测试结果中显示。在自动执行完成后,包括 Suite 名称,测试环境,Case 信息,测试结果等这些数据将会被写入,生成结果文件。最后通过一个事先定义好的样式表文件进行格式化,用网页的形式呈现。
另外,对于失败的 Case,最主要的错误信息及屏幕截图也会一并记录下来,以方便进行分析查错。
LibException
这个 Class 作为所有运行时异常的基类。这个父类里增加了以下功能:
通过这些功能,结合使用上述的 TestResult 类,就可以很方便地保存每个错误发生时的详细信息,而不仅仅是一个简单的错误消息。
ObjectFinder
一般来说,在 RFT 测试中,通常使用 Object Map 来在回放中定位界面对象。Object Map 是在测试脚本录制中自动生成的,因此使用起来较为方便。但它有一个缺点是,它记录了所有对象的层次结构,并据此进行查找。一旦对象的关键属性或层次发生改 变,则必须重新录制以更新 Object Map。当项目里 Case 比较多的时候,这就变成了一项费时费力的工作。因此,RFT 还提供了另一种动态查找的方法 TestObject.Find(),在编写脚本时可以即时地调用该函数,通过给定的属性值进行查找定位。目前它的效率已经与通过 Object Map 定位不相上下。
ObjectFinder 类是一个用来动态查找对象的工具类,它通过调用 TestObject.Find() 来实现,并提供多种不同的查找方法。最常见的是指定对象的 .class 和 .text 属性来在某个窗口中进行查找。这个类主要在 AppLib 层中的 Dialog 对象中使用,在那些对窗口中对象进行操作的函数中,第一条语句很可能就是调用 ObjectFinder.getObject(className, captionText, parent) 来找到所要操作的界面对象。
另一个使用动态查找的好处是,可以将那些用于识别界面元素的关键属性保存到一个配置文件中。当有属性值变化时,就可以很简单地通过修改配置文件实 现,而无需修改脚本。同时,如果需要进行其他语言版本的测试,也可以直接将配置文件替换为其他语言版本的文件来实现对多语言版本测试的支持。在我们这个项 目中,我们还有另外一个工具可以直接从源码的资源文件中提取界面上的文本资源,自动生成我们所需的属性识别配置文件中,从而大大简化了手工编写的工作量, 同时也可以轻松应对因界面文本改变而带来的查找修改的困扰。
结束语
开发一个适合自己项目的 RFT 自动化框架,需要了解架构设计方面的基础知识,以及清楚地知道一个好的测试框架所需要提供的功能。但这并不很困难,因为有很多这方面的文章和经验可供参考,同时也有很多成熟的框架可资借鉴。
除了结构清晰,关注分离,易于扩展之外,一些通用服务也是一个好的测试框架不可或缺的部分。通过利用这些服务,RFT 的脚本开发人员可以更方便、快捷地开发出自动化脚本,同时保证使用统一的方法,生成格式一致的测试结果。这些也是使用框架的意义所在。
转载于 IBM:
http://www.ibm.com/developerworks/cn/rational/r-cn-rftwindows1/