章国俊 , IBM中国软件开发中心的Lotus部门, IBM
2005 年 11 月
IBM Rational Functional Tester是由IBM推出的针对Java,.Net和Web应用程序的自动化测试工具,拥有功能强大的编辑器并支持多种脚本语言,还集成了ScriptAssure 技术、模式匹配功能及数据驱动,以增强测试脚本的灵活性。借助这一工具,测试人员可以轻松地录制或编写脚本来进行自动化测试,极大地提高了测试效率。
软件在开发过程中是不断变化的,之后各个版本间的更替也会不可避免地引起界面、功能的变化,可以说"唯一不变的,就是变化"。这对自动化测试提出了很高的要求:要随需应变。自动化测试的脚本必须具有相当的自适应能力,在各种环境下都能正常工作。只有提高脚本的复用程度和兼容性,自动化测试才有实际意义,否则随着软件的各个版本的变更、发布,测试人员只能不停地去更新脚本,造成效率的低下、人力物力的极大浪费。
要达到这个目标,可以从以下两个方面来着手:
1) 充分利用Rational Functional Tester的强大功能,比如ScriptAssure? 技术、正则表达式,数据驱动,Rational Functional Tester API等;
2) 合理地编写、优化脚本。提纲挈领地对测试过程进行抽象,对关键过程进行必要的验证。
本文将从录制一个简单脚本开始,一步步对其进行改造和完善,不断提高脚本的自适应能力,使之能够摆脱种种束缚,灵活可靠地在多变的测试环境中顺利执行。
一 摆脱初始录制环境的束缚
文中我们以这个简单的Web页面测试场景为例:用户进入IBM网站,搜索关键字"lotus",验证"www.lotus.com"这一链接存在于结果集内。
首先,我们可以使用Rational Functional Tester录制这段脚本:
1. 打开Rational Functional Tester, 新建一个"Functional Test项目",命名为"SearchLotusProject";
2. 右键单击项目管理器中的"SearchLotusProject",选择"使用记录器添加脚本";
3. 将新脚本命名为"SearchLotusLink",单击"完成"按钮;此时会弹出脚本记录器的窗口,脚本记录已经开始了;
4. 依次打开IE,输入地址:"www.ibm.com", 回车;在搜索栏里输入"lotus",单击"Search"按钮,可以得到一个结果列表,其中就有"www.lotus.com";
5. 插入验证点。点击脚本记录器上的按钮 ,会出现"验证点向导"窗口。按住手形的对象选择器 ,选中链接"www.lotus.com"(红框高亮显示),以默认的设置创建一个"数据验证点",点击按钮"完成"。
6. 点击脚本记录器上的按钮"停止记录",完成录入过程。
通过以上的操作,我们得到脚本:
public void testMain(Object[] args)
{
// HTML Browser
browser_htmlBrowser(document_htmlDocument(),DEFAULT_FLAGS).click(atPoint(455,108));
browser_htmlBrowser(document_htmlDocument(),DEFAULT_FLAGS).inputKeys("www.ibm.com{ENTER}");
// Document: IBM United States: http://www.ibm.com/us/
text_q().click(atPoint(25,7));
browser_htmlBrowser(document_ibmUnitedStates(),DEFAULT_FLAGS).inputChars("lotus");
button_search().click();
httpWwwLotusCom_textVP().performTest();
}
|
Java语言的脚本有很好的可读性,稍有Java知识的人便能看懂这些操作的具体含义。
接下来我们试着回放这段脚本(注意:此时搜索结果页面尚未关闭)。点击工具栏上的按钮"运行Functional Tester脚本",开始回放。
整个回放过程大约需要1分钟,但回放日志中有2条警告信息:"对象识别较困难(在警告阈值以上)",并给出了识别分数和警告阈值。这是由于Rational Functional Tester所使用的ScriptAssure? 技术认为这两次识别是弱识别,可能存在问题。下面我们来看看具体原因:
ScriptAssure技术给各个界面元素赋予了一定的权重,再综合目标对象的各个属性得出一个量化的特征值,便于在对象之间进行比较,因此可以大大提高脚本对频繁变更的应用程序界面的弹性适应能力,ScriptAssure技术的原理并不难懂。打开脚本资源管理器中的对象"document_htmlDocument",可以看到这个对象在识别时,使用到3个属性:".class",".title"和".url",权重各有不同。
权重用来标识这个属性在识别过程中的重要程度,权重为100,表明这个属性非常关键,必须和原值完全一致;权重为0,表明这个属性是无关紧要的。识别分数就是在这些权重的基础上进行一定的运算而得到的。识别对象时,如果有一个权重100的属性值与原值不符,识别分数就要加上"权重×100",即10000分。在这个例子中,录制时使用的是空白的IE页面,而回放时使用的是录制遗留下的搜索结果页面,导致".title"和".url"这两项属性值不符,因此它的识别分数就是".title权重×100"再加上".url权重×100",一共是9000分+4000分=13000分,这就是警告信息里识别分数的由来。
至于警告信息里提到的"警告阈值",可以在Rational Functional Tester "首选项"的ScriptAssure高级选项里修改。缺省值是10000,表明有一个阈值为100的重要属性不匹配。"警告阈值"越小,则意味着识别过程越严格。
对于这一例子而言,使用空白页面和非空页面对结果都没有影响,只要操作对象是浏览器即可,因此我们可以把".title"和".url"这两项属性的权重设为0。修改之后,保存,再回放一次。同样使用刚才的结果页面,但在回放日志里不再有警告信息,而且回放过程也仅仅用了13秒,快速合格地完成了测试。
在录制脚本中,除了有录制时浏览器页面的限制,还有一处隐藏的限制:脚本中没有打开浏览器的操作。如果没有在回放前打开一个浏览器页面,回放操作肯定会失败。幸好Rational Functional Tester的API提供了这样的功能,在所有Java脚本的公共父类com.rational.test.ft.script.RationalTestScript里,有一个方法startBrowser(java.lang.String url),可以用来启动浏览器,并打开某个URL。因此需要在脚本的第一行前加上"startBrowser("");"用来打开一个空白页面,然后再执行其他操作。
通过修改页面对象的识别属性和添加必要的Java代码,我们完成了对脚本内容和录制环境的解耦工作。
二 摆脱时间的束缚
回放时,Java脚本是由Java虚拟机解释执行,进行速度很快;而浏览器的打开,页面的装入则往往由于机器性能、网络带宽、服务器负载而速度缓慢。如果被测程序的运行和脚本执行的时间差过大,极有可能导致测试的失败,因此等待时间是我们应该妥当设置的一个要素。可以在工具级和程序级这两个层次上进行设置。
1) 工具级设置
在Rational Functional Tester "首选项"的"回放"选项里,可以看到四项时间有关的回放设置。缺省的"尝试查找测试对象的最长时间"值为20秒。由于此处设置比较简单,而且是一个对所有项目和脚本都有效的全局设置,因此很难设定一个能满足所有脚本的值。建议接受默认设置,不在这一层上做时间定制,而是去程序级进行更为灵活的控制。
2) 程序级设置
我们可以在程序级上,对脚本做适量的加工。在某些关键操作后加上适当的等待,直到该操作完全执行完毕,再继续之后的操作。"步步为营"地执行脚本,确保每一步的前提都是正确的。
脚本可以使用以下两种方式来添加等待时间:
- 定长等待
调用Java脚本的公共父类com.rational.test.ft.script.RationalTestScript里的方法:sleep(double seconds)。这一方法可以使回放过程等待若干秒。 这种方式直观、简单。但缺点也是明显的:固定的时间常常不能适应多变的真实环境:等待时间设置得过长,无疑会拉长测试的回放时间,降低效率;等待时间设置得过短,在某些情况下,又无法起到延时应有的效果,仍然错过了被测对象。
- 不定长等待
脚本记录器记录下的这些页面对象都是从接口com.rational.test.ft.object.interfaces.TestObject继承下来的,在TestObject中有一个方法waitForExistence()可以用以实现不定长的等待。在一定的时间限度内,等待该对象的出现;一旦出现后就不再等待,程序继续往下执行。最大时间限度是在"首选项"的"回放"选项里设置的。不定长等待既达到灵活等待的目的,又没有浪费不必要的等待时间,是一个值得推荐的解决方案。
本例中,我们在脚本中添加了不定长的等待设置,如下:
public void testMain(Object[] args)
{
startBrowser("");
document_htmlDocument().waitForExistence();
// HTML Browser
browser_htmlBrowser(document_htmlDocument(),DEFAULT_FLAGS).click(atPoint(455,108));
browser_htmlBrowser(document_htmlDocument(),DEFAULT_FLAGS).inputKeys("www.ibm.com{ENTER}");
// Document: IBM United States: http://www.ibm.com/us/
text_q().waitForExistence();
text_q().click(atPoint(25,7));
browser_htmlBrowser(document_ibmUnitedStates(),DEFAULT_FLAGS).inputChars("lotus");
button_search().click();
document_ibmSearchResultsLotus().waitForExistence();
httpWwwLotusCom_textVP().performTest();
}
|
针对新打开的空页面,搜索输入框和结果页面,分别做了相应的等待设置。正常情况下,这段脚本的回放需要20秒钟,耗时略多于设置等待之前,但此时的脚本在等待时间方面,处理得更为灵活。在牺牲部分效率的同时,获得了更高的可靠性。对于持续性的、大规模的自动化测试而言,其中的价值不言而喻。
三 摆脱空间的束缚
脚本记录器将记录下来的对象保存在该脚本的 "专用测试对象图"中,而且是以树形结构保存。专用测试对象图不仅保存了对象本身的相关属性,连它和其他对象的相对关系也一并保存下来。
这段脚本使用方法document_htmlDocument()来调用页面的Document对象,使用方法text_q()来调用搜索输入框,使用方法button_search()来调用搜索按钮。这些方法是由脚本SearchLotusLink的父类SearchLotusLinkHelper定义的:
protected GuiTestObject document_htmlDocument()
{
return new GuiTestObject(getMappedTestObject("document_htmlDocument"));
}
protected TextGuiTestObject text_q()
{
return new TextGuiTestObject(getMappedTestObject("text_q"));
}
protected GuiTestObject button_search()
{
return new GuiTestObject(getMappedTestObject("button_search"));
}
|
脚本回放时,Rational Functional Tester利用"专用测试对象图"进行静态识别,可以从浏览器这个顶级容器开始,层层深入地定位到指定的某个对象。但这种呆板的对象查找方式也留下了很大的隐患:Web页面里层次结构的变化屡见不鲜,而这种变化对于对象的查找而言,有着致命的影响。在这种模式下要想适应页面层次结构的变化,只能重新录制对象,生成新的"专用测试对象图"。代价如此之大的维护方式使得自动化测试几乎没有可行性。
因此必须要将对象的识别同具体的"专用测试对象图"分开,实现对象的动态识别。页面对象都有一个共同的父类:com.rational.test.ft.object.interfaces.TestObject,而它的方法find(Subitem properties)正是用来在某个特定范围内查找满足条件的所有对象。借助它,我们可以对SearchLotusLinkHelper 进行改造,使其与"专用测试对象图"不再紧密地耦合在一起。
改造后,回放过程中所需要用到的页面对象都是在当前浏览器中即时查找得到的。通过目标对象的类型和某个属性值来定位目标对象,脱离了"专用测试对象图"中树形结构的约束。SearchLotusLinkHelper的部分内容如下:
protected GuiTestObject document_htmlDocument() {
return new GuiTestObject(findTestObjectInBrowser(".class","Html.HtmlDocument",null,null));
}
protected TextGuiTestObject text_q() {
return new TextGuiTestObject(findTestObjectInBrowser(".class","Html.INPUT.text",".id","q"));
}
protected GuiTestObject button_search() {
return new GuiTestObject(findTestObjectInBrowser(".class","Html.INPUT.image",".value","Search"));
}
protected GuiTestObject link_httpWwwLotusCom() {
return new
GuiTestObject(findTestObjectInBrowser(".class","Html.A",".text","http://www.lotus.com/"));
}
protected TestObject findTestObjectInBrowser(String property1, String value1, String property2,
String value2)
{
TestObject[] foundTOs ;
//在当前浏览器页面中查找
if(null==property2)
foundTOs = browser_htmlBrowser().find(atDescendant(property1,value1)) ;
else
foundTOs = browser_htmlBrowser().find(atDescendant(property1,value1,property2,value2)) ;
//如果没有找到满足条件的TestObject
if(foundTOs.length<1)
{
throw new com.rational.test.ft.ObjectNotFoundException("Can NOT find TestObject with
"+property1+"<"+value1+">,"+property2+"<"+value2+">");
}
//如果找到多个TestObject满足条件,
else if(foundTOs.length>1)
{
throw new AmbiguousRecognitionException("Found multi-TestObject with
"+property1+"<"+value1+">,"+property2+"<"+value2+">");
}
//返回唯一的查找结果
return foundTOs[0];
}
|
与此同时,针对脚本SearchLotusLink里为选择操作对象而记录下的的鼠标操作,及验证点。我们也同样对其进行抽象,攫取其中的关键行为,代之以程序动作。比如:
browser_htmlBrowser(document_htmlDocument(),DEFAULT_FLAGS).click(atPoint(455,108));
browser_htmlBrowser(document_htmlDocument(),DEFAULT_FLAGS).inputKeys("www.ibm.com{ENTER}");
|
这两行脚本的操作是在浏览器的地址栏内输入URL,再按下回车。这个动作如果借助Rational Functional Tester所提供的API来表示,可以简化很多:
browser_htmlBrowser(document_htmlDocument(),DEFAULT_FLAGS).loadUrl("www.ibm.com");
|
再以程序的方式生成验证点并验证,不与脚本相关的数据结构发生干系。经过优化和精炼后的脚本SearchLotusLink如下:即使将来页面布局和结构发生变化,这段脚本仍然能正确地定位到搜索输入框和搜索按钮。
public void testMain(Object[] args)
{
startBrowser("");
document_htmlDocument().waitForExistence();
// HTML Browser
browser_htmlBrowser(document_htmlDocument(),DEFAULT_FLAGS).loadUrl("www.ibm.com");
// Document: IBM United States: http://www.ibm.com/us/
text_q().waitForExistence();
text_q().setText("lotus");
button_search().click();
link_httpWwwLotusCom().waitForExistence();
IFtVerificationPoint vp1 =
vpManual("vp1","http://www.lotus.com/",link_httpWwwLotusCom().getProperty(".href"));
vp1.performTest();
}
|
四 摆脱语言的束缚
如今的软件大多都要求支持"全球化",能够适应多种语言环境。就用户界面而言,需要在不同的语言环境中展示出本地语言界面。多语言环境下的人工测试往往不尽如人意,自动化测试反而更为迅速和准确。
我们仍然以这个场景为例,所不同的是:用户进入IBM中国网站。再搜索关键字"lotus",验证"www.lotus.com"这一链接存在于结果集内。测试过程的逻辑并没有变化,但界面截然不同, "Search"按钮在这里显示为"搜索"按钮。
为了拓展脚本SearchLotusLink对多语言的支持能力,我们在SearchLotusLinkHelper查找对象的过程中,加上一个中间层,通过它来衔接固定的操作逻辑和多变的界面。原先的方法findTestObjectInBrowser(String property1, String value1, String property2, String value2)只是按这两个属性值找出合适的对象;现在则要对属性做适当的转换,使它能适应其他语言环境,在不同语言的界面里都能定位到这个页面对象。
将Rational Functional Tester切换到"Java透视图",在SearchLotusLinkHelper所在的resources包中,添加一个类:utilities,来实现对多语言环境的支持。这里为简单起见,我们将URL和搜索按钮的文本的多语言表示直接保存在这个类里。用户可以通过它来获取某一属性在特定语言环境下的表示。
public class utilities {
/**
* Script Name : <b>utilities</b>
* Generated : <b>2005-10-25 16:51:51</b>
* Description : Functional Test Script
* Original Host : WinNT Version 5.1 Build 2600 (S)
*
* @since 2005/10/25
* @author zhangguojun
*/
public static String EN_LOCALE = "en";
public static String CN_LOCALE = "cn";
public String CurrentLocale = EN_LOCALE;
private Hashtable textRepositoryForEN = new Hashtable();
private Hashtable textRepositoryForCN = new Hashtable();
private static utilities _instance = null;
private utilities(){
textRepositoryForEN.put("Search","Search");
textRepositoryForEN.put("IBMurl","www.ibm.com");
textRepositoryForCN.put("Search","搜索");
textRepositoryForCN.put("IBMurl","www.ibm.com/cn");
}
public static utilities getInstance() {
if (null == _instance)
_instance = new utilities();
return _instance;
}
public void setCurrentLocale(String locale)
{
CurrentLocale=locale;
}
public String getLocalText(String textId)
{
return getLocalText(CurrentLocale,textId);
}
public String getLocalText(String locale, String textId)
{
String returnVal = null;
if(locale.equals(EN_LOCALE))
returnVal = (String)textRepositoryForEN.get(textId);
else if(locale.equals(CN_LOCALE))
returnVal = (String)textRepositoryForCN.get(textId);
if(null==returnVal)
return textId;
else
return returnVal;
}
}
|
进而我们对SearchLotusLinkHelper中方法findTestObjectInBrowser(String property1, String value1, String property2, String value2)的做相应的修改,使它能够得到当前语言环境下属性的表述形式。
protected TestObject findTestObjectInBrowser(String property1, String value1,
String property2, String value2)
{
TestObject[] foundTOs ;
String val1 = utilities.getInstance().getLocalText(value1);
String val2;
//在当前浏览器页面中查找
if(null==property2)
foundTOs = browser_htmlBrowser().find(atDescendant(property1,val1)) ;
else
{
val2 = utilities.getInstance().getLocalText(value2);
foundTOs = browser_htmlBrowser().find(atDescendant(property1,value1,property2,val2)) ;
}
//如果没有找到满足条件的TestObject
if(foundTOs.length<1)
{
throw new com.rational.test.ft.ObjectNotFoundException("Can NOT find TestObject with
"+property1+"<"+value1+">,"+property2+"<"+value2+">");
}
//如果找到多个TestObject满足条件,
else if(foundTOs.length>1)
{
throw new AmbiguousRecognitionException("Found multi-TestObject with
"+property1+"<"+value1+">,"+property2+"<"+value2+">");
}
//返回唯一的查找结果
return foundTOs[0];
}
|
最后,我们在脚本SearchLotusLink中加上一个开关,用来设置当前所使用的语言环境。我们只需要简单地设置一下这个开关,同样的脚本就可以在中英文两种环境下进行测试。按这种模式,测试还可以延伸到其它语言环境中。
public void testMain(Object[] args)
{
//设置当前所使用的语言环境
utilities.getInstance().setCurrentLocale(utilities.CN_LOCALE);
startBrowser("");
document_htmlDocument().waitForExistence();
// HTML Browser
browser_htmlBrowser(document_htmlDocument(),DEFAULT_FLAGS).loadUrl(utilities.getInstance().
getLocalText("IBMurl"));
// Document: IBM United States: http://www.ibm.com/us/
text_q().waitForExistence();
text_q().setText("lotus");
button_search().click();
link_httpWwwLotusCom().waitForExistence();
IFtVerificationPoint vp1 =
vpManual("vp1","http://www.lotus.com/",link_httpWwwLotusCom().getProperty(".href"));
vp1.performTest();
}
|
六 总结和展望
本文通过一个针对Web页面的测试用例,由浅入深地阐明了如何利用Rational Functional Tester创建随需应变的自动化测试脚本。脚本在经过优化和改造后,对多变的回放环境、等待时间、页面变化及多语言环境都有了较强的自适应能力。使脚本在不经维护或很少维护的情况下,也能在各种环境下顺利地执行测试。
文中只是介绍了一个基本思路和简单实现。在自动化测试的大规模应用中,需要对utilities类、findTestObjectInBrowser()方法等辅助功能做进一步的完善和扩充,衍生为一个支持框架,对脚本的记录、回放、灵活性、健壮性加以全方位的支持。"软硬结合",在这个灵活的软框架的辅佐下,更好地发挥出Rational Functional Tester工具的强大功能。
关于作者
 |
|
 |
章国俊,软件工程师,来自IBM中国软件开发中心的Lotus部门。目前主要从事IBM Workplace产品的功能测试工作。对Java、自动化测试、软件过程比较感兴趣。联系方式:zhanggj@cn.ibm.com |
对本文的评价
|