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