Organização de testes de aceitação com PageObjects
![Organização de testes de aceitação com PageObjects](https://www.alura.com.br/artigos/assets/code/organizacao-de-testes-de-aceitacao-com-pageobjects.1738891800.png)
Testes de aceitação são extremamente úteis quando se trata de verificar se as funcionalidades de um sistema estão se comportando corretamente sem que tenhamos de testar manualmente a aplicação, abrindo um navegador, navegando por ela e visualizar os resultados. Como poderíamos automatizar esses testes que envolvem realizar a navegação na aplicação?
Para isso ser possível, existem algumas ferramentas que permitem tal trabalho, simulando a interação de um usuário com a aplicação. Esse é o caso do Selenium que, com a assistência do JUnit, pode facilitar o trabalho de escrever tais testes.
Para vermos como o Selenium pode nos ajudar, vamos considerar uma página contendo simples HTML, que ao clicarmos em um link dela, seremos redirecionados para outra página.
![Banner promocional da Alura, com chamada para um evento ao vivo no dia 12 de fevereiro às 18h30, com os dizeres](assets/cursos-imersivos-soft-launch/cursos-imersivos-soft-launch-banner-corpo-mobile.png)
<a class="botao" href="outra\_pagina.html">Clique Aqui</a>
Quando clicado, o link deve redirecionar para a outra_pagina.html
, que tem o conteúdo:
</pre> <h2>Estou na outra página!</h2> <pre>
Com essas páginas, podemos escrever um teste, usando o Selenium, para que ele clique no link e em seguida verifique se estamos na outra página. Para isso, podemos escrever um pequeno teste com o JUnit usando a API do Selenium:
@Test public void deveIrParaAOutraPagina() { WebDriver driver = new FirefoxDriver(); driver.navigate().to("http://localhost:8080/pagina.html");
driver.findElement(By.className("botao")).click();
String texto = driver.findElement(By.tagName("h2")).getText(); assertEquals("Estou na outra página!", texto); }
Com a API do Selenium, esse código parece simples, não é? Mas imagine se precisássemos fazer várias ações e navegar por diversas páginas para conseguirmos testar uma funcionalidade. Um possível exemplo seria o login aplicação, onde uma possibilidade de teste é:
@Test public void aoSeLogarNaAplicacaoDeveMostrarQueEstaLogado(){ WebDriver driver = new FirefoxDriver(); driver.navigate().to("http://localhost:8080/login.html");
// pega uma referencia para o formulário de login que estará na tela WebElement formulario = driver.findElement(By.tagName("form"));
// preenche usuário e senha driver.findElement(By.id("usuario")).sendKeys("joao"); driver.findElement(By.id("senha")).sendKeys("123456");
// submete o formulário formulario.submit();
// na próxima tela, o login desse usuário deveria estar aparecendo String usuarioLogado = driver.findElement(By.id("usuario-logado")).getText();
// verifica se o joao conseguiu fazer o login assertEquals("joao", usuarioLogado); }
Apesar de funcionar perfeitamente, o código acima possui um grave problema: lê-lo já está extremamente mais complexo do que o teste que havíamos feito antes! Agora, imagine se estivéssemos escrevendo testes para uma funcionalidade que envolva interação com várias outras telas?
Para simplificar e organizar esse código de teste, vamos separar cada uma das telas em classes diferentes, ou seja, para a tela de login vamos criar uma classe chamada TelaDeLogin
e para a página interna uma classe chamada PaginaInterna
, dessa forma poderíamos ter o método loga
com seguinte código para a TelaDeLogin
:
public class TelaDeLogin {
public void abre() { driver.navigate().to("http://localhost:8080/login.html"); }
public void loga(String usuario, String senha) { // pega uma referencia para o formulário na tela WebElement formulario = driver.findElement(By.tagName("form"));
// preenche usuário e senha driver.findElement(By.id("usuario")).sendKeys(usuario); driver.findElement(By.id("senha")).sendKeys(senha);
// submete o formulário formulario.submit(); } }
Note que o código acima ainda não compila, pois precisamos de uma referência para o WebDriver
. Podemos passar a recebê-la via construtor:
public class TelaDeLogin { private WebDriver driver;
public TelaDeLogin(WebDriver driver) { this.driver = driver; }
public void abre() { // navega para a tela de login }
public void loga(String usuario, String senha) { // código que usa o Selenium para fazer o login } }
Dessa forma, podemos usar a nova classe TelaDeLogin
no lugar do código do Selenium em nossas classes de teste. Com isso, o teste ficará parecido com:
public class TestesDeAceitacao extends SeleniumTest {
private TelaDeLogin telaDeLogin;
// Instanciamos a base de nossa aplicacao @Before public void setup() { WebDriver driver = new FirefoxDriver(); this.telaDeLogin = new TelaDeLogin(driver); }
// Efetuamos o teste @Test public void aoSeLogarNaAplicacaoDeveMostrarQueEstaLogado(){ telaDeLogin.abre(); telaDeLogin.loga("joao","123456");
// na próxima tela, o login desse usuário deveria estar aparecendo String usuarioLogado = driver.findElement(By.id("usuario-logado")).getText();
// verifica se o joao conseguiu fazer o login assertEquals("joao", usuarioLogado); } }
O código do teste melhorou, mas ainda continua com problemas, um deles é que ainda usamos a API do Selenium diretamente dentro do teste e novamente, se ele fosse um pouco mais complexo, continuaria difícil de ler seu código. Como o trecho que ainda está usando o código do Selenium direto no teste diz respeito à página interna do sistema, podemos levar esse código para a classe PaginaInterna
, dessa forma teremos:
public class PaginaInterna { private WebDriver driver;
public PaginaInterna(WebDriver driver) { this.driver = driver; }
public boolean estaLogado(String usuario) { String usuarioLogado = driver.findElement(By.id("usuario-logado")).getText();
return usuarioLogado.equals(usuario); } }
Dessa maneira, podemos fazer uma simples refatoração no nosso teste, para usar a nova classe implementada:
@Test public void aoSeLogarNaAplicacaoDeveMostrarQueEstaLogado() { String usuario = "joao"; telaDeLogin.abre(); telaDeLogin.loga(usuario, "123456");
boolean estaLogado = new PaginaInterna(driver).estaLogado(usuario); assertTrue(estaLogado); }
Agora estamos num nível melhor, mas continua tendo pontos para melhorar. O principal deles agora, é que toda troca de tela que existir em nosso teste, precisamos instanciar um objeto para a nova tela, passar o driver
como parâmetro, para aí sim, podermos invocar o método. Note que ao fazermos o login com sucesso, nesse caso, sempre seremos redirecionados para a mesma página, sendo assim, poderíamos fazer o método loga
já retornar uma instância de PaginaInterna
para nós e assim, só precisaríamos chamar o método verificaSeEstaLogado
:
public class TelaDeLogin {
// construtor e atributo driver
public PaginaInterna loga(String usuario, String senha) { // pega uma referencia para o formulário na tela WebElement formulario = driver.findElement(By.tagName("form"));
// preenche usuário e senha driver.findElement(By.id("usuario")).sendKeys(usuario); driver.findElement(By.id("senha")).sendKeys(senha);
// submete o formulário formulario.submit();
return new PaginaInterna(driver); } }
Essa alteração na classe TelaDeLogin
nos permite encadear as chamadas dos métodos loga
e verificaSeEstaLogado
no nosso teste de aceitação:
@Test public void aoSeLogarNaAplicacaoDeveMostrarQueEstaLogado() { String usuario = "joao"; telaDeLogin.abre();
boolean estaLogado = telaDeLogin.loga(usuario, "123456").estaLogado(usuario); assertTrue(estaLogado); }
Agora nosso teste está bem mais legível, e o código do Selenium todo encapsulado dentro de classes, que chamamos de [PageObject](http://code.google.com/p/selenium/wiki/PageObjects)
s. Ainda há brechas que precisam de melhoras. Podemos fazer o método abre()
retornar a própria instância de TelaDeLogin
e, com isso, poderíamos ter todos os métodos encadeados, o que faria o teste ficar como:
@Test public void aoSeLogarNaAplicacaoDeveMostrarQueEstaLogado(){ String usuario = "joao";
boolean estaLogado = telaDeLogin.abre().loga(usuario, "123456").estaLogado(usuario); assertTrue(estaLogado); }
Com isso, temos um código de teste bem mais legível, onde toda a API do Selenium está encapsulado dentro de classes que representam as páginas, formando os Page Objects.
Que outras ferramentas e práticas você utiliza para facilitar seus testes de aceitação?