Page objects

Testes funcionais com Cucumber e Page Objects

Hoje vamos criar testes funcionais usando Cucumber e mais algumas libs do mundo Ruby. Anteriormente, meu post foi justamente uma tradução do ótimo artigo do Martin Fowler sobre  Page Objects – e agora vou engatar no tema, postando algumas sugestões de como usar esse pattern. Vamos lá! Ingredientes Assim, a idéia é construir o seguinte […]

Background Image

Hoje vamos criar testes funcionais usando Cucumber e mais algumas libs do mundo Ruby.

Anteriormente, meu post foi justamente uma tradução do ótimo artigo do Martin Fowler sobre  Page Objects – e agora vou engatar no tema, postando algumas sugestões de como usar esse pattern. Vamos lá!

Ingredientes

Assim, a idéia é construir o seguinte ambiente:

  1. Testes no cucumber  : consomem suas pages.
  2. Suas pages: definem seus page-objects, que encapsulam chamadas ao seu driver
  3. Watir-webdriver : Manipula chamadas ao Watir
  4. Watir : Manipula o browser, que no meu exemplo será uma instância do Firefox.

O mundo real e os testes funcionais

person encoding in laptop
Photo by Lukas on Pexels.com

Testes funcionais são, por natureza, difíceis de manter. Isso  porque além deles serem sensíveis a mudanças do seu código server-side, eles são sensíveis a mudanças na interface da sua aplicação web (e portanto ao código client-side).

Logo, por exemplo, se o programador front-end mudar o id de algum elemento importante para os seus testes, ou construir um código javascript que obstrua o carregamento da página (no caso de uma requisição ajax para montar um bloco HTML), seus testes facilmente irão falhar.

Além disso, as interfaces web hoje em dia estão bem difíceis de serem testadas. Já eram na “Era Flash” e suas complicações com Selenium e embora as interfaces estejam cada vez mais javascript-based, você ainda tem complicações devido as diversas requisições ajax e “mágicas javascript”, que tendem a dar algumas dores de cabeça ao serem testadas por um driver web.

Proposta dos testes funcionais

Pretendo aqui montar um projeto exemplo de testes funcionais, testando uma busca no meu Blog.

Eu aconselho você, no seu projeto real, manter os testes funcionais (com cucumber e page objects) em um projeto separado da aplicação a ser testada, ainda que esta seja em Ruby também

Isso é bom, principalmente por que você isola as dependências das bibliotecas peculiares a cada projeto (da aplicação a ser testada e dos testes).

Recentemente tive que criar testes funcionais em uma aplicação web Scala/Lift, e manter o projeto de functional tests separado foi fundamental para isolar as dependências J2EE/Scala do projeto de testes em Ruby/Cucumber.

Certamente, além de facilitar o isolamento dos jobs na pipeline do servidor de CI, que no meu caso foi o Jenkins. Caso alguém se manifeste a favor, posso postar futuramente como configurar um job no jenkins para testes funcionais em Ruby/Cucumber.

Passo 1 : Defina suas Páginas

Uma forma de começarmos a testar é definir antes suas pages e então definir seus objects. Não só a gem page-object te permite fazer isso, como também tem uma DSL simples e intuitiva.

Neste projeto exemplo, eu criei a seguinte Page:

# encoding: UTF-8
require 'page-object'
require_relative '../../../config/env'

class HomePage
  include PageObject
  page_url "http://www.blogdopedro.net/"
  expected_title "Pedro Mendes - Desenvolvimento web e um pouco mais."

  # Page Objects
  text_field :input_search, :name => "s"
  button :search, :class => "search-submit"
end

Como você pode ver, eu defini os page  objects usando uma DSL que me remete aos próprios elementos HTMLs. Para cada page object, a lib ruby “page-object” cria diversos métodos “mágicos” que auxiliam nos testes.

Por exemplo:

page.text_field # retorna o valor do campo de text ou (se for um link ou button), clica no element
page.text_field= # Atribuí um valor
page.text_field_element # Retorna o próprio elemento

Existem diversos outros métodos, que você pode encontrar diretamente na documentação da gem.

Passo 2 : Defina suas Features

Essa parte é mais fácil, basta seguir o padrão já estabelecido pelo próprio Cucumber, sem grandes mudanças.

Feature: Search For PageObject Article
As a Blog do Pedro visitor
I want to find for PageObjects article
Scenario:
Given I am on Blog do Pedro's Home Page
When I input a term into search text field
Then I should submit search form

Minha única dica nesse ponto é adicionar um sufixo numérico no nome do arquivo físico da sua feature (ex. 001_minha_feature.rb). Frequentemente seus testes funcionais precisam seguir uma sequência pré-estabelecida  (e só encontrei essa forma no Cucumber para ele respeitar alguma ordem).

Exemplo:

  • 001_cadastrar_usuario.rb
  • 002_ativar_usuario_cadastrado.rb
  • 003_acessar_conta_como_usuario.rb

Passo 3 : Defina seus steps usando suas pages (e seus page-objects)

Usando as gems watir_webdriver e a page object, ganhamos alguns métodos importantes nos nossos steps. Um deles é o visit_page, que aponta o browser para a url definida na Page – geralmente usado no início dos testes. Outro item importante: você pode ter várias pages com a mesma url, caso queria testar uma página que tenha comportamentos diferentes.

No projeto-exemplo, os steps para a HomePage ficaram:

# encoding: UTF-8

Given(/^I am on Blog do Pedro's Home Page$/) do
  visit_page HomePage
end

When(/^I input a term into search text field$/) do
  on_page HomePage do |page|
    page.input_search = "Tradução do artigo sobre PageObjects"
  end
end

Then(/^I should submit search form$/) do
  on_page HomePage do |page|
    page.search
  end
end

Deixo duas dicas para esse passo: crie um arquivo (ou alguns) para passos que se repitam em diversas features.

É comum, por exemplo, nos testes funcionais de uma aplicação que tenha algum tipo de permissionamento baseado em papéis (ACL), que você tenha que se autenticar na aplicação diversas vezes, com usuários diferentes. Para isso, coloque todos os steps necessários para fazer  o login em um arquivo só. Como os arquivos que definem os steps são “compartilhados” por todas as features, todos os steps são públicos a todas as features.

A segunda dica é : cuidado com os waitings explícitos. É comum, graças a problemas com timeouts, você colocar algum tipo de wait na página.

Por exemplo:

on_page HomePage do |page|
  page.input_search_element.when_visible(30)
  page.input_search = "Meu termo de busca"
end

Ou mesmo:


on_page HomePage do |page|
  sleep(30)
  page.input_search = "Meu termo de busca"
end

Geralmente, testes com Selenium possuiam muitos waitings explícitos – e isso era um grande causador de lentidão nos testes, que demoravam horas para rodarem. É óbvio que os waits explícitos às vezes são a única saída para lidar com algum timeout, mas evite sempre que possível.

Conclusão

Caso você baixe o projeto-exemplo, ao rodar no seu terminal você deverá ver algo parecido com isso:


$ rake cucumber
/Users/pedrolopesme/.rvm/rubies/ruby-1.9.3-p448/bin/ruby -S bundle exec cucumber --profile default
Using the default profile...
Feature: Search For PageObject Article
As a Blog do Pedro visitor
I want to find for PageObjects article
Scenario: # features/001_search_for_pageobject_article.feature:6
Given I am on Blog do Pedro's Home Page # features/steps_definitions/001_search_for_pageobject_article.rb:3
When I input a term into search text field # features/steps_definitions/001_search_for_pageobject_article.rb:7
Then I should submit search form # features/steps_definitions/001_search_for_pageobject_article.rb:13
1 scenario (1 passed)
3 steps (3 passed)
0m8.384s

Testes funcionais são custosos, mas são ótimos para capturar erros causados por algum problema na interface ou que não estejam necessáriamente ligados ao seu código (problemas de rede, por exemplo).

Por fim, deixo três conselhos para aqueles que tenham algum tipo de servidor de Continuous Integration ou mesmo Continuous Deployment:

  • Na sua pipeline de testes, rode os testes unitários antes dos testes funcionais.
  •  Rode os testes funcionais em uma aplicação “deployada” em um ambiente isolado do ambiente de produção, mas que seja o mais parecido possível com o mesmo.
  • Rode os testes funcionais em um ambiente gráfico virtual, tal como o XVFB

Deixei uma cópia do código usado nesse projeto no meu github .