Tecnologia

Page Objects – Padrão de projeto para organização de testes funcionais

Por: , janeiro 27, 2014

Introdução

Um grande desafio encarado pelos desenvolvedores do mundo todo é garantir na entrega de um projeto extenso que todas as suas partes estão funcionando perfeitamente.

Geralmente com o crescimento de um projeto as funcionalidades implementadas primeiro acabam ficando no esquecimento caso não sejam realizados testes constantes. O problema é que, quando um sistema fica grande, testar ele se torna uma tarefa desgastante, gerando desconforto nos desenvolvedores e testadores.

Uma forma de resolver isso é automatizar os testes. Existe um framework chamado Selenium que nos permite automatizar as ações de um usuário em um navegador, dessa forma conseguimos automatizar tarefas desgastantes que necessitem ser feitas no navegador como por exemplo cadastrar 100 usuário onde o formulário tem 50 campos, com certeza a realização dessa tarefa de forma manual seria extremamente extressante para o testador do sistema.

Outro framework que pode nos ajudar nessa tarefa é o Junit, que basicamente nos auxilia na automatização de teste nos fornecendo um relatório instantaneo, apontando as falhas, acertos e erros na execução dos testes.

Necessidade

O padrão de projeto proposto nesse post se torna necessário quando, apesar da utilização de testes e de metodologias ageis de desenvolvimento como TDD, em um determinado momento o projeto ficará muito grande, com fluxos extensos, e criar testes para esses fluxos em uma única classe é altamente desencorajado pela comunidade de desenvolvedores pois gera o risco de criar códigos duplicados.

O padrão Page Objects vêm para ajudar a resolver esse problema. A proposta desse padrão é de criar um objeto para cada pagina web, e utilizando a boa e velha orientação a objetos, encapsular em cada classe os atributos e métodos, como campos e ações de cada pagina.

Por exemplo, para o caso da existência de uma pagina de login, criamos então uma classe que contem métodos para simular o comportamento da classe, poderiamos implementar por exemplo um metodo chamado “logar” passando usuário e senha e dentro dessa classe realizamos o login, e retornamos nesse próprio método o objeto referente à proxima pagina.

Geralmente os primeiros testes são mais extensos, pois ainda não há nenhum objeto de uma pagina, nesse momento é importante manter a motivação, não desanimar, pois quanto mais testes são escritos, mais facil fica, pois quanto mais paginas são implementadas nos testes e inseridas nos fluxos, mais elas podem ser reaproveitadas, facilitando os testes futuros.



Metodologia
Para explicar a metodologia vou propor um exemplo prático, onde baseado nele, demais cenários poderão ser escritos. Em nosso exemplo temos uma pagina de login, que após autenticação do usuário irá redirecionar para uma pagina inicial, nessa pagina teremos um link para uma pagina de parâmetros, após alterar os parâmetros, voltaremos para pagina inicial. Neste caso teremos 3 paginas, suas classes podem ser vista logo abaixo. Utilizaremos a linguagem Java de programação, lembrando que esse padrão pode ser utilizado em qualquer linguagem com suporte a orientação a objetos.

Vamos começar pelo código da classe correspondente a pagina de login.

package pageObjects;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class PaginaLogin extends PaginaBase {
	public PaginaLogin(WebDriver driver) {
		super(driver);
	}
	public PaginaInicial logar(String userName, String userPassword) {
		realizaLogin(userName, userPassword);
		return new PaginaInicial(getDriver());
	}
	private void realizaLogin(String userName, String userPassword) {
		getDriver().findElement(By.id("userName")).clear();
		getDriver().findElement(By.id("userName")).sendKeys(userName);
		getDriver().findElement(By.id("userPassword")).clear();
		getDriver().findElement(By.id("userPassword")).sendKeys(userPassword);
		getDriver().findElement(By.id("btLogin")).click();
	}
}

Reparem que no momento em que é realizado o login é retornada uma nova instancia da pagina inicial, isso porque feito o login continuaremos nosso fluxo na pagina inicial.

public PaginaInicial logar(String userName, String userPassword) {
        realizaLogin(userName, userPassword);
	return new PaginaInicial(getDriver());
}

 
Agora vamos para a implementação do código da classe correspondente a pagina inicial.

package pageObjects;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class PaginaInicial extends PaginaBase {
	public PaginaInicial(WebDriver driver) {
		super(driver);
	}
	public PaginaParametros acessaPaginaDeParametros() {
		getDriver().findElement(By.id("btnParametros")).click();
		return new PaginaParametros(getDriver());
	}
}

 

Repare que no momento em que clicamos no botão que nos redirecionará para a tela de parâmetros, retornamos então uma nova instancia da página de parametros. Agora na pagina de parâmetros podemos validar o parâmetro desejado e assim que finalizar retornar uma instância da pagina inicial (aqui assumimos que quando o usuário termina a visualização/edição dos parâmetros, ele é redirecionado automaticamente para pagina inicial).

package pageObjects;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
public class PaginaParametros extends PaginaBase {
	public PaginaParametros(WebDriver driver) {
		super(driver);
	}
	public String obterValorParametro(String id) {
		return getDriver().findElement(By.id(id)).getAttribute("value");
	}
	public void confirmarParametros() {
		getDriver().findElement(By.id("btnConfirm")).click();
	}
}

 

Todas as classes extendem a classe PaginaBase. Nessa classe é configurado o acesso ao sistema.

package pageObjects;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.firefox.FirefoxDriver;
public class PaginaBase {
	WebDriver driver;
	public PaginaBase(WebDriver driver) {
		this.driver = driver;
	}
	public PaginaBase() {
		this.driver = new FirefoxDriver();
	}
	public void navegateTo(String url) {
		driver.navigate().to(url);
	}
	public WebDriver getDriver() {
		return driver;
	}
	public void closeNavegator() {
		getDriver().close();
	}
}

 

Nas dependência adicionei o Junit pois utilizei sua estrutura para validar os fluxos e retornos das paginas. Sua utilização não é recomendada dentro das Page Objects, mas sim nos testes. O teste que implementa o cenário acima pode ser visto logo abaixo:

package funcionalTests;
import org.junit.Assert;
import org.junit.Test;
import pageObjects.*;
public class ParametroTest {
	protected PaginaBase paginaBase = new PaginaBase();;
	protected PaginaLogin paginaLogin;
	protected PaginaInicial paginaInicial;
	protected PaginaParametros paginaParametros;
	@Test
	public void valorParametro() throws Exception {
		acessandoPaginaLogin();
		adotandoQueUsuarioLogado();
		acessaMenuParametro();
		verificaValorParametro();
		confirmaEntaoParametros();
		fechaNavegador();
	}
	private void acessandoPaginaLogin() {
		this.paginaBase.navegateTo("localhost:8080/sys/login.html");
    	}
	private void adotandoQueUsuarioLogado() {
		this.paginaLogin = new PaginaLogin(this.paginaBase.getDriver());
		this.paginaInicial = paginaLogin.logar("admin","admin123");
   	}
	private void acessaMenuParametro() {
		this.paginaParametros = paginaInicial.acessaPaginaDeParametros();
	}
	private void verificaValorParametro() {
Assert.assertEquals("30", this.paginaParametros.obterValorParametro("prTempMax"));
    	}
	private void confirmaEntaoParametros() {
		this.paginaParametros.confirmarParametros();
	}
	private void fechaNavegador() {
		this.paginaBase.closeNavegator();
	}
}

 

Neste momento temos um teste implementado e três Page Objects prontas para reutilização nos demais testes.

Vantagens na utilização

  • Maior independência dos testes.

  • Maior reaproveitamento dos codigo, evitando duplicação de código e “code smell”.

  • Quanto mais testes são criados, mas rápida fica a confecção de novos testes.

  • Menor necessidade de refatorar ou debugar códigos antigos. Pois defeitos aparecerão na execução dos testes.

  • Os testes podem servir como documentação para as funcionalidades do sistema se escritos de forma clara, uma abordagem interessante para se utilizar nesse caso é o BDD.

 

Ambiente de desenvolvimento

Para a confecção do exemplo montei um ambiente com um projeto Java EE no Apache Maven, utilizei a IDE Eclipse Juno juntamente com os frameworks Junit e Selenium. Os exemplos foram desenvolvidos em Java e para simular um cenário real criei dois scripts em html sem validação alguma, meu intuito é somente exemplificar o cenário, juntamente utilizei o Apache TomCat para o acesso.

Para obter os códigos acesse os links:

 

Referências

Descrição do padrão pela equipe de desenvolvimento do Selenium

https://code.google.com/p/selenium/wiki/PageObjects

 

Leituras complementares

Edição da Mundo J com a matéria “Page Objects – Organizando seus Testes Funcionais por meio de um Padrão de Design” por Eder Ignatowicz e Leandro Guimarães

http://www.mundoj.com.br/49conteudo.shtml

Framework com proposta de facilitar a aplicação do Page Objects

https://github.com/FluentLenium/FluentLenium/wiki/Page-Object-Pattern

 

Documentação do Selenium sobre padrões de desenvolvimento de testes

http://docs.seleniumhq.org/docs/06_test_design_considerations.jsp

 

  • Receba nosso conteúdo em primeira mão.