Ruby
Ruby

Testes funcionais com Cucumber e Page Objects


 

Aproveitando que meu último post foi justamente uma tradução do ótimo artigo do Martin Fowler sobre  Page Objects, vou engatar no tema e postar algumas sugestões de como usar esse pattern com o cucumber para criar os testes funcionais da sua aplicação. Caso queria uma introdução sobre Page Objects, recomendo a leitura do post anterior a este, clicando aqui.

Ingredientes

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

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 destes pontos, 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

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 – 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. A gem page-object te permite fazer isso, usando uma DSL simples e intuitiva. Neste projeto exemplo, eu criei a seguinte Page:

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. Alguns exemplos:

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.

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:

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:

Ou mesmo:

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:

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 : https://github.com/pedrolopesme/post-functional-tests .


I'm Pedro Mendes, a passionate developer and technology enthusiast. This blog covers programming and technology in the broadest sense possible. It's the place I collect my thoughts, work and findings to share with the public.

View Comments