.NET

5 abr, 2017

Mocking Test no .NET Core: utilizando o framework NSubstitute

Publicidade

O objetivo deste artigo é descrever o uso de práticas de Mocking Test com o .NET Core, empregando para isto do framework NSubstitute.

Introdução

Nem sempre a implementação de testes de unidade será uma tarefa fácil. Dependências entre as diferentes partes de um software, a inexistência de ambientes específicos para validação e a integração com servidores externos são alguns dos fatores que podem limitar ou, até mesmo, inviabilizar este tipo de prática.

E como superar estas dificuldades? A solução nestes casos envolve a simulação/imitação de comportamentos considerados impossíveis de se testar. Recursos conhecidos como Mocks simplificam tal processo, fornecendo meios para emular o funcionamento de um objeto e assim permitir sua validação sem grandes esforços de codificação.

Frameworks para a criação de Mock Objects dispensam a implementação de estruturas de código que seriam descartadas posteriormente. Isto acontece através de mecanismos que permitem definir o retorno de métodos, propriedades e até o lançamento de exceções dentro de classes contendo testes de unidade. Duas alternativas gratuitas para mocking na plataforma .NET são os frameworks Moq e NSubstitute.

A finalidade deste artigo é demonstrar a criação de Mock Objects em testes de unidade gerados com o .NET Core, utilizando para tanto o framework NSubstitute.

Criando o projeto a ser testado

Para implementar os projetos apresentados nesta e na próxima seção foram usados os seguintes recursos:

  • O Microsoft Visual Studio Community 2017;
  • O .NET Core 1.1;
  • A versão 2.2.0 do xUnit, framework para implementação de testes de unidade em .NET;
  • O NSubstitute 2.0.2.

O exemplo aqui descrito é uma variação de um projeto baseado no framework Moq, o qual foi detalhado em outro artigo que escrevi anteriormente.

Inicialmente será criado um projeto do tipo Class Library (.NET Core) chamado ConsultaCredito, como indicado na imagem a seguir:

A biblioteca ConsultaCredito conterá os recursos empregados na interação com um serviço de consulta a crédito, utilizando para isto o número de CPF de eventuais clientes durante a realização de pesquisas. No próximo diagrama estão as estruturas a serem implementadas nesse projeto:

Na listagem a seguir, é possível observar:

  • O enumeration StatusConsultaCredito, com os status esperados na interação com o serviço de consulta a crédito;
  • A classe Pendencia, com propriedades que identificam a pessoa física e suas eventuais pendências financeiras.
using System;

namespace ConsultaCredito
{
    public enum StatusConsultaCredito
    {
        ParametroEnvioInvalido = -2,
        ErroComunicacao = -1,
        SemPendencias = 0,
        Inadimplente = 1
    }

    public class Pendencia
    {
        public string CPF { get; set; }
        public string NomePessoa { get; set; }
        public string NomeReclamante { get; set; }
        public string DescricaoPendencia { get; set; }
        public DateTime DataPendencia { get; set; }
        public double VlPendencia { get; set; }
    }
}

Dentre as situações previstas pelo enumeration StatusConsultaCredito estão:

  • O envio de um CPF inválido (valor ParametroEnvioInvalido);
  • O lançamento de uma exceção ao invocar o serviço de consulta a crédito (valor ErroComunicacao);
  • A inexistência de pendências para uma pessoa física (valor SemPendencias);
  • A detecção de pendências financeiras associadas a uma pessoa física (valor Inadimplente).

A interface IServicoConsultaCredito representa a base para a implementação do mecanismo de comunicação com o serviço de consulta a crédito, além do meio através do qual serão criados Mocks para o teste deste processo. Tudo isto acontecerá no método ConsultarPendenciasPorCPF, cujos retornos possíveis são:

  • Uma exceção, caso ocorram problemas na comunicação com o serviço;
  • Nulo, em se tratando de um CPF inválido;
  • Uma coleção com uma ou mais instâncias do tipo Pendencia, se a pessoa física em questão possuir pendências financeiras;
  • Uma coleção do tipo Pendencia sem elementos, para pessoas físicas sem qualquer impedimento financeiro.
using System.Collections.Generic;

namespace ConsultaCredito
{
    public interface IServicoConsultaCredito
    {
        IList<Pendencia> ConsultarPendenciasPorCPF(string cpf);
    }
}

Já na próxima listagem está a definição da classe AnaliseCredito:

  • Este tipo receberá em seu construtor uma instância do tipo IServicoConsultaCredito, a qual será empregada no acesso ao serviço de consulta;
  • A comunicação entre a classe AnaliseCredito e tal serviço se dará no método ConsultarSituacaoCPF, que foi preparado para o tratamento dos diferentes retornos ao se invocar a operação ConsultarPendenciasPorCPF de IServicoConsultaCredito.
namespace ConsultaCredito
{
    public class AnaliseCredito
    {
        private IServicoConsultaCredito _servConsultaCredito;

        public AnaliseCredito(IServicoConsultaCredito servConsultaCredito)
        {
            _servConsultaCredito = servConsultaCredito;
        }

        public StatusConsultaCredito ConsultarSituacaoCPF(string cpf)
        {
            try
            {
                var pendencias =
                    _servConsultaCredito.ConsultarPendenciasPorCPF(cpf);

                if (pendencias == null)
                    return StatusConsultaCredito.ParametroEnvioInvalido;
                else if (pendencias.Count == 0)
                    return StatusConsultaCredito.SemPendencias;
                else
                    return StatusConsultaCredito.Inadimplente;
            }
            catch
            {
                return StatusConsultaCredito.ErroComunicacao;
            }
        }
    }
}

Implementando os testes

O próximo passo agora consistirá na criação de um projeto de testes chamado ConsultaCredito.Testes. Selecionar para isto o template xUnit Test Project (.NET Core), no qual será feito uso do framework xUnit para a implementação de testes de unidade:

Acrescentar ainda uma referência à biblioteca ConsultaCredito:

E adicionar o package do NSubstitute ao projeto ConsultaCredito.Testes:

Na classe Testes foram implementadas validações para os diferentes casos de teste envolvendo o uso do tipo AnaliseCredito:

  • Quatro constantes (CPF_INVALIDO, CPF_ERRO_COMUNICACAO, CPF_SEM_PENDENCIAS e CPF_INADIMPLENTE) foram definidas, com as mesmas correspondendo aos diferentes números de CPF empregados em cada validação prevista pela classe Testes;
  • Uma instância do tipo IServicoConsultaCredito (mock) será criada no construtor de Testes, por meio de uma chamada ao método For da classe Substitute (namespace NSubstitute);
  • O Mock Object resultante será configurado, através de chamadas aos métodos ConsultarPendenciasPorCPF (que receberá o CPF específico para um caso de teste) e Returns (este último um Extension Method provido pelo framework NSubstitute, no qual será indicado o retorno esperado para cada CPF);
  • As operações TestarParametroInvalido, TestarErroComunicacao, TestarCPFSemPendencias e TestarCPFInadimplente foram marcadas com o atributo Fact (namespace Xunit), de forma a permitir que o Visual Studio identifique os métodos/casos de teste a serem executados;
  • O método auxiliar ObterStatusAnaliseCredito receberá um CPF e utilizará o Mock Object criado no construtor, a fim de obter o retorno a ser validado por cada um dos 4 casos de teste.
using System;
using System.Collections.Generic;
using Xunit;
using NSubstitute;

namespace ConsultaCredito.Testes
{
    public class Testes
    {
        private const string CPF_INVALIDO = "123A";
        private const string CPF_ERRO_COMUNICACAO = "76217486300";
        private const string CPF_SEM_PENDENCIAS = "60487583752";
        private const string CPF_INADIMPLENTE = "82226651209";

        private IServicoConsultaCredito mock;

        public Testes()
        {
            mock = Substitute.For<IServicoConsultaCredito>();

            mock.ConsultarPendenciasPorCPF(CPF_INVALIDO)
                .Returns((List<Pendencia>)null);

            mock.ConsultarPendenciasPorCPF(CPF_ERRO_COMUNICACAO)
                .Returns(s => { throw new Exception("Erro de comunicação..."); });

            mock.ConsultarPendenciasPorCPF(CPF_SEM_PENDENCIAS)
                .Returns(new List<Pendencia>());

            Pendencia pendencia = new Pendencia();
            pendencia.CPF = CPF_INADIMPLENTE;
            pendencia.NomePessoa = "João da Silva";
            pendencia.NomeReclamante = "Empresa XYZ";
            pendencia.DescricaoPendencia = "Parcela não paga";
            pendencia.VlPendencia = 700;
            List<Pendencia> pendencias = new List<Pendencia>();
            pendencias.Add(pendencia);

            mock.ConsultarPendenciasPorCPF(CPF_INADIMPLENTE)
                .Returns(pendencias);
        }

        private StatusConsultaCredito ObterStatusAnaliseCredito(string cpf)
        {
            AnaliseCredito analise = new AnaliseCredito(mock);
            return analise.ConsultarSituacaoCPF(cpf);
        }

        [Fact]
        public void TestarParametroInvalido()
        {
            StatusConsultaCredito status =
                ObterStatusAnaliseCredito(CPF_INVALIDO);
            Assert.Equal(
                StatusConsultaCredito.ParametroEnvioInvalido, status);
        }

        [Fact]
        public void TestarErroComunicacao()
        {
            StatusConsultaCredito status =
                ObterStatusAnaliseCredito(CPF_ERRO_COMUNICACAO);
            Assert.Equal(
                StatusConsultaCredito.ErroComunicacao, status);
        }

        [Fact]
        public void TestarCPFSemPendencias()
        {
            StatusConsultaCredito status =
                ObterStatusAnaliseCredito(CPF_SEM_PENDENCIAS);
            Assert.Equal(
                StatusConsultaCredito.SemPendencias, status);
        }

        [Fact]
        public void TestarCPFInadimplente()
        {
            StatusConsultaCredito status =
                ObterStatusAnaliseCredito(CPF_INADIMPLENTE);
            Assert.Equal(
                StatusConsultaCredito.Inadimplente, status);
        }
    }
}

Executando os testes

Para executar o projeto de testes será necessário acessar o menu Tests > Run > All Tests:

Na janela Test Explorer constará o resultado da execução dos diferentes casos de teste:

Conclusão

Os frameworks de Mocking Test simulam o comportamento de objetos em diferentes cenários, dispensando os desenvolvedores da necessidade de criar implementações que seriam descartadas/desativadas num ambiente de produção. O NSubstitute exemplifica bem isto, sendo que outro ponto que pesa a seu favor está no fato do mesmo exigir menores esforços de codificação quando comparado ao Moq.

Referências