使用Capybara页面对象测试网站用户功能

页面对象可以用作从技术实现中抽象(隔离)测试的强大方法。 重要的是要记住,通过将功能(网站)封装在简单的方法中,可以将它们(页面对象)用于提高测试的稳定性并保持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 # ... end end 

连接(启用) 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' # It can be beneficial to assert something positive about the page # before progressing with your tests at this point # # This can be useful to ensures that the page has loaded successfully, and any # asynchronous JavaScript has been loaded and retrieved etc. # # This is required to avoid potential race conditions. expect(self).to have_loaded end def has_loaded? self.has_selector? 'h1', text: 'Create Blog' end 

页面对象的方法选择语义正确的名称很重要

 def create(title:, text:) # ... end def has_errors?(*errors) # ... end def has_error?(error) # ... end 

通常,重要的是要遵循功能集成方法的原则,并在可能的情况下遵守单一职责的原则(单一职责原则)。

组件对象


在我们的示例中,我们使用NewBlog类,但是没有要创建的实现。

由于我们与表单交互,因此我们可以另外引入一个类来表示此组件:

 # ... def create(title:, text:) blog_form.new.create title: title, text: text end # ... private def blog_form ::Components::BlogForm end 

可以隐藏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 } # ... end 

注意:我故意在对象文件的顶部手动创建了页面对象。 在某些RSpec测试中,自动下载所有支持文件并在目标文件中提供对它们的访问可能很方便,但是,这在使用大段代码时可能导致过多的工作量。 特别是,这将导致启动缓慢以及潜在的意外循环依赖性。

呼叫页面对象


现在,在每种情况下,我们都可以访问new_blog_pageview_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_loadedhave_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? # ... end def has_blog?(title:, text:) # ... end 

这是一个需要注意的细微区别。 有关此约定的更多信息,我建议阅读以下谓词匹配器链接。

Git,示例中使用的源代码
原来的

Source: https://habr.com/ru/post/zh-CN466527/


All Articles