在进行web前端自动化测试的过程中,Page Object设计模式可以称得上是杀人放火,居家旅行的常备武器。
Page Object将测试对象及单个的测试步骤封装在每个Page对象中,以page为单位进行管理。举例来说,在没有使用Page Object模式的情况下,脚本可能是这样写的:
require 'rubygems' require 'selenium-webdriver' url = 'www.soso.com' dr = Selenium::WebDriver.for :ie dr.navigate.to url ua_links = dr.find_element(:id => 'ua').find_elements(:css => 'a') ua_links[3].click ua_links[3].send_keys(:enter) sleep 2 dr.switch_to.frame('login_frame') user = {:name => 'test', :psd => 'test'} dr.find_element(:id => 'u').send_keys(user[:name]) dr.find_element(:id => 'p').send_keys(user[:psd]) dr.find_element(:id => 'login_button').click sleep 2 dr.close
这段脚本的作用是去到soso,然后点击【登陆】link,待登陆的dialog弹出后输入用户名和密码,点击【登陆】按钮进行登陆。
看上去这段脚本是不错的,因为其很好的完成了登陆的任务,但不妨深入思考一下,如果我们需要验证错误的用户名正确的密码的话,那么上面关于登陆的那几行脚本是不是需要重复写一遍?
答案是否定的,因为登陆的功能可以抽象成函数,如下所示:
def login usr, psd dr.find_element(:id => 'u').send_keys(usr) dr.find_element(:id => 'p').send_keys(psd) dr.find_element(:id => 'login_button').click end
这样就可以通过构造不同的数据,每次测试时只需要调用login函数就可以了。
再深入想一下,假设需要测试不输入用户名和密码直接点击登陆按钮的情况,那该怎么办呢?
首先最容易想到的一点是改造login函数,当然usr和psd没有传入的时候就直接点点击登陆按钮,这是没有问题的。不过如果本着代码增强可读性原则,亦可以定义一个名为login_without_usr_psd的函数,如下所示
def login_without_usr_psd dr.find_element(:id => 'login_button').click end
这样在编写测试用例代码的时候就可以直接凭借函数名来揣测出该函数的作用,起到了self explain的作用。在自动化测试代码中,这一点是被鼓励的。
不过这样多定义1个函数就会带来另外的问题 dr.findelement(:id => ‘loginbutton’).click这行代码就在loin函数和login_without_usr_psd中重复使用。
为了解决这个有些丑陋的问题,使得代码能够稍微美化一点,可以定义另外一个函数来实现点击登陆按钮的功能。
def click_login_btn dr.find_element(:id => 'login_button').click end # now login_without_usr_psd will like below def login_without_usr_psd click_login_btn end # login function will like this def login usr, psd dr.find_element(:id => 'u').send_keys(usr) dr.find_element(:id => 'p').send_keys(psd) click_login_btn end
好了,那么本着使代码更加灵活,self explain 特性更加明显的原则,我们又可以将输入用户名,输入用户密码功能抽象成函数,这时候login函数看起来可能会是这样的:
def login usr, psd set_usr usr set_psd psd click_login_btn end
如果我们重复上面的步骤,一步一步的提升代码的复用性和可读性的话,我们就会发现除了上面演示的setusr,setpsd这样的【基本动作】外,有些测试对象也是可以复用的。比如在登陆的时候我们会用到密码输入框,也许在修改密码的时候我们也会用到这一对象。
很自然的就会想到,如果将一些测试对象以及操作这些测试对象的动作或步骤封装在1个类中,那么代码的灵活性和适用性将会更强。那么按照什么纬度来划分这些类呢?也是很自然的,就像睡醒了就会睁眼,挖完煤就要洗脸一样,我们会发现按照页面也就是page来组织这些类将是很好的解决方案。
这就是Page Object设计模式,将每一个测试页面抽象为1个Page类,并在该类中封装了本页面的测试对象和基本的测试步骤,以提高代码的可读性复用性通用性和一致性。Page Object设计模式带来的好处是显而易见的。比如使用了page object模式后,测试用例可能会如下面所示
soso_page = Site.new(dr).soso_main_page(url).open soso_page.login wrong_usr, wrong_psd soso_page.error_msg.should be_eql('error')
测试用例简单且易读,而且代码的复用性极佳。其他用例需要使用到login功能时候只需要new 包含该功能的Page对象,调用login方法既可。
下面的代码演示了如何使用Page Object设计模式重构本文开头所实现的soso主页用户登陆功能。
base_page.rb class BrowserContainer def initialize driver @dr = driver end end # BrowserContainer class Site < BrowserContainer def soso_main_page url @soso_main_page = SosoMainPage.new(@dr, url) end def close @dr.close end end #Site class BasePage < BrowserContainer attr_reader :url def initialize dr, url super(dr) @url = url end def open @dr.navigate.to @url self end end #BasePage class SosoMainPage < BasePage require './login_dialog' include LoginDialog def login usr, psd open_login_dialog to_dialog_frame usr_field.send_keys usr psd_field.send_keys psd login_btn.click end def open_login_dialog login_link.click login_link.send_keys(:enter) sleep 2 end private def ua_links @dr.find_element(:id => 'ua').find_elements(:css => 'a') end def login_link ua_links[3] end end #SosoMainPage login_dialog.rb module LoginDialog def to_dialog_frame begin @dr.switch_to.frame('login_frame') rescue raise 'Can not switch to login dialog, make sure the dialog was open' exit end end def usr_field @dr.find_element(:id => 'u') end def psd_field @dr.find_element(:id => 'p') end def login_btn @dr.find_element(:id => 'login_button') end end #LoginDialog login.rb require 'rubygems' require 'selenium-webdriver' require './base_page' dr = Selenium::WebDriver.for :firefox url = 'http://www.soso.com' soso_page = Site.new(dr).soso_main_page(url).open soso_page.login 'test', 'test'
上面的代码由3个文件组成。
base_page.rb文件中定义了
- Site类 主要用于管理测试中所需要用到的各种页面,提供生成这些页面对象的快捷方法。比如Site.new(dr).soso_main_page(url)方法就实例化了1个SosoMainPage对象。
- BasePage类 所有Page对象的基类
- SosoMainPage类 代表了soso主页的Page Object类,封装了首页的一些测试对象,原子操作及基本步骤,如login
login_dialog.rb文件中定义了代码登陆弹出框的LoginDialog。由于login dialog可能会出现在多个页面,比如qq音乐的登陆页面也有该弹出框,所以将其抽象成module,需要用到的页面直接include该module既可。
login.rb文件调用page object并实现了具体的测试逻辑,这个文件中可以使用你熟悉的测试框架来组织用例,如unit test和rspec等
关于Page Object设计模式的介绍就要告一段落了,从上面的代码中我们可以感觉到,Page Object模式加上测试用例框架就基本上可以等于简单的自动化测试框架了。所以在构建自动化测试框架的过程中,活用Page Object将为我们带来一系列的实惠和惊喜,就像超级的代金券一样,看上去不起眼但用起来却其乐无穷,浑身舒爽。