SCMLife.com

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 5005|回复: 2

[推荐] Rational Functional Tester 自动化脚本的优化技巧

[复制链接]
发表于 2011-12-28 12:17:29 | 显示全部楼层 |阅读模式
本帖最后由 技术狂人 于 2011-12-28 12:44 编辑 , C8 S% J# r  ^, y
: e* j4 R2 z' v- R5 ~/ D( G- F
背景介绍% [3 U& ]2 M6 a! Q3 P! ?, z
自动化一直是作为测试领域很重要的一块,自动化测试的广泛应用不仅可以节省测试团队的工作量,提高工作效率,同时,也可以避免测试过程中人为输入的错误导致的问题。因此,自动化测试对工具的使用和依赖性是 IBM 的众多测试团队一直致力于研究的。IBM Rational Functional Tester(下文简称 RFT)是 IBM 的一款适合于功能测试、回归测试的自动化测试工具。它的基础是针对于 Java、.NET 的对象技术和基于 Web 应用程序的录制与回放,当然测试人员也可以利用它以 eclipse 为核心的特点用 java 脚本自行编写更具有灵活性的自动化测试脚本用于日常和回归测试。 RFT 支持众多浏览器及其各版本,就目前为止,RFT 的 8.2 版本,将支持 windows 上面的 IE6、IE7、IE8、Firefox3.5、Firefox3.6、chorme 等。
" Q, r: O) e+ x0 F$ \+ rIBM Connections(IC)是一个基于 Web2.0 的企业级社交软件。作为 IC 产品中时间最长的两个组件书签和博客的测试团队,使用 RFT 做自动化测试已经近四年了,书签和博客测试团队的自动化脚本覆盖率已经达到 80%,自动化的高覆盖率的直接好处就是,在产品的每一个回归测试阶段,80% 的基本功能测试都可以依赖自动化脚本来完成,同时,很多边缘测试也不会人为的被遗漏,大大的提高了测试团队的测试效率。同时在这自动化测试中,书签和博客测试团队也总结出了一系列搭建自动化脚本框架,维护自动化脚本的适用性和实用性等的技巧。: d0 r& t  V- ^0 o) w
此文主要介绍了书签和博客测试团队的自动化测试脚本的框架,以及在开发和维护自动化脚本的过程当中要注意的问题和技巧。
" a9 X" M* s: y. l' R
3 {  E! C9 n' u0 `/ {% \

% U/ _, v) T/ M3 }% ^适用于 Rational Functional Tester 的自动化脚本框架
5 _4 k6 u' E1 y& D$ R在自动化测试中,自动化脚本的开发和维护占据了大部分的时间,而运行脚本、收集结果虽然是最终目的,却在庞大的开发维护工作面前,显得不那么恼人了。所以,经历过自动化测试的测试人员一定都会有同样的认知,好的脚本不仅易读性要强,便于维护更是重要。* Z( k$ v' C9 j& x( [
RFT 提供了录制和回放的功能来开发自动化脚本,但这并不是一个很好的方法,很多测试团队也并没有采用,因为录制与回放功能对于程序运行的环境运行依赖性太大,包括了程序的为止,窗口的分辨率,每个变化都将导致脚本无法正确运行,因此书签和博客的测试团队采用了自己手动写脚本的方式来提高脚本的易读性以及可维护性。
7 o2 O2 b0 a4 v/ y# w9 I- UIC 的测试团队采用了一个很普遍的三层脚本架构,所有的自动化脚本都以三层架构为基础进行开发和扩展,由于它的普遍性和合理性,也提高了脚本的整体易读性。下面将简单介绍一下脚本的三层架构:% m: h' [0 Z- h  C8 l5 ~" R) y
基本框架——脚本的三层包装架构$ i+ E  k, M- q$ J1 I
RFT 的脚本可以分别被归类为 AppObjects、Tasks 和 Testcases:
' W; `. s) S2 T& f9 {. k
  • AppObjects:定义页面上的元素。在测试过程中,所有用到的页面元素都定义并储存在这一层中,这一层还包含了所有页面上显示的字符串。
  • Tasks:定义可以单元化,可重用的任务,调用在 AppObjects 中定义的元素。
  • Testcases:一个 case 写成一个脚本,每个测试场景,可以写成一个或多个脚本,每个脚本只调用在 Tasks 中定义的可重用的任务。) p9 V: m9 D" Q) b- X' G

" K) v& i7 r4 P$ f图 1. 三层结构示意图
( j: y, O  [' z2 v
8 n, z+ C+ z/ a在每一层架构中,都允许定义不同的文件夹来分类存储信息。可以根据模块,功能,或者类型来分类并命名。当然,命名的规则同样是要注重易读性,能够体现出文件夹中存储的分类标准。以 IC 为例,博客和书签各创建了一个文件夹,把各自的元素,任务和脚本放在各自的文件夹中以方便管理。当然,每一层架构下都有一个 common 的目录,用来存储一些通用组件的元素,操作和脚本提高脚本的重用性,便于维护。
7 n/ A3 @2 o& c- _除了这三层架构之外,还有两个对于脚本的维护比较有用的,一个是用于管理所有非脚本类的文件内容,例如脚本在运行程序的时候,需要读取的图片,文字等,便于运行自动化脚本的测试人员有个统一的输入内容,和一致的输出信息。另一个是名为 ibm 的包,包里面定义了各种组件的类,类需要的函数等,便于测试人员写脚本时候有个统一的接口调用。5 |; y1 B( J$ e4 Q) O1 ?
Appobjects 层
; r* F2 y% U$ d# @  o3 }appobjects 层是用来且只用来存储定义页面元素,包括字符串的。所有脚本中需要用到的页面元素,都需要在这一层被定义并储存。$ m! ~1 `1 F2 t5 h: M$ Q# ^5 P
命名规则
$ g6 B8 H2 V3 p2 A: ^) w$ _为了更好的维护脚本,一般所有的元素会根据所属的页面存储在不同的脚本中,脚本的文件名通常被根据页面的内容来命名,例如博客,每个脚本为了方便脚本维护及准确定位,被定义的元素需要有特定的命名规则。通常可以根据不同的页面,或者不同的功能模块来命名,例如在博客中,大部分的元素被归类到“blog”、“entry”、“comment”等脚本中,脚本中的元素,大部分用元素的类型加上元素的名称来归类,例如“getButton_MyBlog”、“getLink_AddComment”等等。下面给出在 Work 页面中对于“Save”这个按钮的定义:9 O$ s1 Y1 r3 s" j' Y9 p
2 X7 b* w" D( `1 i
图 2. Work 页面中的登录按钮* C/ R3 a$ T% Y5 ]# ?

. J3 n/ y7 ?# x% t9 C6 Z% |! [) a( [5 w# |( I
清单 1. Appobject 层中定义一个 button
# G- y: f. v; g1 L  @
public class Work extends WorkHelper { // 直接用页面的名称命名类名( J( N3 O) y' o5 g( N
    public WButton getButton_Submit()  // 类型名加上属性名的方式命名元素名
* v% A$ {! w6 M* e         {
8 ^3 B! J1 m/ B8 a) I3 f5 `            …5 W5 E* O9 C, b  V, ?+ g
         }
" l7 \4 O1 k- c; A* Y' a5 n7 p}
9 j1 D( l6 K# w
这样命名无论是前期创建脚本或者是后期的维护脚本时,都能很清晰的在脚本中反应不同元素的位置。) |: d* f! @6 w+ T; a. A

: ~" ?1 x! Y$ h0 Z& _5 @0 C: [元素的获取

. _0 r9 ?; F, Y6 C" H( ]9 y这一层最主要的任务就是获取所有的页面元素,最常用的有以下几种方式:
9 i8 B  \5 Z3 c
  • 根据唯一的属性值,或某几个有唯一性的属性值的集合- z( o- S; R' g0 N! F/ ~# k4 G
通常,规范的应用程序代码中,页面上的每一个元素都会有一个唯一的标识属性,可以直接是具有唯一性的 ID,也可以是元素的类型(按钮,链接,文字等)和页面显示的字符串的组合。所以,每当用 RFT 编写自动化脚本中,需要定义元素的时候,首先,需要检查是否被定义的元素有唯一标识,如果有,可以直接通过唯一标识来定义该页面元素,这是最行之有效的方式。下面给出了一个通过元素的类型(链接)和文字(About IBM)来唯一标识该元素的函数:% l9 @0 ^5 L- W( b- [3 N7 J0 z3 |

7 U; c$ v+ `  a, t" X( l图 3. 页面中的 About IBM 链接2 p0 o& j$ H( n

7 R- ~, C! Z% u2 ]$ X5 d- o5 N6 w
清单 2. Appobject 层中通过 name 定义一个元素" O8 `% q' ~! |* [  K
/**) ]( }. D: Y4 {9 O/ Q( @
* @ 返回页面上“关于”链接,返回类型:WLink
" B! u  z9 Z- ]3 f& v8 x* C# | */% }0 r( o/ A  I5 c' l8 j, P: T5 v
public WLink getLink_AboutIBM(){
6 Q1 D) Q# k) ^! C' ]' D, d try{return new WLink(og.getNthObject(0,Webfuncs.gsTextProp,
( y6 s! ]4 B; I  q GlobalLCStrings.gsAboutIBM, Webfuncs.gsClassProp, Webfuncs.gsLinkRef));
3 o$ U: @4 _& e }
6 \; R5 w5 X) o3 R# r) e catch(Exception e){return new WLink(og.getNullObject());}
6 k. `* V4 A! l3 P+ D6 C }
: B5 l. U1 c$ v( @! X
  • 根据父元素或子元素层层递归
    $ Q! x6 |' B; @3 o6 Z
如果页面元素无法用任何属性值定义,可以在页面中分析一下该元素的父元素或子元素,在整个页面的树结构上寻找有唯一标识的元素,然后可以通过先找到父级元素或子级元素,层层递归找到到目标元素。下面给出了一个通过寻找父级元素层层往下递归最终找到某个 DIV 的代码:通过父级元素找到“My Watchlist”层。 ' u! |$ ^$ o% I& m7 P& {) {

" R  l+ I. z" L- |. y6 r图 4. 页面中的 My Watchlist 层
& [2 d  A) ~8 p# `1 e, k$ _/ l 3 `' [7 X/ D8 ~/ a# K; k

, ^1 d2 M, B3 s9 A3 i清单 3. Appobject 层中通过父级元素递归定义一个元素
  H$ c& E! f; e9 I9 K
/**
2 q0 d9 v" T; u8 x  o * @ 返回“关注我”所在的 div4 z: h  L5 d/ R3 S' ]. i8 Q$ X1 w
*/6 F0 E2 A5 }2 U$ J; R6 i
public TestObject getDIV_WatchMe(){
9 l& d" K+ N  ?5 M) N: i4 c     try{, {( [: b- {1 Y+ e# z
                 // 父级元素 getDIV_MyWatchlist() 会在下文中定义
" ?" o: c* w5 a" D) t' V, x         TestObject[] children = getDIV_MyWatchlist().getChildren();# [- _6 z; C& {; ^
         for(int i=0;i<children.length;i++){- n+ a3 j, E. P( Q; L, v6 W
         if(children.getProperty(".class").equals(Webfuncs.gsHtmlDIVRef) && * L$ k- O& o; a- t0 H( K$ S
                 children.getProperty(".className").equals("lotusChunk"))9 d3 w5 \; U1 F9 G# I* n- b
             return children;
* F. D2 a- {% b; v! e' ?) B5 o     }
# J2 L& V/ p# ~         return (og.getNullObject());- E2 a4 Z; v& P& p- n6 p  |
     }catch(Exception e){8 g6 l0 u* o5 M3 N" f) Z/ X, ~& P9 h
          return (og.getNullObject());
$ o4 L% V7 l& `" s! }# m     }}% B4 V5 u9 {. e9 a# M% Y
# A8 i0 ?7 D5 {! M1 U' x& _
/**0 @0 f( U- F% C3 X6 \; M
* @ 返回“我的关注”所在 div,这是“关注我”所在 div 的父级元素
1 ~8 }! j$ \' a" y  T' q4 p/ g */' p4 g& [) r9 I1 ^' }
public TestObject getDIV_MyWatchlist(){8 l! T5 w, x. W& }
     try{return (og.getNthObject(0,Webfuncs.gsIDProp, "subscriptionsHolder", , ^- p/ k4 Z* A$ J, l/ q1 K
                                 Webfuncs.gsClassProp, Webfuncs.gsHtmlDIVRef));) r3 o- V5 H3 F# j3 C
    }catch(Exception e){return (og.getNullObject());}6 W9 F- I  Q3 [# {  H
}1 {( g: e6 v1 l
. B! T/ g& ~- N- S
  • 用 utilities 包中定义好的查找方法在页面中遍历$ j* F+ N& R  ^. P9 `; w$ u
common 的包中定义了很多方法。其中有一个 find() 方法可以用来在页面中通过遍历所有页面元素的方式定位到该元素。find 的方法中将指定元素的一些特征,例如,它的类型等。之后,可以再通过遍历和比较来获得最终需要获取的元素。: |4 }# ]5 u1 }! c& d# `% z# U
- A& `& n+ Z# u( d# x/ w! T5 q9 o
清单 4. Appobject 层中通过 find() 获取元素; F" t% Y9 H/ r- A6 ]
// find all Html.SPAN objects* s! z0 ^! E$ F$ A, B  R7 B! I6 x) i' E
TestObject[] to1 = find(atProperty(".class", "Html.SPAN"));

/ }4 C. `0 v! d% [8 S( Q定义所有页面字符串的函数
$ s4 u- T' d3 w  W9 \8 A这一层包含了定义页面上所有字符串的函数,用于在统一的地方管理界面的字符串。有任何改动,如果界面有任何的改动,修改一次就可以适用于脚本中所有其他调用了这个字符串的地方,降低了脚本的维护工作量。 - w' c4 g6 e% I
元素的验证
: M( N. X" o7 u' }7 t每个在这一层的脚本,都需要有一个 main 函数,这个函数里包含了在这个脚本中定义的所有页面元素的验证。因此,如果在后期页面上元素进行大量的改动之后,不需要一个一个测试脚本的运行来判断这些元素的正确性,仅仅运行每个 AppObjects 层的脚本就可以判断对与错了。通常,使用最简单的方法就是用下面 verifyGetter() 方法来验证。
- {" V8 {/ V: B
* N/ i$ f) [# a) q清单 5. 元素校验方法$ b5 V+ P* k; F8 H9 ?; m
public void testMain(Object[] args) {! G. w% s$ Q4 z) @( ]6 @% f" B
    og.verifyGetter(getDIV_WatchMe(), "getDIV_WatchMe()");
* f$ \$ v# p0 E; G+ d, Y    og.verifyGetter(getImage_Search(), "getImage_Search()");- w: a- g; D, q5 q3 u' @1 ^
    og.verifyGetter(getLink_ShowBy("10"), "getLink_ShowBy()");- `  {5 ]( h. R. R5 I2 ?1 Y
}
3 R- z+ T) Q; F, t4 I3 P
Tasks 层——定义可重用的任务,调用元素。) s: y8 N: g/ L  f% C/ \5 R1 }

. N, g9 }# Q$ O0 b命名规则, E: c, E  [0 V
通常 Tasks 层的脚本在命名中,跟 AppObjects 层一样,所有属于博客的页面上的任务都归类于博客的文件夹中,其中用任务所属的页面名称或者功能模块来命名,例如“blogs”、“entry”等等,而每个任务的函数名,用任务的目的来命名,例如“clickSave”,“createBlog”等等。下面给出了一个选择“Select All”链接的操作任务。2 ]8 i$ s; I% H/ R4 O
0 k' o: \$ P% ]/ B! N4 B. t' D
清单 6. Task- 单击“Select All”链接操作  L% V$ R1 F4 i: u/ w
/**
2 ^& N6 f; A$ q * 单击“Select All”链接" W' n* m' l8 p, w4 k# B8 A0 V
* @return 返回值为 true,表示单击“Select All”操作成功;否则,失败" ?7 k! x) o; L: K- A$ Z+ b
*/! v$ V( ^1 p: d: E/ M
public boolean clickSelectAll(){
! d9 P2 [5 K( v( `, F2 K      try{
7 d$ ~- Z: S# y  Q Webfuncs.objectExists(bMark.getLink_SelectAll(),
1 k$ z6 v9 {& P& X, c6 w   VisualReporter.giVisualReporterShortTO); // 强制完成页面渲染,确保动态元素已经能被脚本捕捉' i+ A4 {  i3 m& `, O- J
          if(Webfuncs.objectExists(bMark.getLink_SelectAll())){
( _+ k7 a- a: c; b              Webfuncs.selectLink(bMark.getLink_SelectAll());
, `9 L; _( N& |/ ^* w& {              return true;# g4 C' q; Z" {7 A7 l9 o
          }
6 g1 _, i! u9 ^. W9 H  b      }catch(Exception e){1 W( T9 E& ]8 h% B% i, a4 ~8 w
          Logfuncs.errorHandler("cannot locate the select all link");
) G0 W" n" U4 Y, P" y# ^6 T      }
% W' A- g) @1 J! [$ T/ Q# m" w      return false;
5 K3 F# T. K& n! C1 J1 t }
" O2 W8 m8 F# h5 `/ n; L
Testcase——定义测试用例! H( P& Z+ U2 s9 d7 T

1 n) q% a- d2 N$ ~命名规则) M; J# Y. N- d5 o/ i$ l8 b5 e
我们期望产品的自动化脚本的覆盖率能够达到 100%,那么最好的命名规则是,testcase 脚本与手动测试的测试用例的名称相同,这样就能清晰的保证其一一对应的关系。所有的脚本按照子应用程序进行归类,同时我们定义了专门的自动化测试脚本的模板,模板中有专门定义的需要脚本开发人员输入的针对于自动化测试脚本和具体的测试用例的关联信息。(本节中的实例将具体描述相关信息)/ a9 z; j/ M- _! @. c
9 Q. V4 |6 @# v2 l3 ?3 ^
脚本内容
0 r# M) p+ W4 f2 \" Z( T4 R2 A测试用例的脚本的内容主要有两个目的,描写整个测试用例的步骤,并在每个步骤中判断结果正确与否。下面给了程序清单用于判断点击页面的“Add new Test”的链接是否成功:
1 |  P( i* ]7 M0 x4 W+ D
! S( D  X; m8 r清单 7. 判断点击链接是否成功的代码和返回结果
7 H9 |7 J8 a1 ^  W' Y; d$ [" m; Y
altVerifyFatal(true, bmTask.clicklinkAddNewTest(), "click open new Test link");
4 U" p1 `. Z5 J) g9 E
" {! [/ w7 i/ Z/ h, R// 下面是运行这行代码后输出的日志内容,很明确的现实了操作期待值和实际值
- ]9 f: ?8 ^& v/ `: T% K) f; O; P+ D/ E! T% z9 b9 e
2:41:00 AM - 21:14:26:093 - PASS - Verify click open new Test link Expected: true 9 r3 \3 A6 R: u- i8 u
Actual: true
1 W" u% a! ?4 o' n
这句输出语句,一目了然的给了用户所有想要的信息:成功点击了新建测试的链接。/ J: n! [& I* p* s4 L
" X7 y5 o5 Z, Y' R* q4 [4 ?- u
实例
* p3 z! M# o8 Q/ ~+ n下面用一个简单的测试用例来展现测试用例的编写规范。; c: Q: ~0 u' G# c# x
测试步骤:' u7 i1 }2 H1 ]" e' J6 v
  • 打开网页“w3.ibm.com”;
  • 转到“work”页面,点击“IBM Forum”链接
  • 校验点:校验页面上有“Welcome to our online community.”是否存在,如果存在则是正确的结果。2 y/ j* N- N" J2 B8 T: J% I4 L( y

5 ^+ ^  r: j- E( N4 `1 U8 T- ?6 k清单 8. 判断点击链接是否成功的代码和返回结果& y2 j* m+ L5 ?4 j9 R. l8 n
package testcases.connections.dogear;
  v& b3 c) i: y3 q- Dimport ibm.util.Browserfuncs;
* i( T2 H. F: |+ l# J$ q5 h  fimport ibm.util.Logfuncs;2 I' L) J: t3 K0 \9 p8 d: I- [. i. M
import ibm.util.Webfuncs;5 a* j8 T6 I; l
import resources.testcases.connections.dogear.IBMForumStringOnWorkPageHelper;; s6 T3 H% l5 L' c  c
import appobjects.connections.common.GlobalLCStrings;
3 N0 r3 y1 l/ r- p, ~/**8 _3 s# M# {3 f  v
* Description : Functional Test Script/ A5 J- R2 t7 h( i* V
* @author Administrator8 n# r# Y9 S# @1 T& ]/ O
*/
' z( D, M! S; L, Tpublic class IBMForumStringOnWorkPage extends IBMForumStringOnWorkPageHelper
- m& Y# }% ~8 u6 n  implements IMembers# y( t% i# ?* W  y: w1 H
{
& s8 Y8 S, O6 y/**
0 q0 S& P+ A. Y7 Y* Script Name : <b>IBM Forum 链接工作是否正常 </b>
' k: Z- R! h( k0 C9 ?1 N  O$ y* Generated : <b>Oct 24, 2011 11:09:41 PM</b>
9 M7 P7 r1 D+ Y' `8 q* Description : Functional Test Script( q# G- X# l0 j4 ?& w! ?  q
* Original Host : WinNT Version 5.1 Build 2600 (S)+ H2 Z9 p' [0 q
*
9 l; n2 y, L3 i- B" q0 ~, w* @since 2011/10/24
% d* l' t" o+ Z+ x* @author Administrator- |2 O% }+ h0 w: G# P
*/$ T- }0 u2 I2 @5 s0 P* Y+ D  h
# C) q( T7 D# Y% U5 Z7 \
/** 初始化所有函数库 */) o9 v2 J: ]9 {- v/ h/ r
LoginVenturaTasks lvt = new LoginVenturaTasks();8 d  }9 d  L) D1 o# d9 f6 F6 z
BookmarkTasks bTask = new BookmarkTasks();, z: X9 h3 g1 G; V3 ^
VCT v = new VCT();
  |% W5 @3 t+ q$ T# v7 C' E& }3 {. S% D, J  v9 F2 y- C# ?( }
/**
/ }0 h! ?& X; A* 初始化脚本,描述本脚本的所有相关信息。3 t) L! ^3 Q4 |
* Reads in global automation settings., M, [5 A* {' S7 G. q, _, p9 \
* @return
) q, ^$ a7 x5 P+ \3 ]! {" P* @author
/ [% C' X  c. k*/
/ A0 I. \, M2 gpublic void onInitialize(){
; d+ f9 @! C* ]* Q/ E* S// 自动化脚本与测试用例的对应描述
: f7 ~3 M9 z/ M$ h  mgsScriptAuthor = "Lily";
; Y1 i+ e9 i2 B6 ~5 lgsScriptDescription = "Verify the string 'Welcome to our online community.'
% Z( K4 q3 D4 A. q( K' V0 g3 f" N3 p3 L' x  shows correctly in the page";
6 N* n% p- b. z' J) {9 B" `gsScriptTestArea = "Example";
6 l9 l( @7 ~7 n" f! [1 I7 t8 m4 OgsTTTID = "Verify w3.ibm.com page works fine.";
5 R9 r+ y  s% ngsTTTDB = "lc201ttt.nsf";
: S8 G: x4 [) j$ q# \//Setup automation variables and initialize test script
% d& ]: k; W! f. D7 T" X5 Z2 wsuper.onInitialize();
% `; w8 U" f, P) O}
6 Q* ?" D3 a7 A+ o: a/ Z/ W
+ V* N4 c/ G0 z4 Q7 C/**' B' e6 B2 W7 Q, @/ i/ U
* testMain is the starting point for script execution. 7 N; ]1 n- [9 i
* testMain will execute all code and testcases contained within it's braces./ r6 J, K! E, L* O- m) C+ _
* @param Object[] args - array of objects.
5 g* `- @$ |0 M; D* @return& O$ X$ p3 C. q6 T; g
*/
: g. X! E% U" O4 I: s2 ^public void testMain(Object[] args)
2 X# H9 z* i$ a# ]5 U! F5 f& l' N{
/ e* n2 u) Y. P& s0 R' s$ x" b// 页面上的字段都统一定义在一个公共文件中,文件名为:“GlobalLCStrings”6 x2 J/ H) K* |  q9 u/ _
String sTarget = GlobalLCStrings.gsIBMForum;
1 R$ B; F, ~5 {3 z- D
$ k6 d/ S( K% f" x8 H7 g& g: p//hard code 作为参数,定义在测试用例层,确保 Task 层和 AppObject 层没有 hard code 直接写入,便于后期调试和维护。
: @0 e/ w: l! s$ |String sURL1 = "w3.ibm.com";
0 g! p1 F! n+ t9 p9 o3 h) a: ]( l3 T/ r3 r) v. l2 B6 y
try{! r  H# @4 t5 t/ J+ j, @
    startTestCase(gsTTTID);
: f$ ], N1 o5 q: {6 K/ b1 Z
4 H$ S! f4 e' J3 M    //launch the URL – sURL1 -- 脚本中最好能做到针对测试用例中的每一个测试步骤,写出注释。/ C* E9 W2 F0 {2 p
    Browserfuncs.startBrowser(sURL1);
0 L- e" M# A9 N7 g$ Q
7 s+ X) b' ^! |% V    //click on "work" tab link -- 与测试用例中一一对应的注释,写在每一个动作之前。
- ^0 d0 c8 u9 m7 D" Z; ]    //Testcase 层尽量都由 altVerifyFatal 语句组成,确认页面成功转到“work”页面。
6 t; Q; L' s; ^! v5 F, H    altVerifyFatal(true, webPageTask.clickTabLinkWork(), "click on work tab link");
: p2 x( t( ~- ]. t" C
% l2 g7 ]7 K$ O" @. X    //click on "IBM Forum" link) [* B$ o3 |1 j, L: x& T
    // 确认成功点击 IBM Forum 链接$ x0 X* n; R, q0 j
    altVerifyFatal(true, webPageTask.clickLinkIBMForum(), "click on IBM Forum link");    3 n# ^' P8 ~9 z  l! s4 ^7 Z) {
    Browserfuncs.waitForReady(giVisualReporterShortTO);    // 在 testcase 层,等待页面渲染。
5 }; Q5 k7 m' j. v$ Q0 ~* f
3 J4 i& B0 t; A* t: [$ Z) K    //checkpoint: to verify the string - sTarget displays in the current web page
6 p! O* C! H) y) [& s    altVerifyFatal(true, Webfuncs.textExists(sTarget, giVisualReporterShortTO), sTarget +
7 y# X8 ^3 U  y0 `        "shows in the web page successfully.");        // 校验点:目标字段存在于页面上。& w( D% T0 W3 p: b3 A7 G. L* W- a
    }
1 P0 h+ p4 M8 \1 R( P    catch(Exception e){" ~3 `% A9 B9 l
    Logfuncs.errorHandler(gsTTTID, e);8 m3 x( w1 C* N) V' n- p
}}}
, |' q0 i8 f  u# n
通用规范
1 F/ L3 V/ Q1 ?" Y4 v5 a
  • 脚本中需要有 Javadoc 的详尽注释。4 K  j$ p/ E3 U8 {  ?- q
自动化测试脚本的代码为了方便以后的阅读和维护,所有方法都需要有 javadoc 给出正确详尽的注释。这是降低维护成本和开发成本最直接的方法。以前面提到的比较方法为例,下面给出了一个 Javadoc 的范例:8 T) _: C/ h6 _6 c

; {( ^& {; @) @% \; J& x清单 9. Javadoc 的范例
4 @- {5 ?6 R/ D( i
/**                8 S' p% u3 q* c3 `
     * Compares 2 boolean values and displays all result info, throws exception if
: N8 r. L& m1 n) P: U: o" r     * no match <p>% Q0 G1 i* Z1 x. `4 e
     * @param bExpected        Expected Value
# Z& @- A, U: m     * @param bActual        Actual Value
6 d9 E1 o3 I, z$ n0 Z5 P, }$ ]     * @param sDesc            Description of boolean comparison& W/ k% w& G5 s4 N
     * @return true if matched false if no-match# ^+ q6 q; a% z  H
     * Intended to be called within a try/catch block of a testcase and cease execution
8 o& p$ R6 k' u% S$ a# e# U8 L     * of subsequent commands in the testcase if the exception is thrown.
7 N! H, U: [7 J. e7 ?! p9 w     * If the calling method should continue execution even if the comparison fails,
. {. A9 o$ @2 S# G: Y0 S! |; Z6 ^     * use altVerify instead of altVerifyFatal
- }: ?/ Y* Y2 S4 g     */
- ]6 v: \0 k& `    public static boolean altVerifyFatal(boolean bExpected, boolean bActual, ; P% `( x& I1 B  E7 A6 }( S
        String sDesc) throws Exception
+ `; ]. n3 }. G" a+ G    {; L# {! H, W) r; Z( x3 Y
        if (!altVerify(bExpected, bActual, sDesc)){
3 L$ ?; }/ F. W            throw new Exception("Error performing: " + sDesc);6 K* R5 U% j2 M$ m
        } else {
4 d' e7 F' X2 S: ]            return true;  y* b" X0 i/ _6 ?% V7 F
        }) N& m% S* I+ I
    }
) m- h, r5 I, J8 h5 @; h
加上了 javadoc 的注释,易读性就很强了,尤其是在 tasks 层,梳理的较好的注释语句,可以让用户在诵读代码前就能很直观的知道这一段代码的功能。例如我们常用的方法,在同一个脚本中有同一功能的多个函数,唯一不同的是函数要求的输入参数,此时 javadoc 就能非常有效的帮助脚本的开发人员快速的定位到他们想要用的函数。, b* m8 f8 K! g9 B2 y
  • 脚本中尽量避免一些 hardcode。
    / l1 _7 E8 ^1 j
自动化脚本应该尽量写得耦合度低,例如用户想要寻找界面元素的时候,有时常常会 hardcode 一些元素的属性,或者使用的键盘信息在脚本中,例如用“Shift+F5”刷新页面,用特定字符串寻找某个元素,哪怕是在脚本中自定义的一些输入内容,最好也是在脚本的开头定义成变量,以方便后期的阅读和维护。9 h; [) |' L! c8 m
  • 尽量避免用 Tab 操作定位页面元素。
    - u( `1 C4 G9 ?! k* {
在脚本开发时,通常会用特定的属性在页面上查找元素。但是,也经常会遇到元素无法找到的情况。这时,可以通过找到其父级元素或子级元素,再递归到目标元素的方法。但是,通过找到临近元素之后,再用键盘 Tab 操作定位的方法,是需要开发人员尽量避免的。因为一旦页面上 tab index 发生变化,脚本会出错,但是没有足够的错误信息表示是因为 tab index 发生过变化,所以会增加脚本维护的成本。
& O6 Q" M7 g$ u7 J/ s7 c
  • 精简脚本包含的内容。
    8 W: e( ^. J/ |9 ~; v5 G
一个测试用例最好不要包含太多内容的测试步骤。否则,如果脚本中出现问题,调试和维护都将是个很大的难题。) V( T" O3 j& x9 g# C" j
  • 内容调用和内容定义严格遵守规范。/ t( \' ]. d/ [; r+ d1 l/ C  U
每一层函数都只能调用下一层定义的内容,例如,Testcases 层只能调用 Tasks 定义的任务,不能直接调用 AppObjects 层的内容,以便于更好的将两层隔离开来,便于脚本的阅读和维护。同时也要尽可能的提高脚本的可重用性,可以归纳为一个可重用的任务的代码就放在 Tasks 层,以便于降低后期开发的工作量。
( c: X' ]: o2 u1 ~+ u% ^
  • 所有的方法都需要有非空的返回值。
    8 k" b. p0 k& j7 n' N
在 Tasks 层和 AppObjects 层,需要给每个方法返回一个非空的值。在 Tasks 层,主要是完成某个操作,因此需要通过判断操作中涉及到的一些元素的存在与否,以及抓取异常来判断是否成功,从而返回 true 或者 false 供 testcases 层的脚本调用和判断。在 AppObjects 层,每个函数必须返回一个相对应于所获取的元素的类型的变量供 tasks 层调用和验证是否获取成功。这样,不仅能够提高脚本调试的效率,并且,增强了脚本和脚本生成的日志的易读性。
" ?5 h' a1 _6 J% x
  • 使用 waitforready 来等待页面渲染。3 \- c  b! n+ c
在进行一些元素的判断和获取的时候,通常需要使用到 Browserfuncs.waitForReady(i)sleep(i)。对于如何使用这两种函数,有个经验总结,在刷新整个界面的时候,需要是用 waitforready(i),i 代表等待的时间,这样,在 i 指定的时间范围内,程序会不断监听和判断页面是否刷新完成,如果完成了,在到达 i 指定的时间之前程序就可以继续运行,减少了因为等待而浪费的时间。而当页面仅仅是用 ajax call 局部刷新的时候,waitforready(i) 函数没办法通过判断浏览器的状态来判断,此时就只能用 sleep(i) 来判断休眠的时间然后判断元素的存在与否。- a' B5 y1 E; q8 D. n; g& T9 q, Y
  • 程序的异常需要及时获取。# t) Y  q- t+ J$ ~* ~6 T
程序的运行时产生的异常必须要有 try/catch 来获取,以确保所有的错误在 log 里都会正确的现实,如果没有使用 try/catch,脚本会自动停止运行异常后面的内容并且在 log 里不显示任何错误,影响对于脚本的调试,提高了调试的成本。
1 K% F- z+ b& }3 p
  • 在大批量运行脚本的时候,建议多次循环运行。& f' z! }: [9 L: \& d
在后期的回归测试时,往往会定义一个 Suite 脚本连续运行所有的自动化脚本,在跑完所有的脚本后由测试人员查看结果。最好的结果是所有的脚本都 100% 的成功了,但是,常常事与愿违,有的时候,是因为人为的对应用程序的服务器端进行了修改,导致应用程序状态异常,有的时候是因为网络问题,导致自动化脚本无法正确判断应用程序的状态,总之,是各种客观的原因导致的失败,因此就需要再重新跑一次,然后希望这次的运行能够都 pass。为了尽量减少客观因素的影响,我们提高了同一个脚本跑的次数,在跑完一次之后,RFT 自动的将所有失败了得脚本收集起来,重新再运行一次,在连续运行两到三次之后,产生的失败文件的列表,就会大幅度降低客观因素的影响,提高结果的可靠性。1 W1 x+ l; u* ?4 L
  • 使用 VNC 来远程运行 RFT。
    2 n4 k. F1 L1 L
运行脚本时,使用 VNC 监控,能够防止黑屏导致的脚本中断异常。长期运行 RFT 的人会知道,机器的屏保设置必须关闭,以避免在运行一段时间 RFT 之后屏幕自动锁住而影响了 RFT 的继续运行,而且,如果使用 Windows 的远程桌面运行,也会因为远程桌面被关闭而影响 RFT 继续运行。这边介绍一个方法,可以使用 VNC 的帮助,以 TightVNC 为例,在运行脚本的机器上,安装 TightVNC Service. 特别需要注意的是,不要远程安装,否则会到时安装完成之后,客户端这边的连接失败。成功安装之后,启动 TightVNC Service。在客户端,也需要安装 TightVNC 的客户端,通过 vncviewer 去远程连接运行 RFT 的机器,并启动脚本。由于 VNC 在关闭之后依然能再后台保持桌面的继续运行,因此不会因为客户端的关闭而影响 RFT 的继续运行。7 `7 J/ K# n' A0 A! ]


( l* M. B; o+ P" P# b: A5 {
+ Q! z6 \, P* `& B# \# u4 I结束语
* s1 \8 E  S& V1 }5 W1 h本文总结了一系列 Rational Functional Tester 脚本开发过程中的规则和技巧。一旦 Rational Functional Tester 的使用者将它们融会贯通在开发过程之中,将会很大程度上增强脚本质量,并将在脚本的更新和维护时达到事半功倍的效果。- Z( H/ e. {2 q' L* E: S# @

本帖子中包含更多资源

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

x
 楼主| 发表于 2011-12-28 13:29:38 | 显示全部楼层
回复 支持 反对

使用道具 举报

发表于 2013-7-5 14:01:47 | 显示全部楼层
学习了!!!!
回复 支持 反对

使用道具 举报

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

本版积分规则

关闭

SCMLife推荐上一条 /4 下一条

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

GMT+8, 2018-7-23 02:03 , Processed in 0.092623 second(s), 6 queries , Gzip On, MemCache On.

Powered by SCMLife X3.4 Licensed

© 2001-2017 JoyShare.

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