TPW – Testando sistemas legados: classes Utils 2

Posted by Rodrigo Panachi on março 03, 2009

Aproveitando o gancho do post anterior sobre manipulação de dependências, decidi dedicar um post apenas sobre este tema, pois acredito ser de grande ajuda para todos desenvolvedores que precisam manter código legado.

Em projetos legados é comum encontrarmos classes Util (aka Helpers) espalhadas por todo o código, fazendo desde coisas simples como formatar datas ou números, até coisas mágicas como cache de objetos, operações com reflection, escrita de logs, etc. Mesmo que tenham sua “utilidade”, são um terror quando falamos de testes! Além de ser um forte indício de um design fraco, as chamadas a seus métodos, geralmente estáticos, geram dependências nas classes que a utilizam.

Este é a estrutura comum (bem simplificada) de uma classe Util com métodos estáticos:

public class MagicUtil {
    public static String getConstanteSecreta() {
        return "VALOR_SECRETO_AMBIENTE";
    }
}

Neste caso, uma boa estratégia para os testes seria encapsular a chamada da classe Util em um método protected, para que seja sobrescrito na classe de teste, assumindo o comportamento desejado. Porém, se a classe Util for largamente referenciada no projeto (o que é comum) seria preciso refatorar todas a classes que a utilizam para escrever um teste completo.

A estratégia proposta é refatorar a classe Util, aplicando o padrão Singleton e transformando os métodos estáticos em métodos de instância, porém mantendo as assinaturas estáticas, que devem referenciar os métodos da instância. Uma vez que a Util pode ser instanciada (mesmo que internamente), é possível manipular seu comportamento através da injeção de um objeto Mock, por exemplo:

public class MagicUtil {
    private static MagicUtil instance = new MagicUtil();
    protected static MagicUtil getInstance() {
        return instance;
    }
    protected static void setInstance(MagicUtil obj) {
        instance = obj;
    }
    public String getConstanteSecretaInstancia() {
        return "VALOR_SECRETO_AMBIENTE";
    }
    public static String getConstanteSecreta() {
        return getInstance().getConstanteSecretaInstancia();
    }
}

Assim, a Util continua com o mesmo comportamento e seu contrato foi mantido. Agora, no seu teste, basta escrever o mock (estendendo a classe e sobrescrevendo os métodos) e injetá-lo na instância interna da Util:

public class ClasseTest {
    @Test
    public void testaMetodoDependenteDeMagicUtil() {
        new MagicUtil() {
            {
                setInstance(this);
            }
            public String getConstanteSecretaInstancia() {
                return "MEU_VALOR_MOCK";
            }
        };
        Assert.assertEquals("MEU_VALOR_MOCK", MagicUtil.getConstanteSecreta());
    }
}

Neste caso a classe anônima (que estende a Util) passa sua própria instância (this) para o método protegido setInstance(). Note que a chamada do método (estático) da Util continua igual ao da classe original, sem o refactoring.

Nos projetos que preciso manter, esta estratégia tem sido muito útil para resolver os problemas das “teias” de Utils. Porém é um recurso paliativo e não deve ser utilizado como o “padrão”. O ideal é sempre evitar classes Utils, lembrando que uma classe deve sempre ter comportamentos bem definidos e o nome já deve indicar sua responsabilidade.

TPW – Testando sistemas legados: manipulando dependências 3

Posted by Rodrigo Panachi on fevereiro 19, 2009

Pela definição de Michael Feather, código legado é código sem testes! Não importa se o código foi escrito semana passada ou alguns anos atrás. Qualquer manutenção será de difícil entendimento por outra pessoa e não haverá garantias de seu funcionamento. Uma vez que não há “controle”, é mais difícil rastrear as alterações; pior do que uma nova funcionalidade que não funciona, é uma funcionalidade antiga que começa a falhar. Este é um risco que um desenvolvedor não pode correr!

Não altere código legado até que seja possível testá-lo

Um dos problemas mais comuns em sistemas legados é a interdependência de classes, ou seja, o alto acoplamento, que sempre está ligado com a baixa coesão. Se estes termos são difíceis de entender, pense em acoplamento como sendo o grau com que as classes referenciam umas as outras e coesão o quanto uma classe está focada em realizar suas responsabilidades.

Para que seja possível testar o comportamento de uma classe “acoplada”, o comportamento de suas dependências precisa ser simulado. Isto normalmente é feito através de objetos falsos, ou mocks, que são injetados na instância da classe em questão. Este padrão é conhecido como Inversão de Controle e Injeção de dependência, onde o controle sobre as dependências da classe são delegados à outro objeto, ou normalmente um container de objetos, responsável por injetar as dependências nas instâncias das classes. Simples, não?! Mas isso será detalhado em outro post…

Voltando para os testes, no post anterior começamos a organizar o projeto automatizando o build e centralizando a execução dos testes para evitar que fiquem “soltos” pelo código. Agora vamos nos concentrar em escrever os casos de teste, refatorando o necessário para lidar com as dependências das classes.

Partindo da premissa que o projeto não possuí nenhum framework de inversão de controle, utilizaremos um certo “padrão” que permite manipular as dependências de uma classe por meio de herança, sem alterar seu comportamento original. A idéia é resolver as dependências da classe através de getters protegidos, que podem ser sobrescritos em uma classe filha no momento do teste. Isso permite que, na classe estendida, o método sobrescrito retorne um objeto mock, por exemplo, com o comportamento esperado para o teste.

Vamos tomar como exemplo, uma classe simples com algumas dependências e responsável por encapsular algumas regras de negócio referentes à Estoque.

public class EstoqueLogic {
    public boolean verificaDisponibilidade(Produto produto, Integer quantidade) {
        EstoqueDAO dao = new EstoqueDAO();
        Estoque estoque = dao.localizaProduto(produto.getCodigo());
        return estoque.getQuantidade() >= quantidade;
    }
}

Da forma como esta classe foi escrita, é impossível testar a regra de disponibilidade independentemente, pois depende do objeto EstoqueDAO para localizar as informações necessárias para o método. Mas com um pequeno refactoring, a responsabilidade de resolver a dependência EstoqueDAO passa a ser responsabilidade da própria classe Estoque:

public class EstoqueLogic {
    public boolean verificaDisponibilidade(Produto produto, Integer quantidade) {
        EstoqueDAO dao = getEstoqueDAO();
        Estoque estoque = dao.localizaProduto(produto.getCodigo());
        return estoque.getQuantidade() >= quantidade;
    }
    protected EstoqueDAO getEstoqueDAO() {
        return new EstoqueDAO();
    }
}

Desta forma, é possível “injetar” um objeto que simule a dependência do EstoqueDAO estendendo a classe e sobrescrevendo o método getEstoqueDAO() para retornar a instância desejada. O teste ficaria mais ou menos assim:

public class EstoqueLogicTest {

    public void testVerificandoDisponibilidadeDeUmProduto() {

        //Criando o objeto EstoqueDAO mock, simulando o comportamento desejado
        final EstoqueDAO estoqueDAOMock = new EstoqueDAO() {
            @Override
            public Estoque localizaProduto(String codigo) {
                Produto produto = new Produto();
                produto.setCodigo(codigo);
                Estoque estoque = new Estoque();
                estoque.setProduto(produto);
                estoque.setQuantidade(5);
                return estoque;
            }
        };

        //Sobrescrevendo o método getEstoqueDAO para retornar o Mock
        EstoqueLogic logic = new EstoqueLogic() {
            @Override
            protected EstoqueDAO getEstoqueDAO() {
                return estoqueDAOMock;
            }
        };

        //Definindo o teste e executando
        Produto produto = new Produto();
        produto.setCodigo("123456");        

        boolean estaDisponivel = logic.verificaDisponibilidade(produto, 10);
        assertTrue(estaDisponivel);

        boolean naoEstaDisponivel = logic.verificaDisponibilidade(produto, 2);
        assertTrue(naoEstaDisponivel);
    }
}

O método getEstoqueDAO() da classe EstoqueLogic foi sobrescrito para retornar o objeto estoqueDAOMock com as informações necessárias para o teste, ou seja, o comportamento das dependências foi simulado, possibilitando que o teste ficasse concentrado apenas da classe Estoque.

Elimine as dependências e teste onde os bugs estão!

Este padrão fornece apenas uma maneira de lidar com as dependências das classes para escrever testes. A dica aqui é manter o foco: defina apenas testes para as funcionalidades que estiver alterando e que exercitem pontos críticos e/ou regras de negócio. Então, refatore apenas as classes necessárias para simular e validar o fluxo destes testes, nem que seja apenas seus “contratos“. Não há necessidade de escrever testes muito granulares nem alterar todas as classes de um sistema legado.

No caso de classes com muitas dependências, o melhor é refatorá-la, separar as responsabilidades e testá-las individualmente. Para classes com dependências de Utils e/ou muitas chamadas à métodos estáticos, uma estratégia parecida pode ser utilizada. Mas estes são assuntos para os próximos posts. Acompanhem!