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
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.
CurtirCurtir
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
CurtirCurtir