Páginas

segunda-feira, 19 de março de 2012

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.

Nenhum comentário:

Postar um comentário