Após meu post Isolando testes unitários tive uma conversa por email com Eli Lopian, CTO e principal evangelista da TypeMock. Vou repassar aqui algumas coisas que aprendi.
A Primeira lição foi a aula de marketing e evangelismo. O Eli deve ter sido avisado por alguma ferramenta sobre o meu post, que mencionava o seu produto. Mesmo lendo aquela língua estranha, que ele confundiu com espanhol, fez questão de entender o meu exemplo e me enviar um email com sugestões e dicas de como utilizar melhor o TypeMock. Detalhe: no mesmo dia que publiquei o post.
Agora vamos às dicas que ele me passou. Primeiro ele sugeriu que eu sempre validasse os mocks ao final do teste. Isto pode ser feito de duas formas, aplicando um atributo no método de testes:
[Test]
[VerifyMocks]
public void Add_EmptyCart_CreateItem() {}
Ou ainda chamando o método Verify ao final da execução do método:
MockManager.Verify()
A grande vantagem a primeira opção, usando o atributo, é que a validação é feita mesmo quando o teste falha, deixando o estado do mock limpo após o teste. No entanto, o atributo só funciona na versão paga. Uma alternativa possível, que pensei, seria chamar o método Verify no tear down do teste.
Mas para que serve a validação dos mocks? Durante o teste nós estabelecemos comportamentos esperados para estes mocks, não foi? No meu exemplo eu defini que o método GetItem seria chamado e devia retornar um determinado valor:
mockedItem.ExpectAndReturn("GetItem", mockedItemInfo.Object);
Ao chamar o método Verify() o framework verifica se todas as chamadas esperadas aconteceram realmente. Isto é importante se você está realizando um teste interaction based. Calma, vou explicar mais este conceito.
Existem testes state based, que validam o estado de um ou mais objetos após uma ação. Um exemplo clássico é o da conta corrente, no teste você instancia uma conta corrente, executa o saque e depois valida o valor do saldo.
Outro tipo de teste é o interaction based. No qual você executa a ação a ser testada e depois valida se a seqüência esperada de interações entre o objeto testado e o restante da aplicação ocorreu realmente. Por exemplo, eu realizo um teste de cadastro do usuário e quero saber se a camada de acesso a dados foi chamada e se o componente de envio de email foi acionado.
Lógico que os interaction based são mais completos, mas também mais frágeis e suscetíveis a manutenção. Às vezes uma pequena alteração no método exigirá atualização no teste também.
Na minha resposta ao Eli retruquei que o meu teste era state based, não queria validar a interação da classe Cart com as outras, apenas testar o seu estado final. A resposta dele foi a seguinte.
Se você não quer validar [interação] você deve utilizar MockManager.ClearMocks() ou [ClearMocks] que irão limpar todos os mocks.
De qualquer maneira é uma boa prática utilizar MockAll e AlwaysReturn (ou recorder.RepeatAlways()) quando você não se importa quantas vezes o método será chamado. Isto tornará seu teste mais robusto a mudanças futuras na sua implementação.
Vivendo a aprendendo, agradeço o Eli pela conversa e atualizo o meu teste. Veja como fica então o resultado final.
[Test]
public void Add_EmptyCart_CreateItem()
{
//Inicia o framework de mocks
MockManager.Init();
//Cria os mocks, utilizando o MockAll
Mock mockedItem = MockManager.MockAll(typeof(Item));
MockObject mockedItemInfo = MockManager.MockObject(typeof (ItemInfo));
//Determina o comportamento esperado, utilizando o AlwaysReturn
mockedItem.AlwaysReturn("GetItem", mockedItemInfo.Object);
Cart cart = new Cart();
cart.Add("itemId");
Assert.AreEqual(1, cart.Count);
}
//Chama o ClearAll no tear down de cada teste
[TearDown]
public void TearDown()
{
MockManager.ClearAll();
}
Saiba mais sobre interaction x stated based test:
Mocks aren't stubs
TDD Design Starter Kit - State vs. Interaction Testing