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


Publicado

em

, ,

por

Tags:

Comentários

22 respostas para “Grails: testando sua aplicação”

  1. Avatar de Gregory
    Gregory

    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.

    1. Avatar de admin
      admin

      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!

      1. Avatar de Gregory
        Gregory

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

        []’s

        1. Avatar de admin
          admin

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

          1. Avatar de Gregory
            Gregory

            é o que tmb espero! heheh

  2. […] Testing Your Application (in Portuguese) […]

  3. Avatar de yoshi
    yoshi

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

    1. Avatar de admin
      admin

      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?

      1. Avatar de admin
        admin

        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!

    2. Avatar de Leandro Silva
      Leandro Silva

      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.

  4. Avatar de yoshi
    yoshi

    “testar o teste” -> “fazer o teste” ^^’

  5. Avatar de yoshi
    yoshi

    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.

  6. Avatar de Bruno
    Bruno

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

    1. Avatar de admin
      admin

      Oi Bruno, que bom que gostou cara, valeu!

  7. Avatar de Emílio

    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.

    1. Avatar de admin
      admin

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

  8. Avatar de Fred
    Fred

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

    1. Avatar de admin
      admin

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

      1. Avatar de Fred
        Fred

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

        Valeu pelo post,

  9. Avatar de Eduardo Pilla
    Eduardo Pilla

    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.

    1. Avatar de Kico (Henrique Lobo Weissmann)
      Kico (Henrique Lobo Weissmann)

      Oi Eduardo, tem como sim.

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

      1. Avatar de Eduardo Pilla
        Eduardo Pilla

        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

Deixe uma resposta

Esse site utiliza o Akismet para reduzir spam. Aprenda como seus dados de comentários são processados.