• Nenhum resultado encontrado

3.2 Teste Evolutivo

3.2.1 Teste Evolutivo Estrutural

Esta seção apresenta trabalhos anteriores que utilizaram Algoritmos Evolutivos para gerar dados automaticamente para testes caixa-branca, ou seja, testes que possuem acesso ao código fonte e podem utilizar critérios de cobertura para definir a função objetivo. Os trabalhos estão separados em duas categorias: utilização de AEs em software procedimental, ou seja, aqueles que foram escritos em linguagens estruturadas como C e Ada e são baseados em funções; e a utilização de AEs em software Orientado a Objetos, como os escritos em Java, C++ e baseados em classes. Esta separação dentro de testes estruturais deve-se às diferenças entre TE convencional e TE OO, explicadas anteriormente.

(a) Software Procedimental

Sthamer (1996) realizou um trabalho de investigação de teste randômico e de algoritmos genéticos como meio prático de gerar conjuntos de dados para testes automaticamente. Ele criou programas em ADA para exemplificar a utilização de AGs, e verificou que esses algoritmos mostraram bons resultados na busca de entradas para conjuntos de teste. Os experimentos com AGs atingiram 100% de cobertura de decisões, com um número menor de casos de teste, superando em muito a técnica de geração randômica para o mesmo propósito. O trabalho também investigou aspectos relacionados aos AGs como: qual a melhor forma de representação, qual a estratégia de evolução mais eficiente, qual estratégia de cruzamento mais eficiente, qual tamanho ideal da população e a taxa ideal de mutação.

Harman et al. (2002) combinaram técnicas de teste evolutivo e técnicas de transformação de código, visando aumentar a cobertura. As técnicas de transformação de código modificavam programas que possuem flags (variáveis booleanas) representando expressões condicionais, transformando-os em programas livres de flags. Isso foi pensado devido ao fato de que a utilização de flags, em substituição às expressões condicionais tradicionais, interferem na aplicação de funções objetivo baseadas nos predicados dessas expressões. Utilizando a técnica de transformação do código antes da geração dos dados com AGs, uma melhor cobertura do código foi atingida, além de uma diminuição no número de gerações necessárias para atingir a cobertura máxima. Um trabalho semelhante foi realizado por Baresel et al. (2004), utilizando uma abordagem de transformação de códigos com flags atribuídos dentro de loops. A técnica de transformação combinada aos algoritmos evolucionários obteve bons resultados como no trabalho anterior, porém o código transformado não preservava a equivalência funcional do código original.

McMinn (2005) propõe uma abordagem estendida de geração de dados para testes estruturais, que permite a geração de seqüências de entrada para se colocar um objeto de teste em um estado necessário para atingir determinado objetivo do teste. Este trabalho é baseado na abordagem encadeada (chaining approach), cuja idéia é achar uma seqüência de linhas de código (statements), que envolvem variáveis internas e que precisam ser

executadas para atingir o objetivo do teste. Ele concluiu que a utilização dessa abordagem estendida funcionou melhor do que a convencional para geração de dados para testes.

(b) Software OO

Tonella (2004) utiliza um AG para produzir automaticamente dados para testes unitários de classes em um cenário genérico. Os dados de teste, representados por cromossomos no AG, incluem informações de quais objetos instanciar, quais métodos invocar e quais valores usar como entradas para os métodos. O método proposto neste trabalho foi implementado em uma ferramenta chamada eToc e foi aplicado com sucesso em testes de unidade de classes da biblioteca padrão de Java. Tonella concluiu em seu trabalho que a utilização de AGs para testes unitários de classes é extremamente poderosa, tendo sido obtida uma ótima cobertura dos branches dos métodos públicos das classes testadas, além de um tempo computacional razoável.

Wappler e Lammermann (2005) utilizaram uma nova abordagem para aplicar algoritmos evolutivos na geração automática de casos de teste para testes caixa branca de software orientado a objetos. Os autores argumentam que, em contraste com os testes evolutivos convencionais, o escopo de teste evolucionário OO não se resume apenas a achar dados de teste que sirvam de entrada para a unidade sendo testada. Essa busca tem também o objetivo de produzir programas de teste completos. Um caso de teste também precisa descrever como criar os objetos que participam do teste e como colocá-los no estado apropriado para que o objetivo do teste seja atingido. A função objetivo deve ser construída levando em consideração estes novos aspectos. A abordagem utiliza algoritmos evolutivos, providos por toolboxes populares e independentes do domínio da aplicação. Para utilizar estes algoritmos, uma codificação foi definida para representar os casos de teste como estruturas contendo valores de tipos básicos de dados. Um protótipo foi implementado para validar a abordagem, e foi comparado com teste aleatório (random test) através de experimentos. Nesses testes, algoritmos evolutivos puderam ser aplicados com sucesso a testes caixa branca de softwares OO, além de terem superado o teste aleatório em todos os aspectos.

Cheon et al. (2005) afirmam em seu trabalho que teste de software deveria ser menos custoso, consumir menos tempo e ser mais automatizado. Para tentar atingir esse objetivo eles propuseram uma abordagem para automatizar completamente testes de unidade para programas Java, e para tanto foi preciso automatizar três componentes de teste: a seleção de dados de teste, a execução do teste e a comparação dos resultados da execução com os resultados esperados (test oracle). A essência da abordagem deste trabalho é combinar JML (Java Modeling Language), que é uma linguagem formal de especificação comportamental de interfaces, com Algoritmos Genéticos. Para completar, o JUnit é usado como plataforma de execução de testes. No trabalho, JML é usada como ferramenta para descrever test oracles e como base para geração de dados de teste. Cada classe a ser testada precisa conter afirmações (assertions) em JML, como pré-condições, pós-condições e constantes que descrevam o comportamento da classe. JML possui um componente chamado runtime assertion checker que é usado como detector de violações dos assertions em tempo de execução, além de interpretá-los como sucesso ou fracasso do teste. Para isso, uma classe de teste JUnit, chamada pelo sistema de test oracle, é gerada automaticamente.

A automação da geração de dados em Cheon et al. (2005) é realizada usando AGs. O conjunto inicial de dados de teste é gerado aleatoriamente baseado nas assinaturas dos métodos das classes. O valor de aptidão de cada dado indivíduo é calculado baseado em cobertura de caixa-preta (cobertura de condições em pós-condições) ou cobertura de caixa- branca (cobertura de decisões ou branches). O conjunto de dados é filtrado usando as pré- condições para eliminar os testes chamados de insignificantes. Se o conjunto de dados satisfaz os critérios de cobertura, o conjunto ideal foi achado, do contrário, a população de dados de teste é enriquecida pela aplicação dos operadores genéticos de cruzamento e mutação, resultado na geração de uma nova população. O processo evolutivo é repetido até que o conjunto apropriado de dados seja achado.