Testes de interação usando mocks

Fala, galera

Este post é uma continuação do post sobre stubs, portanto, recomendo a leitura daquele aqui para que este possa ser melhor entendido. Dito isto, falarei desta vez sobre testes de unidade com mocks e no que eles diferem dos stubs.

Mocks são fakes assim como os stubs, porém um mock decide se um teste passou ou falhou. Outra forma de dizer isso é que stubs são usados em testes de estado, isto é, testes orientados ao resultado. Um método é testado e espera-se que algum resultado seja “X” ou verdadeiro ou não nulo, geralmente checando o valor retornado pelo método.

Por sua vez, mocks são usados em testes de interação, isto é, testes orientados à ação. O mock verifica se o método sob teste executa uma determinada ação (e não se ele retornou algo).

Mais uma vez, vou mostrar um exemplo de código em C#, implementando “na mão” um mock. A classe a ser testada será a mesma AnalisadorDeLog do exemplo com stub, mas agora o método sob teste será o AnalisarArquivo, que faz o seguinte: sempre que um arquivo de log tiver um tamanho inválido, uma mensagem de erro deverá ser passada para um webservice (em seu método LogarErro).

Note que agora nossa dependência externa é um webservice e que queremos garantir que o método AnalisarArquivo chame o método LogarErro do webservice quando necessário. Estamos testando a interação de AnalisarArquivo com o webservice, ao invés de testarmos um resultado (como no exemplo de stub). Esta é a grande diferença de propósitos entre stubs e mocks.

1) Começamos extraindo uma interface que será implementada tanto pelo webservice real como pelo mock:

public interface IWebService
{
   void LogarErro(string mensagemDeErro);
}

2) Agora codificamos nosso mock, o qual implementa a interface IWebService:

public class MockWebService : IWebService
{
   public string UltimoErro;

   public void LogarErro(string mensagemDeErro)
   {
      // nosso mock não é um webservice de verdade
      // ele simplesmente guarda a mensagem passada em um atributo
      // para usarmos mais tarde em nosso teste
      UltimoErro = mensagemDeErro;
   }
}

Note que até agora não temos nada de muito diferente de um stub, a não ser pelo atributo “UltimoErro” que guarda a mensagem de erro recebida para usarmos posteriormente.

3) Antes tarde do que nunca, vamos mostrar a classe AnalisadorDeLog com o método a ser testado. Note que ela já está projetada para ser testável, utilizando injeção de dependência para trabalhar com um webservice qualquer que implemente IWebService (inclusive nosso mock):

public class AnalisadorDeLog
{
   private IWebService _servico;

   public AnalisadorDeLog(IWebService servico)
   {
      _servico = servico;
   }

   // método a ser testado
   // (quando tamanho do arquivo for menor que 8 caracteres,
   // um webservice "loga" o erro)
   public void AnalisarArquivo(string nomeDoArquivo)
   {
      if (nomeDoArquivo.Length < 8 )
         _servico.LogarErro("Tamanho do nome muito pequeno para:" + nomeDoArquivo);
   }
}

4) Ok. Temos nosso mock e nossa classe AnalisadorDeLog. Agora vamos testar o método AnalisarArquivo para verificarmos que ele interage com um webservice quando o nome do arquivo é pequeno:

[TestClass]
public class AnalisadorDeLogTeste
{
   [TestMethod]
   public void AnalisarArquivo_ComNomeMenorQueOitoCaracteres_ChamaWebService()
   {
      // ARRANGE
      // criamos nosso mock (o webservice "de mentira")
      MockService servicoFake = new MockService();
      // definimos um nome de arquivo menor que oito caracteres
      string nomeArquivo = "123.ext";
      // instanciamos o AnalisadorDeLog passando o mock ao invés do webservice real
      AnalisadorDeLog log = new AnalisadorDeLog(servicoFake);

      // ACT
      // agora testamos nosso método AnalisarArquivo
      log.AnalisarArquivo(nomeArquivo);

      // ASSERT
      // verificamos se houve a interação com nosso webservice
      // para isso, verificamos se a propriedade UltimoErro é a esperada
      Assert.AreEqual("Tamanho do nome muito pequeno para:123.ext", servicoFake.UltimoErro);
   }
}

Feito! Agora é rodar o teste e vê-lo passar (espero que sim 🙂 ).  Passando o teste significa que o método AnalisarArquivo chamou o webservice como esperado.

De novo, o propósito foi dar uma idéia do que seja um mock, usando um exemplo bem simples. Na “vida real”, no entanto, fica muito trabalhoso criar mocks e stubs manualmente.

É aí que entram em ação os frameworks de mock (mock object frameworks ou isolation frameworks), os quais criam mocks e stubs pra nós e permitem, por exemplo, que possamos verificar se um sequência de métodos foi chamada ou se um determinado método foi chamado “X” vezes. Tudo isso de forma bem simples e com poucas linhas de código. Mas esse tipo de framework será assunto para um outro post.

Deixem seus comentários, sugestões ou críticas.

Até a próxima!

Referência:
Osherove, Roy. 2009. The Art of Unit Testing with examples in .NET. Manning.

AnalisadorDeLog
Anúncios

2 comentários em “Testes de interação usando mocks

  1. Oi Robson, blz?
    Estou estudando sobre testes e acabei de ler os seus 2 posts sobre testes de interação, e fiquei me perguntando: se eu quisesse testar tanto o envio de mensagem para o webservice (com o mock) quanto a validação do nome do arquivo (stub), teria que ficar “sujando” a classe AnalisadorDeLog com um construtor para o mock e outro para o stub (para não ter que instanciar o mock onde fosse testar o stub e vice-versa) ? Não sei fui bem claro, mas minha preocupação está em justamente ficar alterando a estrutura da classe pra que ela se “encaixe” no teste. Aí você falaria: Mas temos que passar pelo Red, Green, Refactor. Tudo bem, só fiquei com a impressão de não ser legal por não ser utilizado para a aplicação em si e sim somente para os testes. Mas como iniciante posso estar falando bobeira, se for caso pode me corrigir.

    1. Opa, cara
      Desculpe pela demora.
      Não entendi muito bem sua dúvida e sobre o uso dos dois construtores, mas deixa eu tentar…
      A princípio você usaria o mesmo que recebe um IWebService.
      O teste tem um PROPÓSITO e esse propósito é que vai dizer se você vai fazer um stub ou um mock.
      Se você quer testar o estado do seu objeto após a invocação do método, seu assert será contra o objeto sob teste (nesse caso, faz-se um stub do serviço para fingir que o chamou e retornou o que você precisa para continuar a execução do método).
      Se você quer testar a interação com o serviço, seu assert será contra o serviço (nesse caso, faz-se um mock do serviço e o mock é que faz a asserção). Na prática, usamos um framework de isolamento (como o Moq) e teríamos algo como: mockDoServico.Verify(m => m.Validar()). Nessa caso, o teste falha se o serviço não tiver chamado o método Validar.

      E, realmente, ter código de produção que só é usado pelos testes cheira mal. O ideal é que isso não ocorra.
      Não sei se consegui esclarecer alguma coisa, mas fique a vontade pra postar de novo.
      []s

Participe! Vamos trocar uma ideia sobre desenvolvimento de software!

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