Add new comment

27
Jul

Um padrão de projeto para mock de dependências internas em testes de unidade

Em um bom design orientado a objetos, é boa prática minimizar os números de instanciações de dependências dentro de uma classe (o operador "new", no caso do Java).

Consideremos as seguintes classes:

public class Pessoa1 {

    private Dog dog;

    public Pessoa1() {
        this.dog = new PastorAlemao();
    }
}

public class Pessoa2 {

    private Dog dog;

    public Pessoa2(Dog dog) {
        this.dog = dog;
    }
}

Em geral, o design utilizado em Pessoa2 é muito melhor, pois a Pessoa se torna mais flexível, podendo receber qualquer tipo de Dog, não precisando utilizar sempre o mesmo Dog, como em Pessoa1. Isso gera uma consequência muito importante: é possível criar testes de unidade para Pessoa2 em que o objeto dog é um mock [2]. Isso é muito importante para objetos que realizam chamadas a sistemas externos ou que provoquem algum efeito colateral no mundo real.

Exemplo:

public class Carteiro {

    RotaFinder rotaFinder;

    public Carteiro(RotaFinder rotaFinder) {
        this.rotaFinder = rotaFinder;
    }

    public void entrega(Carta carta) {
        Endereco endereco = carta.getEnderecoDestino();
        Rota rota = this.rotaFinder(endereco);
        // usa a rota pra entregar a carta
    }

}

Suponhamos que RotaFinder utilize uma chamada a um web service para obter a rota a ser percorrida pelo carteiro. Ao fazer um teste de unidade do método “entrega” isso é bem indesejável, pois a chamada a um web service real deixará o teste bem lento, além de dificultar a obtenção de um resultado predizível, o que é importante para o teste (suponhamos que o web service chamado pelo RotaFinder fornece rotas diferentes dependendo do horário para fugir dos engarrafamentos! =P).

Oras, a solução pra isso já foi mencionada, basta criar um mock de RotaFinder no teste, e passar esse mock para o Carteiro através de seu construtor. Com um mock, teremos sempre respostas rápidas e predizíveis do rotaFinder.

Mas isso ainda tem um problema... quem deseja utilizar o Carteiro, deverá instanciar um  RotaFinder pra passar pro Carteiro. Oras, o  RotaFinder é um detalhe interno do Carteiro, e não faz sentido que um usuário do carteiro tenha que conhecer essa classe! Eis um acoplamento indesejável... Mas se eu instanciar o RotaFinder dentro do Carteiro, como passar o mock?!

Difícil dilema!

Lendo o capítulo 11. Systems do livro Clean Code [1], a conclusão sobre esse problema é mais ou menos a seguinte: “Separate construct from use”, ou seja, a inicialização de objetos é uma responsabilidade a parte que merece ser lidada por especialistas (até aqui temos a ideia de que é ruim usar o “new” no meio de uma classe pra instanciar suas dependências). O aconselhado pelo livro é a utilização de frameworks, como o Spring, que cuidam de inicializar objetos e utilizar o padrão de injeção de dependências para fornece-los aos objetos interessados em utilizá-los. Eis um trecho que melhor explica a situação:

A powerful mechanism for separating construction from use is Dependency Injection (DI), the application of Inversion of Control (IoC) to dependency management. Inversion of Control moves secondary responsibilities from an object to other objects that are dedicated to the purpose, thereby supporting the Single Responsibility Principle. In the context of dependency management, an object should not take responsibility for instantiating dependencies itself. Instead, it should pass this responsibility to another “authoritative” mechanism, thereby inverting the control. Because setup is a global concern, this authoritative mechanism will usually be either the “main” routine or a special-purpose container.

Bom, quando eu li isso, meu projeto Java do meu mestrado já estava bem adiantado. Não me parecia ser a hora de inventar de usar coisas como Spring. Possivelmente seria o ideal, mas o custo-benefício naquele momento me induziu a procurar outra saída...

Pensando nisso, há outra abordagem mais simples que é comentada no mesmo capítulo, o uso de factories: em vez de um objeto instanciar sua dependência, ele pede pra factory fazer isso. Mas oras, quem instancia a factory? Voltamos ao mesmo problema! Ah, as vezes o método de instanciação pode ser estático... mas nesse caso, como fazer o mock?! AHHHHH, quanta confusão!

Bom, agora resumirei minha situação problema e o padrão (pattern) de solução que eu bolei considerando todos os compromissos ponderados acima.

Problema: tenho uma classe dependente (ex: Carteiro) que possui uma dependência de uma outra classe (ex: RotaFinder). Para fazer um teste de unidade da classe dependente é preciso fazer um mock da dependência. Mas essa dependência é um detalhe interno da classe dependente, portanto usuários da classe dependente não devem conhecer a classe dependência e nem mesmo a factory da dependência.

Padrão de solução que eu bolei:

public class RotaFinderFactory {

    public static boolean testing = false;
    public static RotaFinder rotaFinderForTesting;

    public RotaFinder getNewInstance() {
        if (testing)
            return  rotaFinderForTesting;
        else
            return new RotaFinder();
    }
}

public class Carteiro {

    public Carteiro() {
    }

    public void entrega(Carta carta) {
        RotaFinder rotaFinder = RotaFinderFactory.getNewInstance();
        Endereco endereco = carta.getEnderecoDestino();
        Rota rota = rotaFinder(endereco);
        // usa a rota pra entregar a carta
    }

}

Até aqui vimos que o Carteiro não recebe mais RotaFinder no construtor, o que evita que o cliente do Carteiro tenha que conhecer RotaFinder. O Carteiro utiliza então um método estático de RotaFinderFactory para obter o RotaFinder. O comportamento padrão desse método, getNewInstance, é simplesmente retornar uma nova instância de RotaFinder.

Mas na hora de fazer o teste de unidade do Carteiro, precisamos mockar o RotaFinder. Eis o que eu então faço:

public class CarteiroTest {

    RotaFinder rotaFinderMock;

    @Before
    public void setUp() {
        this.rotaFinderMock = … // cria mock
        RotaFinderFactory.testing = true;
        RotaFinderFactory.rotaFinderForTesting =  this.rotaFinderMock;
    }

    @Afer
    public void tearDown() {
         RotaFinderFactory.testing = false; // importante pra não afetar indevidamente outros testes
    }

    @Test
    public void shouldEntregarCarta() {
        // testa método carteiro.entrega(carta)
    }
}

Pronto! =)
Em código de produção, o carteiro obterá um novo RotaFinder, mas no teste, ele obterá o mock do RotaFinder. E o cliente (no caso representado pelo método “shouldEntregarCarta”) nem sabe que RotaFinder ou RotaFinderFactory existem.

Tenho usado esse padrão em vários testes de meu projeto. Uma primeira desvantagem que vejo, é que os testes não seriam paralelizáveis. Mas como como eu rodo a suíte de testes com o Maven, e o Maven roda os testes de forma serial, então isso não é um problema pra mim.

Agora gostaria do feedback do caro leitor: o que eu fiz já existe? Tem um nome? É muito feio? Por que? O que seria melhor fazer no lugar disso?

Complemento adicionado em 31/07:

Desde que publiquei o post, algumas pessoas vieram me comentar outras soluções para o problema apresentado.

A mais comum foi a de passar o RotaFinderFactory pelo construtor do Carteiro. Assim, teríamos um carteiro como se segue:

public class Carteiro {

    private RotaFinderFactory rotaFinderFac;

    public Carteiro() {
        this.rotaFinderFac = new RotaFinderFac();
    }
    Carteiro(RotaFinderFactory rotaFinderFac) {
        this.rotaFinderFac = rotaFinderFac;
    }

    public void entrega(Carta carta) {
        RotaFinder rotaFinder = this.rotaFinderFac.getNewInstance();
        Endereco endereco = carta.getEnderecoDestino();
        Rota rota = rotaFinder(endereco);
        // usa a rota pra entregar a carta
    }

}

Note que como o construtor adicional não é pra ser usado pelo código em produção, ele possui visibilidade apenas para seu pacote, ou seja, não é público.

Isso remove aquelas variáveis públicas que eu criei no RotaFinderFactory, e que não são de agrado a muita gente. Os principais problemas com aquelas variáveis era o fato de estarmos misturando código de produção com código de testes, e de possivelmente ser uma solução menos flexível do que essa do construtor.

Antes de continuar, gostaria de registrar que, sem o uso de um container para injeção de dependências, parece ser essa a solução mais utilizada por desenvolvedores.

Mas acontece que essa era a solução que eu usava antes de conceber a solução inicialmente apresentada no post. E o que me levou instintivamente a mudar de ideia?

Em primeiro lugar, a possível explosão de construtores dependendo da situação: quantidade de construtores originais e quantidade de dependências internas a serem mockadas. Esse é um problema típico do Java, pelo qual a linguagem costuma ser atacada.

Um jeito de resolver a explosão de construtores, é trocando o construtor adicional por um setter:

setRotaFinderFactory(RotaFinderFactory rotaFinderFac) {
    this.rotaFinderFac =  rotaFinderFac;
}

Note que esse método também não é público.

Mas há uma situação em que essa visibilidade restrita nos causa problemas. Vamos supor que queremos testar a classe Correios, que possui uma lista de Carteiros. A coisa mais certa a se fazer, seria mockar os carteiros para que o teste de unidade de Correios esteja realmente testando somente a classe Correios. No entanto, em muitos casos criar esse mock é um trabalho desnecessário, uma vez que o comportamento padrão do objeto dependência não vai atrapalhar no teste, e esse objeto pode ter muitos métodos a serem mockados. No nosso exemplo, vamos supor que a menos da interação do carteiro com o RotaFinder, a implementação de Carteiro estaria toda OK e seria uma mão na roda pra ser usada no teste da classe Correios. Vamos também supor que Correios adquira novos carteiros através de uma CarteiroFactory.

Com a solução dos construtores, teríamos que fazer algo assim no teste:

1. Cria rotaFinderMock: mock de RotaFinder.
2. Cria rotaFinderFactoryMock: mock de RotaFinderFactory que retorna o rotaFinderMock.
3. Cria carteiroFactoryMock: mock que cria novos carteiros passando rotaFinderFactoryMock no construtor de cada novo Carteiro.

Mas opa! Eu não vou conseguir fazer isso se Correios e Carteiros estiverem em pacotes diferentes, uma vez que para definirmos o comportamento de carteiroFactoryMock precisamos acessar o construtor alternativo do Carteiro.

O que fazer agora?!

Como disse antes, o ideal seria fazer o mock do Carteiro propriamente dito, de forma que o teste de Correios tivesse que conhecer apenas a interface do Carteiro, e não detalhes de sua implementação. Mas ainda assim, vou insistir que dependendo da situação isso pode representar um trabalho considerável. Por isso,  ainda creio que a solução apresentada neste post (factory estática com variável testing) pode ser um compromisso razoável.

Mas há ainda mais um debate, pois poderiam dizer “ah, já que você avacalhou em criar uma variável pública na factory, bastaria deixar o construtor alternativo também público”. Aí eu digo que não. Ao meu ver, o principal sentido do encapsulamento é facilitar a evolução interna da classe independentemente de sua interface, e em segundo lugar auxiliar o programador a entender a API. Do ponto de vista do segundo ponto, deixar público o construtor ou o setter pra injetar RotaFinderFactory é uma coisa bem ruim, pois um programador que vá utilizar o Carteiro em produção terá que se perguntar quando usar um ou outro construtor, quando na verdade deveria utilizar apenas um. Já a solução proposta cria variáveis públicas que são bem claras em seu intento, e que dificilmente seriam utilizadas de forma indevida por mero engano por um cliente da factory. Além disso, a utilização da solução como um “padrão de projeto” faria com que desenvolvedores reconhecessem mais facilmente a situação e utilizassem de forma adequada a factory, tanto em produção, quanto em testes.

Porque um padrão?

Sendo bom ou ruim, me parece que a solução apresentada possa ser considerada um “padrão de projeto”, pois se trata de uma solução adaptável para diferentes contextos em que surge um mesmo problema. O problema é testar classes que possuem dependências internas que precisam ser mockadas. A solução é que as dependências sejam acessadas por uma factory estática, que pode ter em tempo de execução seu comportamento alterado para retornar mocks em vez de novas instâncias da classe da dependência. Essa “adulteração” da factory é feita por meio de duas variáveis públicas e estáticas, uma booleana chamada testing, e outra do tipo do objeto gerado pela factory, chamada objectForTesting. Embora públicas, essas variáveis possuem nomes bem claros, e sendo a classe factory bem pequena, fica fácil seu entendimento.

Obs: caso as variáveis públicas lhe sejam uma falta muito grave, troque-as por variáveis privadas com respectivos getters and setters.

Por fim, é preciso admitir que tal padrão proposto possa ser considerado deselegante. Eu pessoalmente prefiro pensar nele como tendo uma cara de hack :)

Agradecido pela atenção!
Leonardo Leite

[1] Robert C. Martin, “Clean Code”. Prentice Hall, 2009.
[2] Martin Fowler, “Mock Aren't Stubs”. http://martinfowler.com/articles/mocksArentStubs.html

PS: para aprender mais sobre como criar bons testes e sobre TDD (Test Driven Development), conferir o livro Test-Driven Development: Teste e Design no Mundo Real, de Maurício Aniche.

PS2: Esse post acabou se transformando em um artigo aceito no MiniPlop Brasil 2013!

PS3: Sobre o MiniPlop, vale conferir esse post da Caelum: http://blog.caelum.com.br/miniplop-2013-em-brasilia-eu-fui/