C# - Testes de Unidade com métodos private


 Hoje veremos como realizar testes de unidade com métodos private na linguagem C#.

Um método privado é um método de classe que só pode ser chamado de dentro da classe declarante. Se você tentar acessar um método private de fora da sua classe, seu código simplesmente não compilará.

Certamente, se você trabalha com projetos que requerem controle de qualidade, você teve que apresentar um projeto de teste que envolve testes de unidade.

Nesse caso, você provavelmente teve problemas ao tentar testar um método que não é público.

Acessando o método privado com Reflection

Uma forma de contornar este problema é usar Reflection.

Um programa pode olhar para os metadados de outros assemblies ou de si mesmo, enquanto ele está executando. Quando um programa em execução olha para seus próprios metadados, ou de outros programas, a ação é chamada de Reflection.

Nota: Os metadados são informações sobre os dados, ou seja, informações sobre os tipos, o código, assembly e assim por diante - que é armazenada junto com seu programa.

Dessa forma, Reflection é o processo pelo qual um programa pode ler seus próprios metadados. Um programa que faz Reflection sobre si mesmo, extrai metadados de sua montagem e usa estes metadados ou para informar ao usuário ou para modificar o seu próprio comportamento.

Usando Reflection temos a capacidade de ler metadados em tempo de execução e assim é possível descobrir os métodos, propriedades e eventos de um tipo, e a seguir invocá-los dinamicamente; podemos também criar novos tipos em tempo de execução.

Usar Reflection pode ser uma alternativa para resolver o problema pois com Reflection podemos obter informações sobre um método e até mesmo invocar o método e com este recurso podemos também chamar um método privado em uma classe.

Tomando como base o código da classe MinhaClasse abaixo:

public class MinhaClasse
{
    private string _nome { get; set; }
    private string _email { get; set; }

    public MinhaClasse(string nome, string email)
    {
        _nome = nome;
        _email = email;
    }

    public string MeuMetodoPrivado(string _nome)
    {
        if (string.IsNullOrEmpty(_nome))
            throw new FaltouInformarNomeException();

        return this.MeuMetodoPrivado(_nome, _email);
    }

    private string MeuMetodoPrivado(string nome)
    {
        return $"Bem-Vindo {nome} !";
    }
}

E a classe FatouInformarNomeException() que trata a exceção:

public class FaltouInformarNomeException : Exception
{
    public FaltouInformarNomeException() : base("Faltou informar o nome")
     {
     }
}

Podemos acessar o método privado MeuMetodoPrivado() podemos usar o seguinte código:

typeof(MinhaClasse)
     .
GetMethod("MeuMetodoPrivado", BindingFlags.NonPublic | BindingFlags.Instance)
     .
Invoke(new MinhaClasse(), null);

Usando o recurso em um teste de unidade

Parar poder o recurso Reflection mostrado acima em um teste de unidade precisamos inserir algumas coisas como o nome do método, o tipo, uma instância e os parâmetros.

Vamos incluir na solução um novo projeto de testes usando o xUnit e no projeto vamos incluir uma referência a FluentAssertions. (Também não esqueça de incluir uma referência ao projeto principal no projeto de teste.)

A seguir no projeto de teste vamos usar a conhecida função Activator.CreateInstance e buscaremos seus métodos e propriedades usando Linq, em seguida, invocaremos o método para testar com o conhecido método Invoke.

Assim vamos definir o código abaixo no projeto de Testes de unidade  -  TesteProject2:

using FluentAssertions;
using System;
using System.Linq;
using System.Reflection;
using Xunit;

namespace TestProject2
{
    public class UnitTest1
    {
        [Fact]
        public void PrivateMeuMetodoPrivadoBemFormado()
        {
            // Arrange
            var nome = "Macoratti";
            var email = "mac@yahoo.com";

            Type type = typeof(MinhaClasse);
            var instancia = Activator.CreateInstance(type, nome, email);

            MethodInfo method = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
            .Where(x => x.Name == "MeuMetodoPrivado" && x.IsPrivate)
            .First();

            //Act
            var meuMetodoPrivado = (string)method.Invoke(instancia , new object[] { nome, email });

            //Assert
            meuMetodoPrivado
            .Should()
            .StartWith("Bem-Vindo")
            .And
            .EndWith("!")
            .And
            .Contain("Macoratti")
            .And
            .Contain("mac@yahoo.com");
        }
    }
}

Executando o projeto no Test Explorer teremos o resultado do teste conforme abaixo onde acessamos o método privado da classe MinhaClasse:



Vimos assim como testar um método privado.

Afinal , devo ou não devo testar métodos privados ?

Em primeiro lugar os métodos private são privados. Eles não devem ser invocados fora da classe declarante. Mas vamos apenas dizer, para fins de argumentação, que esse é um problema semântico que não se aplica aos testes de unidade. Mesmo se esse fosse o caso, você ainda não deveria realizar testes de unidade em métodos privados.

Veja, o problema com os testes de unidade neste cenário, é que seus testes de unidade serão frágeis. Eles vão depender de muitos de detalhes de implementação que podem e irão mudar.

Você pode encontrar alguma reutilização para um método privado ou decidir refatorar a classe para torná-la mais simples. Ao cobrir esses métodos privados com vários testes, você terá muito mais código para alterar simplesmente porque os detalhes de implementação mudam.

E estamos conversados.

"Porque todos devemos comparecer ante o tribunal de Cristo, para que cada um receba segundo o que tiver feito por meio do corpo, ou bem, ou mal."
2 Coríntios 5:10

Referências:


José Carlos Macoratti