页面对象可以用作从技术实现中抽象(隔离)测试的强大方法。 重要的是要记住,通过将功能(网站)封装在简单的方法中,可以将它们(页面对象)用于提高测试的稳定性并保持
DRY的原理(请勿重复)。
换句话说
Page Object是一个类的实例,该类从测试环境中抽象(隔离)用户界面,提供与用户界面进行交互的方法,并提取必要的信息。
术语学
术语“
页面对象”太笼统了。 以我的经验,
Page Object包括以下3种类型:
- 组件对象表示用户界面中的特定组件或小部件。 例如:包含一组组件的表,菜单,文章和其他块。
- 页面对象描述Web应用程序中的特定区域或用户界面。 它可能包含多个组件对象,并且可能包含用于与该对象中包含的抽象进行交互的便捷方法。
- 经验用于对复杂的功能进行分组,测试这些功能需要几个步骤,或者需要与多个页面进行交互。 根据我自己的经验,我使用此概念来抽象页面上的复杂行为(测试教育页面,创建新用户等)。
例子
考虑简单的
RSpec Capybara测试,该测试创建博客并且不使用页面对象:
require 'feature_helper' feature 'Blog management', type: :feature do scenario 'Successfully creating a new blog' do visit '/' click_on 'Form Examples' expect(page).to have_content('Create Blog') fill_in 'blog_title', with: 'My Blog Title' fill_in 'blog_text', with: 'My new blog text' click_on 'Save Blog' expect(page).to have_selector('.blog--show') expect(page).to have_content('My Blog Title') expect(page).to have_content('My new blog text') end scenario 'Entering no data' do visit '/' click_on 'Form Examples' expect(page).to have_content('Create Blog') click_on 'Save Blog' expect(page).to have_content('4 errors stopped this form being submitted') expect(page).to have_content("Title can't be blank") expect(page).to have_content("Text can't be blank") expect(page).to have_content('Title is too short') expect(page).to have_content('Text is too short') end end
让我们仔细看一下代码;它有几个问题。 有以下操作:切换到相应的页面,与该页面进行交互并检查内容。 部分代码是重复的,但是可以通过遵循
DRY原理来解决。
重要的是要理解,如果要测试的应用程序发生更改,则很难维护此代码。 例如,元素类,名称和标识符可能会更改,这需要定期更新测试代码。
同样,在此代码中没有“语义上下文”,很难理解哪些代码行在逻辑上进行了分组。
页面对象简介
如术语部分所述,
页面对象可用于表示表示层的抽象。
以前面的示例为例,并使用
Page Object创建新博客和查看博客,我们可以清除前面示例的代码。
除去了有关技术实施的特定信息之后,最终结果(代码)应该是可读的,并且不应包含有关用户界面的特定信息(id,css类等)。
require 'feature_helper' require_relative '../pages/new_blog' require_relative '../pages/view_blog' feature 'Blog management', type: :feature do let(:new_blog_page) { ::Pages::NewBlog.new } let(:view_blog_page) { ::Pages::ViewBlog.new } before :each do new_blog_page.visit_location end scenario 'Successfully creating a new blog' do new_blog_page.create title: 'My Blog Title', text: 'My new blog text' expect(view_blog_page).to have_loaded expect(view_blog_page).to have_blog title: 'My Blog Title', text: 'My new blog text' end scenario 'Entering no data' do new_blog_page.create title: '', text: '' expect(view_blog_page).to_not have_loaded expect(new_blog_page).to have_errors "Title can't be blank", "Text can't be blank", "Title is too short", "Text is too short" end end
创建页面对象创建
页面对象的第一步是创建一个
基本的页面类结构:
module Pages class NewBlog include RSpec::Matchers include Capybara::DSL
连接(启用)
Capybara :: DSL以允许
Page Objects实例使用
Capybara中可用的方法
has_css? '.foo' has_content? 'hello world' find('.foo').click
另外,我用
包括RSpec :: Matchers
在上面的示例中使用
期望的 RSpec库。
不要违反协议,
Page Objects不应该包含
Expect(期望) 。 但是,在适当的情况下,我更喜欢这种方法依靠
Capybara的内置机制来处理条件。
例如,下面的
Capybara代码将在
Page对象 (在本例中为
self )中出现
'foo' :
expect(self).to have_content 'foo'
但是,在以下代码中:
expect(page_object.content).to match 'foo'
可能会发生意外错误(可能会发生浮动测试),因为
会立即检查
page_object.content是否符合条件,并且可能尚未声明。 有关更多示例,我建议您阅读
thinkbot 编写的有关
Capybara的 可靠异步集成测试 。
方法创建
我们可以在一种方法的框架内抽象(描述)我们要从中获取数据的位置(区域):
def visit_location visit '/blogs/new'
为
页面对象的方法选择
语义正确的名称很重要
def create(title:, text:)
通常,重要的是要遵循功能集成方法的原则,并在可能的情况下遵守单一职责的原则(单一职责原则)。
组件对象
在我们的示例中,我们使用NewBlog类,但是没有要创建的实现。
由于我们与表单交互,因此我们可以另外引入一个类来表示此组件:
可以隐藏BlogForm的方法实现的位置:
module Components class BlogForm include RSpec::Matchers include Capybara::DSL def create(title:, text:) within blog_form do fill_in 'blog_title', with: title fill_in 'blog_text', with: text click_on 'Save Blog' end end private def blog_form find('.blog--new') end end end
一起
使用上述类,您现在可以查询和实例化
页面的页面对象,作为对象描述的一部分。
require 'feature_helper' require_relative '../pages/new_blog' require_relative '../pages/view_blog' feature 'Blog management', type: :feature do let(:new_blog_page) { ::Pages::NewBlog.new } let(:view_blog_page) { ::Pages::ViewBlog.new }
注意:我故意在对象文件的顶部手动创建了页面对象。 在某些RSpec测试中,自动下载所有支持文件并在目标文件中提供对它们的访问可能很方便,但是,这在使用大段代码时可能导致过多的工作量。 特别是,这将导致启动缓慢以及潜在的意外循环依赖性。
呼叫页面对象
现在,在每种情况下,我们都可以访问
new_blog_page和
view_blog_page的实例:
scenario 'Successfully creating a new blog' do new_blog_page.create title: 'My Blog Title', text: 'My new blog text' expect(view_blog_page).to have_loaded expect(view_blog_page).to have_blog title: 'My Blog Title', text: 'My new blog text' end
命名约定/谓词方法
与Rails / Ruby中的大多数事情一样,有些约定乍一看似乎微不足道(不具有约束力)。
在我们的测试中,我们使用
have_loaded和
have_blog与页面对象进行了
交互 :
expect(view_blog_page).to have_loaded expect(view_blog_page).to have_blog title: 'My Blog Title', text: 'My new blog text'
但是,我们的页面对象的方法名称实际上
has_loaded吗? 和
has_blog? :
def has_loaded?
这是一个需要注意的细微区别。 有关此约定的更多信息,我建议阅读以下
谓词匹配器链接。
Git,示例中使用的源代码原来的