No último post sobre teste unitário mostrei como utilizar testes automatizados pode facilitar a manutenção de código legado. Porém, como deixei claro, no final nossos testes não poderiam ser considerados unitários, pois dependiam do acesso ao Banco de dados e do arquivo de configuração.
Vamos tentar eliminar estas dependências para tornar nosso teste mais rápido, atômico e portável. Existem dois caminhos possíveis para isto:
Tentar refactorar o código para diminuir o seu acoplamento e utilizar uma ferramenta para criar mocks e/ou stubs para emular este relacionamento
Manter o código como está e utilizar o TypeMock para criar mocks e emular as classes que dependem do banco de dados e do arquivo de configuração.
A maioria das ferramentas de mock só consegue criar mocks e stubs de interfaces ou de classes abstratas com métodos virtuais. Isto pode parecer uma limitação, mas seus defensores afirmam que ela traz uma vantagem, forçam o baixo acoplamento entre as classes e camadas. Seguindo um caminho diverso, o TypeMock utiliza técnicas de AOP para interceptar as chamadas aos objetos e alterar o seu comportamento. Desta forma, não é necessário alterar o seu design para torná-lo mais testável, o que, para o criador do TypeMock, é uma vantagem.
Porém minha decisão aqui é menos conceitual e mais pedestre. Fazer refactoring em código não testado é um risco muito alto, que não estou disposto a correr. Portanto, o TypeMock é o escolhido. Agora falta outra decisão, em qual ponto irei isolar o código para o teste? Novamente tenho duas alternativas.
Se eu aplicasse a linha clássica, tentaria criar os mocks apenas nos pontos de contato com dependências externas, neste caso o banco e o arquivo de configuração.
Em uma linha mais radical, eu iria isolar totalmente a classe testada do resto código, com isto teria certeza de que só estou testando o comportamento da classe.
Novamente vou decidir de forma pragmática, eu não conheço muito bem o código legado, portanto é mais fácil isolar completamente a classe Cart.
Vamos para a parte prática então. Quais são as dependências da classe Cart e de seu método Add()?
O método Add, da classe Cart, utiliza três objetos: um do tipo Dictionary, que não apresenta nenhuma funcionalidade especial, portanto não precisa ser isolado, um objeto do tipo CartItemInfo, um do tipo ItemInfo e outro do tipo Item. Estes, portanto, são os objetos que devem ser “mockados”.
O preciso fazer, após instalar o TypeMock, é adicionar a TypeMock.dll ao projeto PetShopTest. Antes de começar a instanciar os mocks é necessário iniciar a ferramenta, chamando o método MockManager.Init().
using NUnit.Framework;
using PetShop.BLL;
using PetShop.Model;
using TypeMock;
namespace PetShop.Test.BLL
{
[TestFixture]
public class CartTest
{
[Test]
public void Add_EmptyCart_CreateItem()
{
//Inicia o framework de mocks
MockManager.Init();
//Cria os mocks
Mock mockedItem = MockManager.Mock(typeof(Item));
MockObject mockedItemInfo = MockManager.MockObject(typeof (ItemInfo));
//Determina o comportamento esperado
mockedItem.ExpectAndReturn("GetItem", mockedItemInfo.Object);
Cart cart = new Cart();
cart.Add("itemId");
Assert.AreEqual(1, cart.Count);
}
}
}
A mesma classe MockManager é utilizada para criar os três mocks. Quando instanciamos um mock de um tipo, o TypeMock automaticamente intercepta o próximo objeto criado deste tipo. Em outras palavras, quando o método Add for chamado e instanciar um objeto do tipo Item o TypeMock irá interceptar esta chamada e retornar um objeto “falso”, criado por ele. E qual o comportamento que desejo destes mocks? Muito pouco, apenas que quando o método GetItem, da classe Item, for chamado não faça nada a não ser retornar o mock do tipo ItemInfo.
Mas e o mock do CartItemInfo, para que serve? Nada, pois esta classe não faz muita coisa durante o processo. Por isto podemos eliminá-lo.
Agora vamos fazer o teste final, excluir o arquivo de configuração do projeto PetShopTest, parar o serviço do Banco de dados e executar o teste. O método GetItem era o que utilizava o arquivo de configuração e, de quebra, ainda chamava o método que acessava o banco. Como ele não é mais chamado, conseguimos isolar nosso teste totalmente das dependências externas.
Tarefa cumprida.