UML软件工程组织

使用 IBM Rational Functional Tester 实现自动化框架: 模块化
作者:Michael Kelly (Mike@MichaelDKelly.com), Consultant, www.MichaelDKelly.com 

编者注:本文是基于使用 IBM? Rational? Functional Tester for Java? 和 Web 6.1 以及 Windows XP Professional SP2 进行编写的。代码范例将使用 Java 语言,但是所有概念也同样适用于 Rational Functional Tester 的 .NET 版本。

抽象、封装,以及其它计算机科学术语

我通常不会大量使用术语,但是由于模块化框架是基于一些计算机科学基本原理的,所以看起来我们要花一些时间来谈论这些基本原理。一个模块化框架的第一个基本原理是,努力尝试在一个时间关注于一个逻辑功能块,也称作为抽象。使用抽象,您可以减少和排除一些细节,不用立即关联到您要进行编码的功能上。在模块中,您要关注于将控制从您的测试用例中分解出来 。通过控制,我的意思是被测试应用程序中的用例流和功能。对比数据驱动的框架(本系列中所涵盖的下一个框架),在那您更加关注于将数据从您的测试用例中提取出来。而控制的抽象就是活动的抽象。

模块化框架的第二个基本原理与抽象紧密相关。这就是封装的基本原理。在封装中,您要将关系紧密的程序元素(脚本、类等等)放在一个更大的、更抽象的实体(其它脚本和类)中。封装为您提供了将相关联的任务和操作集成在一起的技术。它也通过提供一个稳定的接口来为您保护您的脚本免受程序变更的影响,此接口能够隐藏您的脚本所执行的实际操作。这是一类隐藏的信息 ,您将在这里保护您的测试脚本;让它们都调用相同的脚本和类集合,这样在您需要进行变更时,这些变更将自然地对您的所有测试脚本生效。

模块化框架的第三个基本原理关注分离。关注分离是将一个程序分解成在功能上尽可能少重叠的独立特性。这个基本原理在您执行封装时会给您提供指导。通常,在您计划如何为被测试应用的特性和行为进行分离和组织测试代码时,您将会使用关注分离这个基本原理。

在本系列的三个框架中,模块化应当是最容易理解和掌握的。以上描述的这三个基本原理是众所周知的编程方法,并且今天的大多数开发人员在使用它们时甚至没有积极地思考过。模块化框架的目标就是通过应用这些基本原理,以改进您的自动化测试套件的可维护性和可扩展性。让我们看一下模块化的几个简单例子,然后我们将返回来,讨论一下有关这种方法的优势和劣势。

使用一个类来实现模块化

在这个例子中,我们将使用对 www.BookPool.com 的一个测试。在列表1中所显示的记录脚本启动了访问 BookPool.com 的浏览器,搜索有关软件测试方面的书籍,并验证应当在第一个返回结果页面中的三本书。

列表 1. 记录的 BookPool.com 搜索脚本

package tests;

import resources.tests.bookpool_search_recordHelper;
import com.rational.test.ft.*;
import com.rational.test.ft.object.interfaces.*;
import com.rational.test.ft.script.*;
import com.rational.test.ft.value.*;
import com.rational.test.ft.vp.*;

public class bookpool_search_record extends bookpool_search_recordHelper{

public void testMain(Object[] args){
//Start the browser and load BookPool.com
startApp("www.BookPool.com");

//Search for 'software testing'
text_qs().click(atPoint(74,7));
browser_htmlBrowser(document_bookpoolDiscountCompu(),
DEFAULT_FLAGS).inputChars("software testing");

button_search_btnGif().click();

//Verify these three books are returned
//in the first results page
TestingComputerSoftware_textVP().performTest(2.0, 20.0);
LessonsLearnedInSoftwareTestinVP().performTest(2.0, 20.0);
HowToBreakSoftware_textVP().performTest(2.0, 20.0);

//Exit
browser_htmlBrowser(document_bookpoolDiscountCompu(),
MAY_EXIT).close();
}
}

在我查看这段测试脚本时,我看到了某些我想要能够在许多其它脚本 --执行一个搜索(图1)-- 中做的东西。有三行代码用于搜索:在qs 域上的点击,用于我们的搜索标准的 inputChars,以及在搜索按钮上的点击。

图 1. BookPool.com 搜索表单

 
 我们知道,有可能开发人员某天会修改qs域的名字,或者他们可能使用一个搜索按钮的不同 gif 文件,您应当创建一个模块为您做这件事情。使用这种方式,如果您在某一时间必须修改您搜索的方法,您可以在一个地方修改它,它会在您所有调用该模块的测试脚本中生效。除此之外,您在您所有的测试脚本中,减少了执行一个搜索的代码行数量,从三个减少到一个。

列表2 显示了此搜索模块的代码。

列表 2. BookPool.com 搜索类

package classes;

import classes.bookpoolHelper;

public class bookpool_search extends bookpoolHelper {

public void performSearch(String criteria){
text_qs().click(atPoint(74,7));
browser_htmlBrowser(document_bookpoolDiscountCompu(),
DEFAULT_FLAGS).inputChars(criteria);
button_search_btnGif().click();
}
}

列表 3 显示了新的测试脚本:

列表 3. 模块化的 BookPool.com 搜索脚本

Package tests;

import classes.*;
import com.rational.test.ft.*;
import com.rational.test.ft.object.interfaces.*;
import com.rational.test.ft.script.*;
import com.rational.test.ft.value.*;
import com.rational.test.ft.vp.*;

public class bookpool_search_modularity extends bookpoolHelper{
public void testMain(Object[] args){

//Start the browser and load BookPool.com
startApp("www.BookPool.com");

//Search for 'software testing'
new bookpool_search().performSearch("software testing");

//Verify these three books are returned in
//the first results page
TestingComputerSoftware_textVP().performTest(2.0, 20.0);
LessonsLearnedInSoftwareTestinVP().performTest(2.0, 20.0);
HowToBreakSoftware_textVP().performTest(2.0, 20.0);

//Exit
browser_htmlBrowser(document_bookpoolDiscountCompu(),
MAY_EXIT).close();
}
}

这和创建一个搜索类并复制和粘贴搜索代码相比,并不完全一样简单(就像上面的代码所体现的)。我也必须更改脚本的帮助类。如果您注意到列表3,现在有一个 bookpoolHelper 类代替了原来的 bookpool_search_recordHelper 类。这个步骤是一个简单的复制和粘贴。我们在新的 bookpoolHelper 类中所有必须修改的就是类的名字。如果您不熟悉 IBM? Rational? Functional Tester 中帮助类的概念,您需要花一些时间阅读在线帮助来熟悉它们。除了特别细节的任务描述,您将需要理解那里所有的内容。

要继续这个例子,您现在可以增加您想要的类。您可能要增加一些类,处理主页左边上的 BookPool.com 的浏览树。然后,所有这些类可以用到任何脚本中。所有您必须做的就是,在您增加更多的测试脚本和覆盖更多的程序功能时,确保更新 bookpoolHelper 类。

使用多个脚本来实现模块化

对于此例,您将使用对 www.Edwise.org 的一个测试。在列表4中所显示的记录脚本启动了访问 Edwise.org 大学预算计算的浏览器,输入年度收入,然后使用所提供的工作表输入每个分类的费用。对于页面上的每一个计算总计,都记录一个验证点,以确保正确的计算。

列表 4. 被记录的 Edwise.org 脚本

Package tests;

import resources.tests.edwise_recordHelper;
import com.rational.test.ft.*;
import com.rational.test.ft.object.interfaces.*;
import com.rational.test.ft.script.*;
import com.rational.test.ft.value.*;
import com.rational.test.ft.vp.*;

public class edwise_record extends edwise_recordHelper{

public void testMain(Object[] args){
//Start Student Calculator
startApp("http://www.edwise.org/edwise/edFundFrame.html");

//EdWise - Student Calculator Home Page
image_next().click();

//Edwise - Enter Name
browser_htmlBrowser(document_edWiseStudentCalculat(),
DEFAULT_FLAGS).inputChars("Mike");
image_next2().click();

//Edwise - Calculate Budget
//Annual Income
list_incomeBy().click();
list_incomeBy().click(atText("Annually"));
list_aidBy().click();
list_aidBy().click(atText("Annually"));
text_jobEarnings().click(atPoint(75,8));
browser_htmlBrowser(document_edWiseStudentCalculat(),
DEFAULT_FLAGS).inputChars("15000");
text_monFromParent().click(atPoint(50,10));
browser_htmlBrowser(document_edWiseStudentCalculat(),
DEFAULT_FLAGS).inputChars("5000");
text_savings().click(atPoint(35,14));
browser_htmlBrowser(document_edWiseStudentCalculat(),
DEFAULT_FLAGS).inputChars("5000");
text_scholarships().click(atPoint(50,11));
browser_htmlBrowser(document_edWiseStudentCalculat(),
DEFAULT_FLAGS).inputChars("15000");
text_loans().click(atPoint(40,20));
browser_htmlBrowser(document_edWiseStudentCalculat(),
DEFAULT_FLAGS).inputChars("15000");
text_totalIncome().click(atPoint(63,15));

//Verify total amount
totalIncome_textVP().performTest(2.0, 20.0);

//Education-Related Expenses
list_expenseBy().click();
list_expenseBy().click(atText("Annually"));
browser_htmlBrowser(document_edWiseStudentCalculat(),
DEFAULT_FLAGS).inputChars("25000");
text_books().click(atPoint(69,13));
image_edw_detailsGif().click();

//Books/Supplies/Computer Worksheet
text_books2().click(atPoint(42,11));
browser_htmlBrowser(document_books_mod3Html(),
DEFAULT_FLAGS).inputChars("700");
text_supplies().click(atPoint(27,9));
browser_htmlBrowser(document_books_mod3Html(),
DEFAULT_FLAGS).inputChars("200");
text_computer().click(atPoint(24,10));
browser_htmlBrowser(document_books_mod3Html(),
DEFAULT_FLAGS).inputChars("2000");
text_miscSupplies().click(atPoint(24,4));
browser_htmlBrowser(document_books_mod3Html(),
DEFAULT_FLAGS).inputChars("200");
button__OKButton().click();

//Verify total amount
books_textVP().performTest(2.0, 20.0);

//Rent/Mortgage Worksheet
text_rent().click(atPoint(76,11));
image_edw_detailsGif2().click();
text_rent2().click(atPoint(61,11));
browser_htmlBrowser(document_rentMortgageHtml(),
DEFAULT_FLAGS).inputChars("400");
text_tax().click(atPoint(39,18));
text_ins().click(atPoint(31,12));
browser_htmlBrowser(document_rentMortgageHtml(),
DEFAULT_FLAGS).inputChars("100");
text_misc().click(atPoint(25,8));
browser_htmlBrowser(document_rentMortgageHtml(),
DEFAULT_FLAGS).inputChars("50");
button__OKButton2().click();

//Verify total amount
rent_textVP().performTest(2.0, 20.0);

//Utilities Worksheet
text_utilities().click(atPoint(51,8));
image_edw_detailsGif3().click();
text_gas().click(atPoint(24,13));
browser_htmlBrowser(document_utilitiesHtml(),
DEFAULT_FLAGS).inputChars("67");
text_water().click(atPoint(24,15));
browser_htmlBrowser(document_utilitiesHtml(),
DEFAULT_FLAGS).inputChars("34");
text_trash().click(atPoint(16,15));
browser_htmlBrowser(document_utilitiesHtml(),
DEFAULT_FLAGS).inputChars("30");
text_tv().click(atPoint(18,9));
browser_htmlBrowser(document_utilitiesHtml(),
DEFAULT_FLAGS).inputChars("49");
text_phone().click(atPoint(16,12));
text_cellPhone().click(atPoint(16,18));
browser_htmlBrowser(document_utilitiesHtml(),
DEFAULT_FLAGS).inputChars("87");
text_internet().click(atPoint(22,11));
text_misc2().click(atPoint(16,12));
browser_htmlBrowser(document_utilitiesHtml(),
DEFAULT_FLAGS).inputChars("20");
button__OKButton3().click();

//Verify total amount
utilities_textVP().performTest(2.0, 20.0);
//etc...
}
}

由于脚本的长度,我没有包括整个脚本。这是一个问题。象这样的一个脚本实际上很长。由于维护问题,我们并不希望这种情况发生。如果您正在测试的应用程序曾经发生变化,您必须将所有这些代码(以及您用来进行测试的所有其它脚本中的代码)进行排序,以发现和修复问题。这是一个很好的脚本,其自然地被分解成单独的逻辑单元。还记得早先提到的关注分离吗?您可以查看每个费用项以及其作一个独立模块的关联表单,如图2和图3所示。如果您这样做,您可以为每一个页面记录一个脚本,然后将它们放在实际的测试脚本中。

图 2. Edwise.org 月费用

 
 图 3. Edwise.org Rent/Mortgage 工作表
 
 例如,查看在列表5中所显示的一个脚本的代码,可以简单地处理图3中所显示的 Rent/Mortgage 工作表。

列表 5. Rent/Mortgage 工作表模块脚本

Package scripts;

import resources.scripts.rent_mortgage_worksheet_moduleHelper;
import com.rational.test.ft.*;
import com.rational.test.ft.object.interfaces.*;
import com.rational.test.ft.script.*;
import com.rational.test.ft.value.*;
import com.rational.test.ft.vp.*;

public class rent_mortgage_worksheet_module extends rent_mortgage_worksheet_moduleHelper{

public void testMain(Object[] args) {

//Rent/Mortgage Worksheet
text_rent().click(atPoint(76,11));
image_edw_detailsGif2().click();
text_rent2().click(atPoint(61,11));
browser_htmlBrowser(document_rentMortgageHtml(),
DEFAULT_FLAGS).inputChars("400");
text_tax().click(atPoint(39,18));
text_ins().click(atPoint(31,12));
browser_htmlBrowser(document_rentMortgageHtml(),
DEFAULT_FLAGS).inputChars("100");
text_misc().click(atPoint(25,8));
browser_htmlBrowser(document_rentMortgageHtml(),
DEFAULT_FLAGS).inputChars("50");
button__OKButton2().click();
//Verify total amount
rent_textVP().performTest(2.0, 20.0);
}
}

如果您对每个工作表条目这样做,您的测试脚本看起来类似于列表6。

列表 6. 模块化的 Edwise.org 脚本

Package tests;

import resources.tests.edwise_modularityHelper;
import com.rational.test.ft.*;
import com.rational.test.ft.object.interfaces.*;
import com.rational.test.ft.script.*;
import com.rational.test.ft.value.*;
import com.rational.test.ft.vp.*;

public class edwise_modularity extends edwise_modularityHelper{

public void testMain(Object[] args) {

//Start Student Calculator
startApp("http://www.edwise.org/edwise/edFundFrame.html");

//EdWise - Student Calculator Home Page
image_next().click();
//Edwise - Enter Name
browser_htmlBrowser(document_edWiseStudentCalculat(),
DEFAULT_FLAGS).inputChars("Mike");
image_next2().click();

//Edwise - Calculate Budget
//Annual Income
callScript("scripts.annual_income_module");

//Education-Related Expenses
callScript("scripts.education_related_worksheet_module");

//Rent/Mortgage Worksheet
callScript("scripts.rent_mortgage_worksheet_module");

//Utilities Worksheet
callScript("scripts.utilities_worksheet_module");

//Food/Household Worksheet
callScript("scripts.food_household_worksheet_module");

//Student Loan Payment Worksheet
callScript("scripts.student_loan_payment_worksheet_module");

//Credit Card/Personal Loan Worksheet
callScript("scripts.credit_loans_worksheet_module");

//Transportation Costs Worksheet
callScript("scripts.transportation_costs_worksheet_module");

//Clothing Costs Worksheet
callScript("scripts.clothing_costs_worksheet_module");

//Additional Expenses Worksheet
callScript("scripts.additional_expenses_worksheet_module");

//Verify Total Annual Expenses
totalExpense_textVP().performTest(2.0, 20.0);

//Verify Summary amounts
summary_gridVP().performTest(2.0, 20.0);

//Click Next
image_next3().click();

//etc...
}
}

通过使用 callScript 命令,您可以调用您的模块化脚本,这些脚本是您从测试中创建的。这使得代码可读性更强,并且更加维护。这是一个非常好的方法,您可以使用象这样的模块脚本构建模块,以创建不同的测试用例场景。对于上面您所有的每个模块脚本,您可能会有三个(或更多)不同的成本分类:便宜的、一般的、昂贵的。然后您可以进行混合,并将它们匹配到不同场景的测试上,如带有高成本功效的便宜租金,或使用信用卡支付的低成本运输费用。

优势和劣势

此系列的三种框架类型将涵盖模块化、数据驱动和关键字驱动,模块化框架理解和实现起来是最简单的。它也是最易于和其它框架联合使用的--例如,将模块化框架和数据驱动框架组合在一起。通常,在 Rational Functional Tester 中实现模块化要求有底层的技术知识。您真正需要学习的是如何管理帮助类--如果您在使用 Rational Functional Tester 的任何功能(包括记录和回放),您确实应当理解它们。

模块化的主要优势是重用和减少重用伴随而来的维护成本。对比记录和回放,模块化应当产生更多可读的脚本,易于阅读和调试。创建测试模块脚本然后记录和回放脚本通常会有较高的成本,但是维护成本更加便宜。这意味着您需要知道您要使用您的测试脚本多长时间,以完全了解实现模块化的成本。成本-收益分析 可能会帮助您决定模块化是否适合于您目前的项目。可能性就是,即使如果您的整个框架不是模块化的,您将会不在识别核心的功能,而转移到帮助类或帮助脚本上。

使用模块化,应用程序的状态和数据共享会变成一个问题。如果一个模块失败了,对所有依赖它的模块意味着什么呢?如果一个订单从来没有被提交过,您如何检查订单状态?如果您从来没有执行过一个搜索,您如何将一个物品项添加到购物车中?共享数据和状态的复杂性会使脚本调试变得更困难,并且常常会花较长时间来识别,一个脚本失败是测试脚本还是被测应用程序的问题造成的。这些问题将造成可靠性的问题。

下一步

在您正在测试的应用程序中,花一些时间将最常用的功能抽象到脚本或者类中。您也可以考虑为应用程序的大多数动态部分进行这项工作。在您已有的脚本和任何您生成的新脚本中使用模块化。您不需要立刻转化所有的脚本。这是有关模块化的精彩内容的其中一个;您可以按照您的需求,使用或者不使用它们。您并没有被要求一定要使用他们。

另外值得提到的是这样一个事实,即模块化可以扩展以用于高级的框架中,如基于模型的测试框架和用于海量自动化测试的框架。如果您对如何扩展已有的模块化框架感兴趣,可以看下面列表中的许多资源。在本系列的下一篇文章中,将会带您去了解如何在 Rational Functional Tester 中实现一个数据驱动的框架。最后,查看一些资源,以充分理解与一个模块化框架相关联的成本和收益,以及对于更加强大的测试自动化,您可以使用模块化的方式。


版权所有:UML软件工程组织