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