Testes unitários com EasyMock e JUnit – Introdução

A partir de agora, o Blog do Seu Enium terá a participação de um novo colaborador. Seu nome é Leandro, mais conhecido como Mendigo, e trabalhamos juntos no UOL.

Para iniciar a participação dele no blog, ele vai falar um pouco sobre uma biblioteca que auxilia bastante na hora de efetuar testes unitários com java: EasyMock.

Esta biblioteca permite que nós criemos objetos de um determinado tipo (classe ou interface), para podermos isolar a classe a ser testada.

Mas por que fazer isso? Bom, quando estamos testando de forma unitária, é interessante que foquemos somente na classe, ou mesmo somente em um método da classe. Porém, em um projeto orientado à objetos, o que temos, idealmente, é um conjunto de classes efetuando tarefas específicas e interagindo entre si. A grande maioria das classes possui dependências em relação à outras classes. É importante que o grau de dependência (ou acoplamento) seja baixo, para que alterações em dependências não afetem drasticamente a classe dependente, mas esse acoplamento dificilmente será zero (nesse caso teríamos uma classe inchada, fazendo muita coisa, e difícil de manter).

Por exemplo, vamos considerar uma classe que leia informações de uma fonte de entrada, e escreva informações em um destino. Além disso, essa classe é capaz de interpretar alguns comandos básicos para manipular os dados de entrada (neste primeiro exemplo a classe sabe somente enviar a hora atual).

(o código do exemplo está disponível aqui)

Processor

package foo;
 
public class Processor
{
 
   private Source source;
   private Destination destination;
 
   public Processor( Source source, Destination destination ) {
      this.source = source;
      this.destination = destination;
   }
 
   public void process() {
      String input = null;
      try
      {
         input = source.read();
      }
      catch (Exception e)
      {
 
      }
 
      if( input != null ) {
         String output = transform( input );
 
         try
         {
            destination.write( output );
         }
         catch (Exception e)
         {
            throw new ProcessorException("Nao foi possível enviar os dados");
         }
      }
   }
 
   private String transform(String input)
   {
      if( "[[Time]]".equals( input ) ) {
         return "time: " + System.currentTimeMillis();
      }
      return input;
   }
}

Source

package foo;
 
public interface Source
{
 
   String read();
 
}

Destination

package foo;
 
public interface Destination
{
 
   void write(String output);
 
}

Para complicar um pouco, vamos considerar que Source obtém os dados de um banco de dados e que Destination envia os dados escritos pela rede. Dessa foram para testarmos a classe Processor, seria necessário acessar um banco de dados e enviar dados pela rede.

Em testes de integração ou de sistema, isso está OK, mas em testes de unidade não. Estes deveriam ser isolados, contidos, não condicionados à disponibilidade ou não dos recursos externos como rede ou banco. Além disso, eles deveriam rodar de forma muito rápida, para não atrasar o processo de build e de desenvolvimento.

Com o EasyMock podemos criar um objeto que se passe por um Source, mas que não acessa realmente o banco de dados, e um outro que emule o comportamento de um Destination, sem efetivamente enviar dados pela rede, isolando assim a nossa “unidade” (Processor) e tendo um controle total sobre os mocks (Neste post estou chamando tudo de mock, porém há algumas implicações “técnicas” nesta nomenclatura, e para quem quiser se aprofundar melhor, sugiro este texto: http://martinfowler.com/articles/mocksArentStubs.html)

Vamos escrever um teste para o fluxo mais básico do nosso Processor.

package foo;
 
import org.easymock.EasyMock;
import org.junit.Test;
 
public class ProcessorTest
{
 
   @Test
   public void testProcess()
   {
      // Criação dos mocks e configurando o comportamento esperado
      Source source = EasyMock.createMock(Source.class);
      Destination destination = EasyMock.createMock(Destination.class);
 
      EasyMock.expect(source.read()).andReturn("SeuEnium");
      destination.write("SeuEnium");
 
      EasyMock.replay(source, destination);
 
      // Testando a nossa "unidade"
      Processor processor = new Processor(source, destination);
 
      processor.process();
 
      // Verificando o comportamento
      EasyMock.verify(source, destination);
   }
 
}

Neste teste podemos ver um esquema básico de utilização de mocks:

    1. Criação dos mocks – a forma mais simples é a ilustrada no exemplo: EasyMock.createMock( Source.class )
      Este método irá criar um mock do tipo especificado (Classe ou Interface) – O EasyMock é capaz de criar mocks tanto de classes quanto de interfaces (neste exemplo vamos lidar somente com interfaces)
      Comportamento esperado – os mocks são criados em um estado de gravação – tudo o que for feito com o objeto até a chamada de EasyMock.replay( source ) será gravado internamente pelo EasyMock, e esse comportamento será depois validado.
      Como podemos ver no exemplo, utilizando EasyMock.expect( source.read() ) estamos dizendo ao EasyMock que é esperada uma chamada ao método read do mock, e que esta chamada deve retornar a String “SeuEnium”. Porém, como podemos ver em destination.write("SeuEnium"), não é necessário utilizar EasyMock.expect para mockar uma chamada (neste caso não é nem possível, pois o método write é void, e ocorrerá um erro de compilação caso se tente mockar write utilizando EasyMock.expect). O uso de EasyMock.expect se faz necessário quando queremos especificar um valor de retorno para o método mockado.
      Para indicar ao EasyMock que já configuramos o comportamento do mock e que a partir deste momento o objeto deve se comportar realmente como um objeto “normal”, utilizamos EasyMock.replay( source, destination ). A partir deste ponto, não é mais possível efetuar chamadas a EasyMock.expect, a não ser que se utilize EasyMock.reset( source ).
      Execução do teste da “unidade” – aqui vamos fazer o teste propriamente dito e efetuar quaisquer verificações de resultado da unidade testada.
      Verificação do comportamentoEasyMock.verify( source, destination ) diz ao EasyMock para verificar se os métodos esperados foram chamados – se o comportamento esperado não for satisfeito, o EasyMock irá disparar um exceção, fazendo o teste unitário falhar.

O EasyMock oferece uma grande flexibilidade na especificação do comportamento dos mocks, o que permite que sejam testados muitos cenários que de outro modo seriam difíceis de testar – por exemplo, vamos supor que queremos verificar que nosso Processor se comporte de uma determinada maneira quando ocorrer um problema ao ler do banco de dados. Podemos dizer ao EasyMock para disparar uma exceção no momento de leitura, e verificar o comportamento do Processor quando isso acontece.

   @Test
   public void testProcessWhenReadFails() {
      // Criação dos mocks e configurando o comportamento esperado
      Source source = EasyMock.createMock(Source.class);
      Destination destination = EasyMock.createMock(Destination.class);
 
      // Especificando o disparo de uma exceção
      EasyMock.expect(source.read()).andThrow(new RuntimeException("Erro na leitura"));
 
      EasyMock.replay(source, destination);
 
      // Testando a nossa "unidade"
      Processor processor = new Processor( source, destination );
 
      processor.process();
 
      // Verificando o comportamento
      EasyMock.verify(source, destination);
   }

Neste código estamos testando que Processor nunca irá tentar escrever os dados pela rede quando houver uma exceção na leitura dos dados – Como não configuramos nenhum comportamento para destination, qualquer chamada a métodos desse objeto irá disparar uma exceção.

Digamos agora que gostaríamos de testar o comportamento de Processor quando ocorrer um erro ao escrever pela rede. Neste caso, gostaríamos que uma exceção fosse lançada pelo próprio Processor, para notificar o erro.

   @Test(expected=ProcessorException.class)
   public void testProcessWhenWriteFails() {
      // Criação dos mocks e configurando o comportamento esperado
      Source source = EasyMock.createMock(Source.class);
      Destination destination = EasyMock.createMock(Destination.class);
 
      // Especificando o disparo de uma exceção
      EasyMock.expect(source.read()).andReturn("SeuEnium");
      destination.write("SeuEnium");
      EasyMock.expectLastCall().andThrow( new RuntimeException("Erro na escrita") );
 
      EasyMock.replay(source, destination);
 
      // Testando a nossa "unidade"
      Processor processor = new Processor( source, destination );
 
      processor.process();
   }

Um outro ponto em que o EasyMock oferece flexibilidade é na verificação dos parâmetros passados para as chamadas ao mock. Por padrão, o EasyMock irá utilizar o equals() para verificar se o parâmetro fornecido é o mesmo que foi configurado como esperado. Porém, algumas vezes esse comportamento pode ser restritivo, e o EasyMock oferece formas para mudarmos a maneira como ele irá verificar os parâmetros.

Por exemplo, o no processor sabe interpretar o comando “[[Time]]”. Quando essa String é recebida, o processor envia pela rede o tempo em milissegundos atual. Da maneira como está implementado, o tempo enviado irá ser diferente a cada execução do teste. Há diversas maneiras de testar este tipo de comportamento, e a que vamos utilizar aqui não é a melhor delas, mas ela ilustra o conceito. Vamos utilizar um regexp para validar se foi enviado o tempo para o destino.

   @Test
   public void testProcess2() {
      // Criação dos mocks e configurando o comportamento esperado
      Source source = EasyMock.createMock(Source.class);
      Destination destination = EasyMock.createMock(Destination.class);
 
      // Especificando o disparo de uma exceção
      EasyMock.expect(source.read()).andReturn("[[Time]]");
      destination.write( EasyMock.matches( "time: \\d+" ) );
 
      EasyMock.replay(source, destination);
 
      // Testando a nossa "unidade"
      Processor processor = new Processor( source, destination );
 
      processor.process();
   }

Bom pessoal, essa foi uma introdução ao uso do EasyMock para facilitar testes de unidade. No próximo post, vamos dar uma olhada em um exemplo um pouco menos vago, e vamos ver como efetuar mock de classes realmente, e não somente de interfaces.

Um abraço.

Tags: , , ,

  • Veio

    Mendigo,

    Ficou muito bom o post. Eu não conhecia o EasyMock mas, após tão elucidativa descrição e exemplo, pretendo torná-lo parte do meu dia-a-dia.

    Abraço!

  • Veio

    Mendigo,

    Ficou muito bom o post. Eu não conhecia o EasyMock mas, após tão elucidativa descrição e exemplo, pretendo torná-lo parte do meu dia-a-dia.

    Abraço!

  • leandro

    Valeu Veio!
    Eu uso bastante o EasyMock aqui, para facilitar os testes unitários, e dá para fazer coisas bem legais e malucas com ele para testar quase tudo. No próximo post vou falar disso.

    []‘s

  • http://rafaelnaufal.com/blog Rafael

    Leandro,

    mto bom o post, bem explicativo e didático. Eu gosto bastante do EasyMock tb! Há um tempo atrás comecei a explorar um projeto chamado hamcrest, que tem por base permitir a construção de testes de “unidade” mais expressivos e legíveis, como se fosse uma mini DSL para testes. Eu já escrevi um pouco sobre ele aqui.

    []‘s!

  • http://rafaelnaufal.com/blog Rafael

    Leandro,

    mto bom o post, bem explicativo e didático. Eu gosto bastante do EasyMock tb! Há um tempo atrás comecei a explorar um projeto chamado hamcrest, que tem por base permitir a construção de testes de “unidade” mais expressivos e legíveis, como se fosse uma mini DSL para testes. Eu já escrevi um pouco sobre ele aqui.

    []‘s!

  • Bruno

    Legal o post! Tomara que vc continue escrevendo sobre o EasyMock. Mas tenho uma dúvida. Como ficaria a reutilização do conjunto de casos de teste em um possível teste de integração?

    []‘s

  • Bruno

    Legal o post! Tomara que vc continue escrevendo sobre o EasyMock. Mas tenho uma dúvida. Como ficaria a reutilização do conjunto de casos de teste em um possível teste de integração?

    []‘s

  • leandro

    Obrigado Bruno!

    Eu não tenho certeza se entendi sua pergunta, então vou responder baseado no que entendi, e caso eu esteja seguindo uma linha diferente do que você está querendo dizer, me fale :D .

    Os test doubles (mocks, stubs, etc…) são utilizados para testes de unidade, para que você possa isolar a classe que você quer testar.
    Imagine que a gente tenha duas classes, A e B, e A utiliza a classe B. Se a gente não utilizasse mocks, você deveria passar uma implementação real da classe B para a classe A, e o que aconteceria é que você acabaria testando as duas, mesmo que o teste esteja chamando somente métodos de A.

    Não há nada de errado nisso, mas considere que você faça uma alteração na classe B, e essa alteração faça A se comportar de uma maneira que o teste falhe. Neste caso, quem está errado, A ou B?

    Se você garante que as classes estão isoladas, você tem um controle e uma segurança maior sobre alterações efetuadas.

    Isso não excluí de maneira nenhuma um outro tipo de teste, o de integração. Neste tipo de teste, nós queremos realmente testar todas as nossas classes e componentes, com as implementações reais, e neste caso eu não vejo motivo para utilizar mocks (talvez você queira “mockar” sistemas externos ao seu, mas isso é outro ponto, e há outras alternativas para isso).

    Indo no sentido de reutilização de casos de teste para integração, uma boa maneira seria utilizar os PageObjects.

    []‘s

  • Luiz

    Parabéns pelo post. Foi o primeiro post que li e entendi realmente o que o easymock propõe e faz na prática. Melhor que a documentação do próprio easymock.

    Muito obrigado.

  • Luiz

    Parabéns pelo post. Foi o primeiro post que li e entendi realmente o que o easymock propõe e faz na prática. Melhor que a documentação do próprio easymock.

    Muito obrigado.

  • http://twitter.com/edipofederle edipofederle

    Muito bom o post, so uma dica, em cada teste você faz:

    Source source = EasyMock.createMock(Source.class);
    Destination destination = EasyMock.createMock(Destination.class);

    Seria interessnte colocar isso num método assinado com @Before, para tirar a duplicação.

    Valeu e parabens

  • http://pulse.yahoo.com/_F54YPZ2BN7JJCQC6DMWLC2V6RE rodrigo t

    Parabéns pelo post!!!

  • http://twitter.com/marciogh Marcio Ghiraldelli

    Legal, não conhecia. Na próxima vou utilizar

  • http://twitter.com/camilolope camilo lopes

     muito bom o post, de fato bem elaborado e explicativo.! 
    parabens!! vale uma sugestão? Se rolasse uma comparando o easymock com os demais frameworks mocks que temos seria show!! :D
    abracos, 

blog comments powered by Disqus