TDD simples e prático, Parte IV

Fala Desenvolvedores!!!

Neste artigo teremos a continuação da parte III da série sobre TDD. A idéia deste artigo é continuar comentando um pouco mais sobre os Testes de Unidade e também falarmos sobre os Testes de Unidade em relação ao comportamento dos nossos objetos. Também falaremos sobre o conceito de Mock de Objetos, que é extremamente importante no TDD.

Já vou adiantar que você ficará chateado com o que faremos para “Mockar” os nossos objetos, mas prometo que em seguida ficaremos muito felizes com uma solução bem mais bacana para isso. Calma! o conceito de “Mockar” objetos aparecerá logo logo!

Para iniciar este artigo, vamos fugir um pouco do conceito citado acima e fazer a funcionalidade de divisão de dois números para verificarmos a possibilidade de fazer Testes esperando que alguma Exceção ocorra. Isso mesmo! Por algum motivo você deseja lançar uma Exceção caso algo de errado aconteça e podemos fazer isso com o JUnit, apresentado no artigo anterior.

Teste da divisão
A funcionalidade é simples: Fazer a divisão de dois números. Lembrando: É um caso simples e isolado onde a intenção é você imaginar um caso real da sua aplicação.
Então vamos para o Teste!

Mas aqui começaríamos com aquele conceito de BabySteps, onde faríamos passos curtos para chegarmos à solução, correto? Correto, porém os BabySteps não são uma regra xiita que devemos seguir à risca. Segundo o próprio Kent Beck em seu livro sobre TDD, os BabySteps são para quando realmente não temos confiança suficiente em escrever determinado código. Como ele cita também, não devemos desenvolver com BabySteps a todo momento e sim devemos ficar felizes por podermos fazê-lo quando desejarmos.

Agora que lembramos disso, vamos correr um pouquinho mais no código e escrever um pouco mais rápido que no artigo anterior, porém sinta-se à vontade para colocar a sua velocidade:

Adicionando o método de Teste à nossa classe de CalculadoraTeste:

public class CalculadoraTeste {
	@Test
	public void deveriaDividirDoisValoresPassados() throws Exception {
		int valorA = 6;
		int valorB = 2;
		Calculadora calculadora = new Calculadora();
		int divisao = calculadora.divide(valorA, valorB);

		assertEquals(3, divisao);
	}
}

E na nossa classe Calculadora, vamos escrever o nosso método de produção:

public class  Calculadora {

	public int divide(int valorA, int valorB) {
		return valorA / valorB;
	}
}

Legal! Agora podemos rodar o nosso Teste e vê-lo passando. Uma observação simples: Não comentei nos artigos anteriores mas só para ter certeza que é de conhecimento de todos, vou comentar: Rodamos os nossos Testes clicando com o botão direito na classe de Teste, selecionando Run As e em seguida selecionando o JUnit Test.

Agora temos um Teste verde na nossa frente!

Maravilha! Finalizamos? Não.

E quando esperamos por uma exceção?
Vamos atormentar os professores de matemática e fazer a seguinte alteração na nossa classe de Teste:

public class  CalculadoraTeste {
	@Test
	public void deveriaDividirDoisValoresPassados() throws Exception {
	        int valorA = 6;
  	        int valorB = 0;  //divisão por zero!
	        Calculadora calculadora = new Calculadora();
	        int divisao = calculadora.divide(valorA, valorB);

	        assertEquals(?, divisao);
	}
}

O que fizemos: atribuímos o valor zero à variável valorB. E o que esperamos no nosso assertEquals? Não tenho noção! Podemos esperar tudo, menos um valor! Sendo assim, na sua aplicação, você poderia mostrar uma mensagem para o usuário solicitando gentilmente que ele insira um valor coerente. E como podemos fazer um Teste esperando uma exceção? Vamos lá!

Teste esperando por uma exceção
Podemos usar um parâmetro na própria anotação do JUnit (@Test) para indicar qual a exceção que estamos esperando receber. Assim teríamos o seguinte código para o nosso Teste:

public class  CalculadoraTeste {

	@Test
	public void deveriaDividirDoisValoresPassados() throws Exception {
	        int valorA = 6;
		int valorB = 3;
		Calculadora calculadora = new Calculadora();
		int divisao = calculadora.divide(valorA, valorB);

		assertEquals(2, divisao);
	}

	@Test
	public void deveriaLancarUmaExcecaoIndicandoFalhaAoDividirUmNumeroPorZero()
             throws Exception {
		int valorA = 6;
		int valorB = 0;
		Calculadora calculadora = new Calculadora();
		int divisao = calculadora.divide(valorA, valorB);

		assertEquals(0, divisao);
	}
}

Mas infelizmente ao rodar, temos uma barra vermelha:

Agora sim podemos fazer o nosso Teste passar adicionando o parâmetro à anotação do JUnit (@Test):

public class CalculadoraTeste {

	@Test(expected = ArithmeticException.class)
	public void deveriaLancarUmaExcecaoIndicandoFalhaAoDividirUmNumeroPorZero()
            throws Exception {
		int valorA = 6;
		int valorB = 0;
		Calculadora calculadora = new Calculadora();
		calculadora.divide(valorA, valorB);
	}
}

E agora sim temos a barra verde para o nosso Teste:

Este foi um caso simples para mostrar como é possível trabalhar com Testes que devem verificar exceções. Podemos estender este conceito para outros momentos, como fazer um Teste que não deveria esperar uma exceção que já esta poderia ser tratada pela nossa classe Calculadora, pois fazer da forma acima não é tão interessante.

Agora vamos melhorar os nossos testes!

Indo mais além
Até agora fizemos testes bem simples e a idéia é imaginarmos outras funcionalidades em nossas aplicações reais de forma a desenvolvê-las desta forma.Claro que uma classe do tipo Calculadora não é o melhor dos exemplos, mas optei pela simplicidade até agora.

De qualquer forma, tenho certeza que sua aplicação poderá ter muitas funcionalidades que, se bem isoladas, serão também passíveis de testes semelhantes.

Agora podemos avançar um pouco mais! Começamos o artigo com uma nova palavra: “Mock”.
Definição simples: Um Mock é basicamente um objeto falso, que é capaz de simular as dependências de um objeto e é capaz de simular determinadas ações desse objeto.
Por que é usado? Para testar o comportamento de outros objetos desejados.

Por que gostaríamos de testar o comportamento de outros objetos? Justamente para termos certeza de que tudo ocorreu conforme pensamos. Vamos imaginar a seguinte situação: Temos uma aplicação onde cada vez que excluímos uma pessoa, um log é gerado no banco no banco de dados com o nome da pessoa que foi excluída.

Como poderíamos ter certeza que a geração do log realmente vai ser chamada e que nada de ruim acontecerá no caminho?

Podemos fazer este teste usando exatamente um Mock da classe de Log. Vamos fazer o código mais simples que vier na nossa cabeça:

//Classe do nosso Teste
public class PessoaTeste {
	@Test
	public void deveriaCriarUmLogQuandoUmaPessoaForExcluida()
           throws Exception {
		Pessoa pessoa = new Pessoa();
		pessoa.setNome("Alexandre");
		PessoaController pessoaController = new PessoaController();
		pessoaController.exclui(pessoa);
		// Como saberemos se realmente o "criaLog" será chamado?
	}
}
//Nosso Controller
public class PessoaController {

	private PessoaDAO pessoaDAO;
	private Log log;

	public PessoaController() {
		pessoaDAO = new PessoaDAO();
		log = new Log();
	}

	public void exclui(Pessoa pessoa) {
		PessoaDAO.exclui(pessoa);
		log.criaLog(pessoa.getNome());
	}
}
//Nossa classe de criação de Logs
public class Log {

	public void criaLog(String nomeDaPessoa) {
		// Código para criar um Log no banco, em um txt, etc...
	}
}

Eu sei, eu sei. Abandonamos aqui algumas boas práticas mas é em prol de um entendimento melhor da situação. Logo logo vamos melhorar um pouco mais este código.

Voltamos à mesma pergunta: Como vamos saber se a criação do Log foi chamada? Vamos criar um Mock da nossa classe de criação de Logs!

O nosso Mock deve simular o funcionamento da funcionalidade, ou seja, ele não conterá código algum, será apenas para verificarmos se ele foi chamado.

Uma idéia seria criarmos uma classe que estende da nossa classe de Log, mas seremos um pouquinho melhores que isso e vamos criar uma Interface para implementarmos.

Assim poderíamos ter:

//Nossa Interface de criação de Logs
public interface GeradorDeLog {
	public void criaLog(String nomeDaPessoa);
}

Podemos então ter a seguinte classe horrorosa:

//Mock da nossa classe de Log
public class LogMock implements GeradorDeLog {

	private String nome;

	@Override
	public void criaLog(String nomeDaPessoa) {
		this.nome = nomeDaPessoa;
	}

	public String getNome() {
		return nome;
	}
}

Mas temos um detalhe: O nosso Controller está com uma dependência forte que é a classe Log sendo instanciada diretamente pelo Controller. Isso impossibilita o uso do nosso Mock. Então vamos melhorar um pouquinho o Design da nossa aplicação.
Opa! Olha o TDD nos “obrigando” a melhorar o Design da nossa aplicação!

Vamos então aplicar um princípio bem importante que é a Inversão de Controle através da Injeção de nossas Dependências. Vamos enviar então o nosso GeradorDeLog para o Controller através do construtor.

Assim teremos:

//Nossa classe de Teste
public class PessoaTeste {
	@Test
	public void deveriaCriarUmLogQuandoUmaPessoaForExcluida()
            throws Exception {
		Pessoa pessoa = new Pessoa();
		pessoa.setNome("Alexandre");

		LogMock nossoLogMock = new LogMock();
		PessoaController pessoaController = new PessoaController(nossoLogMock);
		pessoaController.exclui(pessoa);

		assertEquals(pessoa.getNome(), nossoLogMock.getNome());
	}
}
//Nosso Controller
public class PessoaController {

	private PessoaDAO pessoaDAO;
	private GeradorDeLog log;

	public PessoaController(GeradorDeLog log) {
		this.pessoaDAO = new PessoaDAO();
		this.log = log;
	}

	public void exclui(Pessoa pessoa) {
		PessoaDAO.exclui(pessoa);
		log.criaLog(pessoa.getNome());
	}
}

E olha que temos aqui, um teste passando!

Recapitulando: Muitas vezes precisamos testar o comportamento dos nossos objetos. No nosso caso qual é o comportamento? A criação de um Log quando uma pessoa é excluída. Mas não queremos criar um Log de verdade quando fizermos o teste de exclusão e sim queremos verificar se o método da criação do Log foi chamado.

Para fazermos isso, usamos um objeto “Mockado” que é um objeto que simula o comportamento do nosso objeto. É, a grosso modo, um objeto falso que não tem inteligência.

Assim, pelo Teste, na exclusão de uma pessoa um Log é gerado pois ao chamar o método de exclusão, o método de criação do Log também é chamado, ou seja, nada de errado acontece pelo caminho.

Para uma visão geral do nosso Teste, vamos listar os nossos passos:
- Criamos a nossa classe de Teste PessoaTeste

- Criamos as classes: PessoaController, Log, Pessoa

- Sentimos dificuldade para fazer o teste no Controller pois ele estava muito acoplado com a classe de Log

- Criamos a Interface GeradorDeLog para a nossa classe de Log implementá-la

- Fizemos a nossa classe LogMock também implementar a Interface GeradorDeLog

- Passamos para o nosso Controller a nossa classe de Log “Mockada”, por Injeção de Dependência pelo construtor

- Identificamos pelo assertEquals se o método de geração de Log foi realmente invocado, verificando se o nome no Log era o mesmo nome da Pessoa

Finalizando
Legal! Conseguimos fazer o nosso Teste rodar, melhoramos um pouco o Design da nossa Aplicação aplicando a Inversão de Controle (mas podemos refatorar para algo bem melhor, claro!) mas acabamos ficando com essa classe horrorosa que é a LogMock.

Mas esta idéia não é só feia! Essa idéia só poderá ser usada caso tenhamos objetos simples. Imagine que temos um objeto que instancia outro objeto e este também instancia outro objeto e cada um tem diversos métodos. A nossa vida se resumiria a criar classes de Mock e isso não é legal.

É neste ponto que podemos usar  Frameworks para isso. Podemos “Mockar” as nossas dependências através destes Frameworks sem precisar criar outras classes para isso!

Mas este assunto é para o próximo artigo, onde trabalharemos com estas mesmas classes utilizando dois Frameworks bem bacanas do mercado!

Abraços pessoal!!!

About these ads

1 comentário

Deixe uma resposta

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s