quarta-feira, 6 de junho de 2012

Mocks com Mockito - Parte 3

Neste post vamos abordar o uso de Spies usando o Mockito.

Ao contrário de Mocks com os Spies é possível chamar os métodos reais da classe.

List<String> listaSpy = new ArrayList<String>();
listaSpy = Mockito.spy(listaSpy);

listaSpy.add("item1");
//Chama o método verdadeiro do objeto e retorna a String = "item1"
listaSpy.get(0); 
Mockito.doReturn("outra coisa").when(listaSpy).get(0);
//Não chama o método verdadeiro e retorna a String = "outra coisa"
listaSpy.get(0); 

Ao contrário dos Mocks um objeto Spy age como se fosse um Semi mock, em outras palavras,  quando não definimos um outro comportamento para o Spy, seu funcionamento é igual ao de um objeto normal. No entanto ao definirmos um outro comportamento (linha 8) ele se comporta como um Mock para o caso que definimos.

Ok e eu com isso?! Vamos ver um uso mais real de um spy.

O uso que eu tive ao usar o Spy foi para Mockar criação/instanciação de objetos.

Por exemplo, na classe Connection temos o seguinte método.

//declaração da classe Connection
public boolean connect() {
  try {
    socket = new Socket(hostAddress, port);
    printStream = new PrintStream(socket.getOutputStream());
    bufferedReader = new BufferedReader(
                       new InputStreamReader(socket.getInputStream()));
    return true;
  } catch (UnknownHostException e) {
    e.printStackTrace();
  } catch (IOException e) {
    e.printStackTrace();
  }

  return false;
}

public String readMessage() { 
//...
  return bufferedReader.readLine();
//...
}

Como faremos para testar esta classe e o método readLine(), se no cenário atual nós não podemos criar um mock de BufferedReader em nosso teste e passa-la para o objeto Connect, pois o BufferedReader é instanciado dentro da classe Connect!. :/

Spy ao seu resgate! Uma solução é usar Spies para simular isso... então nosso primeiro passo é criar métodos na classe Connect que criem os objetos que precisamos.

Ficando assim o código.

//declaração da classe Connection
public boolean connect() {
  //..
  socket = createSocket();
  printStream = createPrintStream();
  bufferedReader = createInputStream();
  return true;
  //...
}

public String readMessage() { 
  //...
  return bufferedReader.readLine();
  //...
}

protected Socket createSocket() //...
protected PrintStream createPrintStream() //...
protected BufferedReader createInputStream() //...


Veja que os métodos que criam os objetos são protected, isso é necessário pois o Mockito não consegue interceptar métodos que são privados.

O teste da classe ficará assim:

import static org.mockito.Mockito.*;
import static org.junit.Asserts.*;

@Test public void shouldReadMessage() throws UnknownHostException, IOException {
  //cria instancia da classe a ser testada
  Connection connection = new Connection("127.0.0.1", 14000);
  //transforma em um spy
  connection = spy(connection);
  //cria os mocks que serão retornados 
  //pelos metodos de criação de objetos 
  Socket socket = mock(Socket.class);
  BufferedReader bufferedReader = mock(BufferedReader.class);
  PrintStream printStream = mock(PrintStream.class);

  //define o comportamento dos métodos 
  doReturn(socket).when(connection).createSocket();
  doReturn(bufferedReader).when(connection).createInputStream();
  doReturn(printStream).when(connection).createPrintStream();
  when(bufferedReader.readLine()).thenReturn("readed from mock");

  connection.connect();
  assertNotNull(connection.readMessage());
}

Veja que apesar de termos que alterar a nossa classe e extrair as criações de objetos para métodos protected, foi possível criar um teste do método readMessage usando mocks dos objetos do socket.

O uso de Spies da maneira que foi mostrada, pode indicar que seu código esteja com um acoplamento alto e como sempre cabe pensar um pouco se não existe um jeito melhor de programar sua classe para evitar isto.

Isso conclui nossa série de posts sobre o Mockito, pelo menos por enquanto, espero muito que a pequena introdução que escrevi aqui os ajudem a criar seus testes unitários assim como tem me ajudado bastante.

Segue os links que usei de referência
Mocking object creation
Mockito class javadoc
Site do Mockito

Código fonte: https://github.com/diegoy/talks-mockito

Qualquer dúvida, como sempre perguntem, farei o possível para ajudar.

terça-feira, 5 de junho de 2012

Mocks com Mockito - parte 2

Para continuar nosso "papo" sobre Mocks vou apresentar mais algumas funções do Mockito

Como talvez você já tenha percebido o mockito não serve só para mocks, mas serve também para testar o comportamento da classe que está sob teste com suas dependências.

Usando o mesmo exemplo da nossa classe que envia mensagens. Imagine agora que temos um novo métod que recebe uma ou várias mensagens e as envia usando o objeto connection.

Usando o Mockito podemos escrever um teste como o abaixo.

import static org.mockito.Mockito.*;
//class declaration bla bla bla.
@Test
public void behaviourTest() {
  Connection connection = mock(Connection.class);
  
  MyMessageSender messageSender = new MyMessageSender(connection);
  
  messageSender.sendMultipleMessages("Mensagem 1", "Mensagem 2");
  
  verify(connection, times(2)).sendMessage(anyString());
}


Traduzindo a Linha 11:
  • Verificar que o método sendMessage com o argumento...
    • uma string qualquer
  • foi chamada duas vezes

O Mockito também possibilita simular o retorno de mensagens.

Para demonstrar isto criaremos um novo para sabe se a nossa classe está recebendo as mensagens corretamente, nesse caso ao invés de usar o mock para testar a passagem de parâmetros para ele teremos que usa-lo para simular os retornos de seus métodos.

Testaremos a classe MyMessageReader que se parece com a seguinte classe.

public class MyMessageReader {
  private Connection connection;
  
  public MyMessageReader(Connection connection) {
    this.connection = connection;
  }

  public String readMessage() { 
   return connection.readMessage();
  }
}

Novamente nossa classe sob teste usa um objeto Connection, mas desta vez o método que chamamos é connection.readMessage()

Nosso teste então terá que simular um retorno de connection.readMessage(), usando o Mockito ficaria como o código abaixo.

@Test
public void readMessageTest() {
  connection = mock(Connection.class);
  
  given(connection.readMessage()).willReturn("minhaMensagem");
  myMessageReader = new MyMessageReader(connection);
  
  String readedMessage = myMessageReader.readMessage();
  
  assertEquals(readedMessage, "minhaMensagem");
}

Claro que como tudo em programação existe mais de uma maneira simular os retornos de chamados dos métodos, as duas linhas seguintes são equivalentes.

given(connection.readMessage()).willReturn("minhaMensagem");
when(connection.readMessage()).thenReturn("minhaMensagem");

A primeira linha tem uma abordagem mais em linha com BDD, tanto que o método given é da classe BDDMockito.

Caso o método que simulamos o retorno receba um parâmetro podemos definir diferentes tipos de retorno para diferentes parâmetros, como no exemplo abaixo.

Map map = mock(Map.class);
given(map.get(anyString())).willReturn(null);
given(map.get("chave1")).willReturn("valor1");

map.get("chave1"); //retorna string "valor1"
map.get("outraChave");//retorna nulo

No próximo post de Mocks mostrarei como usar Spies em seus testes com Mock.

Se quiser o código completo pode pegar no git hub.
https://github.com/diegoy/talks-mockito

Valeu!

Mais:
Mocks com Mockito, parte 3

domingo, 3 de junho de 2012

Mocks com Mockito

Desta vez vamos ter uma breve introdução ao uso de Mocks em seus testes.

Há vários benefícios que o uso de testes unitários podem trazer, eu pessoalmente gosto muito de ter testes pois ajuda muito na hora de refatorar seu código e ter a certeza de que o seu funcionamento não mudou, escrever os testes também nos ajuda a identificar casos em que o código está com um alto acoplamento.

Usando TDD ou não os testes são importantes mas podem ser muito difícil de escreve-los

Muitas vezes ao tentar escrever testes para algumas classes temos alguns desafios.
Um Software geralmente depende de vários outros sistemas para funcionar, por exemplo uma base de dados, levando isso em conta os testes de classes que se comunicam com a base de dados são mais difíceis de escrever e o uso de um framework de mocks pode ajudar muito! 

Neste artigo vamos falar do Mockito, que é um framework de mocks criado para a linguagem Java (há versões para outras linguagens). 

A instalação é simples, baixe o jar no site do mockito, coloque-o no seu classpath e pronto! Ou se preferir configure no dependencies de seu maven :)

Suponha que em seu código você tenha uma classe que seja responsável por enviar mensagens através de um socket, aqui vou chama-la de MyMessageSender, sua classe tem como regra enviar mensagens somente com letras maiúsculas.

public class MyMessageSender {
 
  private Connection connection;

  public MyMessageSender(Connection connection) {
    this.connection = connection;
  }

  public void send(String string) {
    connection.sendMessage(string.toUpperCase());
  } 
}

No construtor da classe MyMessageSender passamos como parâmetro um objeto do tipo Connection. Esse objeto possui a conexão do socket e também possui um método sendMessage que escreve uma mensagem no socket.

Para fazer o teste unitário desta classe necessitaríamos instanciar a classe connection para passarmos à classe testada.
 
public class Connection {

  // declaracao dos atributos

  public Connection(String hostAddress, int port) {
    super();
    
    this.hostAddress = hostAddress;
    this.port = port;
  }
 
  public boolean connect() {
    try {
      socket = new Socket(hostAddress, port);
      printStream = getPrintStream(); //abre um output stream
      inputStreamReader = getOutputStream(); //abre um input stream
      return true;
    } catch (UnknownHostException e) {
      e.printStackTrace();
    } catch (IOException e) {
      e.printStackTrace();
    }
  
    return false;
  }
 
  public void sendMessage(String message) {
    printStream.print(message);
    printStream.flush();
  }
}

Vendo a classe Connection perceba que será um pouco trabalhoso instancia-la dentro do teste, pois ela se conecta a um sistema externo, levando isso em conta precisaríamos também criar um mock desse sistema externo para testarmos adequadamente a nossa classe. E consequentemente estaríamos testando muito mais que somente nossa classe MyMessageSender.

Usando o Mockito podemos facilmente criar um mock de Connection para usar em nosso teste sem termos que nos preocupar com as conexões que são necessárias a classe Connection.

@Test
public void sendMessageTest() {
 Connection connection = Mockito.mock(Connection.class);
 
 MyMessageSender messageSender = new MyMessageSender(connection);
 
 messageSender.send("Uma mensagem");
 Mockito.verify(connection).sendmessage("UMA MENSAGEM");
}

Na linha 3 criamos um mock da classe Connection.

Na linha 8 colocamos uma verificamos que o método sendmessage do objeto connection foi chamado passando como parâmetro a string "UMA MENSAGEM". Qualquer string diferente da informada na linha do verify vai causar uma falha no teste.

Assim conseguimos garantir o correto funcionamento da classe MyMessageSender sem precisar ficar criando uma instância real da classe Connection.

No próximo post escreverei como o Mockito pode te ajudar para simular respostas de métodos.

Código fonte disponível no meu github
https://github.com/diegoy/talks-mockito

Mais:
Mocks com Mockito, parte 2
Mocks com Mockito, parte 3