sexta-feira, 5 de junho de 2015

Testes unitários e JUnit


Um problema bem incoveniente ao codar muitas linhas de um programa é a grande chance de surgirem erros e bugs. Imagine então um projeto grande com vários métodos que desempenham pequenas funções, onde uma só falha impacta no produto final. Funcionar no primeiro teste é impossível!

Existem técnicas de desenvolvimento que buscam resolver isso. Uma delas é o desenvolvimento guiado por testes: dado os testes que o programa deve passar, a ideia é escrever um código que cumpra os requisitos e checar as falhas, corrigindo a cada etapa.

Para facilitar, tudo é dividido em módulos que cumprem funções determinadas. Sabendo o que cada coisa deve fazer, fica mais fácil elaborar uma bateria de testes e saber o que não funciona. (Se você quiser ter uma noção melhor, este blog aqui explica legal)

Este é um tópico muito extenso na engenharia de softwares, mas a ideia aqui é apresentar uma framework de testes em Java: JUnit.
Q: Ok, mas por que testar enquanto desenvolve? Não dá para escrever e debuggar depois?
A: NÃO! Desenvolver testes unitários que abordam erros específicos demora menos tempo do que tentar achar o erro em um código pronto que não funciona.

A framework automatiza este trabalho, dado que os testes são rodados a cada modificação no programa, e fornece resposta rápida. JUnit ainda tem a grande vantagem de vir integrada em IDEs como Eclipse e NetBeans.

É possível rodar Test Cases (unitários) e Test Suites (vários testes). Vamos rodar alguns casos simples para testar uma pequena calculadora:
public class SimpleCalculator {
    public int multiply_mod10(int a, int b) {
        return (a*b)%10;
    }
    
    public int soma(int a, int b) {
        return a+b;
    }
}

Usando NetBeans, basta clicar com botão direito na Classe > Ferramentas > Criar Testes. Testando o método multiply_mod10:
    @Test
    public void testMultiply_mod10() {
        System.out.println("multiply_mod10");
        
        SimpleCalculator instance = new SimpleCalculator();
        int expResult, result;
        
        expResult = 6;
        result = instance.multiply_mod10(3, 2);
        assertEquals(expResult, result);
        
        expResult = 0;
        result = instance.multiply_mod10(1000000000, 100000000);
        assertEquals(expResult, result);
    }
A função assertEquals compara se expResult e result são iguais, e indica na nossa tela. Para esses casos, temos o resultado:



Opa! Esquecemos de tratar o overflow da operação (a vezes b) modulo 10. Vamos trocar por (a modulo 10) vezes (b modulo 10) modulo 10. E:



Sucesso! Podemos definir mais casos de teste. Se quisermos que nosso código pegue uma exceção, vamos testar este caso. Por exemplo, queremos que a operação soma lance uma exceção caso dê overflow no resultado final, como no caso da soma de dois números muito grandes.


Testamos então o caso:
    @Test
    public void testExceptedException() {
        try {
            final int a = 2000000000;
            SimpleCalculator instance = new SimpleCalculator();
            System.out.println(a + " + " + a + " = " + instance.soma(a, a));
            fail("IllegalArgumentException was expected. Overflow!");
        } catch (IllegalArgumentException ex) {}
    }
E o resultado do teste...

O caso que já passava antes deu certo, mas não havíamos tratado a exceção. Se mudamos nossa classe:
    public int soma(int a, int b) {
        if(b > Integer.MAX_VALUE - a)
            throw new IllegalArgumentException("Overflow!");
        return a+b;
    }
E finalmente, deu certo!
Estes são exemplos muito simples, mas usar uma framework de teste como JUnit pode ser muito cômodo, em especial em grandes projetos em que as funções das classes e métodos são bem definidas.

Um comentário: