No presente artigo irei fazer uma abordagem sobre a versão do framework mais famoso para testes de unidade em plataforma Java e as novidades encontradas na sua versão 4. Ressalto que não irei entrar nos méritos de explicação do JUnit visto que o mesmo já se encontra presente em diversos artigos publicados no Portal Java Magazine.
Guia do artigo:
- Mudanças nos Métodos de Teste
- Setup e TearDown
- Suíte-wide initialization
- Exceções no teste
- Ignorando testes
- Testes temporizados
- Novas assertions
- Considerações Finais
JUnit se tornou de fato um padrão de framework para testes de unidades automatizados em plataforma Java. A versão inicial do framework contemplava uma série de recursos para a implementação de testes de unidade de forma fácil e flexível. A versão 4 é considerada uma das mais significativas atualizações por ter como principal proposta a simplificação da elaboração das classes de testes explorando os recursos de anotação presente na JDK 1.5 do Java. O framework agora suporta generics, enumerations, import estático e anotações – adição de metadados no código para reduzir a complexidade de implementação e aumentar consideravelmente o nível de reuso do código.
Mudanças nos Métodos de Teste
Nas versões anteriores do Junit todas as classes de teste eram escritas seguindo uma especificação que definia nomenclatura de nomes e operações para o algoritmo de teste. Utilizando reflexão o framework localizava todos os métodos de testes e os processava, como no trecho de abaixo.
import junit.framework.TestCase;public class TesteUnidade extends TestCase { private int x = 1; private int y =
1; public void testMetodo() { int z = x + y; assertEquals(2, z); }
}
Em contraste, no JUnit 4 os métodos de teste são identificados com a anotação @Test. Observe que agora não é preciso iniciar os métodos de teste com a palavra test o que garante que os teste se aproximem mais dos nomes de métodos implementados como apresentado no código abaixo.
import org.junit.Test;import junit.framework.TestCase; public class TesteUnidade extends TestCase { private int
x = 1; private int y = 1; @Test public void metodo() { int z = x + y; assertEquals(2, z); }
}
Com o novo recurso é possível deixar os nomes dos métodos das classes de teste similares aos métodos reais gerando uma padronização de nomes. Por exemplo, Pessoa.getNome() é testado por PessoaTeste.getNome(); Lista.addAll() é testado por ListTest.addAll().
A classe TestCase não foi descontinuada para garantir compatibilidade reversa com as outras versões embora não seja necessário a sua utilização. Com os recursos de import estático do java é possível escrever uma classe de teste sem estender da classe TestCase e a mesma permanecer com o mesmo formato como no exemplo abaixo
import static org.junit.Assert.assertEquals;public class TesteUnidade { private int x = 1; private int y = 1;
@Test public void metodo() { int z = x + y; assertEquals(2, z); }
}
Setup e TearDown
Na versão anterior do JUnit, existiam 2 métodos que eram utilizados para configurar o ambiente de teste antes e depois da execução de cada caso. O JUnit 4 substituiu os métodos por duas anotações @Before @After as duas respectivamente substituem os métodos setUp() e tearDown(). Qual a diferença?? A diferença é que agora ganhamos uma flexibilidade maior para definirmos os nomes dos métodos de inicialização da unidade de teste visto que o framework agora faz interpretações a partir das anotações.
Abaixo segue um exemplo comparativo de um método setUp() feito na versão anterior do JUnit (Listagem 4) e usando anotação na versão 4 do framework (Listagem 5).
protected void setUp() { System.setErr(new PrintStream(new ByteArrayOutputStream())); inputDir = new
File("data"); inputDir = new File(inputDir, "xslt"); inputDir = new File(inputDir, "input");
}
@Before protected void inicializa() { System.setErr(new PrintStream(new ByteArrayOutputStream())); inputDir =
new File("data"); inputDir = new File(inputDir, "xslt"); inputDir = new File(inputDir, "input");
}
Uma grande observação a ser feita e é destaque nessa nova versão do JUnit são os nomes dados as anotações o que torna o processo de elaboração das classes de teste mais intuitiva e clean.
Note que utilizando anotações @Before, você pode criar múltiplos métodos de teste que serão executados antes de cada método de teste definido na classe.
O antigo método tearDown() segue a mesma regra podendo ser utilizado em múltiplos métodos para finalização de teste.
protected void tearDown() { doc = null; System.gc();
}
@After protected void finalizaDocumento() { doc = null; System.gc();
}
Para classes de teste que possuam superclasses não é mais necessário explicitar as chamadas de métodos setUp() e tearDown() nas superclasses. O framework se encarrega de chamar os métodos para você de forma automática se necessário. Métodos com anotações @Before na superclasse são chamados antes dos métodos @Before na subclasse. Métodos @After são chamados de forma inversa, primeiro os da subclasse e depois os da superclasse.
Suíte-wide initialization
O JUnit 4 acrescentou uma nova funcionalidade que executada instruções antes da classe de teste e logo após a sua execução: @BeforeClass e @AfterClass.
private PrintStream systemErr; @BeforeClass protected void redirectStderr() { systemErr = System.err; // Hold on
to the original value System.setErr(new PrintStream(new ByteArrayOutputStream()));} @AfterClass protected void
tearDown() { // restore the original value System.setErr(systemErr);
}
Imagine que você precisa salvar informações numa tabela do seu banco de dados em tempo de execução das suas classes de teste. A abertura e fechamento de conexões é um processo custoso e que ficaria inviável estar re-criando essa estrutura de dados a cada método de teste. Logo com as anotações @BeforeClass e @AfterClass é possível realizar os testes e garantir que as instruções de conexão e desconexão com o banco de dados seja realizada apenas uma vez.
Exceções no teste
Teste de exceções foi uma das maiores melhoras implementadas pelo JUnit 4. O antigo estilo de testar as exceções exigia que fosse implementado um bloco try/catch em volta do método que lança a exceção e dentro do bloco catch um método para validar a exceção.
public void testDivisaoPorZero() { try { int n = 2 / 0; fail("Divisão por zero!"); } catch (ArithmeticException
success) { assertNotNull(success.getMessage()); }
}
Era necessário saber no código de teste qual seria a instrução que iria levantar a exceção a ser testada e logo após colocar uma chamada para o método fail() que indica que o teste falhou.
O JUnit 4 trouxe de uma forma simples e elegante uma anotação na qual é informada a exceção esperada em um dado método de teste e se a exceção não for levantada ou ainda que levantada não seja a esperada o teste é tido como falho.
@Test(expected=ArithmeticException.class) public void divideByZero() { int n = 2 / 0;
}
Caso no teste seja necessário validar se a mensagem de erro está coerente com o teste é possível incluir o bloco try/catch e realizar a verificação desse tipo de rotina.
Ignorando testes
Quando se é necessário desativar ou ignorar na classe de teste algum método utilizamos a anotação @Ignore para substituir a anotação @Test assim não é preciso excluir ou comentar o código dos testes ignorados.
@Test(expected=ArithmeticException.class) public void divideByZero() { int n = 2 / 0;
}
Testes temporizados
Com o JUnit 4 é possível inserir parâmetros de timeout para o teste e definir um tempo tolerável de execução do mesmo, caso o tempo estoure o teste é considerado falho.
Esta anotação auxilia testes que contenham dentro de sua estrutura instruções bloqueantes como comandos de socket e acesso a banco de dados, por exemplo.
@Test(timeout=500) public void selectAllElements() { doc.query("select * from elements");
}
Novas assertions
No framework JUnit 4 foram adicionadas 2 tipos diferente de métodos assert() para a comparação de arrays.
public static void assertEquals(Object[] expected, Object[] actual)public static void
assertEquals(String message, Object[] expected,
Object[] actual)
Considerações Finais
A versão 4 do JUnit não pode ser considerada uma versão de atualização do anterior e sim um novo framework. Embora simples e dinâmico o JUnit 4 deixa a desejar em alguns aspectos os quais são implementados pela versão anterior do Framework como a distinção entre uma falha (erros antecipados verificado por métodos assert()) e erros (erros não antecipados indicados por exceções). Outro ponto negativo do novo framework é a retirada do método suite() que permitia a execução de múltiplas classes de teste.
No próximo artigo irei apresentar de forma prática como instalar e executar classes de testes no Eclipse IDE e irei comentar alguns exemplos de padrões de classe de teste.