Este artigo foi publicado em 10/02/2015. Por ter sido considerado um dos melhores artigos de 2015, foi republicado hoje.
Aprendi Orientação a Objetos em Java. Inicialmente me foi ensinado a utilizar o construtor para inicializar todas dependências que o objeto necessita para funcionar. Sendo assim, o código da classe Sorteador é perfeitamente válido:
package di; public class Sorteador { private long limite; public Sorteador(long limite) { this.limite = limite; } public long sortear(){ double numero = Math.random()*this.limite; return Math.round(numero); } }
Contudo, o problema dessa abordagem consiste na dificuldade de testar o método sortear. Como ele depende de um número aleatório, o máximo que se consegue é verificar se o sorteio se encontra dentro dos limites esperados, conforme código SorteadorTest:
package di; import static org.junit.Assert.*; import org.junit.Test; public class SorteadorTest { @Test public void testSortear() { Sorteador sorteador=new Sorteador(10); long sorteio=sorteador.sortear(); assertTrue(sorteio>=0); assertTrue(sorteio<=10); } }
Para contornar o problema, poderíamos componentizar com a finalidade de alterar o comportamento no momento de teste. Com esse objetivo, criei a interface Randomizador:
package di2; public interface Randomizador { double random(); }
Para continuar executando o comportamento original, criei a classe RandomizadorReal:
package di2; public class RandomizadorReal implements Randomizador { @Override public double random() { return Math.random(); } }
Dessa maneira alterei o código do Sorteador . Com essa nova abordagem é possível alterar o componente de randomização utilizando o construtor sobrecarregado. Nessa caso, a dependência do objeto é injetada de fora para dentro. Por isso esse conceito é chamado de “Inversão de Controle” ou também “Injeção de Dependência”:
package di2; public class Sorteador { private long limite; private Randomizador randomizador; public Sorteador(long limite, Randomizador randomizador) { super(); this.limite = limite; this.randomizador = randomizador; } public Sorteador(long limite) { this(limite, new RandomizadorReal()); } public long sortear(){ double numero = randomizador.random()*this.limite; return Math.round(numero); } }
Com o código modificado é possível escrever um teste com mais controle. Para isso, basta trocar o componente RandomizadorReal pela classe RandomizadorMock, conforme código a seguir:
package di2; import static org.junit.Assert.*; import org.junit.Test; class RandomizadorMock implements Randomizador{ public double retorno=0; @Override public double random() { return retorno; } } public class SorteadorTest { @Test public void testSortear() { RandomizadorMock mock=new RandomizadorMock(); Sorteador sorteador=new Sorteador(10, mock); mock.retorno=0; long sorteio=sorteador.sortear(); assertEquals(0, sorteio); mock.retorno=0.5; sorteio=sorteador.sortear(); assertEquals(5, sorteio); mock.retorno=0.54; sorteio=sorteador.sortear(); assertEquals(5, sorteio); mock.retorno=0.56; sorteio=sorteador.sortear(); assertEquals(6, sorteio); } }
Esses objetos que se utilizamos para substituir componentes na hora de testes costumam ser chamados de Stub ou Mock. Foi com esses conceitos em mente que migrei para a linguagem Python.
Nela posso escrever classe similar a primeira versão de Sorteador:
from random import random class Sorteador(): def __init__(self, limite): self.limite = limite def sortear(self): return round(self.limite * random())
Poderia empregar a mesma técnica para componentizar o método sorteio. Contudo, Python tem uma particularidade interessante: tudo, absolutamente tudo, é um objeto. Inclusive as bibliotecas importadas em um módulo.
Para provar isso, é muito simples. Basta chamar a função dir para inspecionar os elementos do módulo di.py:
>>> import di >>> [elemento for elemento in dir(di) if not elemento.startswith('__')] ['Sorteador', 'random']
Assim, é possível observar a função random importada como sendo um de seus elementos. Ou seja, bibliotecas importadas em um módulo funcionam como variáveis. Sendo assim, em vez de injetar dependência, a biblioteca alvo pode ser simplesmente alterada na hora do teste:
from unittest import TestCase import di class SorteadorTestes(TestCase): def teste_sortear(self): sorteador = di.Sorteador(10) di.random = lambda: 0.5 sorteio = sorteador.sortear() self.assertEqual(5, sorteio) di.random = lambda: 0.54 sorteio = sorteador.sortear() self.assertEqual(5, sorteio) di.random = lambda: 0.56 sorteio = sorteador.sortear() self.assertEqual(6, sorteio)
De início passei a utilizar essa técnica mas achava que estava fazendo gambiarra. No entanto descobri a biblioteca Mock do Python. Sua função patch serve exatamente para isso, trocar uma biblioteca em tempo de teste por outra. E ela ainda vai além, tomando o cuidado de retornar o componente para seu objeto original após o teste, evitando assim efeitos colaterais indesejados em outros testes.
Enfim, eu gostei muito dessa abordagem distinta do Python. E você o que acha? É gambiarra ou apenas uma forma prática de se fazer Injeção de Dependência?