Thursday, July 05, 2007 2:48 PM

O JP Boodhoo diz neste post que práticas ágeis podem ser implantandas de baixo para cima. O time de desenvolvimento pode, aos poucos, aplicar algumas práticas ágeis e mostrar para a gerência o valor que elas criam para o time, para empresa e para os clientes.

Concordo com ele. Se você acha que uma prática pode ajudar o seu trabalho do dia-a-dia, não espere seu gerente sugerir ou aprová-la. Comece aos poucos, mostre para ele o resultado, sugira que o restante do time aplique. Desta forma você estará contribuindo para que seu dia-a-dia melhore e, de quebra, ganhando alguns pontos com seu gerente.

Mas ai eu fiquei pensando... "como um desenvolvedor que não tem o hábito de criar testes unitários começa de um dia para outro? Onde começar? Como fazer certo?"

Um caminho possível é criar testes unitários para o código legado que você vai dar manutenção. Digamos que você tenha uma aplicação razoavelmente organizada em camadas. Isto significa que existe uma camada de negócios separada do resto da aplicação. Esta camada deve ser a mais alterada em manutencões evolutivas. Mais um motivo para você começar por ai.

Para o meu exemplo ficar mais real peguei uma aplicação conhecida, a famosa Pet Shop, na versão 4. Você pode pegar o código-fonte dela. Esta aplicação é muito bem organizada. Tem uma camada de acesso a dados que aplica o DAL, uma camada de negócios e uma camada de UI em ASP.NET. No entanto, ela não tem testes unitários, nem foi desenhada com isto em mente. Ou seja, um bom caso para nossa experiência. A instalação dela é simples, um msi, que coloca o código-fonte e uma instalação funcionando na pasta C:\program files\microsoft\Pet Shop 4.0\. Basta abrir o arquivo PetShop.sln no Visual Studio. A única alteração que fiz, para debugar, foi colocar o projeto web como start project.

Digamos que eu precise alterar regras de negócio da adição de um item no carrinho de compras, que fica na camada de negócios (BLL). Ao longo do desenvolvimento você terá que testar estas alterações, exercitando o método Cart.Add. A princípio o desenvolvedor pode fazer isto de duas formas:

  • Pela UI – Inicia a aplicação (F5), entra em uma seção, seleciona um animal, clica no add to shop cart. Uma vez, vá lá, mas fazer isto dezenas de vezes... cansa!
  • Instancia o objeto testado em uma aplicação simples – Esta alternativa é melhor, cria uma aplicação console, instancia um objeto Cart e chama o método Add. Isto é mais próximo do ideal, mas já que você vai fazer isto, porque não criar um teste unitário?

O que estou sugerindo não é nada de mais, já que você vai precisar exercitar aquele método trocentas vezes, porque não criar um código que faz isto e ainda testa o resultado, facilitando sua vida?

Vocês vão ver que é bem fácil fazer isto, vamos fazer juntos do zero. Para ficar mais emocionante, pelo menos para mim, vou fazer estilo 24 horas. Escrevendo o texto em paralelo com o desenvolvimento.

Primeiro vamos preparar a infra-estrutura:

  • Faça o download e instalação do NUnit
  • Faça o download e instalação do TestDriven

Só isto? Por enquanto sim. Agora mãos à obra. Com a solução do Pet shop aberta no Visual Studio faça o seguinte:

  1. Adicione um novo projeto do tipo class library com o nome BLLTest
  2. No projeto recém criado, adicione referências a: nunit.framework.dll, BLL (o projeto que contém a camada de negócios) e Model (o projeto que contém os DTO’s)
  3. Crie uma classe CartTest
  4. Adicione o using para dois namespaces: NUnit.Framework e PetShop.BLL
  5. Decore a classe com o atributo TestFixture
  6. Crie um novo método, retornando void e sem parâmetros, chamado AddItemToCart e decore-o com o atributo Test
  7. Implemente uma linha no método: Assert.Fail(“teste não implementado”);
  8. Clique com o botão direito do mouse sobre o teste e selecione Run test(s)

Ué, o teste falhou?! Isto mesmo, afinal nós não implementamos nada nele, mas já temos a base pronta. Por enquanto o código da classe está assim:

using NUnit.Framework;
using PetShop.BLL;

namespace BLLTest
{
    [TestFixture]
    public class CartTest 
    {
        [Test]
        public void AddItemToCart() 
        {
            Assert.Fail("Teste não implementado");
        }
    }
}

Vamos agora implementar o código para executar o método Add, da classe Cart. No big deal, instancio um objeto do tipo Cart e chamo o método Add passando como parâmetro o código de um item que sei que existe no banco de dados: "EST-1". Além disto mudo meu Assert, para testar se existe um item no carrinho de compras. O código fica assim:

    [TestFixture] 
public void AddItemToCart() { Cart cart = new Cart(); cart.Add("EST-1"); Assert.AreEqual(1, cart.Count, "O método Cart.Add não incluiu o item desejado"); }

Ótimo, botão direito, Run test(s) e ... oops! Um erro de run-time! Eu recebo a seguinte mensagem:

System.TypeInitializationException : The type initializer for 'PetShop.BLL.Item' threw an exception.

----> System.ArgumentNullException : Value cannot be null

Dois minutos de pesquisa, começando pela inicialização da classe PetShop.BLL.Item e percebo que algo dá errado nesta chamada:

    private static readonly IItem dal = PetShop.DALFactory.DataAccess.CreateItem();

O problema é que o método CreateItem acessa diretamente o arquivo de configuração da aplicação para saber qual o tipo de objeto DAL ele irá criar, o que acessa o banco SQL Server ou que acessa o banco Oracle. Nesta hora ele não acha o arquivo de configuração, não consegue instanciar o objeto DAL via reflection, retorna null e eu me ferrei na minha tentativa :(

Mas eu ainda não desisti só se passaram uns 15 minutos! Ainda estou no meu prazo. Vou fazer o seguinte: Crio no projeto de teste um Application Configuration File e copio todo o web.config, que se encontra na pasta C:\Program files\Microsoft\Pet Shop 4.0\Web\ neste meu arquivo.

Rodo meu teste e... um novo erro! Que droga! Desta vez recebo a seguinte mensagem:

System.TypeInitializationException : The type initializer for 'PetShop.BLL.Item' threw an exception.

----> System.IO.FileNotFoundException : Could not load file or assembly 'PetShop.SQLServerDAL' or one of its dependencies. The system cannot find the file specified.

Este problema parece simples, como o objeto DAL é instanciando vai reflection, ele precisa do assembly no lugar certo para conseguir pegá-lo. Para isto adiciono uma referência no meu projeto para o projeto SQLServerDAL.

Clico no botão direito, Run test(s) e ... Agora sim! Passed: 1.

Posso começar minha manutenção? Ainda não, você não sabe o quanto que este teste está cobrindo do seu código, tudo bem ele chamou o método, mas será que você está testando todas as opções? Para saber isto, executo novamente o teste, mas desta vez pela opção Test with > Team coverage.

O resultado do coverage aparece em um painel na parte inferior, 89,33% não coberto. O que?! Tudo isto, lógico você queria com um teste unitário cobrir todo o código do projeto? Mas não se assuste só vou me preocupar com a cobertura do método Add(), que é meu objetivo. Vai abrindo a árvore até chegar a PetShop.BLL.Cart.Add(string). Agora sim, estou cobrindo 83,33%.

Para saber o que falta cobrir clique com a direita sobre o método e selecione go to source code. O código pintado em salmão é o que não está sendo executado durante os testes. Lendo o código você percebe que o cenário de incluir um item que já existe no carrinho não está sendo coberto (como é bom ler um código bem escrito assim!).

Então preciso adicionar mais um item do mesmo tipo no carrinho e testar o resultado. Veja como ficou o novo código de teste:

    [TestFixture]
public void AddItemToCart()
    {
        Cart cart = new Cart();

        cart.Add("EST-1");

        Assert.AreEqual(1, cart.Count, "O método Cart.Add não incluiu o item desejado");

        cart.Add("EST-1");

        Assert.AreEqual(1, cart.Count, "Deve haver somente um item, pois foram incluído dois do mesmo tipo");
        Assert.AreEqual(2, cart.GetOrderLineItems()[0].Quantity, "deveriam ter dois itens do mesmo tipo aqui");
    }

E agora minha cobertura é de 100%. Estou pronto para começar a minha manutenção à vontade e testar sempre que quiser em apenas dois cliques do mouse.

Algumas conclusões:

Não gastei muito tempo para preparar tudo isto, com certeza bem menos do que gastaria só de abrir a aplicação no browser cada vez que quisesse testar se o método está funcionando ao longo da minha manutenção. Sem contar que existem algumas vantagens, posso guardar estes testes no controle de versão e disponibilizar para os outros desenvolvedores do meu time.

Eu ainda NÃO estou fazendo testes unitários. Meu teste depende do banco de dados e do arquivo de configuração, o que o torna mais lento e frágil. Mas ainda assim é uma forma de começar a brincar com as ferramentas e sentir o gostinho de quero mais.

Você vai perceber que boa parte das dificuldades de implementar os testes virá de pontos de melhoria do próprio design da aplicação. Veja o meu exemplo, mesmo uma aplicação bem arquitetada como a Pet Shop, mostrou alguns acoplamentos desnecessários, como o acesso direto ao arquivo de configuração em diversos pontos da aplicação.

Sem mais desculpas, baixe as ferramentas e comece a brincar de testes unitários, ontem!

< Exemplos >

Comments

At 7/6/2007 10:41 AM, Alexandre Tarifa said:

# re: Iniciando com testes unitários

Com certeza fazer os testes unitários não gera um trabalho muito grande, e o benefício disso é muito grande.

Nos últimos tempos venho controlando as implementações restantes tb através de testes unitários onde crio testes para todos os métodos que vão ser implementados no projeto... ou seja, tudo dá erro de falta de implementação... conforme vamos evoluindo, sabemos o quanto de código ainda temos pendentes... dependendo da sintonia da equipe isso funciona.

Complementando um pouco, este post http://blogs.msdn.com/jeffbe/archive/2007/07/02/team-system-testing-videos-available.aspx mostra alguns vídeos sobre testes no VSTS... na revista www.codificandomagazine.net tem uma coluna que mostra algumas dicas para criar os testes.

[]´s
At 7/11/2007 7:46 PM, Alfred Myers said:

# re: Iniciando com testes unitários

Usando o que o Tarifa falou dá até pra se ter uma estimativa grosseira do percentual concluído.
Mas o que eu queria falar mesmo é de um caso que aconteceu num projeto recente em que trabalhei.
Eu tive que implementar um protocolo de rede em nível de aplicação semelhante ao FTP ou o HTTP só que por algum motivo telúrico eu não tinha acesso ao código fonte do dito cujo
A única coisa que eu tinha era a lista dos comandos, seus parâmetros e retornos esperados.
Para ter certeza que a nossa implementação em C#/Windows era equivalente à implementação legada em C++/Linux, eu criei uma série de testes unitários e me certifiquei que passavam quando rodados contra a implementação deles.
Depois, foi "só" fazer os testes passarem com a nossa implementação.
O mais legal, é que com isto foi possível escalar bem a equipe com gente entrando e saindo conforme a disponibilidade de agenda, já que a tarefa era basicamente criar uma classe derivando de uma classe base e fazer com que esta nova classe passasse no teste unitário. Deveras supimpa!
Post Comment
Title *
Name *
Email (never displayed)
Website
Comment * (Allowed tags: blockquote, a, strong, em, p, u, strike, super, sub, code)  
Please add 2 and 3 and type the answer here: