Os Objetos de Página podem ser usados como um método poderoso de abstração (isolamento) de seus testes da implementação técnica. É importante lembrar que eles (Objetos de Página) podem ser usados para aumentar a estabilidade dos testes e manter o princípio de
DRY (não se repita) - encapsulando a funcionalidade (site) em métodos simples.
Em outras palavras
Page Object é uma instância de uma classe que abstrai (isola) a interface do usuário do ambiente de teste, apresenta métodos para interagir com a interface do usuário e extrai as informações necessárias.
Terminologia
O termo
Objeto de Página é um conceito muito geral. Na minha experiência, o
objeto de página inclui os três tipos a seguir:
- Objetos de componentes representam componentes ou widgets específicos na interface do usuário. Por exemplo: tabelas, menus, artigos e outros blocos que contêm um grupo de componentes.
- O objeto Página descreve uma área ou interface de usuário específica em um aplicativo da web. Pode consistir em vários objetos componentes e pode conter métodos convenientes para interagir com a abstração contida nesse objeto.
- A experiência é usada para agrupar funcionalidades complexas, cujo teste requer várias etapas ou interação com várias páginas. Por experiência própria, usei esse conceito para abstrair comportamentos complexos em uma página (testando páginas educacionais, criando um novo usuário etc.)
Exemplos
Considere o teste simples
RSpec Capybara , que cria blogs e não usa objetos de página:
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
Vamos dar uma olhada no código, que possui vários problemas. Existem as seguintes ações: alternar para a página correspondente, interagir com a página e verificar o conteúdo. Parte do código é duplicada, mas isso pode ser corrigido aderindo ao princípio
DRY .
É importante entender que esse código é difícil de manter se houver alterações no aplicativo em teste. Por exemplo, classes de elementos, nomes e identificadores podem mudar, o que requer atualizações regulares do código de teste.
Também neste código não há 'contexto semântico', é difícil entender quais linhas de código estão agrupadas logicamente.
Introdução aos objetos de página
Conforme discutido na seção de terminologia, os
Objetos de Página podem ser usados para representar abstrações no nível da apresentação.
Tomando o exemplo anterior e usando o
Objeto de Página para criar novos blogs e visualizar blogs, podemos limpar o código do exemplo anterior.
Tendo se livrado de informações específicas sobre implementação técnica, o resultado final (código) deve ser legível e não deve conter informações específicas sobre a interface do usuário (id, classes css, etc.).
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
Criando objetos de páginaA primeira etapa na criação de
objetos de página é criar uma estrutura
básica de classe de página :
module Pages class NewBlog include RSpec::Matchers include Capybara::DSL
Conectando (ativando) o
Capybara :: DSL para permitir que instâncias do
Page Objects usem os métodos disponíveis no
Capybara has_css? '.foo' has_content? 'hello world' find('.foo').click
Além disso, eu usei
incluem RSpec :: Matchers
nos exemplos acima para usar a biblioteca RSpec de
expectativa .
Não viole o contrato, os
Objetos de Página não devem incluir
expectativa (expectativas) . No entanto, quando apropriado, prefiro essa abordagem a contar com
os mecanismos
internos da Capivara para lidar com as condições.
Por exemplo, o seguinte código
Capybara espera a presença de
'foo' dentro dos
Objetos de Página (neste caso,
self ):
expect(self).to have_content 'foo'
No entanto, no seguinte código:
expect(page_object.content).to match 'foo'
Erros inesperados são possíveis (um teste flutuante pode ocorrer), pois o
page_object.content é imediatamente verificado quanto à conformidade com a condição e pode não ter sido declarado ainda. Para mais exemplos, eu recomendaria a leitura
de testes de integração assíncrona confiáveis da thinkbot com a Capybara .
Criação de método
Podemos abstrair (descrever) o local (região) a partir do qual queremos obter dados, dentro da estrutura de um método:
def visit_location visit '/blogs/new'
É importante escolher os nomes
semanticamente corretos para os métodos para seus
Objetos de Página def create(title:, text:)
Em geral, é importante seguir o princípio dos métodos funcionalmente integrados e, sempre que possível, aderir ao princípio da responsabilidade única (princípio de responsabilidade única).
Objetos componentes
No nosso exemplo, usamos a classe NewBlog, mas não há implementação a ser criada.
Como interagimos com o formulário, também podemos introduzir uma classe para representar este componente:
Onde os métodos de implementação do BlogForm podem estar ocultos:
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
Todos juntos
Usando as classes acima, agora você pode consultar e instanciar os
Objetos de Página da sua página como parte da descrição do objeto.
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 }
Nota: Criei intencionalmente um objeto de página manualmente na parte superior do arquivo do objeto. Em alguns testes do RSpec, pode ser conveniente fazer o download automático de todos os arquivos de suporte e fornecer acesso a eles nos arquivos de objetos; no entanto, isso pode levar a cargas de trabalho excessivas ao usar grandes partes de código. Em particular, isso levará a uma inicialização lenta e a possíveis dependências cíclicas indesejadas.
Objetos da página de chamada
Agora, em cada cenário, teremos acesso às instâncias de
new_blog_page e
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
Convenções de nomenclatura / métodos de predicados
Como na maioria das coisas no Rails / Ruby, existem convenções que podem parecer insignificantes (não vinculativas) completamente de relance.
Em nossos testes, interagimos com o objeto de página usando
have_loaded e
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'
No entanto, os nomes dos métodos do nosso objeto de página realmente foram
carregados? e
has_blog? :
def has_loaded?
Essa é uma distinção sutil que precisa de atenção. Para obter mais informações sobre esta convenção, recomendo a leitura do seguinte link de
correspondentes predicados .
Git, o código fonte usado nos exemplosO original