Pergunta

Ultimamente tive que alterar alguns códigos em sistemas mais antigos onde nem todo o código possui testes de unidade.
Antes de fazer as alterações, quero escrever testes, mas cada classe criou muitas dependências e outros antipadrões que dificultaram bastante os testes.
Obviamente, eu queria refatorar o código para facilitar o teste, escrever os testes e depois alterá-lo.
É assim que você faria?Ou você gastaria muito tempo escrevendo testes difíceis de escrever que seriam em sua maioria removidos após a conclusão da refatoração?

Foi útil?

Solução

Em primeiro lugar, aqui está um ótimo artigo com dicas sobre testes unitários.Em segundo lugar, descobri que uma ótima maneira de evitar fazer muitas alterações no código antigo é apenas refatorá-lo um pouco até poder testá-lo.Uma maneira fácil de fazer isso é proteger os membros privados e, em seguida, substituir o campo protegido.

Por exemplo, digamos que você tenha uma classe que carrega algumas coisas do banco de dados durante o construtor.Nesse caso, você não pode simplesmente substituir um método protegido, mas pode extrair a lógica do banco de dados para um campo protegido e, em seguida, substituí-lo no teste.

public class MyClass {
    public MyClass() {
        // undesirable DB logic
    }
}

torna-se

public class MyClass {
    public MyClass() {
        loadFromDB();
    }

    protected void loadFromDB() {
        // undesirable DB logic
    }
}

e então seu teste será mais ou menos assim:

public class MyClassTest {
    public void testSomething() {
        MyClass myClass = new MyClassWrapper();
        // test it
    }

    private static class MyClassWrapper extends MyClass {
        @Override
        protected void loadFromDB() {
            // some mock logic
        }
    }
}

Este é um exemplo um tanto ruim, porque você poderia usar DBUnit neste caso, mas na verdade fiz isso em um caso semelhante recentemente porque queria testar algumas funcionalidades totalmente não relacionadas aos dados que estavam sendo carregados, por isso foi muito eficaz.Também descobri que essa exposição de membros é útil em outros casos semelhantes em que preciso me livrar de alguma dependência que está em uma classe há muito tempo.

Eu não recomendaria esta solução se você estiver escrevendo uma estrutura, a menos que você realmente não se importe em expor os membros aos usuários de sua estrutura.

É um pouco complicado, mas achei bastante útil.

Outras dicas

@valters

Discordo da sua afirmação de que os testes não devem interromper a construção.Os testes devem ser uma indicação de que o aplicativo não possui novos bugs introduzidos para a funcionalidade testada (e um bug encontrado é uma indicação de um teste ausente).

Se os testes não quebrarem a compilação, você poderá facilmente se deparar com uma situação em que o novo código quebra a compilação e não é conhecido por um tempo, mesmo que um teste tenha coberto isso.Um teste com falha deve ser um sinal de alerta de que o teste ou o código devem ser corrigidos.

Além disso, permitir que os testes não interrompam a construção fará com que a taxa de falhas aumente lentamente, até o ponto em que você não terá mais um conjunto confiável de testes de regressão.

Se houver um problema com testes quebrando com muita frequência, pode ser uma indicação de que os testes estão sendo escritos de maneira muito frágil (dependência de recursos que podem mudar, como o banco de dados sem usar o DB Unit corretamente, ou um serviço web externo que deveria ser ridicularizado), ou pode ser uma indicação de que há desenvolvedores na equipe que não dão a devida atenção aos testes.

Acredito firmemente que um teste com falha deve ser corrigido o mais rápido possível, assim como você consertaria um código que falha na compilação o mais rápido possível.

Não sei por que você diria que os testes de unidade serão removidos assim que a refatoração for concluída.Na verdade, seu conjunto de testes de unidade deve ser executado após a compilação principal (você pode criar uma compilação de "testes" separada, que apenas executa os testes de unidade após a compilação do produto principal).Então você verá imediatamente se as alterações em uma peça interrompem os testes em outro subsistema.Observe que é um pouco diferente de executar testes durante a compilação (como alguns podem defender) - alguns testes limitados são úteis durante a compilação, mas geralmente é improdutivo "travar" a compilação só porque algum teste de unidade falhou.

Se você está escrevendo Java (provavelmente), dê uma olhada http://www.easymock.org/ - pode ser útil para reduzir o acoplamento para fins de teste.

Eu li Trabalhando Efetivamente com Código Legado e concordo que é muito útil para lidar com código "não testável".

Algumas técnicas se aplicam apenas a linguagens compiladas (estou trabalhando em aplicativos PHP "antigos"), mas eu diria que a maior parte do livro é aplicável a qualquer linguagem.

Os livros de refatoração às vezes assumem que o código está no estado semi-ideal ou "consciente de manutenção" antes da refatoração, mas os sistemas em que trabalho não são ideais e foram desenvolvidos como aplicativos de "aprender conforme você usa" ou como primeiros aplicativos para algumas tecnologias usadas (e não culpo os desenvolvedores iniciais por isso, já que sou um deles), portanto não há testes e o código às vezes é confuso.Este livro aborda esse tipo de situação, enquanto outros livros sobre refatoração geralmente não o fazem (bem, não até esse ponto).

Devo mencionar que não recebi nenhum dinheiro do editor nem do autor deste livro ;), mas achei muito interessante, pois faltam recursos na área de código legado (e principalmente na minha língua, o francês, mas isso é outra história).

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top