--01--

PageObject简介

 

PageObject是编写UI测试时的一种模式。简而言之,你可以将所有知道页面细节的部分放入到这个对象上,对于编写测试的人来说,一个PageObject代表了一个页面,或者页面上的一个区域(比如搜索框,搜索结果,侧边栏等都可能是一个独立的Object)。这样做的好处分为两个方面:

  1. 封装了所有的实现细节(内部的HTML是如何组织的)
  2. 对外的接口非常清晰,从而代码更加语义化

我们这里列举一个简单的例子来说明:

我们要测试的场景是:我们在一个搜索应用中,用户输入了ThoughtWorks,我们来判断搜索结果的第一页有10条结果。如果使用原生的capybara,代码大致会如下:

  1. 1
    2
    3
    4
    5
    6
  1. visit '/search'
    fill_in 'Search', :with => 'ThoughtWorks'
    click_button '#search'
    expect(find('#result').find('.tips')).to have_content("10")

首先我们进入/search页面,然后在Search中输入了ThoughtWorks关键字,然后点击#search按钮,最后判断#result .tips下有10的字样。

如果使用PageObject,代码则会变成(这个是伪代码):

  1. search = SearchBox.new
    result = SearchResult.new
    search.type "ThoughtWorks"
    expect(result.count).to eq(10)

--02--

site_prism 简介

site_prism是一个构建在capybara之上的用于建模Page Object的gem。使用site_prism可以很语义化的编写Page Object,可以使代码非常易读。

位于顶层的Page对象可以拥有多个Section对象,每个Section可以对应页面上的一些逻辑上的块,比如内容区域,边栏等。对于现在流行的SPA,我们只需要一个Page和若干个Section就足够了。

  1. 1
    2
    3
    4
    5
    6
    7
    8
  1. class MovingHome < SitePrism::Page
    set_url 'http://localhost:8100/bundles/moving-home'
    element :container, "#tmsCheckout"
    section :personal, PersonalSection, "#acc-personal"
    section :contact, ContactSection, "#acc-contact"
    end

set_url方法制定了如何到达当前页,也是webdriver会实际发送请求的URL。页面本身上可以用element方法来声明一个元素,以及该元素对应的CSS选择器,这样就可以通过元素的名称来访问该选择器对应的HTML元素了。

比如上例中的container,我们在测试中就可以这样来访问它:

  1. 1
    2
    3
    4
  1. @moving = MovingHome.new
    @moving.load
    @moving.container.should be_visible

而对应的section元素,则声明了一个块的名称,块的类和块的选择器。这样我们就可以通过名称来应用该块了:

  1. 1
    2
    3
    4
  1. @moving.personal.name.set "Juntao"
    expect(@moving).to have_personal
    expect(@moving).to have_contact

have_前缀加块的名称,用来判断该块是否可见(比如display: block)。

--03--

一个项目上的实例

目前项目上有一个页面需要添加一些新的特性,对应的需要添加一些UI测试。之前的所有代码都是面向过程的,代码非常多,重复代码都通过抽成一个函数来组织,无法和实际的页面模块对应起来。因此我使用site_prism做了一些尝试。

业务场景是这样的:用户想要办理移机业务(比如搬家了,相应的宽带/有线电视要办理移机),这时候用户需要填写一些个人信息,联系方式,老地址,新地址等,这样我们就可以联系到他并帮他完成移机。而目前的页面也已经按照这些信息的关联度组织成了这样的形式:

可以看到,页面本身的组织已经比较清晰了,这非常方便我们抽取PageObject:每一个块都可以抽取为一个Section类的子类。

3.1 第一次尝试

比如对于个人信息这一个块:

这个块包含称呼,姓名,出生日期等几部分,我们可以很容易找到对应的页面元素,并抽取为:

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
  1. class PersonalSection < SitePrism::Section
    element :myservice, "#personal-my-services"
    element :title, "#personal\\.title"
    element :first, "#personal\\.firstName"
    element :last, "#personal\\.lastName"
    element :dob_day, "#personal\\.dobDay"
    element :dob_month, "#personal\\.dobMonth"
    element :dob_year, "#personal\\.dobYear"
    element :summary, ".tms-accordion-summary-content"
    element :next, ".tms-btn-next"
    end

最直接的使用方法就是直接调用set方法:

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
  1. def fulfill_personal
    @moving.personal.myservice.set "MINE"
    @moving.personal.title.select "Mr"
    @moving.personal.first.set "Juntao"
    @moving.personal.last.set "Qiu"
    @moving.personal.dob_day.select "21"
    @moving.personal.dob_month.select "Jan"
    @moving.personal.dob_year.select "1985"
    @moving.personal.next.click
    end

这样在Cucumber测试中就可以写成:

  1. 1
    2
    3
    4
    5
    6
    7
    8
  1. Given /I am on moving home page/ do
    @moving = MovingHome.new
    @moving.load
    end
    When /I fulfill my personal information/ do
    fulfill_personal
    end

3.2 面向对象

这样的代码事实上已经沦为了面向过程的方式了,更好的做法是讲fulfill_personal放入Personal本身中:

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
  1. def fulfill(personal)
    myservice.set personal["myservice"]
    title.select personal["title"]
    first.set personal["first"]
    last.set personal["last"]
    dob_day.select personal["dob_day"]
    dob_month.select personal["dob_month"]
    dob_year.select personal["dob_year"]
    next_button.click
    end

这样,外部的使用者只需要调用即可:

  1. 1
    2
  1. fixture = YAML::load_file('fixtures/moving.yml')
    @moving.personal.fulfill(fixture["personal"])

对应的moving.yml文件定义如下:

  1. 1
    2
    3
    4
    5
    6
    7
    8
  1. personal:
    myservice: "MINE"
    title: "Mr"
    first: "Juntao"
    last: "Qiu"
    dob_day: "1"
    dob_month: "Jan"
    dob_year: "1985"

3.4 Misc

为了达到视觉效果,UI上通常会有一些延迟的效果。比如点击一个按钮,在100ms之后弹出一个对话框,但是这种效果会导致测试的随机失败。

为了解决这个问题,我们可以通过给元素添加wait_until_前缀来等待。比如我们的测试中,在点击了下一步的按钮之后,预期有一个查看收费详情的对话框出现。根据一般的实现方式,这个对话框是预先写在页面上的,然后在合适的实际通过JavaScript将其显示在页面上(这样我们就不能通过查看该元素是否存在在页面上来编写断言了)。

  1. 1
  1. element :lightbox_view_fees, "#tmsLBViewFees"
  1. 1
    2
    3
  1. Then /I can see the lightbox View Fees shows up/ do
    @moving.wait_until_lightbox_view_fees_visible
    end

--04--

最后的结论

 

通常,我们的UI测试会和特性描述写在一起,以Cucumber为例,在feature文件中,我们会编写诸如这样的描述:

  1. 1
    2
    3
    4
    5
    6
  1. Feature: Platinum Move
    Scenario: Platinum Move
    Given I am on moving home page
    When I select to move my service "Foxtel from Telstra"
    And I select a "Telstra technician install"
    Then I can see the lightbox "View Fees" shows up

而一个良好的实现,我是说,像feature描述一样清晰的实现,可能是这样的:

  1. 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
  1. Given /I am on moving home page/ do
    @moving = MovingHome.new
    @moving.load
    end
    When /I select to move my telstra service "([^"]*)"/ do |selected|
    setup_data
    @moving.service.fulfill selected
    end
    Then /I can see the installation form/ do
    expect(@moving).to have_move_service
    end
    Then /I cannot see the installation form/ do
    @moving.move_service.should_not be_visible
    end
    And /I select a "([^"]*)"/ do |install|
    @moving.move_service.select_install install
    end
    Then /I can see the lightbox "([^"]*)" shows up/ do |name|
    @moving.lightbox(name).should be_visible
    end

基本上,每个step仅仅对应1行(或者很少的几行)代码,而这些代码背后有一组组织良好的PageObject。


作者:邱俊涛

原文地址:http://icodeit.org/2015/01/page-object-with-site-prism/


编写体面的UI测试的更多相关文章

  1. 使用uiautomator做UI测试

    转载~~~~~~~~~~~~~~~~~~~~~~~~ 若有侵权,请及时联系本博主,博主将第一时间撤销 在Android 4.1发布的时候包含了一种新的测试工具–uiautomator,uiautoma ...

  2. [转载]使用uiautomator做UI测试

    这个只是单纯的mark一下.还没有认真去研究.鉴于最近也不会做手机的自动化测试,所以留作以后参考吧. 转自: http://blog.chengyunfeng.com/?p=504 在Android ...

  3. 在Android Studio中进行单元测试和UI测试

    本篇教程翻译自Google I/O 2015中关于测试的codelab,掌握科学上网的同学请点击这里阅读:Unit and UI Testing in Android Studio.能力有限,如有翻译 ...

  4. 《软件测试自动化之道》读书笔记 之 底层的Web UI 测试

    <软件测试自动化之道>读书笔记 之 底层的Web UI 测试 2014-09-28 测试自动化程序的任务待测程序测试程序  启动IE并连接到这个实例  如何判断待测web程序完全加载到浏览 ...

  5. [zhuan]使用uiautomator做UI测试

    http://blog.chengyunfeng.com/?p=504 在Android 4.1发布的时候包含了一种新的测试工具–uiautomator,uiautomator是用来做UI测试的.也就 ...

  6. 利用 Rize 来进行 UI 测试或 E2E 测试

    之前我曾经在<Rize - 一个可以让你简单.优雅地使用 puppeteer 的 Node.js 库>一文简单介绍过 Rize 这个库.当时仅仅是介绍这个库本身,关于如何使用,我没有给太多 ...

  7. 软件“美不美”,UI测试一下就知道

    摘要:软件测试的最高层次需求是:UI测试,也就是这个软件"长得好不好看". 为了让读者更好地理解测试,我们从最基础的概念开始介绍.以一个软件的"轮回"为例,下图 ...

  8. 使用Java语言编写一个五子棋UI界面并实现网络对战功能(非局域网)

    使用Java语言编写一个五子棋UI界面并实现网络对战功能(非局域网) 一,前期准备 1,Java IDE(Eclipse)与JDK的安装与配置jdk-15.0.1-免配置路径版提取码:earu免安装版 ...

  9. 02- web UI测试与UI Check List

    UI英文是 user interface .所以UI测试就是用户界面测试. Web UI测试 用户界面测试:user interface testing,UI Testing指软件中的可见外观及其与用 ...

随机推荐

  1. 传统的DOM渲染方式

    什么是DOM渲染: DOM渲染是浏览器展现给用户的DOM文档的生成的过程. DOM渲染的演化过程: ①纯后端渲染 ②纯前端渲染 ③服务端的js渲染结合前端渲染 纯后端渲染:DOM树的生成完全是在后端服 ...

  2. stringstream字符串流的妙用

    现在有一个数组,其值为从1到10000的连续增长的数字.出于某次偶然操作,导致这个数组中丢失了某三个元素,同时顺序被打乱,现在需要你用最快的方法找出丢失的这三个元素,并且将这三个元素根据从小到大重新拼 ...

  3. 【转】Java 泛型

    转载:https://www.cnblogs.com/lwbqqyumidi/p/3837629.html. 一. 泛型概念的提出(为什么需要泛型)? 首先,我们看下下面这段简短的代码: public ...

  4. 状压DP(超详细!!!)

    一.定义 总述 状态压缩动态规划,就是我们俗称的状压DP,是利用计算机二进制的性质来描述状态的一种DP方式. 很多棋盘问题都运用到了状压,同时,状压也很经常和BFS及DP连用. 状压dp其实就是将状态 ...

  5. Export failed for github.com/hashicorp/consul: Unable to export source: exit status 128

    背景 go项目,使用glide install命令去下载安装依赖,依赖中有个github.com/hashicorp/consul 问题描述 一直无法下载安装依赖成功,报错如下: [ERROR] Ex ...

  6. QSS文件美化界面无效

    问题描述: 同样的qss内容,内置有效,写进qss文件加载无效. 搜寻.........很多网友的经理是qss文件编码和qt文本编辑器的属性........而我怎么尝试,怎么失败. 解决方案: 从fi ...

  7. portal项目启动问题

    错误信息: Disconnected from the target VM, address: '127.0.0.1:58909', transport: 'socket' Process finis ...

  8. 推荐分享AB测试服务商

    推荐分享AB测试服务商 1.吆喝科技 2.testin 3.云眼

  9. 一、WebAPI自定义过滤器的使用

    一.WebAPI自定义过滤器的使用 1.注册过滤器 using System.Web.Http; using KYINT.WebAPIService.Handler; namespace KYINT. ...

  10. Django初见

    什么市WEB应用? 所谓的web应用就是我们基于浏览器打开的一个个网页(对应网址得到的内容) 软件开发架构 C/S架构: 客户端/服务端 B/S架构:浏览器/服务器 所有的B/S架构本质上就是C/S架 ...