SCMLife.com

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 7015|回复: 2

[推荐] 在RFT中开发健壮的 Web 应用功能测试脚本

[复制链接]
发表于 2011-7-10 15:04:09 | 显示全部楼层 |阅读模式
本帖最后由 技术狂人 于 2011-7-10 15:08 编辑
7 y- R8 t) Q5 j% p3 n: Z7 b( y& |  m" E
引言
( [% H& \! f" S& w% x! GIBM® Rational® Functional Tester(简称 RFT)是一款面向对象的自动化测试工具,测试人员应用此工具可以对多种应用程序进行功能测试、回归测试、数据驱动测试以及 GUI 测试。它不仅支持 Siebel 和 SAP 的扩展,同时支持对使用多种浏览器,如 Mozilla Fire-fox、IE 等的 HTML 应用进行测试。本文重点针对网络应用,尤其是基于 Java EE 开发的企业级应用,讨论利用 RFT 编写健壮的自动化功能测试脚本的最佳实践。' E  r5 t+ `! |  h$ d; d
针对网络应用的自动化功能测试,通常在回放过程中,由于录制与回放使用浏览器的不同而造成测试的失败;而对于同一浏览器进行录制与回放,也会因为数据或者页面对象属性的变化而造成测试的失败。本文主要针对开发 Java EE 应用自动化测试脚本问题,如:浏览器变化、测试数据变化、测试对象属性变化等问题进行讨论,同时提供相应的解决方案,目的在于通过这些最佳实践,能够更好的开发健壮的自动化功能测试脚本。
( \  o) I" q- z  v8 C$ M本文主要讨论以下三个主题:
2 D7 f" j9 b2 D) q) C
  • 创建灵活的功能测试脚本以适配浏览器对 HTML 元素的处理
  • 创建基于数据接口的功能测试脚本以响应测试数据的变化
  • 创建优化的对象识别功能测试脚本以支持页面测试对象的变化
    . P2 M2 q0 }% [! T9 w+ R


8 g1 P5 u, e4 r4 D/ P; T
) M" H, ^* m5 C9 N$ {+ r1 }7 F% e' P4 B
创建灵活的功能测试脚本以适配浏览器对 HTML 元素的处理
% f9 T3 s) I4 w$ Z, A/ {+ R0 k  m( s对于实际的 web 应用功能测试,常常会碰到脚本回放中在浏览器端出现错误的场景。由于不同浏览器或者浏览器的不同版本对于 HTML 元素处理方式不同,因此这可能会导致脚本不能平滑的进行回放,甚至导致失败。下文针对此问题,举出三种常见的现象,并提供相应的解决办法。
' i+ j# }) h6 j0 t查找和关闭弹出的浏览器实例窗口, b! _+ g2 c# n' L. X
当浏览器的 JavaScript 脚本被打开时,新的浏览器窗口可以通过以下 JavaScript 代码片断弹出:5 D8 Y' H! L- N' e4 p; l
  ^- i" q9 {3 W# R. J+ n* u+ X
清单 1. 打开新浏览器窗口4 e4 N1 D0 n  S( p( \+ }( W
onclick="Javascript:popupWindow('http://www.ibm.com/myapp/help.html');return false"

0 X4 k+ u( F3 F$ ^0 a网络应用经常显示一些辅助页面来帮助客户自如地使用软件产品,这些辅助页面通常是通过使用上述的 JavaScript 代码而弹出的。当点击该链接后,新的浏览器实例被创建并且浏览器窗口显示出来,其中包含了指定目标 URL 中的内容。1 [% w3 }" L2 Z) C' e# T
然而,如果功能测试脚本编写不当,RFT 回放监控器有可能不能很好地区别这两个浏览器窗口实例,并因多个浏览器的实例被识别从而引发脚本执行错误,即脚本方法 browser_htmlBrowser() 调用失败,该方法本身不带任何参数,是由 RFT 自身在录制脚本时自动创建的浏览器实例查找方法。
- l" l+ J. M% v0 Q$ s4 M1 v6 V' K解决这类问题最有效的办法是动态查找正确的浏览器窗口实例。实现如下,调用 getRootTestObject().find() 方法并且指定‘Html.HtmlBrowser’作为被查找节点的‘.class’属性值。
# f- u0 }" f" i6 ]& x8 M7 P) \
" o. E: a) O$ Q清单 2. 动态查找正确的浏览器窗口实例
) J" T7 z$ U3 U; K9 P6 W7 \
TestObject objects[] = getRootTestObject().find(    atDescendant(".class", "Html.HtmlBrowser"));//Traversal every TopObject get document objectif (((String) document.getProperty(".title"))        .equalsIgnoreCase(“myPageTitle”) { ((BrowserTestObject) objects).close();}
* |! ]4 e& d5 o% C1 Z
示例代码返回一个 TestObject 测试对象列表,实际上是一组浏览器窗口实例,然后通过检查这组浏览器实例的一些特定属性值比如”.title”来识别某个确切的浏览器窗口实例。
, z+ Y% I" E/ i; C+ X0 F) e识别折行的 HTML 链接6 v% n% I, b% Z  x6 A5 x0 s
对一个折行的 HTML 链接,在某些浏览器中运行功能测试脚本时调用该链接的 click() 方法可能出错。. y2 B! A+ p. d/ X
5 b+ ^! L' M: p1 m( ^* u, I+ r) T" e
图 1. 跨多行的 HTML 链接" o. {3 A. k$ B: Y+ f+ ?
* I# g) D' b" X% }
在上图 1 中,HTML 链接“Software download & media access”折行,也就是说链接文本在两行间分散开并且围绕着该链接形成了如图所示的红色矩形区域。当 RFT 在某些浏览器中回放测试脚本时,回放监控器会默认地直接点击围绕链接最小区域的中心,不巧的是上图的场景中某些浏览器认为该中心点是空的并且不可点击操作的,于是 RFT 就可能抛出 Java 的 UnsupportedActionException 异常。( e6 A1 {; l6 g9 \8 ?: m
对上面问题最简单的解决办法就是为 RFT 回放监控器指定一个非空的屏幕点去点击。
) a% q; A6 y; E+ o* @; R% o& N
. r* I6 {# l; V- ]清单 3. 为 RFT 回放监控器指定一个非空的屏幕点
+ U' I3 g9 H7 _1 p/ J7 Z) Q- e
int offset = link.getScreenRectangle().height / 4;link.click(new Point(0, link.getScreenRectangle().height - off-set));
* m  e+ y7 Y! z% \. I, x( G6 r+ f  t
在如上所示示例代码中,指定的 RFT 回放监控器点击的点位于围绕链接的矩形区域的左下角。这就告诉了回放监控器点击的点 X 坐标为 0,Y 坐标为链接的高度减去一个固定的位移,这个位移刚好为链接高度的四分之一。这个点不为空,所以 RFT 回放监控器就能成功地点击。本文建议读者为网络应用创建自动化功能测试脚本时,如果需要在浏览器中执行点击 HTML 链接的操作,都可以应用这个解决办法。
0 S) J9 l. F0 r为文本输入域设置值的小技巧6 |) O9 T8 y) M0 V& C+ M- K( k
在某些版本的浏览器中,如果文本输入框不能获得鼠标焦点,则当 RFT 回放测试脚本时,文本输入框上 textGuiTestObject.setText(text) 方法的执行就会失败。也就是说,在为文本输入域设置值之前,光标必须设定在当前输入框上,否则测试脚本回放失败。
& ^/ z( h: ~' M% _  }7 J" a9 K为解决这个问题,脚本可以先调用文本输入框的 textGuiTestObject.click() 方法将光标焦点置于文本输入框上,然后再调用 textGuiTestObject.setText() 方法设置文本值。请参考下面的实现代码片断:6 `3 B" w0 x9 t+ c7 j# _

- F  ~! x9 p9 h! D' ~清单 4. 设置文本值% M% u& L' ~7 U+ m; J% h* ^: n
text_username(getTestObject(), DEFAULT).click();text_username(getTestObject(), DEFAULT).setText(“myUserName”);

& F2 [' W% t5 E4 |. I& M另外,在录制测试脚本时,文本输入框的值需要直接键入而不能通过剪贴板拷贝粘贴,否则文本的值依赖于每次运行脚本时剪贴板的内容。剪贴板的内容总是随着用户的复制操作而变化的,这是不固定的,并且很容易导致功能测试脚本执行失败。
  Y& v2 K9 [- t0 |: S1 u7 ~' ~

1 m/ l/ f, H+ x- J3 g& V" e* G+ U+ R: S& I3 }9 V! i( ~1 z  E
% v8 {% H! E, t0 G! }6 d* t# K
创建基于数据接口的功能测试脚本以响应测试数据的变化( \$ X+ ~2 e- m- A" f9 m
一个完整的 RFT 录制与回放的工程,通常主要由三部分组成:记录的逻辑操作、记录的页面对象元素以及记录的页面数据。在回放过程中,这三个部分都不可缺少,同时页面的逻辑、对象和数据都不能更新,任何一个部分的变化都可能导致脚本回放的失败。数据的变化是最常发生的情况,其中测试数据的变化主要包括由于页面输入数据的改变、业务逻辑数据的变化(数据库中数据的变化等)以及环境数据的变化(多语言改变)等。
6 s+ ^: X1 H+ N1 O' [( Z2 `9 I1 S测试脚本中常见测试数据定义+ h9 {3 _0 R+ S* |$ U! A) o
针对测试数据的使用和保存,在 RTF 中主要有以下两种方式:: H* m6 D9 I& u$ V/ D+ b; i# p  J
(1)测试数据直接存在于测试脚本中。如图 2 所示,测试数据被包含在测试脚本中,这种情况多出现于在页面中直接填写数据,在录制过程中数据就直接被记录在脚本中。这是最基本的测试数据记录方式,但如果测试数据需要改变,那么这种方式就显得不够灵活,甚至会造成脚本回放的失败。
. K# y3 x9 f% ^! n3 N. q) e! A- v, Z( U: E  B4 S
图 2. 数据存在于测试脚本中
" H0 |0 |, t8 ` $ F, Z( m% m$ |: L( O( n
(2)利用外部数据(DataPool)做数据驱动。如图 3 所示,为了灵活的对数据进行操作和更改,RFT 使用 DataPool 来管理测试数据,即将测试数据与测试脚本分离,利用 DataPool 来驱动测试脚本,从而使测试脚本的灵活性大大增强。但 DataPool 的设计和使用可能对一些复杂的测试场景有一定的局限性,在数据准备上相对繁琐,如果遇到大量数据的变化(数据库中数据的变化)问题时,DataPool 对测试脚本的驱动效果就可能不是十分理想。
% p# i5 |  I, o3 A; p% e# c) K5 X8 x# \1 |! [# q  E
图 3. 数据驱动测试脚本9 j$ f+ l$ u& X6 L
5 C. a# s& c; D8 k( _! [
数据接口的实现$ U, J5 A8 q. ]5 u+ f
数据库接口的实现' {$ V1 }6 s$ _  k
在测试脚本中,对于所记录的页面输入数据、需要验证的信息数据都有可能来自于数据库。在不同的时刻,有可能数据库中的数据会发生相应的变化,导致脚本在回放时,所填写的数据不符合此时逻辑要求,或者原先的验证点不能成功被验证。' }' p" P" F. r5 J5 ~! k/ ]
在一个 RFT 工程中,主要有以下几种记录数据的文件类型:
: i1 H2 o, K) a+ E3 d8 s
  • DataPool(数据池):以表格的形式将数据存放,并在脚本运行之前,根据所需要的数据,选择相应的 DataPool 文件。DataPool 文件是在回放脚本前需要准备好的。
  • Script(测试脚本):测试脚本本身,这里会包含直接与页面展示相关的数据,这些数据多数来源于页面的输入,或者是后台经过数据库查询后数据展示在页面中。# W0 [/ s1 V1 l+ t
数据库接口多数应用在与测试脚本直接交互方面,如图 4 所示。针对这样的问题,可以将应用中关于数据查询的部分作为接口,注入到测试脚本中。将原先脚本中记录数据的地方,用相应的查询方法所替代。这样以来,测试脚本中的数据能够更加灵活的进行变化,并且对于不同测试环境和连接不同的数据库来说,也可以按实际要求进行切换,以使测试脚本顺利的进行回放和复用。7 {. z# k. |1 T2 m9 }+ V0 I

9 @. Y; l' P4 n$ i* v% C! _图 4. 数据库接口注入测试脚本8 G7 o$ a5 h4 F* e  a. j! E
; Y: n# r% \2 U2 N/ K) w3 c3 k% ^
多语言接口的实现
1 h/ d4 c! p7 t/ ]8 wRFT 为了测试脚本支持国际化,提供了内置的对象识别机制来从本地化的属性文件中读取测试对象的属性值。但是这得在 RFT 启动时打开这个功能,一旦该功能打开,RFT 在回放测试脚本时就会将测试对象的属性值作为在属性定义文件中所使用的 Key,通过这个 Key 在属性文件中找到对应的属性真正的被本地化的值。( z* M8 t" T7 U, j0 v" ?# ], K
在 RFT 安装目录里,找到配置文件 ivory.properties,将其中的 rational.test.ft.services.enable_localization 值设置为“true”从而将本地化支持功能打开。ivory.properties 文件一般位于 RFT 安装目录下的子目录 bin 中。3 K5 P8 @6 b, H# i3 y$ n( i6 D4 e
将测试脚本中使用的文本本地化后的属性文件放置在测试工程 resources 目录中(如图 5 所示),并且属性文件的基本名字和项目工程的名字一样,但是后面拼接上相应的 Locale。
# L3 f6 X( c$ s
$ S4 t/ z6 G& V! @( G2 G2 m( n* H图 5. 属性文件位于项目工程 resources 目录下
/ r7 k6 @$ [0 S& U! y2 R 9 A% U  s3 m& q  Y4 k6 a3 l$ I- t
4 V$ K5 X4 g! o+ `3 b
清单 5. 属性文件- w6 c, X- G) U% ]. t
# English propertiesdocTitle =“Demo Project”# And other properties definition here

$ G% z% ^6 T8 E  S3 I更新 RFT 测试对象映射中测试对象的属性(比如 .title)为本地化后的属性文件中的 Key,并且在属性文件中定义该 Key 的本地化 Value 值,如图 6 所示。0 a' T6 q! r& e4 a" l% }
5 G; D# ^0 y) ~6 T1 c8 p6 \/ d
图 6. 使用本地化属性文件中的 Key 作为属性值& I, ^  h6 Q. `0 C8 Y

1 e  F+ F- t  }: e于是 RFT 在回放功能测试脚本时就会从项目工程下 resources 目录中读取被本地化好的属性的 Value 值,这些属性值根据 Locale 的不同会有不同的翻译。' Z: O% O- h8 D9 s! q$ B

' P) A9 w( J' g+ L& N5 C% i

8 v* O3 x/ @5 ^3 h' t4 ~4 ?( ?: }
. o7 f- G/ [" n. p创建优化对象识别功能测试脚本以支持页面测试对象的变化
- t8 P$ p+ j3 O' WRFT 在对页面进行功能测试脚本录制的同时,也会记录页面上对象的标识信息,以便在脚本回放的过程中,根据已记录的信息进行对象的识别和相应的操作,能够使得测试脚本顺利执行。以下根据 RFT 对于页面对象的识别原理和常见识别方法,借助 RFT 的可扩展性,提供几种灵活的对象识别方法,使得对象识别方法更智能、准确。* R2 W6 \5 L5 g1 j$ B# d, f4 A7 g
RFT 对 HTML 中对象的识别原理
+ ]  Y  Z: z' v4 z2 [对于被测页面中的 HTML 对象的识别,RFT 提供了专门的 Proxy 进行处理,并将其转换成测试脚本。其中 RFT 中定义 TestObject 为识别测试对象的基类,测试对象则被映射为 GuiTestObject 对象。对于测试对象来说,每一个对象都有自己唯一的属性,从而能够在脚本回放的过程中被准确的识别,同时这些测试对象的属性也在录制的过程总被存储在工程中。
( m+ o5 N& m2 Q以图 7 的 google 主页的搜索按钮为例,用 RFT 的 Test Object Inspector 工具进行页面对象识别时,会出现以下内容,如图 9 所示。可以看出两个测试对象中“.name”的属性值是完全相同的,但是通过“.class”的属性来唯一区分两个不同的测试对象。测试脚本在回放时,也是按照这个原理来准确识别测试对象,从而使得脚本能够顺利运行。
  j# z/ ?, C4 s# v& j
0 S) Q, x6 K; |% q+ V" K) o图 7. google 主页搜索按钮
0 k: P6 ^; M( J! P! V2 B
+ H; C/ g- Q6 \9 G
* a' [3 c- p9 n! t, I图 8. google 主页搜索按钮属性
- O  n* I' ~' ~3 M# ~: m1 C  O
. x: \/ T) o( @% P2 \; b7 P+ `( ^同时在工程的 resources 文件夹里,生成后缀名为”.rftdef”和”.rftxmap”两个文件,这两个文件记录了测试对象的属性,两个文件的相关内容通过后缀名为”.rftdef”的文件中的 id 相关联。以图 8 中的搜索按钮上的文本为例。2 J- R2 L. H; U  n! z
, b+ u  i' [9 n/ c
“rftmap”文件# x- b$ X- I" A9 |2 D) Q) S
<MTO L=".MTO"><Id>1.1LspE2Ef1EHB:SV5q3:MzptfHo:8WR</Id><Name>Google 搜索 </Name><SimpleName>Google 搜索 </SimpleName><Parent>0.1LspE2Ef1EHB:SV5q3:MzptfHo:8WR</Parent><TO>GuiSubitemTestObject</TO><Dom>Win</Dom><Class>.Text</Class><Role>Text</Role><Proxy>.Win.GenericProxy</Proxy><Prop L=".MtoProp"><Key>.name</Key><Val>Google 搜索 </Val><Wt>75</Wt></MTO>
% b' }( r9 w+ a9 u
7 z: h, f" ]4 }) y
“.rftxdef”文件
% S. U5 [: }% ~' p8 u7 w" G+ A4 R
<TestObject L=".ScriptDefNameMapElement"><Name>google 搜索 text</Name><ID>1.1LspE2Ef1EHB:SV5q3:MzptfHo:8WR</ID><Role>Text</Role><Deleted>false</Deleted></TestObject>
0 z; c" M) M/ b# X# N
灵活定义识别测试对象方法
7 `; j* c2 }& o, R6 `7 j调整权重设定对象属性的重要性
" X" j) U. K) G1 _- C7 vRFT 测试脚本在测试对象映射中定义了所有测试对象。每个测试对象的属性都有一个额外的属性权值,用以表示这个属性在识别对象时的重要性。RFT 计算所有属性的权值,通过权值决定该测试对象是否是用户期望的 HTML 元素,也就是说对象属性是否匹配设定的查找条件。! }: U) q8 l. `: L% K9 H
例如,下面的测试对象映射录制在 c:\DEMO\Project1.html 下,然后使用在 Project2.html 中,这个 URL 和 Project1.html 类似,只是在 Project 名称上有细微的差异。然而,既然 URL 和最开始录制的不完全一样,这个 HTML 链接就不能在项目 2 中匹配上。, ~; |  o, X! S  X$ F7 u( t6 H; F
简单的解决办法是在测试对象映射中减小链接 URL 的”.href”属性权值。比如将”.href”属性权值降到更低的 20 让 RFT 在识别测试对象时不将该属性作为重要属性。所以测试对象查找方法就能够基于其它主要属性比如”.text”和”.name”识别该测试对象。
! c  m" {  H$ v: \& P5 u% B7 c$ x7 X# n+ W. X
图 9. 测试对象的属性定义$ H# l/ G1 X+ X6 ^2 A; C8 I9 d
0 P- S% P( E8 q3 x

+ k1 d) F5 T6 P* l' U对有细微差异的属性值使用正则表达式
5 ^; b; X2 \. z  [; G1 {3 T! x8 b. RRFT 测试脚本也可以使用基于模式的对象识别,这允许更大的灵活性。正则表达式的使用可以忽略被测应用新版本发布中引入的细微差别,而不需要经常根据版本不同频繁更新已经存在的功能测试脚本。这不仅可以应用于测试对象映射中的属性值,而且还可以用于 TestObject.find() 方法来有效查找目标对象。. p' S# O0 Z) I3 Z  E( \
为了识别上例中正确的链接,”.find”查找方法可以接收一个正则表达式作为输入参数。尽管”.href”属性经常变化,但这仍然可以有效地运用。在对象识别时为了匹配正确的链接,为 URL 属性设置一个正则表达式参数,比如:“C:\\\\DEMO\\\\Project[12].html”,”.find”方法就会返回正确的目标链接。! w6 p6 P) ?7 b9 u
; f+ j- V! X9 y- x- y
清单 6. 返回正确的目标链接
( e) D2 K& e% Z* @4 _1 Y: E
String linkHref = "C:\\\\DEMO\\\\Project[12].html";TestObject[] links = getHtmlBrowser(getTestObject(), DEFAULT).find(atChild(".href", linkHref));
+ T7 K0 Q$ n* P5 S) [
通常说来该方法是解决此类在版本间处理细微差别问题最有效的方法,并且这些细微差别大多是文本属性的改动。
, b) u/ W8 P% G! w, V3 d- {为查找测试对象方法传入特定的父对象
' l% C3 B& d% O: d# x; |8 _对象查找方法非常有用,通过使用自定义查找方法,自动测试脚本便易于维护。在使用查找方法时传入父对象作为参数,可以限制查找方法只在父对象所属的容器中完成。虽然测试对象有了细微的变化,指定一个合适的父对象不仅能够缩小查找范围同时能够节省查找时间,对已有脚本的唯一更改是直接使用更改后的测试对象,而没有必要重新录制未更改的测试对象。
- u. B7 \5 j2 @3 h1 v1 }
& u- N" h& z+ |3 {  w$ n( S清单 7. 对象查找方法: G7 [8 M/ U; g% e
protected BrowserTestObject getHtmlBrowser(TestObject anchor, long flags) {                return browser_htmlBrowser(anchor, flags);}//In the super helper class, the anchor will be used to get the test object.protected BrowserTestObject browser_htmlBrowser(TestObject anchor, long flags) {                return new BrowserTestObject( getMappedTestObject("browser_htmlBrowser"), anchor, flags);}

3 e( r  Q) `/ ^9 q, i$ y这使得处理新的版本发布引入的细微差异非常容易,也不需要很大的代码改动。
) n- J2 E) P7 n! a) u0 D$ d1 Q

4 B' G6 @) \2 M7 E4 r
& `/ J4 Z1 [/ S- N4 Y结论: w+ P  d& Z: R# Z$ O% f& m
本文所提及的内容都是来自于平常工作的最佳实践。通过对 RFT 功能测试脚本的编写以及对 RFT 扩展功能的使用,可以使自动化功能测试脚本更加的灵活,同时对录制的脚本的维护更加方便。本文描述的最佳实践可以帮助测试人员和开发者在 IBM® Rational® Functional Tester 创建健壮的功能测试脚本。但是并不保证这些最佳实践能够解决读者所面临的所有问题,读者在决定使用自定义脚本还是录制脚本时请参考更多的官方文档。
6 h% j& T* B* f2 C& I* @

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
 楼主| 发表于 2011-7-10 15:09:25 | 显示全部楼层
回复 支持 反对

使用道具 举报

发表于 2013-7-5 15:23:31 | 显示全部楼层
学习了。!!!
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|小黑屋|手机版|无图版|SCMLife.com ( 京ICP备06056490号-1 )

GMT+8, 2018-1-20 11:49 , Processed in 0.074818 second(s), 6 queries , Gzip On, MemCache On.

Powered by SCMLife X3.4 Licensed

© 2001-2017 JoyShare.

快速回复 返回顶部 返回列表