- 浏览: 248730 次
- 性别:
- 来自: 内蒙古
文章分类
- 全部博客 (237)
- Android 功能实现 (31)
- sql数据库的学习 (8)
- Android 美化界面 (2)
- Android 优化 (1)
- Ruby on Rails 方面 (45)
- git 方面的学习 (1)
- ruby 编程的琢磨 (13)
- linux下工具软件 (13)
- 操作系统的学习 (40)
- 非技术 (13)
- 网站开发 (18)
- js 学习笔记 (19)
- css学习 (5)
- 回顾总结 (2)
- Delphi 学习 (2)
- C 语言学习笔记 (1)
- 数据结构 (1)
- node js 学习 (6)
- 设计模式 (2)
- mongdb 学习笔记 (0)
- 软件服务 (3)
- osx系统 (4)
- 搜索引擎 (1)
- 测试工具 (1)
- Aliyun (1)
- 前端JS (1)
- python学习 (0)
- iOS系统 (1)
- 分布式锁 (1)
- 开发工具 (0)
- java代码 (2)
- java (1)
最新评论
-
jiguanghover:
写的不错,收藏一下
Ubuntu下RVM, Ruby, rails安装详细 和 卸载 -
maoghj:
回顾总结(二) -
yun2223:
对楼主表示感谢
Android控件开发之Gallery3D效果 -
zw_lovec:
说清楚点吧 亲 加点注释
out of memory -
lzyfn123:
http://www.iteye.com/images/smi ...
ruby-string 字符串的学习
应用Selenium进行Web测试时,经常会遇到下面的几个麻烦问题:
- 大量使用name、id、xpath等页面元素。无论是功能修改、UI重构还是交互性改进都会影响到这些元素,这使得Selenium测试变得非常脆弱。
- 过于细节的页面操作不容易体现出行为的意图,一段时间之后就很难真正把握测试原有的目的了,这使得Selenium测试变得难于维护。
- 对具体数据取值的存在依赖,当个别数据不再合法的时候,测试就会失败,但这样的失败并不能标识功能的缺失,这使得Selenium测试变得脆弱且难以维护。
而这几点直接衍生的结果就是不断地添加新的测试,而极少地去重构、利用原有测试。其实这倒也是正常,单元测试测试写多了,也有会有这样的问题。不过比较要命的是,Selenium的执行速度比较慢(相对单元测试),随着测试逐渐的增多,运行时间会逐渐增加到不可忍受的程度。一组意图不明而且难以维护的Selenium测试,可以很轻松地在每次构建(Build)的时候杀掉40分钟甚至2个小时的时间,我就有曾有花2个小时坐在电脑前面等待450个Selenium测试运行通过的悲惨经历。因此合理有效地规划Selenium测试就显得格外的迫切和重要了。而目前比较行之有效的办法,往大了说,可以叫基于领域的Web测试(Domain Based Web Testing),具体来讲,就是Page Object Pattern。
Page Object Pattern里有四个基本概念:Driver、Page、Navigator和Shortcut等。Driver是测试真正的实现机制,比如Selenium,比如Watir,比如HttpUnit。它们懂得如何去真正执行一个Web行为,通常包含像Click、Select、Type等这样的表示具体行为的方法;Page是对一个具体页面的封装,它们了解页面的结构,知道诸如id、name、class和xpath这类实现细节,并描述用户可以在其上进行何种操作;Navigator则代表了URL,表示一些不经页面操作的直接跳转;最后Shortcut就是helper方法了,需要看具体的需要而定。下面来看一个超级简单的例子——测试登录页面。
1. Page Object
假设我们使用一个单独的登录页面进行登录,那么可能会将登录的操作封装在一个名为LoginPage的page object里:
class LoginPage def initialize driver @driver = driver end def login_as user @driver.type 'id=...', user[:name] @driver.type 'xpath=...', user[:password] @driver.click 'name=...' @driver.wait_for_page_to_load end end
login_as是一个具有业务含义的页面行为。在login_as方法中,page object负责通过依靠id、xpath、name等信息完成登录操作。在测试中,我们可以这样来使用这个page object:
page = LoginPage.new $selenium page.login_as :name => 'xxx', :password => 'xxx'
不过既然用了Ruby,总要用一些ruby sugar吧,我们定义一个on方法来表达页面操作的环境:
def on page_type, &block page = page_type.new $selenium page.instance_eval &block if block_given? end
之后我们就可以使用page object的类名常量和block描述在某个特定页面上操作了:
on LoginPage do login_as :name => 'xxx', :password => 'xxx' end
除了行为方法之外,我们还需要在page object上定义一些获取页面信息的方法,比如获取登录页面的欢迎词的方法:
def welcome_message @driver.get_text 'xpath=...' end
这样测试也可表达得更生动一些:
on LoginPage do assert_equal 'Welcome!', welcome_message login_as :name => 'xxx', :password => 'xxx' end
当你把所有的页面都用Page Object封装了之后,就有效地分离了测试和页面结构的耦合。在测试中,只需使用诸如login_as和add_product_to_cart这样的业务行为,而不必依靠像id、name等这些具体且易变的页面元素了。当这些页面元素发生变化时,只需修改相应的page object就可以了,而原有测试基本不需要太大或太多的改动。
2. Assertation
只有行为还构不成测试,我们还要判断行为结果,并进行一些断言。简单回顾一下上面的例子,会发现还有一些很重要的问题没有解决:我怎么判断登录成功了呢?我如何才能知道真的是处在登录页面了呢?如果我调用下面的代码会怎样呢?
$selenium.open url_of_any_page_but_not_login on LoginPage {...}
因此我们还需要向page object增加一些断言性方法。至少,每个页面都应该有一个方法用于判断是否真正地达到了这个页面,如果不处在这个页面中的话,就不能进行任何的业务行为。下面修改LoginPage使之包含这样一个方法:
LoginPage.class_eval do include Test::Unit::Asseration def visible? @driver.is_text_present(...) && @driver.get_location == ... end end
在visible?方法中,我们通过对一些特定的页面元素(比如URL地址,特定的UI结构或元素)进行判断,从而可以得之是否真正地处在某个页面上。而我们目前表达测试的基本结构是由on方法来完成,我们也就顺理成章地在on方法中增加一个断言,来判断是否真的处在某个页面上,如果不处在这个页面则不进行任何的业务操作:
def on page_type, &block page = page_type.new $selenium assert page.visible?, "not on #{page_type}" page.instance_eval &block if block_given? page end
这个方法神秘地返回了page对象,这里是一个比较取巧的技巧。实际上,我们只想利用page != nil这个事实来断言页面的流转,比如,下面的代码描述登录成功的页面流转过程:
on LoginPage do assert_equal 'Welcome!', welcome_message login_as :name => 'xxx', :password => 'xxx' end assert on WelcomeRegisteredUserPage
除了这个基本断言之外,我们还可以定义一些业务相关的断言,比如在购物车页面里,我们可以定义一个判断购物车是否为空的断言:
def cart_empty? @driver.get_text('xpath=...') == 'Shopping Cart(0)' end
需要注意的是,虽然我们在page object里引入了Test::Unit::Asseration模块,但是并没有在断言方法里使用任何assert*方法。这是因为,概念上来讲page object并不是测试。使之包含一些真正的断言,一则概念混乱,二则容易使page object变成针对某些场景的test helper,不利于以后测试的维护,因此我们往往倾向于将断言方法实现为一个普通的返回值为boolean的方法。
3. Test Data
测试意图的体现不仅仅是在行为的描述上,同样还有测试数据,比如如下两段代码:
on LoginPage do login_as :name => 'userA', :password => 'password' end assert on WelcomeRegisteredUserPage registered_user = {:name => 'userA', :password => 'password'} on LoginPage do login_as registered_user end assert on WelcomeRegisteredUserPage
测试的是同一个东西,但是显然第二个测试更好的体现了测试意图:使用一个已注册的用户登录,应该进入欢迎页面。我们看这个测试的时候,往往不会关心用户名啊密码啊具体是什么,我们关心它们表达了怎样的测试案例。我们可以通过DataFixture来实现这一点:
module DataFixture USER_A = {:name => 'userA', :password => 'password'} USER_B = {:name => 'userB', :password => 'password'} def get_user identifier case identifier when :registered then return USER_A when :not_registered then return USER_B end end end
在这里,我们将测试案例和具体数据做了一个对应:userA是注册过的用户,而userB是没注册的用户。当有一天,我们需要将登录用户名改为邮箱的时候,只需要修改DataFixture模块就可以了,而不必修改相应的测试:
include DataFixtureDat user = get_user :registered on LoginPage do login_as user end assert on WelcomeRegisteredUserPage
当然,在更复杂的测试中,DataFixture同样可以使用真实的数据库或是Rails Fixture来完成这样的对应,但是总体的目的就是使测试和测试数据有效性的耦合分离:
def get_user identifier case identifier when :registered then return User.find '....' end end
4.Navigator
与界面元素类似,URL也是一类易变且难以表达意图的元素,因此我们可以使用Navigator使之与测试解耦。具体做法和Test Data相似,这里就不赘述了,下面是一个例子:
navigate_to detail_page_for @product on ProductDetailPage do .... end
5. Shortcut
前面我们已经有了一个很好的基础,将Selenium测试与各种脆弱且意图不明的元素分离开了,那么最后shortcut不过是在蛋糕上面最漂亮的奶油罢了——定义具有漂亮语法的helper:
def should_login_successfully user on LoginPage do assert_equal 'Welcome!', welcome_message login_as user end assert on WelcomeRegisteredUserPage end
然后是另外一个magic方法:
def given identifer words = identifier.to_s.split '_' eval "get_#{words.last} :#{words[0..-2].join '_'}" end
之前的测试就可以被改写为:
def test_should_xxxx should_login_successfully given :registered_user end
这是一种结论性的shortcut描述,我们还可以有更behaviour的写法:
def login_on page_type on page_type do assert_equal 'Welcome!', welcome_message login_as @user end end def login_successfully on WelcomeRegisteredUserPage end def given identifer words = identifier.to_s.split '_' eval "@#{words.last} = get_#{words.last} :#{words[0..-2].join '_'}" end
最后,测试就会变成类似验收条件的样子:
def test_should_xxx given :registered_user login_on LoginPage assert login_successfully end
总之shortcut是一个无关好坏,只关乎想象力的东西,尽情挥洒Ruby DSL吧!
结论
Selenium是一个让人又爱又恨的东西,错误地使用Selenium会给整个敏捷团队的开发节奏带来灾难性的影响。不过值得庆幸的是正确地使用Selenium的原则也是相当的简单:
- 通过将脆弱易变的页面元素和测试分离开,使得页面的变化不会对测试产生太大的影响。
- 明确指定测试数据的意图,不在测试用使用任何具体的数据。
- 尽一切可能,明确地表达出测试的意图,使测试易于理解。
当然,除了遵循这几个基本原则之外,使用page object或其他domain based web testing技术是个不错的选择。它们将会帮助你更容易地控制Selenium测试的规模,更好地平衡覆盖率和执行效率,从而更加有效地交付高质量的Web项目。
此文中涉及的都是我最近几周以来对Selenium测试进行重构时所采用的真实技术。感谢Nick Drew帮助我清晰地划分了Driver、 Page、Nagivator和Shortcut的层次关系,它们构成我整个实践的基石;感谢Chris Leishman,在和他结对编程的过程中,他帮助我锤炼了Ruby DSL;还有Mark Ryall和Abhi,是他们第一次在项目中引入了Test Data Fixture,使得所有人的工作都变得简单起来。
作者简介:徐昊,ThoughtWorks咨询师和敏捷过程教练;BJUG(Beijing Java User Group)和AgileChina主要创始人之一;RSSer(Ruby,Smalltalk & Scheme)。目前主要致力于研究编译理论和推广DSL(Domain Specified Language)在实际项目中的应用。他的博客地址是:http://www.blogjava.net/raimundox。
发表评论
-
Ruby 实现 Java 版本的 PBEWithMD5AndDES 加密:
2024-05-14 10:31 265Ruby 实现 Java 版本的 PBEWithMD5An ... -
gem install mysql2
2022-01-07 15:18 371MACOS M1 gem install mysql ... -
虚拟MFA生成
2021-12-18 15:31 753虚拟MFA生成: import cn.huto ... -
Ruby 字符串连接
2021-01-12 11:15 470Ruby 字符串连接 require 'benc ... -
基于redis实现的锁
2021-01-08 17:43 371代码: class MsgSchedule attr ... -
OpenSSL功能集合
2020-04-22 18:59 435OpenSSL中算法记录 1、证书(X.509证书 ... -
ruby里%的特使用法
2014-05-14 14:50 517转自:http://ruby-china.org/topi ... -
字符串与对象的转化
2014-03-06 10:51 691classify: http://apidock.com/ ... -
ruby 里的正则表示
2014-01-22 17:27 0# ruby 中的正则表示 ... -
ruby mysql2链接数据库
2012-07-04 10:22 829#encoding:utf-8 require " ... -
ruby 解析网页
2012-06-08 11:51 0提示1:在文件头指定程序编码 #coding: ... -
ruby-string 字符串的学习
2012-06-07 18:02 1377String 来自:http://www.kuqin.com ... -
ruby开发中的一些问题及解决办法
2012-06-07 17:39 0ruby开发中各种问题及解决办法 1、ruby调用sh ... -
ruby 操作文件
2012-05-28 17:10 7701.创建文件夹 Dir.new %%1 Dir:: ... -
ruby unicode_str转unicode
2012-04-28 14:17 796endrequire 'yaml' requ ... -
xpath学习
2012-04-25 18:30 941xpath学习 require 'open-uri' r ...
相关推荐
使用Selenium进行web测试,首先需要了解以下关键概念: 1. WebDriver:WebDriver是一个API,定义了如何与各种浏览器进行通信。它提供了一种更直接、更简洁的方式来控制浏览器,相比早期的Remote Control接口,...
**标题:“使用selenium进行web测试项目框架”** 在Web测试领域,Selenium是一个非常流行的自动化测试工具,它允许开发者编写可运行在多种浏览器上的测试脚本。本项目框架主要探讨如何利用Java语言来实现基于...
Selenium 是一个强大的开源自动化测试框架,主要用于Web应用程序的测试。它允许开发人员编写可运行在多种浏览器和操作系统上的测试脚本,极大地提高了测试的覆盖率和效率。Selenium 支持多种编程语言,如Java、...
标题和描述中的信息表明,我们关注的是 Selenium 在Web测试中的应用以及其开源性质。 Selenium 的核心组件包括以下几个部分: 1. **Selenium WebDriver**:这是一个API,允许程序员控制多种浏览器并执行自动化测试...
本文对web自动化测试进行了详细的阐述和实现。 9. 自动化测试框架的设计 自动化测试框架的设计是指如何设计和实现一个高效的自动化测试框架。本文对自动化测试框架的设计进行了详细的阐述和实现。 10. 自动化测试...
Selenium是一个用于Web应用程序测试的工具,它支持多种编程语言和浏览器。它让测试人员能够自动控制浏览器来执行用户操作,比如点击链接、填写表单、导航网页等。Selenium的主要优点是可以进行跨平台测试,支持多种...
### 初次体验 Selenium 在 Web 测试中的应用 #### Selenium 概述 Selenium 是一个由 ThoughtWorks 开发的强大工具套件,专为 Web 应用程序提供自动化测试支持。它能够模拟用户行为,帮助开发者及测试工程师进行功能...
Selenium是一个开源的Web应用程序接口(API),可以模拟用户在浏览器中的操作,如点击按钮、填写表单、导航等,从而实现对Web页面功能的自动化测试。 Pytest是Python的一个测试框架,它提供了灵活且易于使用的测试...
Selenium自动化测试是一种非常流行和广泛使用的自动化测试工具,它主要被用于Web应用程序的测试。Selenium自动化测试可以通过模拟用户的操作来测试Web应用程序的功能,从而提高测试效率,确保软件的质量。 Selenium...
Selenium是一个主要用于Web应用程序自动化测试的工具集合,在行业内已经得到广泛的应用。本书介绍了如何用Python语言调用Selenium WebDriver接口进行自动化测试。主要内容为:基于Python 的 Selenium WebDriver 入门...
"基于Selenium的Web自动化测试技术的研究与应用" 基于Selenium的Web自动化测试技术是当前Web应用测试领域中的热点话题。随着Web应用的发展和普及,自动化测试技术的需求也日益增长。Selenium作为一款流行的自动化...
Selenium,作为一款强大的Web自动化测试工具,是软件测试领域不可或缺的一部分。...同时,通过结合chromedriver.exe这样的浏览器驱动,Selenium可以更加无缝地融入到自动化测试的流程中,为Web应用的质量保驾护航。
Selenium是一款强大的Web应用程序自动化测试工具,它支持多种编程语言,如Java、Python、C#等,使得测试人员和开发人员能够对网页进行自动化控制,从而实现高效且精确的测试。Selenium的核心理念是模拟真实用户的...
《Python测试驱动开发:使用Django、Selenium和JavaScript进行Web编程》这本书是关于现代Web应用开发的一个深度指南,特别关注于使用Python语言以及相关的工具和技术。书中详细介绍了如何运用测试驱动开发(TDD)的...
Selenium自动化测试是一种广泛应用于Web应用测试的技术,它允许开发者使用多种编程语言,如Python,来控制浏览器并模拟用户操作。本资源包“Selenium自动化测试:基于Python语言.rar”显然是一个专注于介绍如何使用...
《Selenium2自动化测试实战——基于Python语言》是一本针对软件测试工程师,尤其是对Web应用程序自动化测试感兴趣的读者的专业书籍。作者虫师在2016年10月编写了这本书,尽管时间已过去数年,但Selenium作为自动化...