in C#

Testes em C# com internal, private e protected (modificadores de acesso)

O uso de modificadores de acesso é extremamente importante para isolar e garantir a separação, organização e uso correto de nossas classes, métodos ou propriedades.

No C# existem diferentes modificadores de acesso:

  • public: acesso irrestrito.
  • protected: o acesso é limitado a classe ou a tipos que derivem da mesma classe.
  • internal: o acesso é limitado ao assembly.
  • protected internal: o acesso é limitado ao assembly, a própria classe ou a tipos derivados dela.
  • private: o acesso é limitado a própria classe.

Com isso, é muito comum em cenários de implementação de testes ter que acessar alguns objetos que estão protegidos, ou inacessíveis de acordo com os modificadores de acesso.
A idéia deste post é mostrar algumas técnicas para testar classes ou métodos que estão configurados com algum modificador de acesso, nos exemplos abaixo, vou mostrar como testar um método internal e outro private.
Para o exemplo vou criar dois projetos: ClassLibraryDemo (Class Library) e ClassLibraryTestDemo (Unit Test).

image

No projeto de class library temos uma classe bem simples chamada LivroService.cs (notem que ela é internal, e possui alguns outros modificadores de acesso).

namespace ClassLibraryDemo
{
    internal class LivroService
    {
        internal int CalculaExemplares(int numeroDeCaixas, int quantidadeDeLivrosPorCaixa)
        {
            //exemplo
            return numeroDeCaixas * quantidadeDeLivrosPorCaixa;
        }
        private double CalculaPesoDaCaixa(double pesoLivro, int quantidadeDeLivros)
        {
            //exemplo
            return pesoLivro * quantidadeDeLivros;
        } 

        protected double CalculaPesoDaCaixaComACaixa(double pesoLivro,double pesoCaixa, int quantidadeDeLivros)
        {
            //exemplo
            return (pesoLivro * quantidadeDeLivros)+pesoCaixa;
        }
    }
}

internal

Existe uma configuração no projeto que defini para quais assemblies os objetos configurados como internal serão visíveis. Para configurar isso, precisaremos ir no AssemblyInfo.cs do nosso class library e adicionar a seguinte linha:

[assembly: InternalsVisibleTo(“ClassLibraryTestDemo”)]

Deixando ele da seguinte maneira:

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; 

// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("ClassLibraryDemo")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ClassLibraryDemo")]
[assembly: AssemblyCopyright("Copyright ©  2014")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: InternalsVisibleTo("ClassLibraryTestDemo")] 

// Setting ComVisible to false makes the types in this assembly not visible
// to COM components.  If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)] 

// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("71cc02d6-daeb-4cf8-bcfb-78fdde90d6b8")] 

// Version information for an assembly consists of the following four values:
//
//      Major Version
//      Minor Version
//      Build Number
//      Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Com isso já podemos ir em nosso projeto de testes ClassLivraryTestDemo e implementar nosso primeiro teste, como por exemplo:

using ClassLibraryDemo;
using Microsoft.VisualStudio.TestTools.UnitTesting; 

namespace ClassLibraryTestDemo
{
    [TestClass]
    public class LivroServiceTest
    {
        [TestMethod]
        public void Verifica_Se_2_Caixas_Com_3_Livros_Tem_Um_Total_De_6_Livros()
        {
            const int numeroDeCaixas = 2;
            const int totalEmCadaCaixa = 3; 

            var livroService = new LivroService();
            var totalDeLivros = livroService.CalculaExemplares(numeroDeCaixas, totalEmCadaCaixa); 

            Assert.AreEqual(6,totalDeLivros);
        }
    }
}

Notem que tanto a classe como o método estão acessíveis normalmente no nosso projeto de testes \o/

image

private

Existem diversas maneiras de acessar e trabalhar com métodos que estão configurados como private, no exemplo abaixo utilizei Reflection para acessar e implementar um teste para validar método CalculaPesoDaCaixa.

[TestMethod]
public void Verifica_Se_2_Livros_De_200_Gramas_Pesam_400_Gramas()
{
    const int numeroDeLivros = 2;
    const double pesoDeCadaLivro= 0.2; 

    var livroService = new LivroService();
    var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic;
    MethodInfo mInfoMethod = typeof(LivroService).GetMethod("CalculaPesoDaCaixa", bindingFlags);
    var pesoTotal = (double)mInfoMethod.Invoke(livroService, new object[] { pesoDeCadaLivro, numeroDeLivros });  

    Assert.AreEqual(0.4, pesoTotal);
}

Claro que o using do System.Reflection é necessário:

using System.Reflection;

protected

Da mesma maneira que utilizamos Reflection para testar o método privado, podemos utilizar ele para testar um método configurado como protected, abaixo eu testo o CalculaPesoDaCaixaComACaixa utilizando Reflection.

[TestMethod]
public void Verifica_Se_2_Livros_De_300_Gramas_Com_Uma_Caixa_De_100_Gramas_Pesam_700_Gramas()
{
    const int numeroDeLivros = 2;
    const double pesoDeCadaLivro = 0.3;
    const double pesoDaCaixa = 0.1; 

    var livroService = new LivroService(); 

    var bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic; 

    MethodInfo mInfoMethod = typeof(LivroService).GetMethod("CalculaPesoDaCaixaComACaixa", bindingFlags);
    var pesoTotal = (double)mInfoMethod.Invoke(livroService, new object[] { pesoDeCadaLivro, pesoDaCaixa,numeroDeLivros }); 

    Assert.AreEqual(0.7, pesoTotal);
}

O projeto de exemplo está disponível no GitHub https://github.com/rodolfofadino/TestsInInternalAndPrivate

Estou a disposição para qualquer dúvida, criticas ou sugestões.
abs
Rodolfo