C#  -  Realizando Testes Unitários com XUnit


Neste artigo veremos como realizar testes de unidade usando o XUnit.

Eu apresentei os conceitos básicos sobre XUnit neste artigo: C# - XUnit Básico e hoje vamos continuar mostrando como realizar testes unitários com essa ferramenta.

Assim, veremos uma aplicação prática usando um projeto Console na linguagem C# e o framework XUnit.

Criando o projeto C#

Vamos criar um projeto do tipo Console para .NET Core no VS 2019 Community chamado CShp_Calculos;

Neste projeto vamos criar uma classe chamada Calculo com o seguinte código:

namespace CShp_Calculos
{
    public static class Calculo
    {
        public static double Somar(double numero1, double numero2)
        {
            return (numero1 + numero2);
        }
        public static double Subtrair(double numero1, double numero2)
        {
            return (numero1 - numero2);
        }
        public static double Multiplicar(double numero1, double numero2)
        {
            return (numero1 * numero2);
        }
        public static double Dividir(double numero1, double numero2)
        {
            return (numero1 / numero2);
        }
        public static bool IsNumeroPar(int numero)
        {
            return numero % 2 == 0;
        }
    }
}

Temos aqui uma classe bem simples com algumas operações matemáticas que iremos usar apenas para ilustrar a utilização do XUnit.

Criando o projeto de Teste

Vamos agora criar o projeto de teste na mesma solução.

No menu File selecione Add -> New Project;

A seguir selecione :

Escolha o template xUnit Test Project (.NET Core) e clique em Next :

A seguir informe o nome CShp_CalculosTest clique em Create.

Teremos o projeto incluído na solução conforme mostra a figura abaixo:

Vamos excluir a classe UnitTest1.cs incluida no projeto e a seguir incluir uma referência ao projeto Cshp_Calculos.

Clique com o botão direito do mouse sobre o projeto CShp_CalculosTest e a seguir clique em Add Reference;

Na janela Reference Manager marque o projeto Cshp_Calculos e clique em OK:

Ao final teremos a nossa solução contendo os dois projetos.

xUnit - Conceitos básicos

No xUnit precisamos decorar os métodos de teste com o atributo [Fact], que é usado pelo xUnit para marcar os métodos de testes. Além dos métodos de testes, também podemos ter vários métodos auxiliares na classe de teste.

Com o XUnit para tornar um método comum em método de testes basta adicionar [Fact] ou [Theory] acima de sua assinatura, os atributos diferem apenas no seguinte, para testes sem parâmetros deve-se usar [Fact], para testes como parâmetros utiliza-se o [Theory].

O atributo [Theory] indica um teste parametrizado que é verdadeiro para um subconjunto de dados. Esses dados podem ser fornecidos de várias maneiras, mas o mais comum é com um atributo [InlineData]. Assim este atributo permite executar um método de teste várias vezes passando diferentes valores a cada vez como parâmetros.

Podemos ainda desativar um teste por qualquer motivo. Para isso basta definir a propriedade Skip no atributo Fact com o motivo que você desativou o teste (o motivo não é exibido).

        [Fact(Skip = "Teste ainda não disponível")]
        public void Teste()
        {
        }

Á medida que o número de seus testes aumenta, você pode organizá-los em grupos para que poder executar os testes juntos. O atributo [Trait] permite organizar os testes em grupos, criando nomes de categoria e atribuindo valores a eles.

        [Fact(DisplayName = "Teste Numero 2")]
        [Trait("Calculo", "Somar")]
        public void Somar_DoisNumeros_RetornaNumero()
        { }

No Test Explorer, este teste aparecerá sob o título Calculo[Somar] (cada combinação Name/Value aparece como um cabeçalho separado no Test Explorer).

Ao escrever testes unitários, em geral seguimos o princípio AAA :  Act, Arrange e Assert (Organizar, Agir e Assertir):

Arrange - É aqui que você normalmente prepara tudo para o teste, em outras palavras, prepara a cena para testar (criar os objetos e configurá-los conforme necessário)

Act - É onde o método que estamos testando será executado;

Assert - Esta é a parte final do teste em que comparamos o que esperamos que aconteça com o resultado real da execução do método de teste;

Os nomes dos métodos de teste devem ser tão descritivos quanto possível. Na maioria dos casos, é possível nomear o método para que nem seja necessário ler o código real para entender o que está sendo testado.

No exemplo, usamos a seguinte convenção de nomenclatura :

1 - A primeira parte do nome representa o nome do método que está sendo testado;
2-  A segunda parte do nome nos informa mais sobre o cenário de teste;
3-  A  última parte do nome é o resultado esperado;

Exemplo : Soma_DoisNumerosInteiros_RetornaNumeroInteiro

Criando os testes de unidade

Vamos criar no projeto CShp_CalculosTest a classe CalculoUnitTest.

Agora vamos criar os casos de testes iniciando com o teste de unidade para o método Somar.

Crie na classe CalculoUnitTest o código a seguir que representa nosso primeiro caso de teste de unidade:

Neste caso de teste de unidade estamos somando os números 2.9 e 3.1 e esperamos o resultado igual a 6.

Estamos usando o objeto Calculo e o método estático Somar na classe de teste e essa é a classe que queremos testar. É importante observar aqui que fazemo isso antes de cada método de teste, o que significa que estamos sempre redefinindo o estado da classe Calculo.

Isso é importante porque os métodos de teste não devem depender uns dos outros e devemos obter os mesmos resultados de teste, não importa quantas vezes executemos os testes e em que ordem os executamos.

Agora no menu Test clique em Windows->Test Explorer para abrir a janela do Test Explorer:

Temos a janela exibindo no lado esquerdo o nosso teste de unidade e acima um menu com opções que permite controlar a execução dos testes. No lado direito temos os detalhes do teste selecionado.

Nesta janela iremos executar e acompanhar a execução dos testes de unidade.

Conforme você executa, grava e executa novamente os testes, o Test Explorer exibe os resultados nos grupos padrão de Testes com Falha, Testes Aprovados, Testes Ignorados e Testes Não Executados. Você pode também alterar a forma como o Test Explorer agrupa seus testes.

O painel de detalhes de teste exibe as seguintes informações:

Se o teste falhar, o painel de detalhes também exibe:

Podemos executar todos os testes ou apenas um teste selecioando. Executando o teste que criamos vamos obter o seguinte resultado:

Temos que o teste passou e a sua duração.

Vamos agora definir os casos de testes para os demais métodos da classe Calculo.

using CShp_Calculos;
using Xunit;
namespace CShp_CalculosTest
{
    public class CalculoUnitTest
    {
        [Fact]
        public void Somar_DoisDouble_RetornaDouble()
        {
            // Arrange  
            var num1 = 2.9;
            var num2 = 3.1;
            var valorEsperado = 6;
            // Act  
            var soma = Calculo.Somar(num1, num2);
            //Assert  
            Assert.Equal(valorEsperado, soma);
        }
        [Fact]
        public void Subtrair_DoisDouble_RetornaDouble()
        {
            // Arrange  
            var num1 = 2.9;
            var num2 = 3.1;
            var valorEsperado = -0.2;
            // Act  
            var subtracao = Calculo.Subtrair(num1, num2, 1);
            //Assert  
            Assert.Equal(valorEsperado, subtracao);
        }
        [Fact]
        public void Multiplicar_DoisDouble_RetornaDouble()
        {
            // Arrange  
            var num1 = 2.9;
            var num2 = 3.1;
            var valorEsperado = 8.99;
            // Act  
            var mult = Calculo.Multiplicar(num1, num2);
            //Assert  
            Assert.Equal(valorEsperado, mult, 2);
        }
        [Fact]
        public void Dividir_DoisDouble_RetornaDouble()
        {
            // Arrange  
            var num1 = 2.9;
            var num2 = 3.1;
            var valorEsperado = 0.94; //Rounded value  
            // Act  
            var div = Calculo.Dividir(num1, num2);
            //Assert  
            Assert.Equal(valorEsperado, div, 2);
        }
    }
}

Definimos 4 casos de testes sendo que um já foi executado. Abrindo o Teste Explorer veremos:

Executando todos os testes iremos obter o seguinte resultado:

Observe que o teste para o método Subtrair falhou, e, analisando os detalhes vemos que era esperado o valor -0,2 e foi obtido o valor 0,2000000000000018. Temos um problema de precisão.

Para resolver isso basta ajustar o código incluindo o valor da precisão igual a  no Assert:

       [Fact]
        public void Subtrair_DoisDouble_RetornaDouble()
        {
            // Arrange  
            var num1 = 2.9;
            var num2 = 3.1;
            var valorEsperado = -0.2;
            // Act  
            var subtracao = Calculo.Subtrair(num1, num2);
            //Assert  
            Assert.Equal(valorEsperado, subtracao, 1);
        }

 

Executando novamente veremos que agora todos os testes vão passar:

 que todos os testes passaram e temos também o tempo de execução de cada teste.

Vamos agora definir um caso de teste usando o atributo [Theory] para o método Somar.

        [Theory]
        [InlineData(1, 2, 3)]
        [InlineData(-4, -6, -10)]
        [InlineData(-2, 2, 0)]
        [InlineData(int.MinValue, -1, int.MaxValue)]
        public void Somar_DoisNumerosRelativos_RetornaNumeroRelativo(int num1, int num2, int valorEsperado)
        {
           //Act
            var resultado = Calculo.Somar(num1, num2);
            //Assert
            Assert.Equal(valorEsperado, resultado);
        }

Agora ao invés de especificar os valores para somar (num1 e num2) no corpo do teste, passamos os valores como parâmetros para o teste.

Passamos também o resultado esperado do cálculo para usar na chamda do Assert.Equal().

Aqui os dados dados são fornecidos pelo atributo [InlineData] onde cada instância de [InlineData] criará uma execução separada do método Somar_DoisNumerosRelativos_RetornaNumeroRelativo.

Os valores passados ​​no construtor de [InlineData] são usados ​​como parâmetros para o método, e, a ordem dos parâmetros no atributo corresponde à ordem em que eles são fornecidos ao método.

Vejamos como a janela Test Explorer exibe esse teste de unidade:

Executando o teste criado iremos obter:

Note que o teste de unidade falhou para os valores  [InlineData(int.MinValue, -1, int.MaxValue)] pois era esperado o valor 2147483647 e foi obtido o valor -2147483649.

Vamos corrigir definindo alterando os parâmetros para  [InlineData(int.MinValue, +1, int.MinValue +1)].

Executando novamente agora teremos o resultado a seguir:

Temos assim uma apresentação dos principais recursos do XUnit para realização de testes de unidade.

Pegue o código do projeto aqui:  CShp_Calculos.zip

"Qual é mais fácil? Dizer ao paralítico: Estão perdoados os teus pecados, ou dizer: Levanta-te, toma o teu leito e anda?
Ora, para que saibais que o Filho do Homem tem sobre a terra autoridade para perdoar pecados — disse ao paralítico:  Eu te mando: Levanta-te, toma o teu leito e vai para tua casa."

Marcos 2:9-11

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

Quer aprender a criar aplicações Web Dinâmicas usando a ASP .NET MVC 5 ?

Referências:


José Carlos Macoratti