Testes unitários com o framework xUnit.net
O objetivo deste artigo é apresentar o framework xUnit.net, uma alternativa para a codificação de testes unitários automatizados na plataforma .NET.
Introdução
Embora muitas vezes relegados a um segundo plano, é inegável o papel dos testes unitários como um meio rápido e eficaz para a validação de classes e métodos em projetos de software. Metodologias ágeis como XP e Scrum têm nesta prática um dos pilares para a obtenção de aplicações não apenas confiáveis, como também flexíveis diante da necessidade de eventuais modificações.
Essa importância alcançada pelos testes unitários serviu de base inclusive para o surgimento de uma nova abordagem conhecida como Test-Driven Development (TDD). Ao priorizar a codificação de testes antes mesmo da implementação das partes a serem analisadas, TDD procura evitar a elaboração de validações “viciadas”. Uma das consequências diretas disto é uma menor possibilidade de ocorrência de erros lógicos, quando estes geralmente seriam detectados apenas em tempo de execução (ou até mesmo já em ambiente de produção).
A implementação em TDD de uma unidade (classe ou método) que será submetida a análise segue um ciclo chamado “Red-Green-Refactor”, o qual está representando na figura apresentada a seguir (importante ressaltar que a execução de testes unitários acontece em todos os estágios):
São consideradas como características típicas de testes unitários bem definidos:
- A rapidez na execução;
- Facilidade de implementação, empregando normalmente para isto um framework pré-existente;
- São automatizados (por meio da integração de um framework com uma IDE) e repetíveis;
- A possibilidade de reuso em ações futuras.
Dentre as alternativas para a codificação de testes unitários na plataforma .NET merecem destaque:
- O Visual Studio Unit Testing Framework (MS Test), opção que integra a própria IDE de desenvolvimento da Microsoft;
- O NUnit, um projeto open source derivado do JUnit (um dos primeiros frameworks para testes automatizados concebido para a plataforma Java);
- O xUnit.net, também uma solução aberta e que surgiu como uma evolução do NUnit (mas contemplando melhoramentos de forma a acompanhar a evolução do .NET Framework).
No caso específico do xUnit.net, deve-se mencionar que o mesmo vem crescendo rapidamente em popularidade dentro da comunidade .NET. O fato do próprio time de desenvolvimento do ASP.NET 5 adotá-lo como padrão para a escrita de testes unitários nesta nova plataforma Web é bastante significativo, o que acaba por atestar o potencial deste framework em projetos com as mais variadas finalidades.
O objetivo deste artigo é demonstrar em linhas gerais a utilização do xUnit.net na definição de testes unitários, partindo para isto da implementação de um exemplo prático que será detalhado mais adiante.
Exemplo de utilização
Para implementar os projetos demonstrados nesta e na próxima seção foram utilizados os seguintes recursos:
- O Microsoft Visual Studio Professional 2013 Update 4 como IDE de desenvolvimento;
- O .NET Framework 4.5.1;
- O xUnit.net 2.0.0.
A solução descrita neste artigo foi disponibilizada no Technet Gallery, podendo ser baixada a partir do link:
https://gallery.technet.microsoft.com/Exemplo-de-utilizao-do-a68c714c
Inicialmente será criado um projeto do tipo “Class Library” chamado “ExemploFinancas”, conforme apresentado na próxima imagem:
A ideia é que a biblioteca ExemploFinancas contenha uma classe estática que terá por nome “CalculoFinanceiro”. Este tipo será responsável por retornar um valor de empréstimo, considerando para isto o número de meses e uma taxa em um cálculo de juros compostos.
A seguir está um diagrama com a representação em UML da classe CalculoFinanceiro:
Na próxima listagem está a definição inicial para a classe CalculoFinanceiro. O retorno do método CalcularValorComJurosCompostos neste primeiro momento será zero, já que a implementação deste tipo segue uma abordagem típica de TDD (com a codificação definitiva da funcionalidade acontecendo somente após a escrita de testes unitários):
using System;
namespace ExemploFinancas
{
public class CalculoFinanceiro
{
public static double CalcularValorComJurosCompostos(
double valorEmprestimo, int numMeses, double percTaxa)
{
return 0;
}
}
}
Na sequência será criado um projeto de testes chamado “ExemploFinancas.Testes”, a partir da seleção do template “Unit Test Project”:
Adicionar então ao projeto ExemploFinancas.Testes os packages “xUnit.net” e “xUnit.net [Runner: Visual Studio]” (o primeiro contempla os recursos necessários para a escrita de testes unitários, ao passo que o segundo possibilitará a execução de tais validações de forma integrada com o Visual Studio)
ExemploFinancas.Testes deverá apontar ainda para a Class Library ExemploFinancas, além de ser removida a referência à biblioteca Microsoft.VisualStudio.QualityTools.UnitTestFramework (MS Test). Além disso, a classe contendo os testes unitários receberá o nome “Testes”.
Ao término destes procedimentos o projeto ExemploFinancas.Testes estará organizado da seguinte maneira:
Os diferentes casos a serem validados por meio da classe Testes estão listados na próxima tabela:
Já na listagem apresentada a seguir está o código-fonte da classe Testes. Analisando a definição deste tipo é possível notar:
- A existência de 3 métodos (Teste1JurosCompostos, Teste2JurosCompostos e Teste3JurosCompostos), com cada um destes correspondendo a um caso de teste específico;
- Estas operações estão marcadas com o atributo FactAttribute (namespace Xunit). Será através desta associação que o mecanismo de testes do Visual Studio identificará quais métodos precisarão ser executados;
- As validações são realizadas por meio de chamadas ao método Equal do tipo Assert (namespace Xunit). Esta operação recebe como parâmetros o resultado da execução do método a ser testado, além do valor esperado para esta ação. Caso tais valores não coincidam, o teste falhará e um alerta será gerado dentro do próprio Visual Studio.
using System;
using Xunit;
namespace ExemploFinancas.Testes
{
public class Testes
{
[Fact]
public void Teste1JurosCompostos()
{
double valor = CalculoFinanceiro
.CalcularValorComJurosCompostos(10000, 12, 2);
Assert.Equal(valor, 12682.42);
}
[Fact]
public void Teste2JurosCompostos()
{
double valor = CalculoFinanceiro
.CalcularValorComJurosCompostos(11937.28, 24, 4);
Assert.Equal(valor, 30598.88);
}
[Fact]
public void Teste3JurosCompostos()
{
double valor = CalculoFinanceiro
.CalcularValorComJurosCompostos(15000, 36, 6);
Assert.Equal(valor, 122208.78);
}
}
}
OBSERVAÇÕES:
- Por convenção, nomes de classes que representam atributos terminam com o sufixo Attribute. A utilização de expressões que envolvam um atributo vinculando o mesmo a uma estrutura de código dispensa o uso de tal sufixo ao final do nome. Logo, ao se empregar o atributo FactAttribute, o método marcado com essa construção estará associado apenas a uma instrução com o valor “Fact” (entre colchetes);
- Além de Equal, a classe estática Assert conta ainda com outros métodos para validação como NotEqual (verifica se os valores fornecidos como parâmetros são diferentes), False (checa se o retorno de uma condição é falso), True (verifica se o retorno de uma expressão é verdadeiro), Null (checa se o valor associado a um objeto é nulo) e NotNull (verifica se o conteúdo de um objeto não é nulo).
Formulados os testes unitários destinados à validação da classe CalculoFinanceiro, chega agora o momento de efetuar o processamento dos mesmos. As diferentes opções que permitem acionar a execução de testes unitários encontram-se no menu Test do Visual Studio:
Ao acionar a opção “All Tests” será exibida a janela “Test Explorer”, com os resultados da execução da classe Testes. Conforme pode ser observado, todos os testes falharam:
Na próxima listagem está o código referente ao tipo CalculoFinanceiro, considerando desta vez as instruções necessárias para o cálculo de juros compostos:
using System;
namespace ExemploFinancas
{
public class CalculoFinanceiro
{
public static double CalcularValorComJurosCompostos(
double valorEmprestimo, int numMeses, double percTaxa)
{
return Math.Round(
valorEmprestimo * Math.Pow(1 + (percTaxa / 100), numMeses), 2);
}
}
}
Uma nova execução dos testes unitários resultará então em sucesso, como indicado na próxima imagem:
Data-Driven Unit Testing
Observando a forma como a classe Testes foi implementada, nota-se que os métodos que correspondem aos 3 casos de teste seguem um mesmo padrão de codificação. Se novas verificações fossem adicionadas para validação, tal fato implicaria na definição de outros métodos de maneira a atender estas novas situações.
Na tabela apresentada a seguir estão os casos de teste utilizados anteriormente, além de 2 novas verificações:
Seguindo a alternativa descrita na seção anterior, 2 novos métodos precisariam ser implementados na classe Testes. Embora se trate de uma solução simples, esta abordagem pode se mostrar como bastante improdutiva em cenários que envolvam um grande número de validações.
Ao invés de apelar para a repetição de código ao longo de uma classe de testes, uma opção seria a utilização de uma técnica conhecida como “Data-Driven Unit Testing”. O framework xUnit.net suporta a especificação de testes que seguem este padrão, empregando para isto métodos parametrizados e atributos nos quais são indicados os diferentes valores a serem checados.
Na próxima listagem é possível observar uma versão refatorada da classe Testes (contemplando inclusive as 2 situações novas para teste):
- O método TestarJurosCompostos foi marcado com o atributo TheoryAttribute (namespace Xunit), o que indica que o mesmo possuirá o comportamento de um Data-Driven Unit Test;
- A operação TestarJurosCompostos receberá como parâmetros o valor de um empréstimo, o número de meses para pagamento, o percentual mensal de juros, além do valor total com juros que deverá ser retornado pelo método CalcularValorComJurosCompostos do tipo CalculoFinanceiro;
- Através do atributo InlineDataAttribute serão especificados os diferentes casos de teste a serem executados pela operação TestarJurosCompostos. Os valores indicados neste atributo serão mapeados para os parâmetros correspondentes em TestarJurosCompostos (a cada execução deste método).
using System;
using Xunit;
namespace ExemploFinancas.Testes
{
public class Testes
{
[Theory]
[InlineData(10000, 12, 2, 12682.42)]
[InlineData(11937.28, 24, 4, 30598.88)]
[InlineData(15000, 36, 6, 122208.78)]
[InlineData(20000, 36, 6, 162945.04)]
[InlineData(25000, 48, 6, 409846.79)]
public void TestarJurosCompostos(
double valorEmprestimo, int numMeses, double percTaxa,
double valorEsperado)
{
double valor = CalculoFinanceiro
.CalcularValorComJurosCompostos(
valorEmprestimo, numMeses, percTaxa);
Assert.Equal(valor, valorEsperado);
}
}
}
Ao executar esta nova versão da classe Testes será apresentado o seguinte resultado (cada linha indicando sucesso corresponde a um caso de teste indicado em “InlineData”):
Conclusão
Este artigo procurou demonstrar, em linhas gerais, a implementação de testes unitários empregando o xUnit.net. Considerado uma evolução do NUnit, este framework vem ganhando popularidade graças à sua simplicidade e praticidade de uso. O próprio fato do ASP.NET 5 empregar esta solução em seu desenvolvimento é um claro sinal da importância que a mesma tem adquirido junto à comunidade .NET, além de atestar a capacidade de adaptação do xUnit.net a qualquer tipo de cenário.
Referências
xUnit.net - Documentation
http://xunit.github.io/