 | 级别: 中级 祝 天光, 测试工程师, IBM 张 捷, 测试工程师, IBM 杨 莉, 测试工程师, IBM
2009 年 3 月 27 日 在使用 IBM Rational Functional Tester(RFT)进行软件测试的时候,为了减少对象层次结构发生变化带来的脚本维护的成本,通常我们会使用动态识别而不是静态识别机制。所谓动态识别,一般会使用 TestObject.find() 方法查找对象,而不去关心对象的层次结构。RFT 对于对象层次结构相对复杂,或者编码不是很规范的控件的识别,尤其基于 Windows 开发的产品,在使用动态查找方法时会遇到一些困难,甚至有无法正确获取对象的情况。本文将结合实际应用 TestObject.find() 方法遇到的问题,分析并给出解决方案。
前言
 | |
本系列从如何构建结构良好的测试框架、高效的对象缓存机制,以及测试 Windows 应用程序的技巧和方法等方面,介绍如何运用 RFT 测试 Windows/.NET 应用程序。
|
|
在使用 IBM Rational Functional Tester(RFT)进行软件测试的时候,为了减少对象层次结构发生变化带来的脚本维护的成本,通常我们会使用动态识别而不是静态识别机制。所谓动态识别,一般会使用 TestObject.find() 方法查找对象,而不去关心对象的层次结构。RFT 对于对象层次结构相对复杂,或者编码不是很规范的控件的识别,尤其基于 Windows 开发的产品,在使用动态查找方法时会遇到一些困难,甚至有无法正确获取对象的情况。本文将结合实际应用 TestObject.find() 方法遇到的问题,分析并给出解决方案。
理解并使用 TestObject.find() 方法
在 RFT 提供的 API 包中 TestObject.find() 方法功能强大,使用灵活。通常情况下,只需要提供对象的属性值,并保证属性的唯一性就可以查找所需的对象。
TestObject.find() 方法提供了以下几种使用方式:
-
atProperty -- A name/value pair representing a TestObject property
-
atChild -- One or more properties that must be matched against the direct child of the starting TestObject
-
atDescendant -- One or more properties that can be matched against any child of the starting TestObject
-
atList -- A sequential list of properties to match against. Valid subitems for atList are:
-
atChild
-
atDescendant
-
atProperty
下面是一个简单的使用 TestObject.find() 方法查找 button 控件的例子:
首先通过 RFT 自带的 Object Map tool 获取所要查找的控件的可用属性值,以便我们清楚的了解使用 TestObject.find() 方法查找时需要提供哪些属性。
下图是一个 button 控件在 object map 中的表现形式:
图 1. button 控件在 object map 中的表现形式 object map
下面是具体的实例(实例 1):
代码 1. 使用 TestObject.find() 方法查找 button 控件的例子
/**
* 通过属性”.class”、”.name”和”.text”查找 button 控件对象
* @param nameValue button 的 .name 属性值
* @param textValue button 的 .text 属性值
* @param object button 的父对象
* @return TestObject
*/
public TestObject getButtonTestObject(Object nameValue,Object textValue,
TestObject object){
TestObject[] tos = null;
tos = object.find(atList(atDescendant(".class",”.Pushbutton”,".name",nameValue),
atProperty(".text",textValue)));
if(tos.length >0 ) {
return tos[0];
}
}
|
例如我们要查找到一个叫”Log in”的按钮,现在我们可以调用上面的方法:
getButtonTestObject(“Log in”,”Log in”,parentObj);
|
使用 TestObject.find() 方法时遇到的问题
在 RFT 的 script 中,我们经常使用的就是 TestObject.find() 方法的 atDescendant() 方式,它可以不用关注对象层次结构,直接对根对象的深层次子对象进行查找,但对于具有复杂的层次结构,编码又不是很标准的控件对象,尤其是基于 windows 开发的应用程序,使用 atDesendant() 经常会遇到查找超时或者根本无法查找到对象的问题。
例如测试一个基于 Office2007 开发的 Add-In 产品,开发语言为 .NET framework,如果要在一个 search 面板上查找一个 Search button,通过下面的 ObjectMap 图我们可以看到,它的层次关系十分复杂,search button 是作为顶层的 Search pane 的多级子孙对象存在的。虽然这个 button 控件具有唯一的属性值,但我们尝试用 TestObject.find() 方法的各种方式去查找,返回值都为空,无法找到该对象。
图 2. .NET 开发的控件,被 RFT 识别为 win domain 而不是 Net domain
通过上面的 ObjectMap 图,我们看到使用 .NET 开发的控件,被 RFT 识别为 win domain 而不是 Net domain,而且对象结构又比较复杂,这可能是造成 atDescendant() 方法无法识别对象的原因。
可见,RFT 的 TestObject.find() 提供的 atDescendant() 方法对于处理复杂的,编写不规范的对象控件,有其不足。对于这个问题,在此我们不做过多讨论。我们所关心的是如何利用现有的技术和 RFT 提供的可用的方法,来解决这个问题,从而顺利的查找到我们需要的对象。
如何解决
正如上面所提到的,RFT 的动态识别方法,在解决复杂结构尤其是 windows 控件的查找时会出现一些问题,所以我们尝试用其他方法来查找对像,在实践中我们发现 TestObject.getChildren() 方法总是可用,不会出现查找超时而无法找到对象的情况,而且效率很好,所以如果我们利用 getChildren() 递归查找所有对象的各层子对象,并利用 TestObject.getProperty() 方法获取所需对象属性,进行比较筛选,这样,只要我们提供的属性能唯一标识该对象,利用递归的方法逐一进行查找比较,我们就能顺利找到这个对象。
下面是递归结构图:
图3. 递归结构图
下面给出一个简单的实现递归查找对象的例子(实例 2):
代码 2. 实现递归查找对象的例子
/**
* 递归遍历查找父对象的直接子对象,比较对象的属性值,如果找到则返回,若没有符合的条件的对象,
* 则进入二级子对象进行查找,依此类推,通过遍历查找所需对象。
* 例如,在 Search 面板中查找一个叫 search 的按钮,可以调用如下的方法:
* getObjectByProperty(".class",".Pushbuttn",".name","Search",searchPaneObject);
* @param pro1, 欲查找控件对象的第一个属性,例如 ".class"
* @param value1, 欲查找控件对象的第一个属性的值,例如 ".Pushbutton"
* @param pro2, 欲查找控件对象的第二个属性,例如 ".name"
* @param value2, 欲查找控件对象的第二个属性的值,例如 "Search"
* @param testObj, 包含这个控件对象的父对象
* @return TestObject 返回找到的控件对象
*/
public TestObject getObjectByProperty(String pro1,Object value1,String pro2,
Object value2,TestObject testObj){
// 定义子对象的层次
int level = 0;
TestObject[] to1 = null;
// 获取父对象的所有直接子对象
TestObject[] to = testObj.getChildren();
// 比较 TestObject[] 的长度是否大于 0,其中 isExist 是一个全局标识,用来判断对象是否找到
if (to.length > 0 && !isExist){
// 循环查找直接子对象
for (int i = 0; i < to.length; i++) {
String clsName = "";
String value = "";
// 根据属性名,获取对象的第一个属性值
Object className = to[i].getProperty(pro1);
// 根据属性名,获取对象的第二个属性值
Object valueName = to[i].getProperty(pro2);
if(className!=null){
clsName = className.toString();
}
if(valueName!=null) {
value = valueName.toString();
}
to1 = to[i].getChildren();
// 如果找到对象则跳出,如果没找到,则递归继续查找
if (clsName.contains(value1.toString())&& value.equals(value2)){
testObject = to[i];
isExist = true;
break;
}
else if ( to1.length >0 ) {
level = 0;
getObjectByProperty(pro1,value1,pro2,value2,to[i]);
}
else {
level++;
}
if(level == to.length){
return testObject;
}
}
}
else {
return testObject;
}
return testObject;
}
|
这个例子中,我们提供了两个属性值对,对所需对象进行匹配查找,通常情况下,这个两个属性是 .class 和 .name,当然,如果两个属性并不能满足唯一确定一个对象,你还可以扩展到三个或者多个属性作为函数的参数,从而实现准确查找匹配的对象,这里不再赘述。
改进并提高查找效率
虽然随着现在电脑的运算能力的提高,使得循环和递归查询效率在很大程度上提高了,但对于层次结构很深的对象的查询,简单的递归在效率上还需要做适当的优化。
针对我们测试的不同产品,我们可以定义不同的规则来提高查询效率,例如我们要查找一个叫 Search 的 button,它不可能作为 scrollbar 的子对象,所以我们可以在查找 button 时可以跳过对 scrollbar 的查找,从而提高效率。通过实践和总结,我们可以定义一些常用查找规则,在函数中调用这些规则,提高查找对象的效率。
另外,我们还可以通过缩小所需查询的对象的相对父对象的层次结构,并利用 cache 对象的方法来提高查询效率,这里不再赘述。
与 TestObject.find() 方法结合
RFT 提供的 TestObject.find() 方法也有它的优点,我们并不想完全弃用,只是针对有些 TestObject.find() 方法无法找到的对象,结合以上方法,将大大提高对象的查找力度,既保证查找效率,又保证查找的准确性,从而更好的为产品的 automation 测试服务。
下面是一个简单的结合 TestObject.find() 方法的示例:
代码 3. 简单的结合 TestObject.find() 方法的示例
// 结合 RFT framework 提供的 find 方法(实例 1),
// 和本文介绍的通过属性 - 值递归查找子对象的方法(实例 2),
// 我们就可以大大提高对所需对象的动态查找的准确率。
public TestObject findTestObject(String pro1,Object value1,String pro2,
Object value2,TestObject object){
TestObject testObj = null;
// 调用实例 1
testObj = getButtonTestObject(pro1,value1,pro2,value2,object);
// 如果没有找到,则调用实例 2
if(testObj==null) {
getTestObjectByProperty(pro1,value1,pro2,value2,object);
}
}
|
总结
软件产品的开发人员注重的是功能的实现,不会关心复杂的对象层次结构或不规范的属性命名给测试人员编写自动化测试带来的困难,这就需要在编写测试脚步的时候考虑的更全面,并提高对象的查找识别能力,以保证 automation 测试顺利。RFT 在基于 Web 和 Java 的测试中显示了强大的功能,但在 window application 的测试中,还是有些不足的,没有一个测试软件能做到完美的运行于各个平台,但遇到问题时,我们应该尝试用一些替代的方法去解决。本文所介绍的方法正在作者所在项目组实际使用,经检测在提高动态查找的能力和准确度上有很大的提高。
免责声明
本文包含解决方案。IBM 授予您(“被许可方”)使用这个解决方案的非专有的、版权免费的许可证。然而,解决方案是以“按现状”的基础提供的,不附有任何形式的(不论是明示的,还是默示的)保证,包括对适销性、适用于某特定用途或非侵权性的默示保证。IBM 及其许可方不对被许可方使用该软件所导致的任何损失负责。任何情况下,无论损失是如何发生的,也不管责任条款怎样,IBM 或其许可方都不对由使用该软件或不能使用该软件所引起的收入的减少、利润的损失或数据的丢失,或者直接的、间接的、特殊的、由此产生的、附带的损失或惩罚性的损失赔偿负责,即使 IBM 已经被明确告知此类损害的可能性,也是如此。
参考资料 学习
获得产品和技术
讨论
作者简介  | |  | 祝天光,现为 IBM 中国开发中心负责 FileNet 功能测试的工程师。于 2001 年毕业于东北大学,获学士学位,有数年 Java 开发经验,及三年使用 Rational Robot 进行测试自动化的经验。现从事使用 RFT 进行更加稳定有效的测试自动化工作。 |
 | |  | 张捷,现为 IBM 中国开发中心负责 FileNet 功能测试的工程师。于 2002 年毕业于清华大学计算机系,获得硕士学位,有数年 J2EE 开发经验,及一年使用 Rational Robot 进行测试自动化的经验。现从事使用 RFT 进行更加稳定有效的测试自动化工作。 |
 | |  | 杨莉,2007 年加入 IBM,之前有三年 IT 领域功能和系统测试经验,现为 Filenet 产品测试工程师,主要使用 Rational Functional Tester 和 Rational Robot 进行自动化脚本开发。 |
对本文的评价
|  |