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:
- O Grails os cria automaticamente pra você
- Criando a classe de teste manualmente (nesta seção ficará claro como isto é feito)
- 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
Deixe uma resposta