Compartilhar via


Mocking Test na plataforma .NET: utilizando o framework Moq

O objetivo deste artigo é descrever o uso de Mock Objects em testes automatizados implementados em .NET, partindo para isto de um exemplo prático que emprega o framework Moq.

Introdução

Embora haja uma forte recomendação quanto ao uso de testes automatizados em projetos de software, existem diversas situações nas quais podem surgir empecilhos para a adoção deste tipo de prática. Como resultado direto disto há uma tendência em se relegar este processo a um segundo plano, não sendo raro que a validação de determinadas funcionalidades aconteça apenas em ambientes de produção. Esta última abordagem é totalmente desaconselhável, acarretando muitas vezes na indisponibilidade de um sistema durante um intervalo considerável de tempo.

Dentre os aspectos técnicos que costumam dificultar a utilização de testes automatizados estão:

  • As dependências existentes entre diferentes partes de um software;
  • A inexistência de ambientes com configurações específicas para testes;
  • Integrações com parceiros que não disponibilizam condições adequadas para testes.

A solução para tais cenários passa, basicamente, pela simulação/imitação de comportamentos até certo ponto “impossíveis” de se testar. Isto pode ser conseguido através do uso de Mocks, construções que emulam o comportamento de um objeto e tornam possível assim a validação de estruturas que dependam do mesmo.

No caso da plataforma .NET, existem algumas alternativas disponíveis para a implementação de técnicas de Mocking Test. Uma destas opções é o framework open source Moq, o qual simplifica a geração de Mocks para o teste de soluções construídas em .NET.

Do ponto de vista prático, o Moq dispensa a criação de estruturas de código que seriam descartadas posteriormente. Para isto o mesmo conta com recursos que permitem definir dentro de testes unitários o retorno de métodos, propriedades e até o lançamento de exceções em tempo de execução.

A intenção com este artigo é demonstrar a implementação de Mock Objects em testes automatizados codificados em .NET, empregando para tanto o framework Moq em um exemplo a ser detalhado nas próximas seções.

Criando o projeto a ser testado

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

  • O Microsoft Visual Studio Community 2015 como IDE de desenvolvimento;
  • A versão 4.6 do .NET Framework.

A solução descrita neste artigo foi disponibilizada no Technet Gallery, podendo ser baixada a partir do link:

https://gallery.technet.microsoft.com/Mocking-Test-na-plataforma-031496ce

Inicialmente será criado um projeto do tipo “Class Library” chamado “ExemploConsultaCredito”, como indicado na imagem a seguir:

Na biblioteca ExemploConsultaCredito constarão recursos que serão empregados na interação com um serviço de consulta a crédito, tomando como base para isto o número do CPF de eventuais clientes. No próximo diagrama estão as estruturas que deverão ser implementadas neste projeto:

Na listagem a seguir encontram-se definidos os seguintes elementos:

  • O enumeration StatusConsultaCredito, que contém os diferentes status possíveis na interação com o serviço de consulta a crédito;
  • A classe Pendencia, na qual estão informações que identificam a pessoa física e uma pendência financeira vinculada a esta última.

No caso específico do enumeration StatusConsultaCredito, tal estrutura conta com valores prevendo as seguintes situações:

  • O envio de um CPF inválido (valor “ParametroEnvioInvalido”);
  • A ocorrência de uma exceção ao se 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”).
using System;
 
namespace ExemploConsultaCredito
{
    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; }
    }
}

Já a interface IServicoConsultaCredito (detalhada na próxima listagem) representa o meio a partir do qual acontecerá a interação com o mecanismo de consulta a crédito. Os diferentes serviços utilizados pela solução aqui apresentada deverão implementar esta interface, na qual é possível notar a presença de um método chamado ConsultarPendenciasPorCPF. Esta operação receberá como parâmetro o CPF de uma pessoa física, retornando como resultado uma coleção de instâncias do tipo Pendencia.

A opção por uma interface tornará mais fácil as simulações através do framework Moq. Com isto se evita a realização de pesquisas junto a um serviço que é pago, algo que poderia resultar em gastos adicionais durante as fases de desenvolvimento e testes da solução.

using System.Collections.Generic;
 
namespace ExemploConsultaCredito
{
    public interface  IServicoConsultaCredito
    {
        IList<Pendencia> ConsultarPendenciasPorCPF(string cpf);
    }
}

Por fim, na listagem a seguir está a implementação da classe AnaliseCredito. Caberá a este tipo invocar o serviço de consulta a crédito, de forma a analisar o retorno produzido por este último. Observando a estrutura de AnaliseCredito é possível notar:

  • O construtor desta classe recebe como parâmetro uma instância baseada na interface IServicoConsultaCredito, a qual será associada a um atributo privado (_servConsultaCredito) para uso posterior;
  • O método ConsultarSituacaoCPF receberá como parâmetro o número de CPF de uma pessoa física. Esta operação invoca então o método ConsultarPendenciasPorCPF de IServicoConsultaCredito, empregando para isto a referência informada como parâmetro ao se acionar o construtor da classe AnaliseCredito. Caso o retorno deste processo seja nulo, assume-se que o CPF informado é inválido (valor “ParametroEnvioInvalido” do enumeration StatusConsultaCredito). Do contrário, será analisado o conteúdo associado à coleção de referências do tipo Pendencia;
  • Se nenhuma despesa em atraso for encontrada, será retornado o valor “SemPendencias” do enumeration StatusConsultaCredito. Caso contrário, o valor “Inadimplente” será devolvido como retorno da execução do método ConsultarSituacaoCPF;
  • Eventuais erros durante a execução da operação ConsultarSituacaoCPF retornarão o valor “ErroComunicacao” do enumeration StatusConsultaCredito.
namespace ExemploConsultaCredito
{
    public class  AnaliseCredito
    {
        private IServicoConsultaCredito _servConsultaCredito;
 
        public AnaliseCredito(IServicoConsultaCredito servConsultaCredito)
        {
            this._servConsultaCredito = servConsultaCredito;
        }
 
        public StatusConsultaCredito ConsultarSituacaoCPF(string cpf)
        {
            try
            {
                var pendencias =
                    this._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

Finalizada a codificação das estruturas que serão submetidas à validação, chega agora o momento de proceder com a criação de um projeto de testes chamado “ExemploFinancas.Testes”. Selecionar para isto o template “Unit Test Project”:

O projeto ExemploConsultaCredito.Testes fará uso, por default, do Visual Studio Unit Testing Framework (também conhecido como MS Test) para a implementação de testes unitários automatizados. Será necessário adicionar ainda referências à biblioteca ExemploConsultaCredito e ao framework Moq (este último via NuGet), como indicado nas próximas imagens:

OBSERVAÇÃO: o exemplo descrito nesta seção emprega a versão 4.2.1510.2205 do framework Moq.

Na próxima listagem está parte do código correspondente à classe TestesMoq. Nesta estrutura serão implementados os casos de testes para validação do tipo AnaliseCredito:

  • A classe TestesMoq foi marcada com o atributo TestClassAttribute (namespace Microsoft.VisualStudio.TestTools.UnitTesting). Isto se faz necessário pois, diferente de uma Console Application ou outro aplicativo criado em .NET, projetos de testes não contam com um método Main para iniciar a sua execução. Logo, o Visual Studio irá buscar sempre classes marcadas com o atributo citado, com o objetivo de acionar os diferentes testes presentes nas mesmas;
  • A classe genérica Mock<T> (namespace Moq) permitirá que Mock Objects sejam gerados, com T representando normalmente uma interface. No caso específico da classe TestesMoq, a interface IServicoConsultaCredito foi informada como parâmetro ao tipo Mock<T>;
  • 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 caso de teste previsto por TestesMoq;
  • O método InicializarMockObject foi marcado com o atributo TestInitializeAttribute (Microsoft.VisualStudio.TestTools.UnitTesting), de maneira a ser acionado automaticamente no início da execução de um ou mais testes unitários. Tal ação tem por meta configurar o Mock Object empregado na validação da classe AnaliseCredito, simulando os comportamentos esperados para implementações concretas da interface IServicoConsultaCredito;
  • Já o método ObterStatusAnaliseCredito será utilizado na análise de crédito a partir de um número de CPF. Acessando a propriedade Object do tipo Mock<IServicoConsultaCredito>, esta operação terá acesso a uma instância derivada da interface IServicoConsultaCredito em tempo de execução (sem que isto implique na codificação de uma classe baseada em IServicoConsultaCredito). Este objeto servirá de base então para a criação de uma referência da classe AnaliseCredito, por meio da qual será invocado o método ConsultarSituacaoCPF.

Quanto à forma como o método InicializarMockObject foi implementado, é possível ainda observar:

  • A criação de uma instância do tipo Mock<IServicoConsultaCredito>, em cujo construtor se informou o valor MockBehavior.Strict. Tal ajuste fará com que ocorram exceções, caso invocações a métodos da interface IServicoConsultaCredito não tenham sido devidamente configuradas no objeto representado pela variável “mock” (ao invés de um erro, o comportamento padrão seria o framework Moq retorna o valor default para chamadas não definidas previamente);
  • Uma primeira invocação do método Setup a partir da variável “mock” fará uso de uma expressão lambda, configurando o retorno nulo (via operação Returns) para chamadas ao método ConsultarPendenciasPorCPF de IServicoConsultaCredito que informem como parâmetro o valor associado à constante CPF_INVALIDO;
  • Ao se invocar uma segunda vez o método Setup será configurado o lançamento de uma exceção, com isto ocorrendo em chamadas que forneçam como parâmetro um valor idêntico àquele representado pela constante CPF_ERRO_COMUNICACAO;
  • A terceira chamada ao método Setup tem por objetivo prever cenários que envolvam clientes sem pendências financeiras. Uma lista vazia baseada no tipo Pendencia será empregada neste processo, além de se utilizar a constante CPF_SEM_PENDENCIAS;
  • Uma coleção com um item do tipo Pendencia também será gerada, de forma a configurar chamadas que envolvam um CPF que possua pendências (indicado pela constante CPF_INADIMPLENTE).
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using ExemploConsultaCredito;
 
namespace ExemploConsultaCredito.Testes
{
    [TestClass]
    public class  TestesMoq
    {
        private Mock<IServicoConsultaCredito> mock;
 
        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";
 
        [TestInitialize]
        public void  InicializarMockObject()
        {
            mock = new  Mock<IServicoConsultaCredito>(MockBehavior.Strict);
 
            mock.Setup(s => s.ConsultarPendenciasPorCPF(CPF_INVALIDO))
                .Returns(() => null);
 
            mock.Setup(s => s.ConsultarPendenciasPorCPF(CPF_ERRO_COMUNICACAO))
                .Throws(new Exception("Testando erro de comunicação..."));
 
            mock.Setup(s => s.ConsultarPendenciasPorCPF(CPF_SEM_PENDENCIAS))
                .Returns(() => new  List<Pendencia>());
 
            List<Pendencia> pendencias = new  List<Pendencia>();
            pendencias.Add(new Pendencia()
            {
                CPF = CPF_INADIMPLENTE,
                NomePessoa = "João da Silva",
                NomeReclamante = "ACME Comercial LTDA",
                DescricaoPendencia = "Cartão de Crédito",
                DataPendencia = new  DateTime(2015, 02, 14),
                VlPendencia = 600.47
            });
            mock.Setup(s => s.ConsultarPendenciasPorCPF(CPF_INADIMPLENTE))
                .Returns(() => pendencias);
        }
 
        private StatusConsultaCredito ObterStatusAnaliseCredito(string cpf)
        {
            AnaliseCredito analise = new  AnaliseCredito(mock.Object);
            return analise.ConsultarSituacaoCPF(cpf);
        }
 
        ...
 
    }
}

A segunda parte da classe TestesMoq está na listagem seguinte:

  • Os diferentes métodos que representam testes (TestarParametroInvalido, TestarErroComunicacao, TestarCPFSemPendencias e TestarCPFInadimplente) também estão marcados com um atributo, sendo que neste último caso foi utilizado o tipo TestMethodAttribute (namespace Microsoft.VisualStudio.TestTools.UnitTesting). Mais uma vez, será por meio deste vínculo que o mecanismo de execução de testes do Visual Studio identificará quais métodos precisarão ser executados;
  • As verificações de cada caso de teste serão feitas através do uso do método AreEqual do tipo Assert (namespace Microsoft.VisualStudio.TestTools.UnitTesting). Esta operação recebe como parâmetros o resultado da execução do método ObterStatusAnaliseCredito, 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 Visual Studio.
using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using ExemploConsultaCredito;
 
namespace ExemploConsultaCredito.Testes
{
    [TestClass]
    public class  TestesMoq
    {
        ...
 
        [TestMethod]
        public void  TestarParametroInvalido()
        {
            StatusConsultaCredito status = 
                ObterStatusAnaliseCredito(CPF_INVALIDO);
            Assert.AreEqual(
                StatusConsultaCredito.ParametroEnvioInvalido, status);
        }
 
        [TestMethod]
        public void  TestarErroComunicacao()
        {
            StatusConsultaCredito status =
                ObterStatusAnaliseCredito(CPF_ERRO_COMUNICACAO);
            Assert.AreEqual(
                StatusConsultaCredito.ErroComunicacao, status);
        }
 
        [TestMethod]
        public void  TestarCPFSemPendencias()
        {
            StatusConsultaCredito status =
                ObterStatusAnaliseCredito(CPF_SEM_PENDENCIAS);
            Assert.AreEqual(
                StatusConsultaCredito.SemPendencias, status);
        }
 
        [TestMethod]
        public void  TestarCPFInadimplente()
        {
            StatusConsultaCredito status =
                ObterStatusAnaliseCredito(CPF_INADIMPLENTE);
            Assert.AreEqual(
                StatusConsultaCredito.Inadimplente, status);
        }
    }
}

OBSERVAÇÃO: 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 TestClassAttribute, a classe marcada com essa construção estará associada apenas a uma instrução com o valor “TestClass” (entre colchetes).

Codificados os testes destinados à validação da classe AnaliseCredito, a próxima ação consistirá na execução dos mesmos. As diferentes opções que permitem acionar o processamento de testes unitários encontram-se no menu Test do Visual Studio:

Ao acionar a opção “All Tests” será exibida na janela “Test Explorer” os resultados da execução da classe TestesMoq. Conforme pode ser observado, todos os testes foram concluídos com êxito:

Conclusão

Este artigo procurou apresentar, em linhas gerais, como Mock Objects podem ser úteis na implementação de testes unitários. Simulando o comportamento de objetos em tempo de execução e dispensando a construção de estruturas que seriam posteriormente descartadas, frameworks como o Moq constituem uma importante ferramenta na condução de testes em cenários mais complexos.

Referências

Moq
https://github.com/Moq/moq4

Moq – Quickstart
https://github.com/Moq/moq4/wiki/Quickstart