Friday, November 23, 2007

Aventuras com JUnit

Bom, hoje aconteceram 2 eventos importantes que levaram a esse post, um deles foi a Thânia, software developer na minha equipe, me colocar na cabeça a idéia de que seria uma boa coisa eu fazer um blog - portanto, já sabem uma das culpadas desse blog existir - a outra culpada eu aponto em um momento futuro ;). O outro evento foi uma pergunta que a Thânia me fez. O cenário, descrito em um post dela, é resumido em: eu inicializava alguns atributos da suite de testes em um método @Before, agora cheguei a conclusão que esses atributos devem ser inicializados apenas 1 vez para todos os testes. A solução que ela relutantemente aceitou é mover a inicialização para um método @BeforeClass, e transformar os atributos em static (pois todo método @BeforeClass deve ser static, e não é possível acessar atributos não static em um método static, bom, pelo menos não de modo simples e direto no código). Depois vi o post no blog dela falando sobre isso, e como ela não tinha aceitado muito bem, resolvi ir atrás e descobrir algumas razões para fazer desse jeito.

A primeira coisa que eu fui ver foi a FAQ do JUnit, onde encontrei uma pergunta falando sobre os @BeforeClass e @AfterClass, e nessa pergunta, eles basicamente diziam que @BeforeClass e @AfterClass são do lado negro da força, e normalmente indicam um problema. Bom, o fato é que as vezes eles são um mal necessário, por exemplo, quando os testes dependem fortemente de algum recurso caro para ser inicializado, como uma conexão a um banco de dados, ou algo do gênero. Não vou entrar no mérito de se o caso dela é um indicativo de problema ou não, mas sim levar o foco à pergunta não bem respondida: por que fazer os atributos virarem static ao invés de usar inicialização em um construtor ou na declaração.

Entre as duas últimas opções, pessoalmente prefiro a opção do construtor. Fica a promessa de um novo post sobre isso. Mas independente disso, as duas são equivalentes. Então a pergunta se resume a: tornar static ou manter como atributos de instância. Quando respondi pra ela me ocorreu uma primeira ideia para justificar a transformação para static: execução paralela dos testes. Isso não foi suficiente pra convencê-la, e para falar a verdade, nem eu acredito mais nisso. A outra justificativa para ela surgiu depois uma busca nos meus arquivos de memória por alguma coisa na documentação do JUnit que garantisse que cada suite seria instanciada uma única vez. Não achei. E isso para mim já é motivo suficiente para adotar a alternativa mais segura. Resolvi pesquisar um pouco mais sobre isso quando cheguei em casa, mas optei dessa vez por um método "hands on". Montei alguns cenários e mandei rodar o JUnit. Basicamente, fiz testes com System.out.println() em vários momentos do ciclo de vida de um teste JUnit. Inicializadores static, métodos @BeforeClass e @AfterClass, métodos @Before e @After, inicializadores regulares, métodos @Test, e depois usei herança entre testes, para ver como a coisa se comportaria.

Mandei rodar, e estava quase pensando em o que mais procurar para justificar a transformação em static, achando que eu veria a saída dos inicializadores somente antes do primeiro @Before. No entanto, o que eu vi foi: o JUnit cria uma nova instancia da suite para cada teste, portando, colocar código em construtor ou em inicializadores não-static é a mesma coisa que definir eles em um método @Before, que era a solução anterior e por algum motivo deixou de ser adequada.
Não fui na documentação do JUnit buscar se isso tem alguma explicação ou foi simplesmente decisão de projeto. Parece fazer sentido do ponto de vista de independência de testes essa decisão, dessa forma, o framework dá uma mãozinha para evitar que um teste influencie no resultado de outro.

That's all, folks!

2 comments:

Unknown said...

Eu não acho tão ruim esta decisão de projeto, mas seria legal saber se tem um motivo mais forte que os levou a fazer assim.

Bom, de qualquer forma, obrigada por tirar minhas dúvidas, Ota. Esse blog promete.. :-)

ps.: Vai ficar devendo um post sobre a inicialização no construtor - você tinha me dito que era pecado na filosofia do JUnit... :P

Ota said...

não exatamente pecado, mas os métodos @Before e @After fazem a mesma coisa, com a vantagem do suporte oficial do JUnit :)

Vai ser bastante improvável você ver uma suite JUnit com um construtor, e mesmo que veja uma, vai ser menos provavel ainda que o código precisa estar no construtor e não num método @Before. Colocando em @Before o JUnit consegue gerenciar a ordem que as coisas são rodadas.