Grails: testando sua aplicação

Sabe esta aplicação linda que você está escrevendo em Grails usando todo o dinamismo que o framework te oferece?  Funciona perfeitamente, e em sua cabeça não aparece uma situação sequer na qual algo possa dar errado, certo?

Como você pode ter certeza de que esta aplicação está funcionando? Só há uma maneira: testando, e da maneira certa, ou seja, com testes automatizados. Não há como evitar, sua aplicação VAI virar um monstrinho abominável se voce não escrever testes automatizados.

Os testes automatizados são a sua rede de segurança (safety net): são eles que te dão segurança no momento da manutenção.

Neste post meu objetivo é expor como tirar proveito do Testing Framework, que é o framework de testes adotado pelo Grails. Porém, antes de começar quero acabar com um mito cretino.

“Eu não tenho tempo para escrever testes!”

Papo furado! Se o programador não escreve testes, como pode saber que sua aplicação está DE FATO funcionando? Debugando e executando a aplicação manualmente. E sabe qual o grande problema com esta prática? Ela consome tempo: muito mais do que a escrita e execução dos testes automatizados. E sabe o que é pior? Ainda é sucetível a erros.

Isto sem mencionar que os testes unitários e de integração acabam se tornando a melhor documentação possível para o seu código, pois descrevem exatamente como espera-se que este funcione.

Lido este argumento sua desculpa para ser irresponsável evaporou.

Testes unitários e de integração: qual a diferença?

Testes unitários levam em consideração o objeto a ser verificado isolado. Não há conexões com bancos de dados, web services ou qualquer outro tipo de componente: a classe deve ser vista como um elemento autista. Como veremos, isto trás algumas dificuldades que o testing framework do Grails nos ajuda a resolver de uma maneira simples usando mock objects.

Testes de integração, por sua vez, levam em consideração, como o próprio nome já diz, a integração do objeto a ser testado com componentes externos, como por exemplo bancos de dados, web services ou outros serviços de natureza diversa. Testes de integração são portanto muito mais caros do ponto de vista computacional, visto que precisamos iniciar a aplicação para que estes possam ser executados.

É muito comum encontrar testes unitários que, na realidade, são de integração. Os mock objects entram em cena portanto como elementos externos “de mentirinha”, que nos permitem tratar o objeto isoladamente. Veremos mais sobre este recurso neste post.

Criando seus testes

Todos os testes unitários se encontram no diretório test/unit (ou integration) presente na raiz do seu projeto Grails. Já reparou que toda vez que você cria uma classe de domínio, controlador ou biblioteca de tags (e outros artefatos), automaticamente são incluidos no diretório test/unit?

Há três maneiras de se criar estes testes:

  1. O Grails os cria automaticamente pra você
  2. Criando a classe de teste manualmente (nesta seção ficará claro como isto é feito)
  3. Usando o comando “grails create-unit-test”

Assim como diversos aspectos do Grails, aqui também devemos nos ater a algumas convenções. Toda classe de teste possui o sufixo “Tests” em seu nome. Sendo assim, os testes unitários para a classe de domínio Usuario, por exemplo, ficariam em test/unit/UsuarioTests.groovy.

Uma classe de teste vazia é simples como a abaixo:


import grails.test.*

class UsuarioTests extends GrailsUnitTestCase {
protected void setUp() {}

protected void tearDown() {}

void testSomething() {}
}

O comando “grails create-unit-test” ou “grails create-integration-test” deve receber o nome do teste unitário ou de integração a ser gerado. Você não precisa incluir o “Tests” no final do arquivo, Grails o incluirá para você.

Testando classes de domínio

Quando lidamos com linguagens dinâmicas como Groovy precisamos lidar com o seguinte problema: como testar uma classe que contém métodos e atributos que só serão injetados em tempo de execução? Funções como save(), validate() ou constraints só funcionam após injetados pelo framework.

Podemos escrever testes de integração. O problema é que leva tempo até a aplicação ser iniciada, o que irá reduzir a sua produtividade. O ideal é podermos executar testes unitários, que por sua própria natureza são ordens de magnitude mais rápidos. A solução para o problema é mockar seus objetos usando o Testing Framework do Grails!

(assim como o GORM é baseado no Hibernate, o Testing Framework é baseado no JUnit que você já conhece (ou deveria conhecer! :D))

Para ilustrar, tenha como base esta classe de domínio:


class Usuario {
String nome
String login

static constraints = {
nome(nullable:false, blank:false, maxSize:128, unique:true)
login(nullable:false, blank:false, maxSize:16, unique:true)
}
}

Nosso teste unitário encontra-se na classe abaixo:


import grails.test.*

class UsuarioTests  extends GrailsUnitTestCase {
protected void setUp() {super.setUp()}

protected void tearDown() {super.tearDown()}

void testConstraints() {
mockDomain Usuario
def usuario = new Usuario()
assertFalse usuario.validate()
def usuario_ok = new Usuario(nome:"Joselino", login: "joca")
assertTrue usuario_ok.validate()
}

void testUnicidade() {
mockDomain Usuario, []
def usuario1 = new Usuario(nome: "Joselino", login: "jose")
usuario1.save()
def usuario2 = new Usuario(nome:"Joselino", login:"joca")
assertFalse usuario2.validate() // já existe um Joselino!

}

}

Repare que interessante: mesmo se tratando de um teste unitário estou testando métodos que só existem em tempo de execução: no caso, o validate. Para isto, uso o método mockDomain, herdado de GrailsUnitTestCase. Este injeta na classe domínio todos os métodos que uma classe de respeito deste tipo deve ter, como por exemplo os métodos de validação, save(), delete(), etc.

Assim é possível testar fácilmente a validação. Mais interessante ainda é o segundo teste: testUnicidade. Repare que passo para o método mockDomain uma lista vazia. Quando evoco o método save(), este objeto é na realidade armazenado naquela lista que passei como parâmetro, simulando assim um banco de dados! Não é legal? Assim posso verificar se a constraint unique está de fato funcionando ou não.

No caso de testes de integração, óbviamente você não precisa do método mockDomain, pois as classes já estão prontas.

Testes unitários para controladores

É muito comum encontrarmos projetos nos quais apenas classes de domínio são testadas. Após ler esta seção, sua desculpa para não testar seus controladores acabou! :D

Para testar suas classes, você deve criar um teste tal como faria normalmente. A diferença é que este teste não extenderá a classe GrailsUnitTestCase, mas sim ControllerUnitTestCase. Nosso controlador de exemplo segue abaixo. Observe que ele só tem uma action.


class BoboController {

def bobo = {

if (! params.valor) {
redirect(action:"outra", controller:"outro")
                return
}
switch (params.valor) {
case "boo":
render "Não sou bobo não"
break
case "bonito"
return [resposta:"sou lindo mesmo!"]
case "nada"
throw new Exception("Nada???")
}
}

}

Escrevi alguns testes para esta classe que ilustra bem o que podemos fazer com este framework:

import grails.test.*

class BoboControllerTests extends ControllerUnitTestCase {
    protected void setUp() {
        super.setUp()
    }

    protected void tearDown() {
        super.tearDown()
    }

    void testRedirect() {
		controller.bobo()
		assertEquals "outra", redirectArgs.action
		assertEquals "outro", redirectArgs.controller
    }

	void testRender() {
		mockParams.valor = "bobo"
		controller.bobo()
		assertEquals mockResponse.contentAsString, "Não sou bobo não"
	}

	void testModel() {
		mockParams.valor = "bonito"
		def model = controller.bobo()
		assertEquals model.resposta, "sou lindo mesmo!"
	}

}

Nosso primeiro teste é o de redirecionamento. Observe o método testRedirect(). A classe ControllerUnitTestCase possui um atributo chamado chamado controller, que simula o nosso controlador (o controlador é identificado por default pelo nome que demos à classe do nosso teste).  Chamando nossa action sem nenhum parametro, esperamos, pelo código que expus acima, que sejamos redirecionados para a action “outra” do controller “outro”.

Entra em cena então outro atributo de nossa classe: redirectArgs. Ele normalmente possui 3 parâmetros: controller, action e model. Assim podemos ver se o nosso controlador está se comportando como esperavamos inicialmente.

Se quisermos testar o funcionamento de nosso controlador passando-lhe parâmetros, usamos o atributo mockParams, que funciona exatamente como o params que estamos acostumados a trabalhar. Primeiro devemos incluir os valores neste atributo e em seguida executar nossa action para ver o resultado. Em testeRender podemos ver um exemplo de como verificar se o texto renderizado pelo nosso controlador foi o que esperávamos receber. Para isto, usamos o atributo mockResponse.

Finalmente, inclui também um teste para o valor de retorno de nosso controlador. Observe o método testModel. Basta executar nossa action como se fosse uma função convencional. O restante do código é similar ao que escreveriamos usando um teste unitário convencional.

Há mais alguns atributos que merecem ser mencionados: são estes:

  • mockRequest: usado para simular uma instância de HttpServletRequest
  • mockSession: usado para simular uma sessão (você pode, por exemplo, verificar se seu controller alterou ou não sua sessão após a execução de uma action, o que é MUITO útil)
  • mockFlash: usado para simular o contexto flash. Muito útil para verificar mensagens de alerta que precisam ser enviadas ao usuário, por exemplo.

Executando os testes

Com sua aplicação funcionando, o próximo passo é executar seus testes. Para isto, você usa o comando “grails test-app”, que irá primeiro executar todos os seus testes.

Caso queira executar apenas alguns testes, basta passar como parâmetro os nomes dos testes excluindo o sufixo “Tests”, como por exemplo

grails test-app BoboController

Para executar apenas testes unitários, execute

grails test-app unit

e para executar apenas os testes de integração, execute

grails test-app integration

Executados os seus testes, será criado o diretório target/test-reports em seu projeto, contendo o relatório de execução dos seus testes. Caso esteja usando Grails 1.1, estes relatórios irão estar em test/test-reports.

Concluindo

Um programador que não escreve testes automatizados é como um cirurgião que não lava as mãos antes de uma cirurgia (não me lembro aonde li isto). Com Grails, a escrita dos testes, como pode ser visto, é um processo simples e direto, que irá lhe garantir noites de sono tranquilas após ter entregue seus sistemas.

Agora, se mesmo assim você ainda não quer escrever seus testes, bem: sinto muito, você deve procurar outro ramo se quiser manter um nível mínimo de saúde. :D

22 thoughts on “Grails: testando sua aplicação

  1. Esse teste de constraints é apenas um exemplo, correto?
    Ao meu ver não acho necessário testar as constraints e sim os métodos
    que forem adicionados ao domínio.

    Muito bom o post, ja tive muitas dessas dúvidas.
    Parabéns.

    Responda

    admin Reply:

    Oi Gregory, exatamente: é apenas um exemplo, expondo como testar um dos recursos que é injetado pelo Grails.

    Agora, há casos nos quais o teste de constraints é interessante, como por exemplo quando você cria constraints personalizadas (ai é uma boa ter um teste unitário pra garantir a sua segurança).

    Fico feliz que tenha gostado, valeu!

    Responda

    Gregory Reply:

    No caso de criar novas contraints sim, tambem concordo em testar.
    Mas as que o grails já oferece acho desnecessário.

    []’s

    Responda

    admin Reply:

    Concordo 100%, ao menos em teoria, as constraints oferecidas já tão testadas né? (espero! :D)

    Responda

    Gregory Reply:

    é o que tmb espero! heheh

    Responda

  2. Tentei testar o teste para a entidade Usuario e sempre dá o erro “junit.framework.AssertionFailedError: null” quando tento usar “asserFalse”. Bem, pelo menos quando o “assertFalse” para “assertTrue” das linhas “assertFalse usuario2.validate()” e “assertTrue usuario_ok.validate()”, funcionou.

    O Stack gerado no html do report do teste foi.
    junit.framework.AssertionFailedError: null

    junit.framework.AssertionFailedError: junit.framework.AssertionFailedError: null
    at yoshi.modelo.UsuarioTestTests.testUnicidade(UsuarioTestTests.groovy:23)

    É normal dar todo esse erro? Não deveria vir uma mensagem relacionada à falha do teste (erro de falha de restrição, por exemplo).

    Valeu :)

    Responda

    admin Reply:

    Oi Yoshi, neste caso, eu teria de ver com maiores detalhes o seu código. Ele é igual ao que postei aqui? Você pode incluir uma mensagem de erro também se quiser, por exemplo:

    assertTrue “Era pra ser true”, condicao

    No caso destas dúvidas mais técnicas Yoshi, que podem ajudar mais gente, rola de você postar no Grails Brasil e por lá mesmo a gente resolver? Assim se tiver mais pessoas com o mesmo problema, a gente consegue ajudá-las também. O que me diz?

    Responda

    admin Reply:

    Neste caso que você está apontando, pode ser até um bug de versão velha do Grails, não? De qualquer maneira, posta aqui depois o link pro Grails Brasil e a gente debulha o problema ok? Grande abraço!

    Responda

    Leandro Silva Reply:

    Olá Yoshi.

    Pelo que vi, o problema está no nome da sua classe de teste. Está “UsuarioTestTests”, quando o correto é “UsuarioTests”.

    Acredito que você tenha criado a classe de teste com o comando “grails create-unit-test yoshi.modelo.UsuarioTest”. Tente usar “grails create-unit-test yoshi.modelo.Usuario”, que deverá resolver o problema.

    Espero ter ajudado.
    Abraço.

    Responda

  3. E mais um detalhe: quando mudei para “assertTrue” os testes deveriam falhar, correto? Mas os teste executaram e ficar com o status final de “success”!

    Meus métodos de teste ficaram assim:

    void testResticoes() {
    mockDomain Usuario
    def usuario_vazio = new Usuario()
    assertTrue usuario_vazio.validate()
    def usuario_ok = new Usuario(nome:”Joselino”, login: “joca”)
    assertTrue usuario_ok.validate()
    }

    void testUnicidade() {
    mockDomain Usuario, []
    def usuario1 = new Usuario(nome: “Joselino”, login: “jose”)
    usuario1.save()
    def usuario2 = new Usuario(nome:”Joselino”, login:”joca”)
    assertTrue usuario2.validate() // já existe um Joselino!
    }

    A classe Usuario está igual à do seu post.

    Responda

  4. Parabéns Kico!!! Excelente artigo.

    Queria apenas acrescentar uma informação que pode ser útil, relacionado aos testes da classe de domínio.

    Peguando como exemplo a classe de domínio “Usuario”. Caso a mesma tenha uma relação com uma classe “Grupo” por exemplo, caso seja necessário definir o relacionamento como belongsTo = [grupo: Grupo].

    Neste caso, nos construtores deverá ser incluído o grupo no construtor como em:

    new Usuario(nome:”Joselino”, login: “joca”, grupo: new Grupo(nome: “Administradores”))

    Caso contrário, os testes geração erro.

    Valeu ;)

    Responda

    admin Reply:

    Oi Bruno, que bom que gostou cara, valeu!

    Responda

  5. Olá Henrique!
    Fico muito bom seu post sobre teste no grails. Muito bem lembrado sobre essa desculpa de não ter tempo para fazer teste. Sem falar que o teste é o melhor documento de um software.
    O tempo que se gasta fazendo repetidos testes manuais daria muito bem para escrever um teste automatizado. Na hora de fazer refatoração, quem salva são os benditos testes.
    Parabéns.

    Responda

    admin Reply:

    Oi Emílio, fico feliz que tenha gostado cara, valeu!

    Responda

  6. Autista, não “altista”. Altista seria um elemento que fica no alto.

    Responda

    admin Reply:

    Oi Fred, arrumado.
    Valeu pelo comentário construtivo!

    Responda

    Fred Reply:

    Ok!! Tem vários errinhos menores, recomendo passar num corretor.

    Valeu pelo post,

    Responda

  7. Kiko, sobre testes com grails, eu utilizo o netbeans, gostaria de saber se tem como utilizar o jUnit para testar minha app em grails. Não consegui rodar os tests como faço em java.

    Responda

    Kico (Henrique Lobo Weissmann) Reply:

    Oi Eduardo, tem como sim.

    Da uma lida neste link: http://groovy.codehaus.org/Using+JUnit+4+with+Groovy

    Responda

    Eduardo Pilla Reply:

    Kiko, obrigado pela ajuda. Eu estou conseguindo realizar os testes utilizando a classe GroovyJUnitTest e GrailsTests, o problema é que ele escreve o resultado dos testes la em “../test-reports/html/index.html”, correto ? e como a aplicação é grande, ele demora bastante tempo para terminar, cada vez que eu rodo os testes (alt + f6) demora bastante. Quando estamos em um abiante java ou até mesmo PHP os resultados dos testes são impressos na IDE mesmo na janela Test Results, é isso o que eu gostaria de saber se é possivel.

    Abraços

    Responda

Leave a Reply

Your email address will not be published. Required fields are marked *