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:

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.

Listagem 1 – Exemplo de código da versão anterior do JUnit.

        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.

Listagem 2 – Exemplo de código JUnit 4

        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

Listagem 3 – Exemplo de código JUnit 4 sem estender de TestCase

        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).

Listagem 4 – Exemplo de código JUnit 3 ou anterior utilizando setUp().

        protected void setUp() { System.setErr(new PrintStream(new ByteArrayOutputStream())); inputDir = new
        File("data"); inputDir = new File(inputDir, "xslt"); inputDir = new File(inputDir, "input");
        }
        
Listagem 5 – Exemplo de código JUnit 4 usando anotação

        @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.

Listagem 6 – Exemplo de código JUnit 3 ou anterior utilizando tearDown().

        protected void tearDown() { doc = null; System.gc();
        }
        
Listagem 7 – Exemplo de código JUnit 4 usando anotação

        @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.

Listagem 8 – Exemplo de anotações suite-wide.

        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.

Listagem 9 – Exemplo de anotações suite-wide.

        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.

Listagem 10 – Exemplo de anotações exception expected.

        @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.

Listagem 11 – Ignorando um teste.

        @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.

Listagem 12 – Anotação para testes temporizados.

        @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.

Listagem 13 – Acerts de 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.

Confira também