Friday, July 27, 2007 12:42 AM

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.

< Negócios >

Comments

At 7/30/2007 10:06 AM, Eduardo Miranda said:

# re: Isolando testes unitários

Leia atualizações neste exemplo no post seguinte:
http://www.eduardomiranda.net/blogs/dotnet/archive/2007/07/30/dicas-sobre-o-typemock.aspx
Post Comment
Title *
Name *
Email (never displayed)
Website
Comment * (Allowed tags: blockquote, a, strong, em, p, u, strike, super, sub, code)  
Please add 8 and 3 and type the answer here: