quinta-feira, 9 de setembro de 2010

Tagged under: , , , , ,

Testes Unitários com Groovy - Parte 1

Nos meus primeiros passos com Groovy falei sobre como instalar o Groovy e configurar o Gedit no Ubuntu. Ficou pendente tratar sobre testes unitários. Se eu soubesse que era tão fácil, tinha falado disso também. Vamos lá.

É só pensar em testes unitários para nos lembramos da família *Unit, então fui logo procurar se existia um "GUnit". Rapidamente descobri que não precisa, pois Groovy é Java, portanto totalmente integrável ao JUnit. Na documentação oficial do Groovy esta seção sobre o essencial http://groovy.codehaus.org/Unit+Testing. Farei aqui um resumo, indo direto ao código. Eu deveria fazer isso com TDD, mas num blog seria meio chato (quem sabe eu faça um vídeo hora dessas).

Vamos começar por uma classe simples, arquivo GroovyCalc.groovy, uma calculadora com as 4 operações básicas. Inicialmente vou fazer todos os métodos retornarem o valor -100, para garantir a falha de 100% dos testes que vem em seguida.



class GroovyCalc {
def soma(a,b) { -100 }
def multiplica(a,b) { -100 }
def subtrai(a,b) { -100 }
def divide(a,b) { -100 }
}


Para fazer uma classe de teste, basta criar o arquivo GroovyCalcTest.groovy no mesmo pacote. A classe GroovyCalcTest deve estender de groovy.util.GroovyTestCase (não é preciso import, pois groovy.util.* já está disponível por default), que já oferece tudo do JUnit 3 e mais algumas coisinhas interessantes. Criei 8 testes para as 4 operações, buscando mostrar formas diferentes de implementação.

class GroovyCalcTest extends GroovyTestCase {
def calc

void setUp() {
calc = new GroovyCalc()
}

void testSoma3mais2igual5() {
assert 5 == calc.soma(3,2)
}

void testMultiplica3por2igual6() {
assert 6 == calc.multiplica(3,2): "2 multiplicado por 3 deveria ser 6"
}

void testSubtrai() {
assertEquals 1, calc.subtrai(3,2)
}

void testDivide3por2igual1eMeio() {
assertEquals "3 dividido por 2 deveria ser 1.5", 1.5, calc.divide(3,2)
}

void testDivisaoPor0geraExcecaoForma1() {
try {
def result = calc.divide (3, 0)
} catch(ArithmeticException e) {
return;
}
fail();
}

void testDivisaoPor0geraExcecaoForma2() {
try {
def result = calc.divide (3, 0)
} catch(java.lang.ArithmeticException e) {
assertEquals("/ by zero", e.getMessage());
return;
}
fail("Divisão por 0 deveria lançar ArithmeticException '/ by zero'");
}

void testDivisaoPor0geraExcecaoForma3() {
shouldFail(ArithmeticException) {
def result = calc.divide (3, 0)
}
}

void testDivisaoPor0geraExcecaoForma4() {
def msg = shouldFail(ArithmeticException) {
def result = calc.divide (3, 0)
}
assertTrue msg == "/ by zero"
}
}
Para executar os testes, é só digitar groovy GroovyCalcTest na linha de comando. Todos os testes irão falhar, e você receberá uma saída como esta abaixo. Saliento que, por questões de espaço (a saída completa tem 273 linhas!), omiti as linhas de "debug" do Java, deixando apenas as 3 primeiras do erro 1 (linhas 5 a 8).


.E.E.F.F.F.F.F.F
Time: 0,016
There were 2 errors:
1) testSoma3mais2igual5(GroovyCalcTest)java.lang.AssertionError: Expression: (5 == calc.soma(3, 2))
at org.codehaus.groovy.runtime.InvokerHelper.assertFailed(InvokerHelper.java:394)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.assertFailed(ScriptBytecodeAdapter.java:676)
at GroovyCalcTest.testSoma3mais2igual5(GroovyCalcTest.groovy:9)
...
2) testMultiplica3por2igual6(GroovyCalcTest)java.lang.AssertionError: 2 multiplicado por 3 deveria ser 6. Expression: (6 == calc.multiplica(3, 2))
There were 6 failures:
1) testSubtrai(GroovyCalcTest)junit.framework.AssertionFailedError: expected:<1> but was:<-100>
2) testDivide3por2igual1eMeio(GroovyCalcTest)junit.framework.AssertionFailedError: 3 dividido por 2 deveria ser 1.5 expected:<1.5> but was:<-100>
3) testDivisaoPor0geraExcecaoForma1(GroovyCalcTest)junit.framework.AssertionFailedError: null
4) testDivisaoPor0geraExcecaoForma2(GroovyCalcTest)junit.framework.AssertionFailedError: Divisão por 0 deveria lançar ArithmeticException '/ by zero'
5) testDivisaoPor0geraExcecaoForma3(GroovyCalcTest)junit.framework.AssertionFailedError: Closure GroovyCalcTest$_testDivisaoPor0geraExcecaoForma3_closure1@df1832 should have failed with an exception of type java.lang.ArithmeticException
6) testDivisaoPor0geraExcecaoForma4(GroovyCalcTest)junit.framework.AssertionFailedError: Closure GroovyCalcTest$_testDivisaoPor0geraExcecaoForma4_closure2@1576e70 should have failed with an exception of type java.lang.ArithmeticException

FAILURES!!!
Tests run: 8, Failures: 6, Errors: 2

Vamos agora a alguns comentários. Observe que os 2 primeiros testes são encarados como Erros e os 6 restantes como Falhas. Isso porque nos testes 1 e 2 utilizamos diretamente a instrução assert, bastante versátil mas não muito boa para indicar exatamente a causa do problema. Veja no erro 1 que a mensagem é apenas um AssertionError. No erro 2 é exibida a mensagem "2 multiplicado por 3 deveria ser 6", configurada como parâmetro adicional no assert.

Os demais testes sempre utilizam o assert*, encarados como Falha se suas condições não são satisfeitas. Todos os métodos herdados do JUnit podem ser utilizados (veja relação) e o Groovy nos dá ainda algumas opções adicionais (seção Groovy Test Assertions). Os assert* podem receber uma mensagem como primeiro parâmetro (foi o que fiz na linha 21). Usei basicamente assertEquals e assertTrue, que normalmente atendem satisfatoriamente a maioria das situações.

Os 3 testes finais verificam se a divisão por zero está corretamente lançando exceções. A forma 1 usa bloco Try/Catch e provoca uma falha usando o método fail(). A forma 2 é semelhante, mas também testa se a mensagem da exceção é correta, adicionalmente passando uma mensagem ao fail(). As formas 3 e 4 fazem o mesmo, mas de maneira mais elegante (na minha opinião) usando o método shouldFail() fornecido pelo GrooyvTestCase. Legal, não?

class GroovyCalc {
def soma(a,b) { a + b }
def multiplica(a,b) { a * b }
def subtrai(a,b) { soma(a, -1*b) }
def divide(a,b) { multiplica(a,1/b) }
}


O resultado dos testes será:

$ groovy GroovyCalcTest
........
Time: 0,023

OK (8 tests)

É isso. Para programas simples, um editor de textos e a linha de comando são suficientes para desenvolver e testar com qualidade. Sistemas maiores e mais complexos normalmente precisam do apoio de um IDE. É isso que vermos no próximo post. Até!

4 comentários:

André Faria disse...

Bacana. Eu gosto bastante de usar o Framework Spock para testar com Groovy.

Serge Rehem disse...

Legal André. Vi seu buzz e cheguei a passar o olho no Spock. Outro dia também vi o Tellurium. Se resolver escrever algo sobre um deles, avisa que a gente posta aqui no blog também!

rkmael disse...

Serge, trabalho com o André Faria na Bluesoft e fiz duas apresentações sobre Spock (blog.bluesoft.com.br). Os videos estão disponíveis no vimeo (http://vimeo.com/19257715 e http://vimeo.com/19270063).
O terceiro vídeo desta série será hands one, e por talvez até um artigo.

Manuele Ferreira disse...

Muito bom!