O Junit é um framework de testes escrito por Erich Gamma e Kent Beck que facilita a implementação de unidades de teste em Java. JUnit é open source e oferece um conjunto de classes permitindo a fácil integração e execução regular de testes durante o processo de desenvolvimento, de forma a ter uma medida mais concreta do seu progresso.
Em http://www.junit.org/, poderá fazer o download da versão mais recente de JUnit. Após guardar o ficheiro para o seu disco, descomprima-o numa directoria à sua escolha e está concluída a instalação. Eventualmente, poderá ser conveniente incluir o ficheiro junit.jar na variável de ambiente classpath.
Imagine que pretende representar uma classe que defina o funcionamento das portas. Uma porta pode ser aberta, fechada, trancada ou destrancada. Não é possível abrir uma porta que esteja trancada nem trancar uma porta que esteja aberta. Tentativas deste tipo não alterarão o estado da porta. Acções redundantes (tais como tentar abrir uma porta já aberta, tentar trancar uma porta já trancada, etc.) também não alterarão o estado da porta. Escreva, no ficheiro Porta.java (assumido na directoria c:\javacode\porta\), a seguinte classe:
public class Porta { private boolean _fechada = true; private boolean _trancada = true; public boolean estaAberta() { return !_fechada; } public void abrir() { if(!_trancada) _fechada = false; } public void fechar() { _fechada = true; } public void destrancar() { _trancada = false; } public void trancar() { if(_fechada) _trancada = true; } }
Agora convém testar se a classe que escrevemos está a funcionar correctamente. Para criar uma unidade de testes através do JUnit basta criar uma subclasse de TestCase. Vamos chamar-lhe TestePorta. Esta classe vai testar se o comportamento de uma porta é o pretendido. Escreva, no ficheiro TestePorta.java (assumido na directoria c:\javacode\porta\), o seguinte código:
import junit.framework.TestCase; public class TestePorta extends TestCase { private Porta _portaCobaia = new Porta(); public static void main(String[] args) { junit.textui.TestRunner.run(TestePorta.class); } public void setUp() { _portaCobaia.destrancar(); _portaCobaia.abrir(); } public void testAbrirFechar() { assertTrue("A porta devia estar inicialmente aberta", _portaCobaia.estaAberta()); assertEquals("A porta devia estar inicialmente aberta", true, _portaCobaia.estaAberta()); _portaCobaia.fechar(); assertFalse("Ao fechar uma porta aberta esta continuou aberta", _portaCobaia.estaAberta()); _portaCobaia.fechar(); assertFalse("Ao fechar duas vezes a porta esta ficou aberta", _portaCobaia.estaAberta()); _portaCobaia.abrir(); assertTrue("Ao abrir uma porta apenas fechada esta continuou fechada", _portaCobaia.estaAberta()); _portaCobaia.abrir(); assertTrue("Ao abrir duas vezes a porta esta ficou fechada", _portaCobaia.estaAberta()); } public void testTrancarDestrancar() { assertTrue("A porta devia estar inicialmente aberta", _portaCobaia.estaAberta()); _portaCobaia.fechar(); _portaCobaia.trancar(); assertFalse("Ao fechar e trancar uma porta aberta, esta ficou aberta", _portaCobaia.estaAberta()); _portaCobaia.abrir(); assertFalse("Foi possivel abrir uma porta que foi trancada", _portaCobaia.estaAberta()); _portaCobaia.destrancar(); assertFalse("O destrancar de uma porta fez com que esta ficasse aberta", _portaCobaia.estaAberta()); _portaCobaia.abrir(); assertTrue("Ao destrancar e abrir uma porta, esta permaneceu fechada", _portaCobaia.estaAberta()); } }
Note-se que, como fazemos referência à classe TestCase, é necessário importar esta classe definida no "package" junit.framework. Foi definido o método "main" de modo a que para executar os testes baste apenas executar a classe TestePorta.
A nossa classe de testes tem dois testes (os métodos cujos nomes começam por "test"). O primeiro testa o funcionamento das operações de abrir e fechar enquanto que o segundo testa o funcionamento das operações de trancar e destrancar. É importante perceber que ambos os métodos de teste assumem que a porta está inicialmente destrancada e aberta. Isto é possível (sem repetição de código) graças ao método setUp (nome reservado) que é automaticamente executado antes da execução de cada método de teste.
Um "assert" serve para verificar se determinada situação se verifica pois quando tal não acontecer, a falha será relatada. Qualquer dos "asserts" pode e deve receber como primeiro argumento a mensagem que deve ser mostrada quando ocorre a respectiva falha. Neste exemplo usámos o assertFalse, assertTrue e assertEquals. Como seria de esperar, o primeiro verifica se um dado objecto tem o valor true, o segundo verifica se tem o valor false e o terceiro verifica se dois objectos são iguais (através do método equals existente para qualquer objecto). É de notar que os objectos que queremos comparar com assertEquals devem estar na seguinte sequência: Primeiro o valor que estamos à espera de encontrar e em seguida o valor que encontramos. Assim, caso exista uma falha no teste, a mensagem de erro indicará correctamente esta mesma informação.
Testes mais exaustivos poderiam ser definidos. Cabe ao programador elaborar testes que abranjam o maior número de casos tipo para garantir uma melhor fiabilidade do seu código. O que pode parecer trabalho inútil para programas tão simples, pode levar à detecção de erros que, de outro modo, quando programas mais complexos integrassem estas classes, levariam muito mais tempo a depurar.
Se estiver na directoria c:\javacode\porta\ pode compilar o código da seguinte forma:
javac -classpath c:\junit3.8\junit.jar *.javan.b. Substitua c:\junit3.8\ pela directoria onde instalou o Junit.
n.b. Se o Junit estiver no seu classpath não necessita de utilizar o parâmetro -classpath da JVM.
Após a execução deste comando, dois ficheiros (Porta.class e TestPorta.class) deverão ter sido criados na mesma directoria. Para correr o teste, basta executar a classe junit.textui.TestRunner, dando-lhe como parâmetro a nossa classe de teste TestePorta. Isto pode ser feito da seguinte forma:
java -classpath .;c:\junit3.8\junit.jar junit.textui.TestRunner TestePorta
Se tudo tiver sido bem feito o resultado deverá ser:
.. Time: 0 OK (2 tests)
O método fail pode também receber uma dada mensagem de erro e serve para forçar que o teste falhe (equivale à instrução "assertTrue(false)").Este método pode ser útil quando queremos indicar que se chegarmos a uma determinada linha no nosso código de testes é porque algo falhou.
Para que possamos testar se um dado método lança ou não uma excepção numa determinada situação basta que essa situação seja simulada e que se tente apanhar a eventual excepção através de um bloco de "try-catch". Se a excepção não devia ocorrer basta que se coloque um "fail" na área de "catch", onde a excepção é tratada. Caso contrário, se a excepção deve ocorrer, basta que se coloque um "fail" na linha imediatamente a seguir à instrução que a lança, ainda dentro da área de "try".
Vejamos um exemplo. O método estático "parseInt" da classe "Integer" permite converter uma "String" num "int" e lançará uma "NumberFormatException" se a String não contiver um número inteiro válido. Eis a assinatura deste método:
public static int parseInt( String s) throws NumberFormatException
A seguinte classe permite testar se o método lança as excepções só e apenas quando deve, da forma que explicámos atrás.
import junit.framework.TestCase; public class TesteParseInt extends TestCase { public static void main(String[] args) { junit.textui.TestRunner.run(TesteParseInt.class); } public void testExcepcoes() { final String NUMERO = "10"; final String NAO_NUMERO = "ola"; try { Integer.parseInt(NUMERO); } catch (NumberFormatException excepcao) { fail("Foi lancada uma excepcao ao fazer o parsing de \"" + NUMERO + "\": " + excepcao); } try { Integer.parseInt(NAO_NUMERO); fail("Nao foi lancada excepcao ao fazer o parsing de \"" + NAO_NUMERO + "\""); } catch (NumberFormatException excepcao) { } } }
O JUnit vem também acompanhado de interfaces gráficas (junit.awtui.TestRunner e junit.swingui.TestRunner). Para mais informação sobre a sua utilização, consulte o ficheiro doc\cookbook\cookbook.htm acessível a a partir da directoria de instalação.
Uma nota final: a maioria dos IDE Java permite configurar directorias de bibliotecas Java para cada aplicação e utilizá-las de forma transparente no processo de criação da mesma, evitando a configuração da variável classpath. É algo que merece ser investigado pois pode facilitar o seu processo de trabalho. No entanto, deverá conhecer os procedimentos acima para os casos em que apenas tiver uma linha de comando num terminal não gráfico à sua disposição.
O arquivo de instalação contêm um conjunto de documentos, tutoriais e exemplos valiosos para a compreensão do framework. Inclui também a descrição completa da API. Após correr o sua primeira unidade de testes, investigue como ligar hierarquicamente várias unidades de testes e como activar um particular teste de um conjunto de unidades.