Outras Atividades Durante uma fase do Motion, precisamos começar a testar tudo que estávamos desenvolvendo e, para isto, foi necessário escrever alguns testes automatizados para facilitar o trabalho. A idéia de um teste automatizado é montar um cenário para testar uma determinada funcionalidade de uma classe. A principal ferramenta para escrever tais testes é o JUnit [4]. No entanto, o JUnit foi desenvolvido para trabalhar apenas com classes e não com entidades EJB ou com Servlets. Por isso, existem algumas extensões do JUnit que facilitam a escrita de testes automatizados, como o EJBUnit [12] e o Cactus [10]. No Motion utilizamos o Cactus [10] como base de testes de entidades, usando Servlets para obter acesso às entidades EJB do sistema. Além disso, fazer o deploy de testes do Cactus no servidor de aplicações é mais fácil que fazer o deploy de entidades (são muitas no Motion). O Motion possui muitas entidades EJB e criar um cenário para testes utilizando estas entidades não é uma tarefa tão simples. Para facilitar o trabalho da escrita de testes, foi desenvolvido uma espécie de "menu de entidades para teste". Neste "menu", algumas entidades estariam previamente criadas no sistema e possuíam uma chave de busca. Um framework foi desenvolvido para carregar tais entidades no servidor de teste e para permitir a busca dessas entidades por suas respectivas chaves. Além disso, o framework era responsável por criar e remover entidades criadas durante um teste, para evitar que elas ficassem ocupando espaço no banco de dados. Apesar de tudo, não havia nenhuma estrutura para escrever testes automatizados para as telas do sistema. A única ferramenta que facilitava um pouco nosso trabalho era o StrutsTestCase [9], uma extensão do Cactus [10] para trabalhar diretamente com o Struts [3]. A grande dificuldade era que estes testes precisavam simular uma série de comportamentos que um usuário poderia assumir ao navegar pelas telas do sistema e, para isto, era preciso escrever muitas linhas de código. Por isso participei do desenvolvimento de um framework para facilitar a escrita de tais testes. Num sistema Cliente-Servidor HTTP padrão, a troca de informações é feita através de Requests e Responses, que trafegam entre o navegador web do cliente e o servidor web, como ilustrado na figura abaixo:
Toda requisição de um cliente web chega no servidor web e o Struts [3] mapeia tal requisição para uma classe ActionForm especificada por um arquivo de configuração XML. Esta classe ActionForm possui um método - perform() - responsável por tomar alguma ação e devolver uma resposta ao cliente (geralmente processa uma página JSP para mostrar no navegador web). A classe ActionForm também preenche os dados exibidos pelo JSP numa outra classe Form. O mapeamento de uma classe ActionForm com uma classe Form e um JSP também é especificado pelo arquivo XML. Além disso o Struts [3] também especifica uma maneira uniforme de tratamento de erros. O StrutsTestCase [9] permite que um teste automatizado atue como um cliente navegando pelas telas do sistema. Você indica no teste qual o caminho para onde enviará sua requisição e coloca todos os parâmetros necessários para que o Struts [3] processe tais informações e devolva uma resposta válida. Após tal procedimento, você pode fazer as devidas asserções para garantir que a tela fez o que deveria fazer (inclusive verificar que nenhum erro aconteceu ou que um erro aconteceu na hora certa). A idéia do framework que desenvolvi foi aproveitar a forma padronizada de comunicação do Struts [3] - através do método perform() - e utilizar programação orientada a aspectos (AspectJ [11]) para interceptar tais chamadas e escrever automaticamente as linhas de código do teste automatizado. Vale ressaltar que o framework permite que o usuário escreva seu código de teste simplesmente navegando pelas páginas do sistema, como um usuário normal faria. A figura ilustra onde o framework atua:
Ao interceptar a chamada ao método perform(), eu conseguia saber o destino da requisição e quais as informações que ela continha. Bastava apenas traduzir esses dados para as linhas de código e caberia ao desenvolvedor, escrever apenas as asserções para verificar se o resultado da navegação foi esperado. Para utilizar o framework o usuário precisava entrar numa tela inicial onde dava as configurações iniciais do teste (página que iria testar, classe onde o teste deveria ser escrito e nome do método de teste). A partir daí, a tela desejada era aberta para que ele navegasse e uma barra de menu permitia que ele gravasse ou cancelasse o teste. Tudo que ele fazia era interceptado pelo aspecto, que ia escrevendo as linhas de código num buffer. Quando o usuário apertava o botão gravar, o buffer era escrito no arquivo desejado e o desenvolvedor só precisava escrever o código para validar sua navegação. As figuras abaixo ilustram as 3 etapas para escrita do teste automatizado (na última figura as linhas em cinza foram escritas automaticamente pelo framework e as linhas em branco são as linhas escritas pelo desenvolvedor do teste):
O framework também ajudava o programador na hora de criar e apagar o cenário de teste. Aproveitando o "menu de entidades" já existentes, refatoramos o código para utilizá-lo nos testes da web também. Com isso, antes do teste começar a ser gravado, nós olhávamos quais os IDs das entidades do "menu" existentes naquela instância de teste e, sempre que o usuário escolhia uma dessas entidades no seu teste, substituíamos o ID no código por um método que recuperava a respectiva entidade, tornando o teste mais robusto pois ele não depende do ID da entidade que utilizou naquela simulação. Com isso, as entidades de teste poderiam ser recarregadas no sistema com outros IDs que os testes automatizados continuariam funcionando. Desenvolver este framework foi um trabalho muito gratificante, pois ele possibilitou um aumento na produção de testes automatizados. O que um programador levaria 30 minutos para escrever 1000 linhas de código, o framework fazia em menos de um minuto. |