Testando a funcionalidade do usuário do site com objetos de página da Capybara

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ágina

A 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 # ... end end 

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' # 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 

É importante escolher os nomes semanticamente corretos para os métodos para seus Objetos de Página

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

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:

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

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 } # ... end 

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? # ... end def has_blog?(title:, text:) # ... end 

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 exemplos
O original

Source: https://habr.com/ru/post/pt466527/


All Articles