实例解析AUTOMATION TEST FRAMEWORK (2)

上一篇 / 下一篇  2013-03-25 10:29:57 / 个人分类:测试

WATIR, WEB DRIVER,CUCUMBER测试框架分析和建立产品框架的过程

Watir是一个Ruby库,可以利用多版本浏览器进行Web应用程序测试

Web Driver是一个由Google团队创建的简洁快速的框架,用于Web应用程序的自动化测试

而Watir Web Driver就是将二者合二为一形成一个新的测试框架。

Cucumber是一个能够理解用普通语言描述的测试用例的支持行为驱动开发(BDD)的自动化测试工具。

我们可以通过Cucumber + Watir Web Driver 实现一个功能更加强大的BDD测试框架。

Cucumber框架的目的很简单:

由自然语言书写的Feature文件>>通过Define Step进行匹配>>调用相匹配的Watir Web Driver实现的自动化测试脚本。

所以,我们的主要工作就在于如何使用Watir Web Driver进行自动化脚本的实现。

首先要知道Web Driver是一个独立的framework,并没有依赖于任何现有的framework,所以在跟其他framework集成时,不需要做过多的屏蔽工作。

Web Driver的目的,就是作为一个简单有效的工具,来进行web application的自动化测试。它提供了能够直接被其他框架使用的API。

所以,Web Driver的作用就是简化了对Browser操作的封装。它已经包含了足够使用的方法,使得你在创建自己的框架时,可以无需再提供额外的Browser操作的封装模块。

Watir Web Driver,等于是将Watir和Web Driver融合,使用Watir来进行页面元素,操作,数据控制等模块,使用Web Driver来达到在Browser上对这些元素进行控制的目的。

而我们在项目实际应用中,需要根据具体情况再次封装我们的产品,从而实现特定产品的test framework。


对于web driver,我们无需理会,它等于封装了底层的对browser的操作。我们需要了解的是Watir 里,如何对页面元素进行控制。

一般来说,对于一个web页面进行自动化测试的框架,无非需要实现以下几个功能模块:

页面元素的查找
页面元素的操作
页面元素的判断
页面元素的获取
页面元素的断言
错误处理
数据驱动
日志管理
执行管理


我们可以看出,这个framework的核心就是围绕着element进行的。

对于Watir Web Driver Framework来说,element的所有控制都是基于该元素的tag进行分类的,这就保证了它对任何语言和任何浏览器能够进行操控,只要该产品的页面是 遵循HTML标准的。

我们来看看,它能支持的element对象有哪些:

Area - <area>
Button - <input type=”button”>, <input type=“image”>, <input type=”reset”>, <input type=”submit”>
Cell - <td>
Checkbox - <input type=”checkbox”>
Div - <div>
File_field - <input type=”file”>
Form. - <form>
Frame. - <frame>, <iframe>
H1, H2, H3, H4, H5, H6 - <h1>, <h2>, <h3>, <h4>, <h5>, <h6>
Hidden - <input type=”hidden”>
Image - <img>
Label - <label>
Li - <li>
Link - <a>
Map - <map>
P - <p>

Pre - <pre>
Radio - <input type=”radio”>
Row - <tr>
Select_list - <select>
Span - <Span>
Table - <table>
Text_field - <input type=”password”>, <input type =”text”>, <textarea>
Ul - <ul>

从上面的列表,我们看到,基本上HTML元素都被涵盖在内了。这样,我们在封装我们自己的方法时,可以使用完全OO的思想,来封装我们要进行的操作。

作为一种纯粹OO的语言,ruby和纯粹的OO的自动化框架Watir,它提供的element的通用方法有如下:

==是否相等
attribute_value返回指定属性的值
add_checker添加checker
browser返回所在browser对象
click左键单击
clear清除选择
close关闭
double_click左键双击
drag_and_drop_by拖拽到指定区域
drag_and_drop_on拖拽到指定元素上
driver返回所用的driver
enabled?是否可以点击
exists?是否存在
fire_event执行指定的事件
flash高亮该对象
focus焦点移至该对象上
focused?焦点是否在对象上
hash返回该对象的hash值
hover鼠标悬浮在该对象上
html返回该对象的html内容
inspect返回该对象的inspect值
parent返回该对象的父节点
present?是否存在并可见
right_click右键点击一次
run_checkers返回正在运行的对象列表
style返回对象的style值
set设置值
send_keys发送值参
tag_name返回对象的tag值
text返回对象的text值 
to_subtype返回对象的子类型
type返回对象的类型
value返回对象的value值
visible?是否可见
wd返回该对象的driver值
wait_until_present对象出现前等待
waith_while_present对象消失前等待
when_present对象出现前等待

这些方法是基本所有element都通用的方法。之所以这些方法没有封装到一个方法类总库,就是因为watir要实现的纯粹OO的框架,所以,所有的操作都是基于实例化的element上,而不是element再继承方法抽象类来实现自己的方法。

这点和Java的理念有所不同。Java 中, 对象是对象,方法是对象的属相,而在基于ruby的Watir中,对象是对象,方法也是对象,所有的一切都是封装好的对象。


当然,每个element都还有一些私有的方法,满足各自的不同需求。我们也可以根据这些element,来组合一个新的element来重载或者继承多种方法,来满足一些特殊情况的需要。

比如,我们完全可以把Login action 封装成一个login对象。该Login对象是通过user name 和password 这两个子元素,以及login button元素组合形成的业务模块。

对于user name和password都分别 属于一个text_field 元素,而 login button属于一个button元素。我们把它们封装成为一个login元素。

当我们查找这个元素时,需要同时匹配user name,password和login button三个属性值,然后调用内置的方法set和click 形成一个新的方法login action。

通过上面的例子,我们对基于业务模块或者商业逻辑如何封装产品有了个初步的了解。

下面我们要了解最终的核心部分,那就是如何获取页面的元素。

获取页面元素,其实就是如何能够定位该页面元素,而定位的方法,就是根据该元素的一些属性信息进行查找。这些属性信息有哪些呢,让我们来一起看一下。


因为watir是使用标准的html tag来作为对象来进行识别的,所以这些元素的属性信息,基本上和html中每个tag元素具有的属性相同,这种方式就大大减少了我们学习新封装方法的工作量。

我们来看看,每个tag都有哪些属性可以用来进行定位:

Area - <area> - :class, :id, :index, :name, :text
Button – <button> - :alt, :class, :id, :index, :name, :text, :value
- <input type=”button”> - :alt, :class, :id, :index, :name, :text, :value
- <input type=“image”>  - :alt, :class, :id, :index, :name, :src, :text, :value
- <input type=”reset”> - :alt, :class, :id, :index, :name, :text, :value
- <input type=”submit”> - :alt, :class, :id, :index, :name, :text, :value
Cell - <td> - :class, :id, :index, :name, :text
Checkbox - <input type=”checkbox”> - :class, :id, :index, :name, :text, :value
Div - <div> - :class, :id, :index, :name, :text
File_field - <input type=”file”> - :class, :id, :index, :name, :title, :value
Form. - <form> - :action, :class, :id, :index, :method, :name
Frame. - <frame>, <iframe> - :id, :index, :name, :src, :text
H1, H2, H3, H4, H5, H6 -    <h1> - :class, :id, :index, :name, :text
-<h2> - :class, :id, :index, :name, :text
-<h3> - :class, :id, :index, :name, :text
-<h4> - :class, :id, :index, :name, :text
-<h5> - :class, :id, :index, :name, :text
-<h6> - :class, :id, :index, :name, :text
Hidden - <input type=”hidden”> -:class, :id, :index, :method, :name, :text, :value
Image - <img> - :alt, :class, :for, :id, :index, :name, :src, :text, :value
Label - <label> - :class, :id, :index, :name, :text
Li - <li> - :class, :id, :index, :name, :text
Link - <a> - :after?, :class, :href, :id, :index, :name, :text
Map - <map> - :class, :id, :index, :name, :text, :value
P - <p> - :class, :id, :index, :name, :text
Pre - <pre> - :class, :id, :index, :name, :text
Radio - <input type=”radio”> - :class, :id, :index, :name, :text, :value
Row - <tr> - :class, :id, :index, :name, :text
Select_list - <select> -:class, :id, :index, :name, :text, :value
Span - <Span> - :class, :id, :index, :name, :text
Table - <table> - :class, :id, :index, :name, :text
Text_field - <input type=”password”> - :class, :id, :index, :name, :text, :value
                                 - <input type =”text”> - :class, :id, :index, :name, :text, :value
                                 - <textarea> - :class, :id, :index, :name, :text, :value
Ul - <ul> - :class, :id, :index, :name, :text


我们用一些具体的例子来更好的理解这些。

对于一个具有class,id,index,name,和text属性的area,我们可以按如下方法查找:

Browser.area(:class,  “area class name”)
Browser.area(:id,  “area id”)
Browser.area(:index, 0)
Browser.area(:name, “area name”)
Browser.area(:text, “area text”)

我们也可以任意选取其中几个属性来更精确的定位:

Browser.area(:class=>“area class name”, :id=>“area id”, :index, 0)

另外,我们为了更好的,或者说更无奈的方法,是采用Xpath定位。Xpath定位很强大,但是经常会出现多个match对象时返回并非需要的对象,并且造成代码过于复杂的识别,影响执行速度,所以Xpath定位要少用。

Browser.area(:xpath, “//area1”)


Waitr直接采用了ruby本身的File来处理Data-driven的行为。

我们来看一个操作Excel文档的实例:

require 'win32ole'
#支持excel文件操作

require 'watir-webdriver'
#watir web driver支持

#打开数据文件
excel = WIN32OLE::new('excel.Application')
workbook = excel.Workbooks.Open('d:\rubycode\test.xlsx')

#激活指定的tab
worksheet = workbook.Worksheets('test')
worksheet.Activate

#读入用户名和密码信息
@username = worksheet.Range('a1').Value
@password = worksheet.Range('b1').Value
#关闭文件
excel.Quit

#使用Watir webdriver进行自动化测试
#设置Base URL
$BASE_URL=http://10.32.148.243:8080

#生成webdriver的实例
$browser=Watir::Browser.new:ie

#转向指定的URL
$browser.goto($BASE_URL+'/parkinglot/login')

#判断登录界面出现
Watir::Wait.until {$browser.text.include?('停车场管理系统登录')} 

#写入从数据文件中得出的测试数据
$browser.text_field(:name, "username").set(@username)
$browser.text_field(:name, "password").set('1234')

#执行登录
$browser.div(:class, "login_btn").click

#确认正确登录到主页
Watir::Wait.until {$browser.text.include?('Copyright')}


从上例可知,watir webdriver 只是提供了如何获得element的方法。文件操作的方法,都是直接调用了已有的Ruby on Rails框架中的方法。 而对浏览器的操作,也是调用的Web Driver提供的方法。 

这在我们做测试框架中很常见,我们可以再次体会到,框架就是用来被引用,或者被其他框架所包含,从而提供更丰富的API接口这种理念。

现在我们已经理清了这套框架的基本架构:

Ruby on Rails框架,提供给了Watir Web Driver框架的底层应用,例如文件的操作
Web Driver框架,提供给了Watir Web Driver框架对Browse进行操作的方法
Watir Web Driver框架本身提供了对页面元素进行识别,操作的方法
Cucumber框架则提供了一个可以用自然语言去调用Watir Web Driver框架的方法
好了,明白了这一切,我们就可以动手做自己的Automation Test Framework了。当然,一般来说,选用这些现成的框架即可。

我们真正需要做的,就是如何封装自己的产品。封装的方式无外乎这几种:

基于Page的封装
基于流程的封装
基于业务逻辑的封装
基于功能模块的封装
基于行为的封装
基于数据模型的封装

一般来说,怎么封装都可以,每一种封装方式都有自己的优缺点,所以在封装不同的东西选择不同的封装方式很常见。比如上面提到的例子,login的封装,你可以理解为基于Page的封装,也可以看作是行为的封装,甚至是流程的封装等等,你的着眼点不同,它的意义也就不同。

一般来说,封装的目的就是形成三层结构。现有的框架我们可以认为是底部驱动层。而我们依据产品的封装可以看成是逻辑层,而我们测试用例就是所谓的应用层。

而Cucumber + Watir Web Driver也可以进行任意的封装,但是最好的封装方式,是基于对象的方式。为什么这么说呢,我们通过实际的例子来看一下。

Cucumber执行自动化测试用例,是通过feature文件来实现的,我们拿一个feature文件来看看。

# language: zh-CN
功能:区域管理
  作为一个管理人员
  我能够进行触摸屏管理

  场景: Case 1 - 添加触摸屏-确定(S2-ST-JBXXGL-CMPGL-0001)
    假如进入触摸屏管理界面
    而且按下新增按键
    而且在触摸屏名称里输入ATT
    而且从所属楼层列表中选择地下一层
    而且在触摸屏IP地址里输入188.0.0.1
    而且在触摸屏X坐标里输入88
    而且在触摸屏Y坐标里输入99
    而且按下触摸屏确定按键
    当判断预期结果时
    那么可以看到ATT
  场景: Case 2 - 添加触摸屏-取消(S2-ST-JBXXGL-CMPGL-0002)
    假如进入触摸屏管理界面
    而且按下新增按键
    而且在触摸屏名称里输入Touch
    而且从所属楼层列表中选择地下一层
    而且在触摸屏IP地址里输入188.0.0.1
    而且在触摸屏X坐标里输入88
    而且在触摸屏Y坐标里输入99
    而且按下触摸屏取消按键
    当判断预期结果时
    那么看不到Touch

  场景: Case 3 - 修改触摸屏-取消(S2-ST-JBXXGL-CMPGL-0003)
    假如进入触摸屏管理界面
    而且触摸屏选择值为ATT
    而且按下修改按键
    而且在触摸屏名称里输入A
    而且按下触摸屏取消按键
    当判断预期结果时
    那么看不到ATTA

  场景: Case 4 - 修改触摸屏-确定(S2-ST-JBXXGL-CMPGL-0004)
    假如进入触摸屏管理界面
    而且触摸屏选择值为ATT
    而且按下修改按键
    而且在触摸屏名称里输入A
    而且按下触摸屏修改按键
    当判断预期结果时
    那么可以看到ATTA

  场景: Case 5 - 删除触摸屏-取消(S2-ST-JBXXGL-CMPGL-0005)
    假如进入触摸屏管理界面
    而且触摸屏选择值为ATTA
    而且按下删除按键
    而且按下确定取消按键
    当判断预期结果时
    那么可以看到ATTA





  场景: Case 6 - 删除触摸屏-确定(S2-ST-JBXXGL-CMPGL-0006)
    假如进入触摸屏管理界面
    而且触摸屏选择值为ATTA
    而且按下删除按键
    而且按下确定删除按键
    当判断预期结果时
    那么看不到ATTA


我们可以看到,对于user story:

功能:区域管理
  作为一个管理人员
  我能够进行触摸屏管理

我们一共有6个场景来保证该story已经能够被完成,至于有bug在内,那不是判断story是否可以被认定为完成的依据,那是交付的依据。


我们随便取其中的一个场景来分析:

  场景: Case 5 - 删除触摸屏-取消(S2-ST-JBXXGL-CMPGL-0005)
    假如进入触摸屏管理界面
    而且触摸屏选择值为ATTA
    而且按下删除按键
    而且按下确定取消按键
    当判断预期结果时
    那么可以看到ATTA

第一行,我们可以认为是一个navigation的过程,那么所有的navigation操作,可以作为一个通用的流程进行封装。那么封装的方式就可以是:

Given /进入(.*)界面/ do |module_name|
  $autotest.navigate_to_module(module_name)
end  

从上面可以看出,它是根据要转向的module name,或者是页面名称来进行转向的,是一个纯粹的业务流程的封装。

但是,我们也可以基于Page去封装,那么又如何去书写语句呢?


我们可以把每个Page都当作一个对象,那么我们就把“触摸屏管理界面”封装成一个名叫touchscreen的page对象,那么上面的语句就可以写成:

Given /进入*界面/ do |module_name|
if module_name ==”  触摸屏管理” then
$autotest.touchscreen.visit();
end
end  

我们可以看到,这么封装的方式好处是page上所有的操作都可以封装到一个page对象中,当该页面发生跟新时,我们只需要更新该page对象即可,这在以前是很流行的封装方式。

但是如果考虑到这种情况呢:

页面里面有个element,比如search button,是被很多页面共同使用的。在基于page的封装方法中,你不得不在每一个包含该search button的页面中进行封装。一旦该元素进行了修改,那么意味着你需要更新所有的page对象。 在这个过程中,很容易犯错。

所以cucumber推崇的是基于最基本的页面元素的封装。例如button的封装:

#按下xx按键
Given /按下(.*)按键/ do |button_name|
  $autotest.click_button(button_name)   
end

它是封装了button的click事件。这样所有的button的click都可以调用这一个统一的方法,我们也可以完全基于某个element做属性封装,例如:

#按下xx按键
Given /(..)(.*)按键/ do |button_action, button_name|
  $autotest.button(button_name, button_action)   
end

而在button中封装了一个方法就是Button.action(button_action), 这里key就是button_action=click。从而触发这个click事件。

所以说,那种封装方式不重要,重要的是怎么能减少你后期维护的时间和精力,以及如何更大限度的复用代码,这两者必须达到平衡,不能偏向任何一边。


TAG:

 

评分:0

我来说两句

日历

« 2024-04-16  
 123456
78910111213
14151617181920
21222324252627
282930    

数据统计

  • 访问量: 150204
  • 日志数: 185
  • 文件数: 6
  • 建立时间: 2007-08-06
  • 更新时间: 2015-01-06

RSS订阅

Open Toolbar