Criando e testando objetos com construtores privados

Imaginem a seguinte situação. Temos uma classe qualquer. Essa classe possui uma dependência. Além disso, vamos supor que a criação desse objeto seja uma coisa complexa, ou seja, não podemos simplesmente deixar que essa classe seja instanciada por um new.

// MeuObjeto.cs
    public class MeuObjeto
    {
        public IMinhaDependencia Dependencia { get; set; }

        internal MeuObjeto()
        {
            // internal para garantir que apenas
            // a factory possa criar este objeto
        }

        public int RetornarCalculo(int a)
        {
            return Dependencia.Calcular() + a;
        }
    }

    // IMinhaDependencia.cs
    public interface IMinhaDependencia
    {
        int Calcular();
    }

Temos então uma classe factory para criar uma instância desse objeto. Problema resolvido, correto? Se eu precisar de uma instância dessa classe, simplesmente chamo sua factory.

// MeuObjetoFactory.cs
    public class MeuObjetoFactory
    {
        public MeuObjeto CriarObjeto()
        {
            return new MeuObjeto()
            {
                Dependencia = new MinhaDependencia()
            };
        }
    }

    // MinhaDependencia .cs
    // faz de conta que é um objeto muito custoso para criar
    public class MinhaDependencia : IMinhaDependencia
    {
        public int Calcular()
        {
            return 1;
        }
    }

Do ponto de vista funcional, funciona que é uma beleza. Mas como podemos testar unitariamente a classe MeuObjeto? Neste caso, teríamos duas opções:

  1. Usamos a factory no nosso teste de unidade para criar o objeto. O problema nesse caso é que nosso teste de unidade passaria a testar a classe MeuObjeto, a factory MeuObjetoFactory além de obrigatoriamente criar uma instância da dependência MinhaDependencia, o que implica que a testaríamos a sua criação também. Isso deixou de ser um teste de unidade.
  2. Alteramos o escopo de MeuObjeto e deixamos seu construtor público. Com isso, podemos instanciá-lo diretamente, sem usar nada do factory nem criar sua dependência, e passar um objeto dublê de teste (mock) para simular o comportamento da dependência. Só que com isso, qualquer desavisado poderia instanciar diretamente MeuObjeto, e era exatamente isso que queríamos evitar quando fizemos seu construtor com escopo internal (visível apenas dentro do assembly que ele reside) e criamos a factory.
E agora, qual opção seguir?

Bom, temos uma terceira opção, pegando o que há de bom em cada cenário acima, sem suas desvantagens!

Vamos usar uma classe chamada PrivateObject, disponível no namespace Microsoft.VisualStudio.TestTools.UnitTesting. Com ela, podemos criar e chamar métodos de qualquer classe, mesmo que esses métodos sejam privados. Olhem só a classe de testes abaixo.

// UnitTest1.cs
    [TestClass]
    public class UnitTest1
    {
        [TestMethod]
        [Ignore]
        public void TestMethod1()
        {
            //var obj = new MeuObjeto(); -- isso dá erro de compilação
        }

        [TestMethod]
        public void TestMethod2()
        {
            // Arrange
            var mock = new Mock<IMinhaDependencia>();
            mock.Setup(m => m.Calcular()).Returns(1);
            var po = new PrivateObject(typeof(MeuObjeto));
            var obj = (MeuObjeto)po.Target;
            obj.Dependencia = mock.Object;
            // Act
            var resultado = obj.RetornarCalculo(2);
            // Assert
            Assert.AreEqual(3, resultado);
        }
    }

O método TestMethod1 é só para mostrar que não funciona instanciar direto um objeto com construtor não visível (privado ou como no nosso caso, internal). Vamos olhar o método TestMethod2, que é mais interessante para nós. A primeira coisa que ele está fazendo é criar um objeto dublê de teste usando o framework Moq. Não é escopo deste post explicar mocks ou o Moq, então aqui está um link para mais informações http://code.google.com/p/moq/wiki/QuickStart. Mas explicando de forma bem rápida, o que estamos fazendo é criar um objeto que possua os mesmos métodos da interface IMinhaDependencia, e ajustando o comportamento que ele terá quando formos chamar o seu método Calcular (no nosso exemplo, estamos cravando que seu retorno será o valor 1). Com isso, "enganamos" a instância de MeuObjeto, fazendo com que ele acredite que esteja trabalhando com uma implementação concreta.

Bom, voltando ao assunto original, chegamos agora na parte onde efetivamente criamos o objeto de construtor não-visível. Simplesmente criamos uma instância de PrivateObject passando o tipo que queremos que ele crie. De forma "mágica", ele nos cria esse objeto (internamente ele deve usar .NET Reflection para criar a instância, mas como eu não tenho certeza disso, considero que é por mágica). Assim, conseguimos pegar a sua referência acessando a propriedade Target de PrivateObject. Com isso, podemos continuar nosso teste de unidade, passando o mock que foi gerado e testando a sua funcionalidade dentro do teste de caixa-branca.

Por hoje é só. Em breve eu disponibilizo os fontes desse exemplo em algum repositório público (ainda estou decidindo se eu uso o BitBucket ou o GitHub).

[]'s

ATUALIZAÇÃO: Disponibilizei o exemplo em https://github.com/fabiogouw/Exemplos. Decidi deixar minha conta no Github para coisas públicas, e o Bitbucket para coisas pessoais.

Comentários

Postagens mais visitadas deste blog

Trocando configurações padrão do Live TIM

Uma proposta de Clean Architecure com Modelo de Atores

Testes automatizados em sistemas autenticados com certificados digitais, usando Selenium e PhantomJS