Por que eu devo ler este artigo:Este artigo aborda o tema teste unitário com o objetivo de mostrar como o desenvolvedor pode usá-lo para testar o código-fonte de suas aplicações. Neste sentido, o artigo provê conhecimento ao desenvolvedor sobre como implementar casos de teste unitários utilizando NUnit através de exemplos práticos.

O tema se torna fundamental para desenvolvedores que buscam cada vez mais melhorar suas aplicações ao adotarem políticas de teste. Essa tarefa será apoiada pelo conhecimento adquirido aqui sobre construção de casos de testes unitários usando NUnit.

O objetivo deste artigo é fornecer conhecimento prático para desenvolvedores que buscam aprender sobre teste unitário de software utilizando NUnit. Neste artigo serão apresentadas as teorias importantes que envolvem a construção de testes unitários, bem como um exemplo simples e inicial para visualizar a aplicação de tais conceitos.

Ao longo dos tempos várias literaturas técnicas diferentes vêm abordando o tema teste unitário de software. A importância da realização de testes é propagada e defendida por vários autores. Várias técnicas para teste unitário de software vêm sendo utilizadas por diversos desenvolvedores, que cada vez mais buscam aprender sobre estas técnicas e como utilizá-las. Existem diferentes tipos de testes e diferentes tecnologias que apóiam sua utilização.

Nesse sentido, o objetivo deste artigo é fornecer conhecimento prático para desenvolvedores que buscam aprender sobre teste unitário de software utilizando NUnit. Neste artigo serão apresentadas as teorias importantes que envolvem a construção de testes unitários, bem como um exemplo simples e inicial para visualizar a aplicação de tais conceitos.

Um primeiro exemplo consiste na apresentação de um método a ser testado e como escrever código de teste para testá-lo. Posteriormente, será apresentado outro exemplo, com um nível de dificuldade maior, com o intuito de demonstrar como é a implementação de código de teste antes mesmo da existência do método a ser testado, o que é descrito em algumas literaturas técnicas como sendo o ideal. A ideia é implementar o código de teste com base nas informações que se conhece sobre o software que será desenvolvido. Após a concepção do código de teste, será possível implementar a aplicação e, em paralelo, executar os testes anteriormente escritos, permitindo que a aplicação passe por um ciclo de desenvolvimento e teste.

Outro importante objetivo deste artigo é demonstrar de forma prática como se dá o processo de manutenção em um trecho de código, no caso um método, garantindo que as alterações feitas não eliminaram nenhuma funcionalidade do mesmo. Isto será garantido pela execução dos testes antes e depois da manutenção ter sido realizada.

Ao final, espera-se que o desenvolvedor seja capaz de entender as teorias importantes que devem ser consideradas para a escrita de código de teste, seja capaz de escrever código de teste para métodos existentes, escrever código de teste antes mesmo do método a ser testado existir, e seja capaz de realizar manutenções em métodos de aplicações que já estão em uso, garantindo através da execução de testes, que as manutenções não comprometeram o método mantido.

NUnit, um framework livre para testes

O NUnit é um framework livre para testes unitários. Com o NUnit é possível criar casos de teste unitários no Visual Studio e executá-los, obtendo o resultado (uma das formas) em um aplicativo gráfico que permite a visualização simplificada do sucesso ou fracasso dos testes executados.

Neste artigo o desenvolvedor será capaz de entender como utilizar de forma prática os recursos do NUnit, entendendo desde o processo de instalação, até a escrita de casos de teste, que são métodos implementados para testar métodos de uma aplicação.

Utilizando o NUnit no Visual Studio 2008

O primeiro passo para a utilização do NUnit dentro do Visual Studio é fazer o download do framework NUnit no site (ver seção Links). A versão a ser utilizada é a 2.5.10, para Windows. Feito o download, é necessário a instalação do executável.

O processo de instalação é simples. Após iniciada a instalação, o próximo passo consiste em ler e aceitar os termos de uso e avançar.

É possível alterar algumas configurações da instalação utilizando a instalação personalizada, inclusive o local da instalação, que por padrão é na pasta Arquivos de Programas. Em seguida é iniciada a instalação, quando o NUnit estará pronto para ser utilizado.

Um aplicativo que permite a execução de casos de teste previamente escritos no Visual Studio foi instalado no computador (Figura 1). Acessando o menu File é possível adicionar um projeto do Visual Studio que contenha casos de teste para que os testes possam ser executados. O Botão Run, e a barra abaixo do botão serão utilizados para executar e exibir o processo de execução dos casos de teste. A partir deste momento, o Visual Studio e o computador estão prontos para trabalharem com testes unitários.

Aplicativo NUnit
Figura 1. Aplicativo NUnit.

Criando e executando casos de teste com NUnit

O primeiro exemplo a ser utilizado é bem simples. Consiste em testar um método que verifica se um parâmetro recebido está vazio ou não, retornando um valor booleano que indicará true para parâmetro de tamanho maior que zero (em número de caracteres, por se tratar de String) ou false, caso contrário. O método a ser testado é chamado de verificarCampoVazio, e pertence à classe FormularioSimples, que herda de Form, por se tratar de uma classe atrelada a uma interface. Ao clicar no botão Salvar do formulário, é invocado um método que verifica se um textbox está vazio. A Listagem 1 mostra a classe FormularioSimples contendo o evento do botão Salvar, linhas 7 a 14, e o método verificarCampoVazio, linhas 15 a 25.

Listagem 1. Classe FormularioSimples

  1.  public partial class FormularioSimples : Form
  2.  {
  3.     public FormularioSimples()
  4.     {
  5.         InitializeComponent();
  6.     }
  7.     private void buttonSalvar_Click(object sender, EventArgs e)
  8.     {
  9.         String campo = textBoxNome.Text;
  10.        if(verificarCampoVazio(campo)== false)
  11.        {
  12.             MessageBox.Show("Campo Vazio");
  13.        }
  14.    }
  15.    public Boolean verificarCampoVazio(String campo)
  16.    {
  17.        if (campo.Length > 0)
  18.        {
  19.            return true;
  20.        }
  21.        else
  22.        {
  23.             return false;
  24.        }
  25.     }
  26.     }

O próximo passo é definir o caso de teste que será utilizado para testar o método verificarCampoVazio. Para essa tarefa, é definida uma classe que ficará responsável por carregar os casos de teste que se aplicam à classe que ela representa, no caso, a classe a ser criada é TFormularioSimples, que receberá os casos de teste de todos os métodos da classe FormularioSimples que se desejar testar. Nesse exemplo, apenas o método verificarCampoVazio da classe FormularioSimples será testado. A classe TFormularioSimples recebe o mesmo nome da classe que representa, com o acréscimo da letra “T”, que servirá para indicar que se trata de uma classe de teste, assim como “I” é usado para mostrar que se trata de uma interface de uma classe. A Listagem 2 mostra a classe TFormularioSimples.

Listagem2. Classe TFormularioSimples.

  1.  public class TFormularioSimples
  2.  {
  3.  }

A classe criada deve agora poder receber um caso de teste para testar o método verificarCampoVazio. Para que isso seja possível, deve-se adicionar ao projeto a referência ao NUnit, com o botão direito sobre o projeto no Solution Explorer, acessando Adicionar referência, como mostra a Figura 2.

Adicionando referência
Figura 2. Adicionando referência.

Feito isso, busca-se pela referência chamada nunit.framework, como mostra a Figura 3.

Buscando a referência do NUnit
Figura 3. Buscando a referência do NUnit.

O projeto já contem uma referência ao NUnit (Figura 4).

Referência do NUnit
Figura 4. Referência do NUnit.

Para que a classe TFormularioSimples possa utilizar os recursos da referência adicionada, deve-se inserir na classe a referência ao namespace do NUnit, como pode ser visto na Listagem 3, linha 5.

Listagem 3. Classe TFormularioSimples.

  1.  using System;
  2.  using System.Collections.Generic;
  3.  using System.Linq;
  4.  using System.Text;
  5.  using NUnit.Framework;
  6.  namespace Projeto01
  7.  {
  8.     public class TFormularioSimples
  9.     {
  10.    }
  11. }

O próximo passo consiste na criação dos métodos de teste que servirão para testar o método verificarCampoVazio. A Listagem 4 mostra os métodos de teste criados.

Listagem 4. Classe TFormularioSimples.

  1.  using System;
  2.  using System.Collections.Generic;
  3.  using System.Linq;
  4.  using System.Text;
  5.  using NUnit.Framework;
  6.  namespace Projeto01
  7.  {
  8.      [TestFixture]
  9.      public class TFormularioSimples
  10.     {
  11.           private FormularioSimples formulario = new FormularioSimples();
  12.           [Test]
  13.           public void testarVerificarCampoVazio1()
  14.           {
  15.                Boolean resultado = formulario.verificarCampoVazio("");
  16.                Assert.AreEqual(false, resultado);
  17.           }
  18.           [Test]
  19.           public void testarVerificarCampoVazio2()
  20.           {
  21.               Boolean resultado = formulario.verificarCampoVazio("Jacimar Tavares");
  22.               Assert.AreEqual(true, resultado);
  23.           }
  24.      }
  25.     }

O código da Listagem 4 contem, primeiramente, a referência ao NUnit (linha 5). TextFixture (linha 8) é a indicação de que a classe abaixo é uma classe que contém casos de teste. A declaração da classe na linha 9 possui nome idêntico ao da classe que contém o método a ser testado (Listagem 1) acrescido do prefixo “T”. Test (linha 12) é utilizado para fazer com que o método que vem abaixo se torne um método de teste. O mesmo pode ser visto na linha 18, o que torna o método da linha 19 um método de teste.

A classe de teste da Listagem 4 possui dois métodos de teste, pois o método a ser testado, verificarCampoVazio, linha 15 a 25 da Listagem 1, pode retornar dois diferentes tipos de informação, dependendo do tipo de informação passada a ele por parâmetro.

Na linha 11 da Listagem 4 tem-se a instanciação de um objeto da classe FormularioSimples, que permitirá o acesso, através do objeto instanciado, ao método a ser testado.

O primeiro método de teste da classe de teste TFormularioSimples, linhas 12 a 17 da Listagem 4, foi nomeado testarCampoVazio1, onde o número 1 é utilizado para indicar que esse é o primeiro dos casos de teste do método a ser testado. A utilização do número não é regra. Aqui é usado para indicar que se trata do primeiro método de teste apenas. O corpo do método possui a declaração de uma variável booleana de nome resultado que receberá o retorno do método a ser testado. Em seguida, é utilizado o método AreEqual da classe Assert (linha 16 da Listagem 4). Como parâmetro é passado o valor esperado e o valor que deve ser usado para comparar se o valor esperado é igual ao valor obtido. No caso, o valor armazenado na variável resultado. Se o valor esperado for igual ao valor obtido, quer dizer que o teste funcionou. Se o resultado for diferente, indica que há algo errado com o método que está sendo testado.

Neste momento os testes se encontram prontos para serem executados. O primeiro passo para a execução dos casos de teste é a compilação do projeto. Se o projeto que recebe a classe de teste não for compilado, não será possível prosseguir. Compilada a aplicação, é chegado o momento de executar os testes. Ao iniciar o aplicativo do NUnit (menu iniciar/ NUnit 2.5.10) deve-se adicionar o projeto do Visual Studio (.exe do projeto) no NUnit. Para isso, basta acessar o menu File e Open Project. Busca-se pelo .exe do projeto do Visual Studio. A Figura 5 mostra o projeto adicionado na ferramenta do NUnit.

Projeto adicionado ao NUnit
Figura 5. Projeto adicionado ao NUnit.

Na Figura 5 é possível perceber que o projeto adicionado possui uma classe de teste, exatamente como no projeto do Visual Studio. Ao adicionar o executável do projeto, automaticamente o NUnit identifica quais classes contém métodos de teste e quais são esses métodos. A classe TFormularioSimples possui dois métodos de teste, assim como foi implementado na Listagem 4. Ao selecionar e executar o método de teste testarVerificarCampoVazio1, a barra abaixo do botão run deve ficar verde, indicando que o método passou no teste, como pode ser visto na Figura 6.

Teste 1 executado com sucesso
Figura 6. Teste 1 executado com sucesso.

O mesmo procedimento deve ser feito com o segundo método de teste, que também deve passar, como pode ser visto na Figura 7.

Teste 2 executado com sucesso
Figura 7. Teste 2 executado com sucesso.

Os casos de teste passaram, e isto indica que o método verificarCampoVazio funciona como se esperava. Se um dos métodos de teste for alterado, como por exemplo, trocar o valor que se espera do teste, o teste certamente falhará. Para isso, considere a Listagem 5.

Listagem 5. Código método de teste 1.

  1.  ...
  2.  [Test]
  3.  public void testarVerificarCampoVazio1()
  4.  {
  5.       Boolean resultado = formulario.verificarCampoVazio("");
  6.       Assert.AreEqual(true, resultado);
  7.  }
  8.  ... 

Na Listagem 5, linha 6, é possível perceber que o método de teste testarVerificarCampoVazio1 teve o valor esperado alterado. Agora se espera que o método retorne true ao invés de false. Como o método a ser testado está implementado de forma correta, ele retornará false e, portanto o teste falhará. A Figura 8 mostra o teste após as mudanças da Listagem 5.

Teste 1 falhando
Figura 8. Teste 1 falhando.

Na Figura 8 é possível verificar na mensagem o motivo do erro, que define que o método de teste estava esperando True, mas foi retornado False. O exemplo apresentado, apesar de testar um método simples, serviu para ilustrar como é a escrita e o funcionamento de um caso de teste.

Algumas literaturas técnicas sobre teste de software sugerem que os casos de teste sejam definidos antes mesmo do método a ser testado. Isto permitirá que o método a ser testado já seja passível de teste desde a sua concepção. Além disso, a criação do teste antes do método a ser testado permite que o método seja implementado e testado assim que uma “primeira versão” do método surgir. Caso todos os testes sejam bem sucedidos, significará que o método já está pronto e devidamente implementado. Outro benefício está ligado à capacidade que algumas equipes têm de trabalhar com tarefas e funções distribuídas. Um membro da equipe pode assumir a tarefa de desenvolver o código da aplicação com base nas informações que possui sobre ela (uma especificação do sistema, por exemplo), enquanto isso, outro membro da equipe pode assumir o papel do testador e, de posse da mesma especificação, desenvolver o código de teste antes mesmo de conhecer o resultado do trabalho do desenvolvedor. Ao final, o testador executaria os testes que ele escreveu no código que o desenvolvedor implementou.

A partir de agora será considerado o seguinte cenário: surgiu a necessidade de se implementar um aplicativo que calcule o novo valor de uma prestação com base em algumas informações. Toda prestação a ser paga, no ato do pagamento, deve ser revisada. Isso implica em dar desconto para prestações que estão sendo pagas antes do vencimento e cobrar juros de prestações pagas depois da data de vencimento. Nesse sentido, sabe-se que será preciso calcular o novo valor da parcela. É interessante que o aplicativo tenha um método que calcule este novo valor de parcela. Parcelas pagas antes da data de vencimento devem ter desconto de R$ 7,25. Parcelas pagas depois da data de vencimento devem ter um acréscimo de R$ 2,38 em forma de multa. Percebe-se que existem dois problemas a serem implementados. Um se resume na tarefa de descobrir se a prestação a ser paga, dada sua data de vencimento, já está vencida. A outra tarefa é calcular o desconto ou juro, caso a prestação esteja vencida ou não.

Mediante essas informações, deve-se se certificar de que, a toda data de vencimento inserida na aplicação, deve-se descobrir se já venceu. Depois disso, deve-se certificar que o cálculo do desconto, ou da multa, está sendo feito corretamente mediante a informação descoberta sobre o vencimento da prestação. Isso leva à definição de dois métodos: descobrirSePrestacaoVenceu e outro método chamado calcularNovoValorPrestacao. A Listagem 6 mostra a classe Prestacao com a definição dos dois métodos.

Listagem 6. Classe Prestacao

  1.  public class Prestacao
  2.  {
  3.       public Decimal calcularNovoValorParcela(DateTime dataVencimento, Decimal valorParcela)
  4.       {
  5.           return 0;
  6.       }
  7.       public Boolean descobrirSePrestacaoVenceu(DateTime dataVencimento)
  8.       {
  9.           return false;
  10.      }
  11. }

O primeiro método, linhas 3 a 6 da Listagem 6, mostra a definição do método que calculará o novo valor da prestação. As linhas 7 a 10 contêm o método que será o responsável por descobrir se a prestação já venceu. O objetivo de definir dois métodos, e não apenas um que faça tudo, é a divisão de responsabilidades, devido ao fato de que cada método deve desempenhar apenas uma função para que a faça bem e não carregue mais responsabilidades do que deveria, e também para facilitar a concepção de testes, pois cada uma das funcionalidades será testada individualmente.

A ideia é fazer com que, dada a data de vencimento e o valor da parcela ao método calcularNovoValorParcela, ele descubra, ao invocar o método descobrirSePrestacaoVenceu, se a parcela venceu e só depois disso realize sua função que é calcular o desconto ou a multa. Antes que os dois métodos sejam desenvolvidos serão implementados os testes para, na medida em que as primeiras versões dos métodos forem saindo, os testes sejam executados.

Primeiramente define-se a classe de teste que receberá os casos de teste. A Listagem 7 mostra a classe criada.

Listagem 7. Classe TPrestacao

  1.  using System;
  2.  using System.Collections.Generic;
  3.  using System.Linq;
  4.  using System.Text;
  5.  using NUnit.Framework;
   
  6.  namespace Projeto02
  7.  {
  8.     [TestFixture]
  9.     public class TPrestacao: Prestacao
  10.    {
  11.  
  12.    }

Vale lembrar que para se criar uma classe de teste, primeiramente deve-se inserir a referência ao nunit.framework, e usá-la na classe. Nesse momento deve-se definir os casos de teste para o método descobrirSePrestacaoVenceu, visto que ele será utilizado pelo outro método de cálculo de parcela. Já se sabe que o método descobrirSePrestacaoVenceu recebe por parâmetro a data de vencimento da prestação. Obviamente, para saber se a prestação venceu, deve-se compará-la com a data atual. Nesse momento deve-se definir as datas que serão utilizadas como data de vencimento, e somente depois implementar casos de teste que executem testes com essas datas. Para isto, considera-se que a data atual é 28/10/2011. Sendo assim, as datas que serão utilizadas para teste devem ser anteriores, iguais e maiores que a data atual.

A etapa de definir quais argumentos serão utilizados para testar o método que ainda não foi implementado é uma das mais complexas, pois se o método ainda não foi implementado, não se conhece a sua complexidade ciclomática, o que auxilia na definição do número de casos de teste. Se não se definir todas as datas que farão com que o método possa ser testado de forma correta, corre-se o risco de o teste não identificar todos os caminhos que o método pode tomar, mediante diferentes entradas. Os casos de teste implementados para estas datas podem ser vistos na Listagem 8.

Listagem 8. Classe TPrestacao

  1.  using System;
  2.  using System.Collections.Generic;
  3.  using System.Linq;
  4.  using System.Text;
  5.  using NUnit.Framework;
  6.  namespace Projeto02
  7.  {
  8.      [TestFixture]
  9.      public class TPrestacao: Prestacao
  10.     {
  11.        [Test]
  12.        public void testeDescobrirSePrestacaoVenceu1()
  13.        {
  14.           DateTime dataVencimento1 = new DateTime(2010, DateTime.Today.Month, DateTime.Today.Day);
  15.           Boolean resultado = descobrirSePrestacaoVenceu(dataVencimento1);
  16.           Assert.AreEqual(true,resultado);
  17.        }
  18.        [Test]
  19.        public void testeDescobrirSePrestacaoVenceu2()
  20.        {
  21.           DateTime dataVencimento2 = new DateTime(2011, DateTime.Today.Month, DateTime.Today.Day);
  22.           Boolean resultado = descobrirSePrestacaoVenceu(dataVencimento2);
  23.           Assert.AreEqual(false, resultado);
  24.        }
  25.       [Test]
  26.       public void testeDescobrirSePrestacaoVenceu3()
  27.       {
  28.           DateTime dataVencimento3 = new DateTime(2012, DateTime.Today.Month, DateTime.Today.Day);
  29.           Boolean resultado = descobrirSePrestacaoVenceu(dataVencimento3);
  30.           Assert.AreEqual(false, resultado);
  31.       }
  32.       [Test]
  33.       public void testeDescobrirSePrestacaoVenceu4()
  34.       {
  35.           DateTime dataVencimento4 = new DateTime(DateTime.Today.Year, 09, DateTime.Today.Day);
  36.           Boolean resultado = descobrirSePrestacaoVenceu(dataVencimento4);
  37.           Assert.AreEqual(true, resultado);
  38.       }
  39.       [Test]
  40.       public void testeDescobrirSePrestacaoVenceu5()
  41.       {
  42.           DateTime dataVencimento5 = new DateTime(DateTime.Today.Year, 11, DateTime.Today.Day);
  43.           Boolean resultado = descobrirSePrestacaoVenceu(dataVencimento5);
  44.           Assert.AreEqual(false, resultado);
  45.       }
  46.       [Test]
  47.       public void testeDescobrirSePrestacaoVenceu6()
  48.       {
  49.            DateTime dataVencimento6 = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 27);
  50.            Boolean resultado = descobrirSePrestacaoVenceu(dataVencimento6);
  51.            Assert.AreEqual(true, resultado);
  52.       }
  53.       [Test]
  54.       public void testeDescobrirSePrestacaoVenceu7()
  55.       {
  56.            DateTime dataVencimento7 = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 29);
  57.            Boolean resultado = descobrirSePrestacaoVenceu(dataVencimento7);
  58.            Assert.AreEqual(false, resultado);
  59.       }
  60.    }
  61. }

O método das linhas 11 a 17 executa um teste com a data 28/10/2010, já o método das linhas 18 a 24 com a data 28/10/2011. Linhas 25 a 31, a data 28/10/2012, linhas 32 a 38 a data 28/09/2011, linhas 39 a 45 a data 28/11/2011, linhas 46 a 52 a data 27/10/2011 e, por último, as linhas 53 a 59, com a data 29/10/2011. Essas datas foram selecionadas de forma a realizar comparações de datas em função do dia, mês e ano, antes e depois da data selecionada. O projeto do Visual Studio deve ser compilado. Se aberto no aplicativo do NUnit, será possível visualizar os casos de teste implementados, como mostra a Figura 9.

Casos de teste implementados
Figura 9. Casos de teste implementados.

Nesse momento tem-se definido todos os casos de teste necessários para o método descobrirSePrestacaoVenceu. O próximo passo é a escrita do corpo do método descobrirSePrestacaoVenceu, por enquanto inexistente (linhas 7 a 10 da Listagem 6). A implementação do método pode ser vista na Listagem 9.

Listagem 9. Método descobrirSePrestacaoVenceu implementado

  1.  public Boolean descobrirSePrestacaoVenceu(DateTime dataVencimento)
  2.  {
  3.      DateTime dataAtual = DateTime.Today;
  4.      Int32 anoAtual = dataAtual.Year;
  5.      Int32 anoDataVencimento = dataVencimento.Year;
  6.      Int32 mesAtual = dataAtual.Month;
  7.      Int32 mesVencimento = dataVencimento.Month;
  8.      Int32 diaAtual = dataAtual.Day;
  9.      Int32 diaVencimento = dataVencimento.Day;
  10.     if (anoAtual > anoDataVencimento)
  11.     {
  12.         return true;
  13.     }
  14.     else
  15.     {
  16.         if (mesAtual <= mesVencimento)
  17.         {
  18.             return true;
  19.         }
  20.         else
  21.         {
  22.             if (diaAtual <= diaVencimento)
  23.             {
  24.                 return true;
  25.             }
  26.             else
  27.             {
  28.                 return true;
  29.             }
  30.         }
  31.     }
  32.     }

O método está implementado, mas ao rodar os testes previamente escritos, alguns erros são encontrados, como mostra a Figura 10.

Métodos de teste executados
Figura 10. Métodos de teste executados.

Os testes que falharam foram os testes 2, 3 5 e 7. No teste 2, com data de 28/10/2011, esperava-se false, indicando que a prestação não venceu, mas o retorno foi true. No teste 3, com data de 28/10/2012, esperava-se false, indicando que a prestação não venceu, mas o retorno foi true. Para o teste 5, dada a data 28/11/2011, esperava-se false, indicando que a prestação não venceu, mas o retorno foi true. Por último tem-se o teste 7, que mediante a data de entrada 29/10/2011, esperava-se false, indicando que a prestação não venceu, mas o retorno foi true.

Todos estes testes que falharam indicam que o método foi escrito de forma errada. Algo deve ser refeito. O método foi reescrito como uma segunda versão. A Listagem 10 mostra o método depois de reescrito.

Listagem 10. Método descobrirSePrestacaoVenceu reescrito

  1.  public Boolean descobrirSePrestacaoVenceu(DateTime dataVencimento)
  2.  {
  3.      DateTime dataAtual = DateTime.Today;
  4.      Int32 anoAtual = dataAtual.Year;
  5.      Int32 anoDataVencimento = dataVencimento.Year;
  6.      Int32 mesAtual = dataAtual.Month;
  7.      Int32 mesVencimento = dataVencimento.Month;
  8.      Int32 diaAtual = dataAtual.Day;
  9.      Int32 diaVencimento = dataVencimento.Day;
  10.     if (anoDataVencimento < anoAtual)
  11.     {
  12.         return true;
  13.     }
  14.     else
  15.     {
  16.        if (mesVencimento < mesAtual)
  17.        {
  18.             return true;
  19.        }
  20.        else
  21.        {
  22.            if (diaVencimento < diaAtual)
  23.            {
  24.                 return true;
  25.            }
  26.            else
  27.            {
  28.                return false;
  29.            }
  30.        }
  31.      }
  32.     }

As mudanças foram feitas na lógica das linhas 16 e 22 da Listagem 10, em relação à Listagem 9, que apresentava o método que não passou em todos os testes. Com essas mudanças, depois de rodar os testes, pode-se comprovar que o método descobrirSePrestacaoVenceu está pronto para testar todo tipo de data, fazendo uma comparação com a data atual que é 28/10/2011 (ver Figura 11).

Métodos de teste executados
Figura 11. Métodos de teste executados.

Um problema com os casos de teste escritos é que o teste é feito com alguns valores que foram inseridos de forma manual, para a data atual, que no caso é 28/10/2011, como citado. Entretanto, isso pode ocasionar problemas, se os testes forem executados em outra data, que não seja a inserida manualmente. Para resolver esse problema, devem-se considerar as mudanças feitas na Listagem 11, em relação ao mesmo código da Listagem 8. As mudanças podem ser vistas nas linhas 7, 14, 21, 28, 35, 42 e 49.

Listagem 11. Métodos de teste da classe TPrestacao modificados

  1.  [TestFixture]
  2.  public class TPrestacao: Prestacao
  3.  {
  4.     [Test]
  5.      public void testeDescobrirSePrestacaoVenceu1()
  6.      {
  7.         DateTime dataVencimento1 = new DateTime(DateTime.Today.Year - 1,     DateTime.Today.Month, DateTime.Today.Day);
  8.         Boolean resultado = descobrirSePrestacaoVenceu(dataVencimento1);
  9.         Assert.AreEqual(true,resultado);
  10.     }
  11.     [Test]
  12.      public void testeDescobrirSePrestacaoVenceu2()
  13.      {
  14.          DateTime dataVencimento2 = new DateTime(DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day);
  15.          Boolean resultado = descobrirSePrestacaoVenceu(dataVencimento2);
  16.          Assert.AreEqual(false, resultado);
  17.      }
  18.      [Test]
  19.      public void testeDescobrirSePrestacaoVenceu3()
  20.      {
  21.          DateTime dataVencimento3 = new DateTime(DateTime.Today.Year + 1,   DateTime.Today.Month, DateTime.Today.Day);
  22.          Boolean resultado = descobrirSePrestacaoVenceu(dataVencimento3);
  23.          Assert.AreEqual(false, resultado);
  24.      }
  25.      [Test]
  26.      public void testeDescobrirSePrestacaoVenceu4()
  27.      {
  28.         DateTime dataVencimento4 = new DateTime(DateTime.Today.Year, DateTime.Today.Month - 1, DateTime.Today.Day);
  29.         Boolean resultado = descobrirSePrestacaoVenceu(dataVencimento4);
  30.         Assert.AreEqual(true, resultado);
  31.      }
   
  32.      [Test]
  33.      public void testeDescobrirSePrestacaoVenceu5()
  34.      {
  35.          DateTime dataVencimento5 = new DateTime(DateTime.Today.Year, DateTime.Today.Month + 1, DateTime.Today.Day);
  36.          Boolean resultado = descobrirSePrestacaoVenceu(dataVencimento5);
  37.          Assert.AreEqual(false, resultado);
  38.      }
  39.      [Test]
  40.      public void testeDescobrirSePrestacaoVenceu6()
  41.      {
  42.          DateTime dataVencimento6 = new DateTime(DateTime.Today.Year,  DateTime.Today.Month, DateTime.Today.Day - 1);
  43.          Boolean resultado = descobrirSePrestacaoVenceu(dataVencimento6);
  44.          Assert.AreEqual(true, resultado);
  45.      }
  46.      [Test]
  47.      public void testeDescobrirSePrestacaoVenceu7()
  48.      {
  49.          DateTime dataVencimento7 = new DateTime(DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day + 1);
  50.          Boolean resultado = descobrirSePrestacaoVenceu(dataVencimento7);
  51.          Assert.AreEqual(false, resultado);
  52.      }
  53.   }

No ponto em que o projeto se encontra, o método descobrirSePrestacaoVenceu está devidamente implementado e testado. O próximo passo é a implementação dos casos de teste referentes ao método calcularNovoValorParcela (ver Listagem 6, linhas 3 a 6).

O método calcularNovoValorParcela, como já foi dito, deve calcular o valor da nova parcela acrescida de juros, caso já esteja vencida, ou dar desconto na parcela, caso ainda esteja em dia. Analisando esse contexto, fica evidente que o caso de teste a ser implementado deve esperar um valor acrescido de juros, dada uma data vencida e uma parcela, e em outro teste, esperar uma parcela com desconto, dada uma data de vencimento que ainda não aconteceu e uma parcela. Os casos de teste escritos de acordo com estas particularidades podem ser vistos na Listagem 12. Os casos estão implementados na mesma classe, TPrestacao, que recebeu os outros casos de testes definidos anteriormente para o método descobrirSePrestacaoVenceu.

Listagem 12. Classe TPrestacao

  1.  [TestFixture]
  2.  public class TPrestacao: Prestacao
  3.  {
  4.      ...
  5.      [Test]
  6.      public void testeDescobrirSePrestacaoVenceu7()
  7.      {
  8.          DateTime dataVencimento7 = new DateTime(DateTime.Today.Year,        DateTime.Today.Month, DateTime.Today.Day + 1);//2011/10/29
  9.          Boolean resultado = descobrirSePrestacaoVenceu(dataVencimento7);
  10.         Assert.AreEqual(false, resultado);
  11.     }
  12.     [Test]
  13.     public void testeCalcularNovoValorParcela1()
  14.     {
  15.          DateTime dataVencimento = new DateTime(DateTime.Today.Year, DateTime.Today.Month, DateTime.Today.Day - 1);
  16.          Decimal novaParcela = calcularNovoValorParcela(dataVencimento, 100.00m);
  17.          Assert.AreEqual(102.38m, novaParcela);
  18.     }
   
  19.     [Test]
  20.     public void testeCalcularNovoValorParcela2()
  21.     {
  22.          DateTime dataVencimento = new DateTime(DateTime.Today.Year,          DateTime.Today.Month, DateTime.Today.Day + 1);
  23.          Decimal novaParcela = calcularNovoValorParcela(dataVencimento, 100.00m);
  24.          Assert.AreEqual(92.75m, novaParcela);
  25.     }
  26. }

Nas linhas 12 a 25 da Listagem 12 estão os métodos de teste que foram implementados para testar o método calcularNovoValorParcela. O primeiro método de teste, linhas 12 a 18, espera (linha 17) que, ao ser passada a data de vencimento (vencida por um dia) e o valor da parcela que é R$ 100,00 (linha 16), que o método retorne o valor da nova parcela igual a R$ 102,38. Já o segundo método de teste, linhas 19 a 25, passa uma data a vencer no dia seguinte, mais uma parcela no valor de R$ 100,00 esperando que o método retorne o valor de R$ 92,75.

Implementados os métodos, o próximo passo é a implementação do método calcularNovoValorParcela, que ainda não possui código em seu corpo (Listagem 6). A implementação do método pode ser vista na Listagem 13.

Listagem 13. Classe Prestacao

  1.  public class Prestacao
  2.  {
  3.        ...
  4.        public Decimal calcularNovoValorParcela(DateTime dataVencimento, Decimal valorParcela)
  5.        {
  6.             if (descobrirSePrestacaoVenceu(dataVencimento))
  7.             {
  8.                 Decimal multa = 2.38m;
  9.                 Decimal valorParcelaCorrigida = valorParcela + multa;
  10.                return valorParcelaCorrigida;
  11.            }
  12.            else
  13.            {
  14.                valorParcela = valorParcela - 7.25m;
  15.                return valorParcela;
  16.            }
  17.       }      
  18.     }

Como pode ser visto na Listagem 13, linhas 4 a 17, o método escrito implementa as regras para cálculo das novas prestações. Sabe-se que o método descobrirSePrestacaoVenceu, sendo invocado na linha 6, já foi implementado e devidamente testado, então não deverá haver problemas para identificar se a prestação venceu. O próximo passo é rodar os testes escritos para o método calcularNovoValorParcela e ver se a lógica de sua implementação está correta. A execução dos testes 1 e 2 para o método podem ser vistas nas Figuras 12 e 13.

Método de teste 1
Figura 12. Método de teste 1.
Método de teste 2
Figura 13. Método de teste 2.

Os testes foram executados com sucesso, isto indica que os métodos escritos passaram em todos os testes que foram pré-estabelecidos.

Considere então o seguinte cenário. Os casos de teste foram devidamente implementados e posteriormente os métodos da aplicação também foram implementados, testados e modificados, quando foi necessário. O código de teste foi guardado e a aplicação foi entregue ao cliente. Em um determinado momento, um desenvolvedor, ao dar manutenção no software desenvolvido, percebeu que a lógica do método descobrirSePrestacaoVenceu não é boa, devido à grande complexidade ciclomática (número de caminhos) existente. O desenvolvedor resolve, portanto, substituir todo o código do método por outro bem mais simples, que usa um método de comparação de datas da biblioteca de classes do .Net Framework. Essa mudança irá simplificar o método descobrirSePrestacaoVenceu.

Como ainda se tem o código de testes, fica mais fácil se certificar que, após as mudanças, o método ainda estará passando em todos os testes. A Listagem 14 apresenta o método descobrirSePrestacaoVenceu após ter o código do seu corpo substituído por um mais simples e a Figura 14 mostra todos os testes executados após as mudanças no método. As mudanças foram realizadas com sucesso e o método ainda continua fazendo o que sempre fez, agora com um código mais simplificado, e com a garantia de que as mudanças não afetaram sua função devido à execução dos testes.

Listagem 14. Método descobrirSePrestacaoVenceu após ter o código do seu corpo substituído

  1.  public Boolean descobrirSePrestacaoVenceu(DateTime dataVencimento)
  2.  {
  3.      Int32 valor = dataVencimento.CompareTo(DateTime.Today);
  4.      if (valor < 0)
  5.      {
  6.          return true;
  7.      }
  8.      else if(valor == 0)
  9.      {
  10.         return false;
  11.     }
  12.     else
  13.     {
  14.         return false;
  15.     }
  16.     }
Testes executados depois das mudanças em descobrirSePrestacaoVenceu
Figura 14. Testes executados depois das mudanças em descobrirSePrestacaoVenceu.
Conclusão

Neste artigo foram apresentadas diversas formas de se implementar código de teste unitários. As diferentes formas apresentadas atuam em diferentes contextos, cada um com sua importância. Este artigo visa ser uma referência para a aplicação de testes unitários utilizando NUnit em diferentes cenários que podem ser encontrados em diferentes empresas de desenvolvimento.

Se a tecnologia a ser utilizada para testes unitários é o NUnit framework, então aqui estão os conceitos e práticas fundamentais para a definição de código de teste, independentemente dos testes serem implementados antes ou depois do método a ser testado, além de também ser possível testar métodos que sofreram manutenções.

  • Site para download do NUnit
  • MARTIN, Robert C, 2009. Código Limpo: Habilidades Práticas do Agile Software, 1ed. Rio de Janeiro: Alta Books, 2009.