Desenvolvimento - Visual Basic .NET

Introduzindo TDD para .Net com NUnit

O conceito de TDD vem crescendo muito com a adoção da metodologia “Extreme Programming” (XP), mas pode ser usado em separado, dentro de qualquer outra metodologia de desenvolvimento de software.

por Rodrigo Vieira



1) O que é TDD e por quê ele é uma boa idéia?

O conceito de TDD vem crescendo muito com a adoção da metodologia "Extreme Programming" (XP), mas pode ser usado em separado, dentro de qualquer outra metodologia de desenvolvimento de software.

A grande vantagem de TDD é produzir código confiável em menos tempo, e com menos bugs. Entre outras coisas, essa técnica permite que você, após fazer uma determinada mudança (grande ou pequena) no seu sistema, possa testá-lo e ter certeza de que tudo ainda funciona, em questão de segundos. Certamente muito melhor do que ter que testar todo o sistema manualmente, tentando pensar em todo tipo de erro possível, ou ter que seguir uma checklist, não?

O processo de TDD é simples:

  1. Antes de começar a codificar uma determinada função, pense em testes que você poderia fazer pra ter certeza que ela funciona;
  2. Declare a sua função, mas apenas o "envelope", não entre nenhum código propriamente dito;
  3. Crie os testes pra essa função: eles irão falhar, afinal, sua função ainda não faz nada;
  4. Escreva o código da função, e rode os testes; ajuste o código até que todos testes passem;
  5. Repita o passo 1 para a próxima função.

Como você pode ver, uma vantagem dessa metodologia é que ela é extremamente pragmática, e faz que você programe guiado por intenção, ou seja, primeiro você pensa qual o objetivo que você quer alcançar pra só então começar a escrever o código que alcance esses objetivos. Uma outra consequência interessante é que esse método reduz a possibilidade de que você sobrecarregue o código, ou seja, escreva código desnecessário: se você pensou em todos testes possíveis, e seu código passa em todos eles, é porque você terminou o seu trabalho.

2) Um exemplo prático

Vamos ver como isso funcionaria então, com um exemplo bem simples: suponha que eu precise criar uma função para cálculo de impostos sobre um valor, com as seguintes regras:

a) Caso o valor seja menor que 1.000, está isento de impostos;
b) Para valores entre 1.000 e 10.000 é cobrado 3% de imposto;
c) Para valores acima de 10.000 é cobrado 3% de imposto mais 250 reais de sobretaxa.

Para executarmos os testes, precisaremos utilizar uma ferramenta que nos permita rodá-los rapidamente. Obviamente poderíamos criar funções-teste em uma biblioteca à parte e criarmos algumas janelas ou páginas para testá-las (provavelmente você, assim como eu, já fez isso muito na vida), mas isso significaria uma perda de tempo enorme, além de termos que nos preocupar não só com os bugs do programa em si mas também com os bugs do formulário-teste, uma distração desnecessária. Uma ferramenta feita especificamente pra testes é fundamental, e já existe: chama-se NUnit.

NUnit é software de código-aberto, escrito em C#, baseado no JUnit pra Java. A interface do NUnit é bem simples: ela nos mostra todos os testes, com o seguinte código de cores ao lado de cada um: vermelho se o teste não passou, e verde se passou. Uma barra de progresso à direita dá um resultado geral (ou seja, verde só se todos passaram), como podemos ver na figura abaixo:

Obs.: Caso você use Visual Studio.Net, a forma mais prática de instalar NUnit no seu sistema é instalando o pacote Test Driven.Net, que da versão mais recente do NUnit, instalará um add-in pro Visual Studio.Net para que você possa executar os testes de dentro do ambiente, diretamente. Outra alternativa (por exemplo pra quem não usa VS.Net) é instalar apenas o NUnit executá-lo como um programa independente.

2.1) Criando os testes

Seguindo a receita em 5 passos que vimos na introdução, primeiramente vamos pensar nos testes que poderíamos fazer em nosso programa de cálculo de impostos:

  • Um teste para valor menor que 1.000;
  • Outro pra um valor entre 1.000 e 10.000;
  • Finalmente, outro pra valor maior que 10.000.

Naturalmente que em um programa de verdade precisaríamos testar mais coisas, por exemplo casos de overflow (valores muito grandes), underflow (valores negativos) e valores-fronteira (0, 1.000, 10.000), mas por motivo de clareza no artigo vamos nos ater a apenas esses 3 agora.

A seguir, vamos criar a nossa função, sem código interno. Adicione uma classe ao seu projeto, e a função CalculaImpostos:

Public Class Impostos

    Public Shared Function CalculaImpostos(ByVal valor As Decimal) As Decimal

    End Function

End Class
Agora, vamos criar os testes, em uma nova classe. Lembre-se de adicionar uma referência à biblioteca nunit.framework ao seu projeto, clicando com o botão direito do mouse sobre o item "references" no Solution Explorer e selecionando nunit.framework.dll, que está no diretório onde você instalou o NUnit - algo como C:\Arquivos de Programas\Test Driven.net\NUnit\bin.
Imports NUnit.Framework

<TestFixture()> _
Public Class TestaImpostos

    "menos de 1000 - nao incide impostos
    <Test()> _
    Public Sub TestaImposto_Ate1000()
        Dim novo_valor As Decimal = Impostos.CalculaImpostos(400)
        Assert.AreEqual(400, novo_valor)
    End Sub

    "entre 1000 e 10.000 - 3% de imposto
    <Test()> _
    Public Sub TestaImposto_Ate10000()
        Dim novo_valor As Decimal = Impostos.CalculaImpostos(5000)
        Assert.AreEqual(5150, novo_valor)
    End Sub


    "mais de 10.000 - 3% de imposto mais 250 de sobretaxa
    <Test()> _
    Public Sub TestaImposto_Maior10000()
        Dim novo_valor As Decimal = Impostos.CalculaImpostos(20000)
        Assert.AreEqual(20850, novo_valor)
    End Sub

End Class

Você vai notar algumas novidades nesse trecho de código:

  • Logo na primeira linha, importamos a biblioteca Nunit.Framework;
  • a diretiva <TestFixture()> indica que a classe a seguir é uma classe a ser usada pelo NUnit, para executar testes;
  • Os métodos-teste são identificados pela diretiva <Test()>, e, assim como a classe, precisam ser declarados com escopo "público".
  • O comando Assert.Equal, que é o comando básico para execução de testes: ele aceita 2 valores, e os compara: caso sejam iguais, o teste passa, caso contrário, falha.

Além de Assert.Equal, temos os seguintes comandos para nos auxiliar nos testes:

  • Assert.AreSame: o mesmo que Assert.AreEqual, mas para objetos ao invés de valores;
  • Assert.IsFalse e Assert.IsTrue, para valores booleanos;
  • Assert.Fail, para testar exceções (exceptions);
  • Assert.IsNull e Assert.IsNotNull, para valores que possam ser nulos.

2.2) Testando pela primeira vez

Com isso já podemos testar pela primeira vez nosso código! Se você instalou o pacote Test Driven.Net, basta clicar com o botão direito em cima do nome do projeto que você verá a opção Test With… -> NUnit GUI:

Como ainda não implementamos a função, esperamos que todos testes falhem:

Realmente, tudo vermelho! Repare que na parte direita da janela vemos mensagens do tipo "expected: <400> but was: <0>", que nos indica o quê exatamente deu errado no teste.

2.3) Escrevendo o código e testando de novo

Agora que temos os testes que falham, é hora de fazê-los funcionar! Vamos entrar o código para cálculos dos impostos:

Public Class Impostos

    Public Shared Function CalculaImpostos(ByVal valor As Decimal) As Decimal
        Select Case valor
            Case Is < 1000
                Return valor
            Case Is < 10000
                Return valor * 0.03
            Case Is >= 10000
                Return (valor * 0.03) + 250
        End Select

    End Function

End Class

E vamos testar de novo!

Ué, o primeiro teste, para valores menores que 1000, deu certo, mas os outros 2 testes falharam. Pela mensagem na direita, eu vejo "expected: 5150, but was 150". Porquê será?

(hora de coçar a cabeça e dar uma pensada)

Sim, claro, a funcão está errada. Veja que eu entrei

Case Is < 10000
   Return valor * 0.03

Mas na verdade eu precisaria adicionar 3%, logo, eu deveria multiplicar por 1.03, e não 0.03! Esse tipo de erro é muito comum, e eu quis mostrá-lo aqui porque em geral, quando nao usamos TDD, esse tipo de bug só costumar ser detectado depois de algumas horas, ou dias, ou mesmo só depois de fecharmos o programa! Mas com testes sistemáticos, foi encontrado em questão de minutos.

O código correto então seria:

Select Case valor
    Case Is < 1000
        Return valor
    Case Is < 10000
        Return valor * 1.03
   Case Is >= 10000
        Return (valor * 1.03) + 250
End Select

Vamos então pra segunda rodada de testes:

Agora sim! Tudo verde! Acredite em mim, depois de usar NUnit por um tempo, você vai se apaixonar por essa barrinha verde, principalmente depois de fazer grandes alterações no sistema: se todos testes passarem, você pode ter certeza que não quebrou nada, ao invés de só rezar. Também vai ter certeza que não danificou funcionalidades implementadas por outros programadores, o que em um ambiente de produção é fundamental. Você ganha confiança no seu código.

Uma dica importante quanto à metodologia de TDD é a seguinte: toda vez que você encontrar um bug, primeiramente crie um teste que faça o bug aparecer. Só então, escreva o código que vai corrigir o bug. Com isso, o seu conjunto de testes melhora com o tempo, fica mais completo, e você vai ter certeza dali em diante que o bug nunca mais voltará despercebido.

Boa sorte!

Rodrigo Vieira

Rodrigo Vieira - MCSD e MCAD, formado em Ciência da Computacão, trabalhando há 5 anos em uma empresa de telecomunicacões em Oslo, Noruega, desenvolvendo aplicativos para Intranet nas plataformas .Net e Oracle. Entusiasta de Python, Mono, Linux e software livre em geral.
Blog The Spoke:
http://br.thespoke.net/MyBlog/rodviking/MyBlog.aspx