Uma coisa sobre testes em Java
Quando vou dar manutenção em um projeto, seja ela evolutiva ou preventiva, tenho que olhar para os testes que já foram implementados. Quero garantir que o processo de CI vai continuar funcionando depois das minhas alterações. Geralmente existe implementação e me deparo, geralmente com dois tipos de métodos:
- public: um ou mais, mas poucos
- private: um monte deles
Há algum problema nisso? Sim! Se existe somente um método público, ele orquestra todos os outros privados. Provavelmente os métodos privados fazem alguma integração, ou formatam alguma String
, ou fazem transformações, enfim, inúmeras tarefas.
Happy Flow
e Corner Case
Eu tenho dois termos na minha cabeça:
Happy Flow
: o fluxo principal do algoritmoCorner Case
: os fluxos alternativos, exceções, etc...
No Happy Flow
todas as integrações funcionam, todas as transformações são bem sucedidas, ou seja, tudo funciona, tudo vai bem.
Os Corner Cases
ocorrem quando uma integração falha, uma transformação não é bem sucedida, faltam dados para formatar uma String
, etc. Tudo de errado pode acontecer.
Mas, dependendo da quantidade de integrações, transformações, formatações, etc, a combinação para fazer testes pode ser grande. Vamos imaginar que um service, por exemplo, precisa fazer duas integrações, acessar um repositório, formatar três String
s e fazer cinco transformações.
(E existem combinações piores do que isso)
A quantidade de testes com tudo isso, é imensa. A quantidade de combinações pode ser gigantesca. Sei que podem estar pensando que seria melhor dividir as coisas em classes pequenas (e isso é o que eu pensaria), mas já está implementado assim.
O que fazer nesse caso?
Um pouco de OO
Você conhece Orientação à Objetos. Não perguntei, afirmei. Vamos ao que interessa.
Você conhece os modificadores de acesso:
public
: membros que podem ser acessados a partir de uma instância de um objeto pelo operador de ponto:instance.member
.private
: somente acessíveis dentro de uma mesma instância.protected
: membros privados, que podem ser acessíveis por herança e por instância no mesmo package.- default: sem uma palavra chave reservada, "confina" um membro a ser visível dentro de uma package, seja através de uma instância ou por herança.
Entenda membros de uma class como métodos (comportamentos) e atributos.
Enfim, o que isso tem a ver com testes? Quando implementamos um teste com JUnit, a classe de teste fica separada em um diretório, mas com a mesma estrutura de pacotes. Isso faz com que quando carregamos, o Classloader pensa que ambas as classes estão no mesmo pacote.
Se estão no mesmo pacote, então podemos utilizar do modificador protected
ao invés do modificador private
para os métodos de nossa classe. Assim podemos testar de maneira independente nossos métodos privados.
Converter tudo para protected
Mas, espera um pouco? Devemos traduzir tudo para protected? Não. Nem tudo, devemos ter bom senso. É claro que devemos ter um julgamento do que é melhor a fazer. Algumas coisas devem estar escondidas, devem ser internas. Outras podem expostas.
A cobertura de Corner Cases
será mais fácil. A detecção de algum problema vai ser tranquila.
E ainda é possível testar resultados dos métodos privados pelos métodos privados. Por exemplo, se um método privado formata uma String
e depois é utilizado na criação de um objeto em um método protected
, essa formatação pode ser testada nesse método protected.
Conclusões
Em suma, como engenheiros de software ou programadores, temos que tomar cuidado com o nosso código. Não devemos encadear muitas chamadas à métodos privados, principalmente quando realizam integrações.
Código legado, deixado por programadores que só tinham preocupação em entregar tarefas, independendo da qualidade final do software, pode possuir essas falhas graves e que não tiveram devida atenção para serem refatorizados.
Aplicar SOLID, Orientação a Objetos e Design Patterns pode deixar o código mais limpo, legível e evitar essas más práticas.