Testes de interação usando mocks

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.

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

AnalisadorDeLog

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

Adicione o seu

  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.

    Curtir

    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

      Curtir

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 )

Foto do Google

Você está comentando utilizando sua conta Google. 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 )

Conectando a %s

Blog no WordPress.com.

Acima ↑

%d blogueiros gostam disto: