Testes: Ferramentas e Boas práticas

Muito além do JUnit

Conheça ferramentas e estratégias para maximizar os benefícios do Test-Driven Development em seus projetos de desenvolvimento Java

A comunidade Java incorporou em larga escala a filosofia do Test-Driven Development (TDD), o desenvolvimento orientado a testes. Prova disso é a inclusão do suporte a JUnit (descendente direto do XUnit, criado junto com o próprio XP) em 100% dos IDEs Java no mercado; e a profusão de frameworks de testes alternativos ou complementares ao JUnit como TestNG, DBUnit, XMLUnit, Cactus, HttpUnit, entre muitos outros.

Os benefícios do TDD são vários, não só no aspecto da qualidade de código mas especialmente em relação à gerenciabilidade do projeto e à manutenibilidade do produto final. Entretanto, é fácil se perder dentro da diversidade de frameworks e ferramentas de apoio ao processo de testes. Também é comum se iludir, acreditando a bateria de testes construída ao longo do desenvolvimento da aplicação irá garantir uma aplicação em produção sem incidentes.

Neste artigo apresentamos os principais tipos de testes que podem ser desenvolvidos ao longo de um projeto Java e quais ferramentas livres podem ser utilizadas para facilitar a construção destes testes. Vemos também como inserir os testes dentro de um processo de desenvolvimento de modo a maximizar os resultados, e para evitar surpresas desagradáveis na passagem para a produção. Apresentamos ainda algumas melhores práticas para se avaliar a qualidade dos próprios testes e assim evitar que se tornem um peso morto no projeto.

Testes no ciclo de vida do software

Testes formais podem e devem ser definidos em todas as etapas do desenvolvimento de um software. Mesmo no caso de não poderem ser automatizados, defini-los precisamente permite identificar desde cedo problemas mais sérios com os requisitos ou a modelagem de uma aplicação. Podemos dizer que, quando faltam informações para a definição formal de um teste em termos de entradas exatas e de resultados esperados, isso significa que os requisitos ainda não foram suficientemente entendidos e documentados.

Passado o levantamento de requisitos e a modelagem de negócios de uma aplicação, os testes passam a se focar mais em aspectos particulares da implementação, o que aumenta a demanda pela sua automação. Neste momento, a falta de informações suficientes para se implementar um teste automatizado pode indicar que as especificações de classes, pacotes, componentes, formatos de dados etc. ainda estão incompletas, ou então que surgiram requisitos novos ainda não completamente documentados.

O desenvolvimento orientado a testes traz o benefício de validar de modo objetivo os requisitos documentados desde o início do ciclo de vida. O TDD expõe erros, omissões e inconsistências que de outra forma só seriam detectados quando já houvesse código executável entregue ao usuário.

A Figura 1 ilustra um exemplo de processo de desenvolvimento e os tipos de testes que são produzidos em cada etapa. Vale a pena ressaltar aqui que em muitos casos as mesmas tecnologias e ferramentas podem ser utilizadas para construir diferentes tipos de testes. A distinção entre, por exemplo, “testes de unidade” e “testes de sistema” é feita em relação à finalidade de cada teste, e não em relação à sua forma.

 

imagem

Figura 1. Etapas de um processo de desenvolvimento de software (azul) x tipos de testes (amarelo)

É importante observar que na etapa de manutenção temos, na verdade, um novo ciclo de desenvolvimento, com novos requisitos, nova modelagem etc – e todos os tipos de testes são produzidos novamente, sendo agregados aos testes já existentes. Mas na figura destacamos um novo tipo de teste que surge nesta etapa, o teste de regressão.

Embora a figura sugira, à primeira vista, um processo seqüencial, ela se aplica igualmente a processos iterativos como XP. Nestes processos, cada iteração passa por todas as etapas, desde o detalhamento de requisitos até a homologação pelo usuário, e cria e utiliza todos os tipos de testes.

A efetividade dos testes é diminuída quando eles são executados em uma etapa isolada, que seria uma etapa de “QA” (Quality Assurance ou controle de qualidade) realizada no final do processo, ou no final de cada grande fase. Esse adiamento dos testes faz com que o feedback demore mais a chegar, e pode até mesmo levar a se apontarem culpados em vez de se identificarem os reais problemas (afinal, nenhum profissional gosta de dar uma tarefa por completada, para depois alguém vir afirmar que seu trabalho está com problemas).

Integração Contínua

Equipes mais maduras na adoção de processos de software terão incorporado, além do TDD, a prática da Integração Contínua (Continuous Integration) ou IC. Esta prática consiste em se ter um ambiente automatizado, que várias vezes ao dia baixa o código mais recente no repositório de código (ou no sistema de controle de versões, como o CVS ou o Subversion[1]) e executa todos os testes já definidos. O resultado final é um relatório geralmente publicado em uma intranet, que fornece estatísticas gerais dos testes (quantos passaram, quantos falharam), com quebras por projeto, módulo, subsistema, pacote e classe Java, e detalhes sobre os testes que falharam.

O uso da IC permite tanto os desenvolvedores como o gerente do projeto terem feedback freqüente sobre o impacto de mudanças recentes no código, em relação ao progresso (que pode ser realimentado para uma ferramenta de gerência de projetos) e também em relação a incidentes. Os desenvolvedores podem resolver problemas, como novos bugs ou regressões, enquanto as causas ainda estão “frescas” em suas cabeças. E efeitos colaterais gerados pelo novo código (como falhas em outros subsistemas teoricamente não-relacionados) são detectados imediatamente, em vez de tardiamente pelo usuário final ou pela equipe de QA.

Daqui em diante serão apresentados mais detalhes sobre cada etapa do processo genérico, focando nos tipos de testes relevantes em cada uma. Dentro de cada seção serão vistas também sugestões de ferramentas e frameworks Java para automatizar e facilitar a criação, a execução e o acompanhamento dos testes.

Levantamento de requisitos – testes de carga, de estresse e de aceitação

Como sabemos, todo projeto de desenvolvimento de software é iniciado pelo levantamento e documentação dos requisitos que devem ser cumpridos pelo software. Seguindo a definição clássica, estes requisitos podem ser funcionais, referindo-se diretamente ao funcionamento da aplicação sob o ponto de vista do usuário final; ou serem não-funcionais, tratando de questões independentes dos processos de negócios, por exemplo alta disponibilidade ou limites sobre tempos de resposta.

Os testes que validam requisitos funcionais estão mais focados nos processos de negócios em si do que na tecnologia utilizada para implementar o software. Eles devem tanto quanto possível ser definidos de modo independente da interface com o usuário ou da formatação final de documentos e relatórios. Isto facilita sua posterior aplicação aos testes de sistema em etapas seguintes.

Os testes que validam requisitos funcionais e não-funcionais são muitas vezes chamados conjuntamente de Testes de Aceitação. Isso porque o seu sucesso ou insucesso determina se a aplicação atingiu ou não seus objetivos e condiciona a aceitação do produto final pelo usuário, especialmente se o desenvolvimento é terceirizado.

É importante não confundir os testes de aceitação, que validam requisitos funcionais, com os testes funcionais, que são focados na interface com o usuário e serão apresentados mais adiante.

Testes de Estresse verificam se a aplicação continua se comportando de modo correto sob um alto volume de transações e de usuários concorrentes, ou sob condições adversas de rede, memória e outras condições de hardware. Eles ajudam a identificar problemas de contenção[2] e de concorrência[3] em uma aplicação, assim como falhas no tratamento de erros.

...
Quer ler esse conteúdo completo? Tenha acesso completo