Facilitando a manutenção dos testes ao diminuir o acoplamento com o código

Facilitando a manutenção dos testes ao diminuir o acoplamento com o código
maniche
maniche

Compartilhe

É fácil entender por que devemos diminuir o acoplamento entre classes: a alteração em um ponto do sistema pode propagar a necessidade de mudanças em outros. Dependendo do acoplamento, uma simples alteração exige um esforço enorme. Em alguns casos as mudanças não são feitas, e a funcionalidade é simplesmente descartada, devido a esse alto custo de manutenção deixado pelo acoplamento . Uma das tentativas para diminui-lo é tentar sempre programar voltado para interfaces e não para uma implementação e TDD pode ser utilizado para atingir esse objetivo.

Mas é mais difícil perceber que o acoplamento entre classes possa existir até mesmo entre o código de teste e o código de produção. Isso pode também fazer com que alterações também sejam caras.

Testes de integração, que visam verificar o comportamento de várias unidades juntas dentro de um sistema, dão um excelente feedback sobre a corretude do programa, mas geram um acoplamento muito forte entre o teste e o código. Observe o teste de integração abaixo:

 public void deveCompararOValorDaAcaoDeDuasEmpresasDiferentes() { // usando um HttpClient simples String uri = "http://servico\_falso\_da\_bovespa\_para\_testes"; HttpClient client = new HttpClient(uri);

BuscadorDePrecos buscador = new BuscadorDePrecosNaBovespa(client);

Comparador compara = new Comparador(buscador); double diferenca = compara.acoesDa("petrobras", "vale");

assertEquals(1.0, diferenca, 0.001); } 

O teste acima verifica se a classe Comparador calcula a diferença entre duas ações. Para isso, ela usa a classe BuscadorDePrecosNaBovespa que, por sua vez, acessa um cliente qualquer de Http (aqui ilustrado como HttpClient) para fazer as requisições.

Esse teste entrega feedback sobre a corretude do programa; batendo contra um servidor parecido com o da Bovespa, recebendo os valores e executando as comparações devidas. Se o programador deseja alterar a implementação do BuscadorDePrecosNaBovespa, removendo o HttpClient para fazer uso de uma outra ferramenta, como o Restfulie, além de quebrar os testes da classe BuscadorDePrecosNaBovespa (que já era esperado), ainda quebrariam os do Comparador. Uma mudança simples, que deveria acarretar em consequências apenas nos testes específicos dessa classe, afeta diferentes classes e testes desse sistema; aumentando o custo de manutenção da bateria de testes, um tradeoff que pode ou não fazer sentido em seu projeto.

Perceba que a classe Comparador não está interessada em como a classe BuscadorDePrecosNaBovespa faz seu trabalho; ela apenas espera que alguém busque esse valor. Se a classe Comparador fosse testada isoladamente, tudo que ela precisaria é receber "alguém" que retorne o valor de uma determinada ação. Ou seja:

 public interface BuscadorDePrecos { Acao pega(String empresa); }

public class ComparadorTest { @Test public void deveCompararOValorDaAcaoDeDuasEmpresasDiferentes() { BuscadorDePrecos buscador = mock(BuscadorDePrecos.class); when(buscador.pega("petrobras")).thenReturn(new Acao("petrobras", 101.0); when(buscador.pega("vale")).thenReturn(new Acao("vale", 100.0);

Comparador compara = new Comparador(buscador); double diferenca = compara.acoesDa("petrobras", "vale");

assertEquals(1.0, diferenca, 0.001); } } 

A classe Comparador, testada isoladamente, ainda depende de um BuscadorDePrecos, um objeto que saiba como buscar o preço de ações. Mas agora o teste passa para ela um dublê, uma classe que finge ser a outra. O teste desconhece a existência de uma implementação concreta dessa interface.

A classe BuscadorDePrecosNaBovespa (que implementa essa interface) pode ser modificada, sem propagar o custo de manutenção para além da sua própria classe de testes.

Programar para interfaces não só diminui o acoplamento entre as classes de produção, mas também entre seu código de teste e de produção. Devemos buscar sempre o baixo acoplamento, para diminuir os custos de manutenção, não importando onde ele esteja presente.

Testes de integração são fundamentais para validar a corretude do sistema, mas podem apresentar uma barreira para sua evolução. Os desenvolvedores devem pesar a abordagem de testes para garantir o máximo de qualidade interna e externa, mantendo um custo aceitável para o desenvolvimento e evolução a longo prazo.

Veja outros artigos sobre Inovação & Gestão