20
Universidade do Estado de Santa Catarina Centro de Educação Superior do Alto Vale do Itajaí Departamento de Engenharia de Software Trabalho apresentado como requisito para obtenção da titulação de especialista no curso de Pós Graduação lato sensu em Engenharia de Software, sob orientação do Prof. Fernando dos Santos, em Novembro de 2014. FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA Samuel Yuri Deschamps UDESC [email protected] Resumo Este documento apresenta o desenvolvimento de uma ferramenta para geração de testes automatizados de regressão em formato JUnit. São explicadas três técnicas de testes implementadas neste gerador: análise de cobertura de código, execução simbólica e particionamento por equivalência. É apresentado também o CodePro Analytix, software correlato que foi utilizado como comparação, e uma ferramenta para geração de valores de testes que foi utilizada de forma integrada. É revelada a estrutura estática da ferramenta desenvolvida através de diagrama de classes, explicando os componentes mais importantes e o princípio de funcionamento. São apresentados os resultados obtidos a partir de testes de geração realizados com códigos fonte alvo hipotéticos, alcançando cobertura mais alta quando comparados com o Analytix. Por fim, são apresentadas possibilidades de extensão. Palavras-chave: Testes Automatizados. JUnit. Testes de Regressão. Técnicas de Testes. Abstract This document presents the development of a software tool for automated regression tests generation in JUnit format. It explains three test techniques that are implemented by this generator: code coverage analysis, symbolic execution and equivalence partitioning. It’s also shown CodePro Analytix, a related software that was used as comparison source, and a tool for test values generation that was integrated to the software. It’s revealed the tool’s static structure through a class diagram, explaining the main components and how they work. Then, are presented the results reached through generation tests done with hypothetical target source code, achieving higher coverage percentages when comparing the developed tool with Analytix. At the end, are show the extension possibilities. Keywords: Automated Tests. JUnit. Regression Tests. Test Techniques. 1. Introdução Os testes automatizados ganharam bastante notoriedade depois do surgimento de ferramentas como JUnit (BECK e GAMMA, 2014). Antes disto, poucas empresas tinham a prática de automatizar testes de software. Mesmo nos dias atuais, nem todas as empresas aplicam esta prática, e mesmo as que aplicam, dificilmente a utilizam em todos os projetos de software por questões de tempo e custo de desenvolvimento. A consequência disto é um grande volume de código legado sem cobertura de testes automatizados. Este código legado muitas vezes sofre manutenção e até novas implementações após estar em produção, gerando um risco de incluir novos defeitos. Uma maneira de mitigar estes defeitos é realizar testes manuais a cada liberação, outra maneira é implementar testes automatizados de regressão para o código existente (HUSTON, 2014). Porém esta última opção pode gerar um custo de desenvolvimento desproporcional que é difícil de ser diluído no ciclo de desenvolvimento do software.

FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · [email protected] Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

  • Upload
    vuquynh

  • View
    220

  • Download
    0

Embed Size (px)

Citation preview

Page 1: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

Universidade do Estado de Santa Catarina

Centro de Educação Superior do Alto Vale do Itajaí

Departamento de Engenharia de Software

Trabalho apresentado como requisito para obtenção da titulação de especialista no curso de Pós

Graduação lato sensu em Engenharia de Software, sob orientação do Prof. Fernando dos Santos,

em Novembro de 2014.

FERRAMENTA PARA GERAÇÃO DE

TESTES AUTOMATIZADOS EM JAVA

Samuel Yuri Deschamps

UDESC [email protected]

Resumo

Este documento apresenta o desenvolvimento de uma ferramenta para geração de testes

automatizados de regressão em formato JUnit. São explicadas três técnicas de testes

implementadas neste gerador: análise de cobertura de código, execução simbólica e

particionamento por equivalência. É apresentado também o CodePro Analytix, software

correlato que foi utilizado como comparação, e uma ferramenta para geração de valores de testes

que foi utilizada de forma integrada. É revelada a estrutura estática da ferramenta desenvolvida

através de diagrama de classes, explicando os componentes mais importantes e o princípio de

funcionamento. São apresentados os resultados obtidos a partir de testes de geração realizados

com códigos fonte alvo hipotéticos, alcançando cobertura mais alta quando comparados com o

Analytix. Por fim, são apresentadas possibilidades de extensão.

Palavras-chave: Testes Automatizados. JUnit. Testes de Regressão. Técnicas de Testes.

Abstract

This document presents the development of a software tool for automated regression tests

generation in JUnit format. It explains three test techniques that are implemented by this

generator: code coverage analysis, symbolic execution and equivalence partitioning. It’s also

shown CodePro Analytix, a related software that was used as comparison source, and a tool for

test values generation that was integrated to the software. It’s revealed the tool’s static structure

through a class diagram, explaining the main components and how they work. Then, are

presented the results reached through generation tests done with hypothetical target source

code, achieving higher coverage percentages when comparing the developed tool with Analytix.

At the end, are show the extension possibilities.

Keywords: Automated Tests. JUnit. Regression Tests. Test Techniques.

1. Introdução

Os testes automatizados ganharam bastante notoriedade depois do surgimento de ferramentas

como JUnit (BECK e GAMMA, 2014). Antes disto, poucas empresas tinham a prática de

automatizar testes de software. Mesmo nos dias atuais, nem todas as empresas aplicam esta

prática, e mesmo as que aplicam, dificilmente a utilizam em todos os projetos de software por

questões de tempo e custo de desenvolvimento. A consequência disto é um grande volume de

código legado sem cobertura de testes automatizados.

Este código legado muitas vezes sofre manutenção e até novas implementações após estar em

produção, gerando um risco de incluir novos defeitos. Uma maneira de mitigar estes defeitos é

realizar testes manuais a cada liberação, outra maneira é implementar testes automatizados de

regressão para o código existente (HUSTON, 2014). Porém esta última opção pode gerar um

custo de desenvolvimento desproporcional que é difícil de ser diluído no ciclo de

desenvolvimento do software.

Page 2: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

É possível, entretanto, automatizar a própria geração dos testes automatizados, utilizando

como partida o código fonte existente e assumindo a premissa de que o comportamento atual

deste código está correto. Este artigo aborda algumas técnicas que podem ser utilizadas para este

fim, e apresenta um protótipo de ferramenta para geração de casos de teste de regressão em

linguagem Java.

A finalidade desta ferramenta é minimizar o esforço manual de desenvolvimento de testes,

diminuindo desta forma o custo do projeto. Ela pode ser utilizada para aumentar a cobertura de

testes em projetos já finalizados, e também para aumentar a assertividade e cobertura de testes de

projetos que estão em desenvolvimento.

Este artigo está dividido nas seguintes seções: a seção 2 aborda os assuntos JUnit e testes e

regressão para delimitar o contexto do trabalho; a seção 3 explica algumas das técnicas de testes

utilizadas na ferramenta: análise de cobertura de código, execução simbólica e particionamento

por equivalência; a seção 4 apresenta dois trabalhos correlatos e suas características principais:

CodePro Analytix (GOOGLE DEVELOPERS, 2014) e o artigo e protótipo intitulado “Covering

User-Defined Data-flow Test Requirements Using Symbolic Execution” (ELER, ENDO e

DURELLI, 2014); a seção 5 revela a estrutura da ferramenta através de diagrama de classes,

explicando os componentes principais e como eles colaboram entre si; a seção 6 apresenta os

resultados obtidos com testes de geração realizados, submetendo o mesmo código fonte alvo a

duas ferramentas diferentes e comparando os resultados; e a seção 7 dá as considerações finais

desse artigo e apresenta algumas possibilidades de extensão deste trabalho.

2. JUnit e Testes de Regressão

Conforme Tahchiev et al (2011), JUnit é um framework de código fonte aberto criado por Kent

Beck e Erich Gamma em 1995 para criação de testes automatizados na linguagem de

programação Java. Desde lá, a sua popularidade vem crescendo, e atualmente é o padrão de facto

para testes unitários em aplicações Java. Ele é distribuído juntamente com ambientes de

desenvolvimento como o Eclipse (ECLIPSE FOUNDATION, 2014) e Netbeans (ORACLE,

2014) e está em evolução até os dias atuais.

O JUnit facilita a criação de código para a automação de testes com apresentação dos

resultados. Com ele, pode ser verificado através de assertivas se cada método de uma classe

funciona da forma esperada, exibindo possíveis erros ou falhas. É possível utilizar o executor de

testes do JUnit como um plug-in do próprio ambiente de desenvolvimento, ou então gerando os

resultados em relatórios integrando-se com, por exemplo, ambientes de integração contínua

como o Hudson (ORACLE, 2013).

Quando utilizado na forma de plug-in, o programador pode com alguns cliques disparar a

execução de um caso de teste específico ou de uma suíte inteira de testes e verificar o resultado

da execução de cada caso de teste através das cores verde (teste ok), azul (houve falha, valores

inválidos) ou vermelho (exceção na execução do teste). Os casos de teste, porém, precisam ser

implementados manualmente pelos programadores, realizando chamadas ao código fonte alvo

com parâmetros específicos e realizando assertivas com os valores esperados.

Conforme Huston (2014), teste de regressão é nome que se dá ao teste que é executado após

uma alteração feita em um programa, a fim de certificar-se que esta alteração não “quebrou”

nenhuma funcionalidade já existente do programa. Ou seja, para garantir que o software não

regrediu. Seu propósito é encontrar defeitos que podem ter sido acidentalmente introduzidos em

uma nova release do programa, e assegurar-se que os defeitos já erradicados não voltem. Ao

reexecutar casos de teste que foram originalmente criados quando os problemas conhecidos

foram corrigidos, pode-se garantir que qualquer nova mudança não tenha resultado em regressão.

Estes testes podem ser executados manualmente em projetos pequenos, mas na maioria dos casos

repetir uma bateria de testes a cada vez que é feita uma mudança no código é algo que consome

muito tempo. Portanto automatização de testes é geralmente requerida neste processo.

Page 3: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

3. Técnicas de Testes

3.1 Análise de Cobertura de Código

Segundo Atollic (2014), a análise de cobertura de código é uma técnica utilizada para medir a

qualidade dos testes de um software, geralmente utilizando análise do fluxo da execução

dinâmica do programa.

Existem várias formas diferentes de se analisar a cobertura do código. Para explicar estas

variações, considere-se o código fonte mostrado na figura 1.

Figura 1 – Análise de Cobertura de Código (Adaptado de Atollic, 2014)

Este código possui um bloco em cor vermelha que é sempre executado, um código em cor

verde que é executado ou não dependendo do resultado da condição testada (“if") e um terceiro

bloco azul que é sempre executado. Isto pode ser visualizado na forma de um grafo, conforme

mostrado ao lado do código.

O gráfico da execução deixa claro que este código fonte contém dois caminhos de execução

diferentes (vermelho-azul e vermelho-verde-azul). Dependendo de como está o estado do

sistema durante uma determinada execução (ou seja, o estado das variáveis), o fluxo será

executado por um caminho ou outro.

Baseado neste gráfico pode-se extrair relatórios de cobertura de uma determinada execução.

Algumas das formas de relatórios são:

a) Cobertura por Instrução: Aponta quais instruções de progama foram executadas ou não

durante a execução. Uma variação deste modelo é a cobertura pro bloco, onde o bloco é

um conjunto de instruções que são executados do início ao fim sem desvios, como os três

blocos do exemplo;

b) Cobertura por Função: Aponta quais funções do programa foram chamadas durante a

execução, porém sem dar detalhes inteiros de como a função foi executada, quem a

chamou nem quais parâmetros foram passados. Existe uma variação que é medir quais

diferentes chamadas de uma função foram realizadas;

c) Cobertura por Ramificação: É um modelo mais avançado que entra em um nível de detalhe

além das instruções, analisando as expressões condicionais independentemente. No código

de exemplo, este modelo aponta quantas diferentes variações do teste condicional “if"

foram executadas (Ex: se entrou ou não entrou no “if"). Além disto, é possível medir quais

diferentes combinações foram testadas neste condicional “if". Como são três variáveis

booleanas, existem oito combinações possíveis.

Embora seja possível extrair relatórios de cobertura gerados por uma simples execução

manual de um programa, geralmente elas são utilizadas de forma integrada com testes

automatizados como JUnit (BECK e GAMMA, 2014) como forma de medir a cobertura dos

testes.

Page 4: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

Segundo Cornett (2014), o uso da análise de cobertura de código como ferramenta de

otimização de testes consiste em três etapas principais:

a) Encontrar áreas de um programa não exercitadas por um conjunto de casos de teste;

b) Criar casos de teste para aumentar a cobertura;

c) Determinar uma medição quantitativa da cobertura de código, que é uma medição indireta

da qualidade do software.

Outro aspecto opcional desta técnica é identificar casos de teste redundantes, ou seja, que não

incrementam a cobertura. Um analisador de cobertura de código automatiza este processo.

Existem algumas ferramentas para medição de cobertura, como TrueAnalyser (ATOLLIC,

2014), Eclemma (MOUNTAINMINDS, 2014), Clover (ATLASSIAN, 2014), entre outras.

Embora a análise de cobertura de código seja uma boa técnica para medir a qualidade dos

testes, Fowler (2012) aponta algumas considerações importantes para que ela não se torne um

falso indicador. Quando aplicada como meta para os desenvolvedores atingirem um determinado

percentual de cobertura do código, podem começar a surgir casos de teste de baixa qualidade

cujo objetivo é apenas aumentar a cobertura dos testes. O nível mais extremo desta má prática

são testes sem assertivas, que podem acabar ocultando defeitos. Portando, o mais correto é

utilizar a cobertura como métrica principalmente pra descobrir partes do código que ainda não

estão sendo testadas. Além disto, a cobertura de código é uma ferramenta que sozinha não

garante a qualidade dos testes, sendo necessário combiná-la com outras técnicas de testes.

3.2 Execução Simbólica

Segundo King (1976) e Cadar e Sem (2013), a ideia geral por trás da execução simbólica é

representar os valores das variáveis ao longo da execução de um programa ou função, a partir de

valores de entrada simbólicos. Considere-se, por exemplo, o código fonte do quadro 1.

1 public static int factorial(int v) { // Nó 1

2 int f = 1; // Nó 1

3 int i = 2; // Nó 1

4 while (i <= v) { // arestas

5 f = f * i; // Nó 2

6 i = i + 1; // Nó 3

7 }

8 return f; // Nó 4

9 }

Quadro 1- Exemplo de código fonte (Adaptado de Eler, Endo e Durelli, 2014).

Conforme Eler, Endo e Durelli (2014), é comum adotar-se uma abstração chamada Grafo de

Fluxo de Controle (CFG – Control-Flow Graph) para representar a estrutura interna do programa

sujeito a teste. Ela serve como ponto de partida para a análise de critérios estruturais de teste.

CFGs são grafos dirigidos onde cada nó representa um bloco básico de instruções (sem desvios

de fluxo internos nem dependências internas) e as arestas representam as transições entre os

blocos no fluxo de controle. A figura 2 mostra o grafo de fluxo de controle do código em

questão. Note-se que as linhas 5 e 6 do código fonte formaram dois nós distintos: 2 e 3. Isto

acontece porque neste caso a ordem de execução é importante (a linha 6 precisa necessariamente

ser executada depois da linha 5, caso contrário o programa teria outro comportamento), diferente

do que acontece nas linhas 2 e 3.

Page 5: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

Figura 2 - Grafo de Fluxo de Controle (Adaptado de Eler, Endo e Durelli, 2014).

Diferentes critérios podem ser adotados para gerar dados de testes a partir de um CFG.

Alguns critérios comuns são all-nodes (cobrir todos os nós do grafo), all-edges (todas as arestas)

e all-paths (todos os caminhos possíveis, incluindo todas as possibilidades de loops. Geralmente

em uma função que possui loops, o critério all-paths tende a formar combinações infinitas. Neste

caso pode-se parametrizar a quantidade máxima de loops desejados, conforme a necessidade dos

testes.

Eler, Endo e Durelli (2014) explicam que, considerando o critério all-paths, cada caminho de

execução do programa pode ser representado como uma sequência de restrições, expressas como

uma função sobre os valores simbólicos de entrada. Considere, por exemplo, o seguinte caminho

de execução do método “factorial”: 1 – 2 – 3 – 4. Para mover-se do nó 1 para o 2, I precisa

atender à restrição (I <= V). A execução simbólica substitui I pelo seu valor inicial (neste caso, o

valor 2, conforme segunda linha do código fonte). Este processo é repetido sucessivas vezes até

que se obtenha uma sequência de restrições que compõe um determinado fluxo de execução. O

fluxo 1 – 2 – 3 – 4, por exemplo, pode ser representado pela sequência {2 <= V} ^ {3 > V}. Um

solucionador de restrições é então utilizado para produzir uma solução que satisfaça a todas as

restrições da sequência. Neste caso, uma possível solução seria o valor 2. Ou seja, o valor 2,

quando informado como entrada para o parâmetro V, faz o programa exercitar o caminho 1 – 2 –

3 - 4.

Com esta técnica é possível determinar valores de entrada para casos de testes. Se for possível

gerar soluções simbólicas para todos os caminhos possíveis de um programa, é possível gerar um

conjunto de casos de teste que atinjam cobertura total sobre o programa. É importante ressaltar

que um programa pode ter caminhos inatingíveis. Isto pode ser detectado quando uma sequência

de restrições não tiver solução, simplificando-se a uma restrição como {0 != 0}, por exemplo.

3.3 Particionamento por Equivalência

Segundo Pressman (2006), Particionamento por Equivalência é um método de teste caixa-preta

que divide o domínio de entrada de um programa em classes de dados, das quais os casos de

teste podem ser derivados. O objetivo principal desta técnica é reduzir o número total de casos de

Page 6: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

teste necessários para encontrar uma mesma quantidade de erros distintos, evitando assim casos

de teste redundantes. Por exemplo, se uma determinada função falha com a entrada de um

número negativo de tal forma que qualquer número negativo gera esta mesma falha, basta um

caso de teste com número negativo. Neste caso, todos os números negativos formam uma única

classe de dados para esta função.

Sommerville (2007) explica que pode haver classes válidas e classes válidas. Por exemplo,

uma função pode se comportar de uma forma para entradas de 1 a 100 e de outra forma para

entradas acima de 100. Esta análise pode ser feita comparando os valores de entrada com os

valores de saída de um determinado caso de teste, conforme exemplificado na figura 3.

Figura 3 – Classes de Entrada versus Classes de Saída (Adaptado de Sommerville, 2007).

Conforme Pressman (2006), o projeto de casos de teste para particionamento por equivalência

é baseado em uma avaliação das classes de equivalência para uma condição de entrada. Se o

conjunto de objetos puder ser ligado por relações simétricas, transitivas e reflexivas, uma classe

de equivalência estará presente. Tipicamente uma condição de entrada é um valor numérico

específico, um intervalo de valores, um conjunto de valores relacionados ou uma condição

booleana. As classes podem ser definidas de acordo com as diretrizes mostradas no quadro 2.

Condição de entrada

especificada

Quais classes de equivalência existem

Um intervalo 1 válida (dentro do intervalo);

2 inválidas (abaixo do intervalo, acima do intervalo).

Um valor específico 1 válida (o valor);

2 inválidas (abaixo e acima do valor).

O membro de um conjunto 1 válida (valor pertencente ao conjunto);

1 inválida (valor fora do conjunto).

Um valor booleano 1 válida, 1 inválida (Falso e verdadeiro). Quadro 2 - Condições de Entrada versus Classes (Adaptado de Pressman, 2006).

3.4 Outras Técnicas de Testes

Existem mais técnicas de testes além das que foram comentadas anteriormente. Algumas delas se

aplicam a geradores de dados para testes ou geradores de testes automatizados. Segundo Micskei

(2013), algumas técnicas que podem ser utilizadas para geração de testes baseados em código

são:

a) Seleção randômica: geração randômica ou randômico-adaptativa de dados pode produzir

bons resultados. Além disto, são mais flexíveis que outras técnicas pois suportam vários

tipos de dados;

b) Anotações de código: Se o código fonte for anotado com pré-condições ou pós-condições,

elas podem direcionar a geração e seleção dos dados de entrada;

Page 7: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

c) Técnicas de busca: Algoritmos de busca (como algoritmos genéticos) podem ser utilizados,

guiados por uma função de adaptação representando algum tipo de cobertura;

4. Trabalhos correlatos

4.1. Google CodePro Analytix

Conforme Google Developers (2014), CodePro Analytix é uma suíte de ferramentas de teste para

desenvolvimento em Java com Eclipse, voltadas para melhoria da qualidade do software e

redução de custo de desenvolvimento. É composta pelas seguintes ferramentas:

a) Code Analysis: Ferramenta extensível e dinâmica que detecta, reporta e corrige desvios ou

não-conformidades como padrões de codificacões pré-definidas e convencões de estilo e

segurança;

b) JUnit Test Case Generation: Ferramenta que utiliza técnicas de análise de fluxos de

caminho para automatizar a criação de testes de regressão compreensíveis em JUnit;

c) JUnit Test Edit: Ambiente de edição de casos de teste que permite criação, modificação e

execução de JUnits utilizando uma interface gráfica de usuário;

d) Similar Code Analysis: Ferramenta que encontra segmentos de código duplicado e os exibe

de forma analítica com cores para facilitar a leitura;

e) Metrics: Ferramenta de medição que calcula itens como quantidades de linhas de códigos,

quantidades de métodos por visibilidade, complexidade ciclomática, média de linhas por

método, comentários, entre outros;

f) Code Coverage: Ferramenta de coleta de cobertura de código a partir de uma execução do

software.

g) Dependency Analysis: Ferramenta que mostra graficamente as dependências entre pacotes

Java ou classes Java, como forma de analisar acoplamento.

Do leque de ferramentas do CodePro, a ferramenta relacionada com este trabalho é a JUnit

Test Case Generation. Esta ferramenta, assim como as demais, é distribuída na forma de um

plugin do Eclipse. Segundo Google Developers (2014), a partir de uma classe Java, ela gera uma

classe de teste correspondente contendo vários métodos de teste para cada método de entrada. A

ferramenta analisa cada método com seus parâmetros de entrada, com a meta de gerar casos de

teste que exercitem cada linha de código. Ou seja, atingir cobertura de código dos métodos alvo

próxima a 100%.

Como parte da análise para geração dos testes, a ferramenta executa o código alvo. Neste

processo, podem ser encontradas exceções. A ferramenta é configurável com três opções

possíveis de tratar exceções: sempre ignorar casos de teste que geram exceção, sempre

considerar casos de teste que gerem exceção, ou considerar apenas casos de teste que geram

exceção caso a exceção disparada esteja declarada na cláusula “throws” do método.

A ferramenta também suporta geração de fixtures. Fixture é um objeto utilizado para invocar

um método de instância da classe. Este recurso é utilizado para testes de métodos não estáticos,

onde a fixture pode ser gerada como um atributo na classe de teste, ou como uma variável local

dentro do próprio método de teste.

A geração dos métodos de teste, que é a funcionalidade principal da ferramenta, consiste nas

seguintes etapas:

a) Gerar uma lista de valores possíveis para a fixture (caso o método não seja estático) e para

cada um dos parâmetros.

b) Determinar quais combinações utilizar para invocar o método;

c) Computar o resultado invocando o método;

d) Descobrir como validar o resultado;

e) Gerar um método de teste para cada combinação de valores gerados.

Page 8: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

4.2. Gerador de Dados de Testes através de Execução Simbólica

O Segundo trabalho correlato é o artigo e protótipo intitulado “Covering User-Defined Data-flow

Test Requirements Using Symbolic Execution” (ELER, ENDO e DURELLI, 2014). Este artigo

foi apresentado no Simpósio Brasileiro de Qualidade de Software em 2014, em Blumenau (SC).

Além de apresentar a técnica de execução simbólica para geração de dados de entrada para casos

de testes, os autores desenvolveram um protótipo que serviu como prova de conceito de geração

de valores para testes.

O protótipo desenvolvido interpreta código Java em formato bytecode (arquivos “.class”),

analisa os métodos utilizando a técnica de execução simbólica e fornece como saída o conjunto

de valores para os parâmetros do método de tal forma a cobrir o máximo dos fluxos de execução

do método de forma não redundante. Este protótipo possui a limitação de suportar apenas

parâmetros do tipo int.

O protótipo utiliza internamente uma estrutura chamada Grafo de Definição e Uso (DUG –

Def-Use Graph, em inglês), que é uma extensão do CFG comentado na seção 3.2 deste artigo. O

DUG foi uma estrutura proposta por Rapps e Weyuker (1985). A figura 4 mostra de forma

resumida a arquitetura do protótipo, que possui quatro partes principais.

Figura 4 – Arquitetura do protótipo (Eler, Endo e Durelli, 2014).

A seguir é explicada a função de cada um destes componentes:

a) Instrumenter: instrumenta as classes Java recebidas e gera o DUG para cada método. O

DUG é utilizado para identificar as expressões simbólicas e as restrições.

b) Path Analyzer: recebe o DUG e identifica todos os caminhos possíveis. Cada caminho

selecionado é enviado para o componente Constraint Analyzer.

c) Constraint Analyzer: gera uma sequência de restrições e identifica restrições que não

podem ser resolvidas.

d) Constraint Solver: recebe a sequência de restrições e produz os dados de teste para cobrir

cada sequência. Para a resolução das restrições, este componente utiliza a biblioteca Choco

(TEAM, 2008), uma ferramenta Java para programação com restrições.

5. Desenvolvimento da Ferramenta

Foi desenvolvida uma ferramenta capaz de gerar casos de testes automatizados a partir de classes

Java. As próximas seções deste artigo explicam o desenvolvimento desta ferramenta, detalhando

os requisitos, o princípio de funcionamento, as técnicas implementadas, o modelo das classes, os

resultados obtidos comparando-a com o CodePro Analytix e por fim as considerações finais.

5.1. Requisitos Funcionais e Não Funcionais

O quadro 3 apresenta os requisitos funcionais e não-funcionais desta ferramenta.

Page 9: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

Requisitos Funcionais

RF01: A ferramenta deve gerar uma classe Java de testes para cada classe alvo

informada. As classes devem ser geradas em um diretório de saída parametrizável.

RF02: A ferramenta deve suportar geração de testes para métodos estáticos públicos

que recebem parâmetros dos seguintes tipos: boolean, byte, char, short, int, float,

double, long, BigDecimal, BigInteger, String e arrays destes tipos. (Observação:

Esta limitação foi criada para reduzir o escopo deste projeto em um período curto de

desenvolvimento: 180 horas).

RF03: A ferramenta deve gerar um ou mais casos de teste para cada método alvo

informado. Quando não for informado um método alvo específico (ou seja, informar

apenas uma ou mais classes alvo sem informar métodos), devem ser considerados

todos os métodos públicos e estáticos das classes informadas.

RF04: Cada caso de teste gerado deve fornecer um valor fixo (estático) como

entrada para cada um dos parâmetros do método alvo, salvo em métodos que não

possuem parâmetros.

RF05: A ferramenta deve gerar assertivas fixas baseadas nos retornos das

invocações aos métodos alvo que forem sucedidas (ou seja, casos onde a chamada

não causou exceção nem loop infinito). Para estes casos, os testes gerados, quando

executados pelo JUnit com o código alvo atual, devem estar todos passando e ao

mesmo tempo “garantindo” os valores retornados.

RF06: A ferramenta deve permitir parametrizar uma lista de classes de exceção a

serem consideradas como falhas do software ao gerar os testes.

RF07: Para os casos de exceção encontrados pela ferramenta ao gerar os testes, deve

ser geradas assertivas fixas que garantam a ocorrência de tal exceção para tais

valores de entrada, podendo também verificar a mensagem da exceção quando

houver. Exceto para os casos onde a exceção ocorrida é de uma das classes

consideradas como falhas (RF05).

RF08: Nos casos de exceções que são consideradas como falhas (RF05), a

ferramenta deve gerar um caso de teste que falhe (sem assertivas) para evidenciar a

falha do código alvo, adicionando também um comentário do tipo “FIXME” sobre o

método.

RF09: Para situações de loop infinito encontradas, deve ser gerado um caso de teste

sem assertivas, adicionando um comentário do tipo “FIXME: Infinite loop

detected!” sobre o método. Para a detecção do loop infinito, a ferramenta deve

considerar um tempo máximo de execução (timeout) parametrizável.

RF10: Os fontes de testes gerados pela ferramenta deverão ser baseados na

biblioteca JUnit 4 (BECK e GAMMA, 2014), utilizando as classes desta biblioteca

para realizar assertivas e anotações para execução como “@Test”.

Page 10: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

RF11: A ferramenta deve selecionar e ordenar os casos de teste pelo grau de

cobertura atingido. Deve haver parâmetros para configurar a meta de percentual de

cobertura para o método alvo e meta de cobertura indireta (métodos “filhos”,

chamados pelo método alvo), que devem ser utilizados como critério de satisfação

dos testes.

Requisitos Não-Funcionais

RNF01: A ferramenta pode ser executada em modo console.

RNF02: A ferramenta deve utilizar ao menos 3 técnicas de testes: Análise de

cobertura de código, execução simbólica e particionamento por equivalência.

RNF03: As classes de teste devem ser geradas de forma que possam ser entendidas

e mantidas por humanos (código Java legível).

RNF04: Para qualquer classe alvo, a ferramenta deve gerar a classe de teste em

menos de 30 minutos. (Hardware de referência: Intel Core i5 2,6 GHz, 8GB RAM).

Quadro 3 - Requisitos funcionais e não-funcionais da ferramenta desenvolvida.

5.2. Princípio de Funcionamento

O princípio de funcionamento desta ferramenta resume-se a dois componentes principais: os

geradores de valores e os validadores de casos de teste.

Os geradores de valores são componentes que geram os valores de entrada para os testes,

podendo utilizar para isto várias técnicas diferentes que podem ser de “caixa preta” (sem analisar

o código fonte alvo, atendo-se somente aos tipos dos parâmetros) ou “caixa branca” (analisando

o código alvo). Os validadores são componentes que validam, ordenam e removem redundâncias

dos casos de teste gerados pelos geradores, podendo utilizar para isto técnicas diferentes (uma

delas é análise da cobertura, por exemplo).

Ambos os componentes foram declarados na forma de interfaces para se fosse possível

estendê-los implementando técnicas diferentes. Com isto, a ferramenta tornou-se amplamente

configurável, podendo ser estendida de várias formas para direcionar e otimizar a forma como os

casos de teste são gerados.

Algumas das técnicas de testes explicadas neste artigo são suportadas pela ferramenta. O

quadro 4 mostra quais técnicas de testes foram implementadas, destacando qual dos

componentes resolve cada técnica.

Componente Descrição Técnicas implementadas

Geradores de

Valores

Geram os valores de entrada para os

testes. Especializam uma interface da

ferramenta para cada tipo de dado

versus técnica desejada.

1. Geração randômica;

2. Execução simbólica;

3. Valores limites.

Validadores de

Casos de Teste

Verificam se os casos de teste gerados

atendem a determinados parâmetros,

priorizando os casos de maior

importância e removendo casos

redundantes. Especializam uma

interface da ferramenta para cada

técnica desejada.

4. Análise de cobertura de

código;

5. Particionamento por

Equivalência (parcial).

Quadro 4 – Técnicas implementadas pela ferramenta.

Page 11: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

5.3. Técnicas Implementadas

A ferramenta conta com 35 implementações diferentes (classes) de geradores de valores. Há

geradores de valores randômicos para todos os tipos de dados suportados pela ferramenta, além

de geradores de valores limites para tipos numéricos, geradores de valores “comuns”, geradores

que analisam o código alvo para determinar valores (exemplo: busca de strings contidas dentro

do código) e também gerador a partir de execução simbólica.

A técnica de execução simbólica foi implementada através de integração com o protótipo de

Eler, Endo e Durelli (2014), explicado na seção 3.2. A partir do bytecode da classe alvo, a

ferramenta solicita a execução simbólica do método e coleta os resultados gerados para cada

parâmetro. Por limitação deste protótipo, são suportados apenas geradores do tipo int para esta

técnica.

Para a técnica de análise de cobertura de código, é utilizada internamente a ferramenta Jacoco

(Java Code Coverage Library), que faz parte do plugin Eclemma (MOUNTAINMINDS, 2014).

Para cada caso de teste gerado pelos geradores de valores, a ferramenta executa o código alvo

com os valores gerados, coletando as seguintes informações: o valor de retorno (pode também

ser exceção ou loop infinito), a cobertura de cada instrução do método alvo e a cobertura das

instruções dos métodos que são chamados pelo método alvo. Para buscar os métodos chamados

por cada método é utilizada uma extensão da ferramenta Javaparser (GESSER, 2010).

O validador de cobertura, além de ordenar os casos de teste por percentual de cobertura,

possui um critério de satisfação configurável. Pode-se configurá-lo, por exemplo, de forma a

parar de gerar novos testes quando chegar ao percentual de cobertura de 100% do método alvo e

80% dos métodos indiretos de primeiro nível. Este percentual é a mescla da cobertura atingida

pelos casos de teste gerados para este método. Este validador também induz o gerador a ignorar

casos de teste redundantes. Por exemplo, casos que, embora tenham uma cobertura razoável,

estejam totalmente cobertos por outro caso de teste mais abrangente.

Para a técnica de particionamento por equivalência, foi implementado um validador que é

satisfeito somente quando foi gerado ao menos um caso de teste para cada retorno possível do

método. Para métodos boolean, deve existir ao menos um caso que retorne true e outro false.

Para enumerações, deve existir um caso de teste para cada constante da enumeração. A

ferramenta suporta apenas estes dois tipos de dados, visto que outros tipos de retorno podem

nunca ser atingíveis (como int e String). Para estes casos as partições deveriam ser extraídas

utilizando análise do código fonte ou outras técnicas.

A técnica de anotações de código não foi implementada porque o objetivo da ferramenta é

gerar testes de regressão a partir de código legado, e esta técnica depende de alteração manual no

código alvo adicionando as anotações específicas.

5.4. Modelo das Classes

A figura 5 apresenta um diagrama contendo as principais classes da ferramenta. O fluxo

principal de processamento começa com a classe JUnitGenerator, que utiliza TestCaseGenerator

para gerar os casos de teste e CodeGenerator para gerar o código Java resultante. A classe

TestCaseGenerator, por sua vez, utiliza ParamValuesGenerator para gerar os valores de entrada,

CaseExecutor para executar o método alvo coletando os resultados e cobertura (que são

encapsulados em objetos TestCaseData), e TestCaseValidator para validar os casos de teste

gerados, selecionando os mais importantes e removendo os redundantes.

A ferramenta pode ser customizada criando-se novas classes filhas de ValueGenerator e de

TestCaseValidator. A ferramenta trabalha com conjuntos de ValueGenerators e conjuntos de

TestCaseValidators, portanto é possível combinar várias implementações destas interfaces.

Page 12: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

Figura 5 – Diagrama de classes da ferramenta.

6. Resultados obtidos

A ferramenta foi testada com vários métodos alvo hipotéticos, cobrindo os tipos de dados

suportados e explorando situações variadas. Foram testados também métodos rotineiros como

validação de CPF ou CNPJ, verificação de números primos, divisão de valores em parcelas, entre

outros.

O quadro 5 apresenta um exemplo de código que foi submetido à ferramenta a fim de

verificar os resultados. Foi realizada uma execução dos testes informando esta classe Java como

código alvo, parametrizando a meta de cobertura para 100%. O tempo de execução foi de

aproximadamente 33 segundos em um notebook Core i5 2.6 GHz. A ferramenta é executada

programaticamente, através de um objeto da classe JUnitGenerator. Após atribuir os parâmetros

desejados (entre eles, a classe Java alvo), chama-se o método “execute” que dispara a execução.

package com.generator.core.res.input; public class ValidaCPF {

public static boolean isCpfValido(String cpf) { if (cpf == null || cpf.length() != 11) { return false; } char dig10, dig11; int sm, i, r, num, peso; sm = 0; peso = 10; for (i = 0; i < 9; i++) { num = (int) (cpf.charAt(i) - 48); sm = sm + (num * peso); peso = peso - 1; } r = 11 - (sm % 11); if ((r == 10) || (r == 11)) { dig10 = '0'; } else { dig10 = (char) (r + 48); } sm = 0;

Page 13: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

peso = 11; for (i = 0; i < 10; i++) { num = (int) (cpf.charAt(i) - 48); sm = sm + (num * peso); peso = peso - 1; } r = 11 - (sm % 11); if ((r == 10) || (r == 11)) { dig11 = '0'; } else { dig11 = (char) (r + 48); } return (dig10 == cpf.charAt(9)) && (dig11 == cpf.charAt(10)); } }

Quadro 5 – Exemplo de código alvo submetido ao gerador de testes.

O quadro 6 mostra o código de teste gerado pela ferramenta nesta situação. Pode-se ver que a

ferramenta gerou cinco casos de teste para que cobrisse todo o método alvo. Cada caso de teste

cobre uma porção do código do método alvo que não é coberta pelos demais, e os casos de teste

foram ordenados de acordo com a cobertura atingida por cada um. A cobertura total atingida foi

de 98,2% das instruções do código alvo. É importante ressaltar que estas medições de cobertura

são feitas por instrução Java (razão entre quantidade de instruções cobertas e quantidade total de

instruções do código alvo).

package com.generator.core.res.expected; import org.junit.Assert; import org.junit.Test; import com.generator.core.res.input.ValidaCPF; public class ValidaCPFTest {

// Coverage: 89,29% @Test public void testIsCpfValido_1() { boolean actual = ValidaCPF.isCpfValido("00933070711"); Assert.assertEquals(false, actual); } // Coverage: 84,82% @Test public void testIsCpfValido_2() { boolean actual = ValidaCPF.isCpfValido("57249755307"); Assert.assertEquals(true, actual); } // Coverage: 83,04% @Test public void testIsCpfValido_3() { boolean actual = ValidaCPF.isCpfValido("14909973232"); Assert.assertEquals(false, actual); } // Coverage: 7,14% @Test public void testIsCpfValido_4() { boolean actual = ValidaCPF.isCpfValido(""); Assert.assertEquals(false, actual); } // Coverage: 3,57% @Test public void testIsCpfValido_5() { boolean actual = ValidaCPF.isCpfValido(null); Assert.assertEquals(false, actual); } }

Quadro 6 – Código de teste gerado pela ferramenta.

Page 14: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

Para comparação, o quadro 7 mostra o código de teste gerado pela ferramenta CodePro

Analytix para o mesmo método alvo. Foram omitidos alguns comentários gerados em cada

método para simplificar a leitura. O tempo de execução foi de aproximadamente 1 segundo no

mesmo computador dos testes anteriores. A execução é disparada por um plug-in instalado no

Eclipse, acionando-se o menu de contexto: CodePro Tools – Generate Test Cases.

A cobertura atingida foi de 62,0% das instruções do código alvo. Pode-se ver que nenhum dos

casos de teste cobriu a situação em que a função retorna o valor true.

/** * The class <code>ValidaCPFTest</code> contains tests for the class <code>{@link ValidaCPF}</code>. * * @generatedBy CodePro at 21/10/14 21:57 * @author Samuel * @version $Revision: 1.0 $ */ public class ValidaCPFTest { @Test public void testIsCpfValido_1() throws Exception { String cpf = null; boolean result = ValidaCPF.isCpfValido(cpf); // add additional test code here assertEquals(false, result); } @Test public void testIsCpfValido_2() throws Exception { String cpf = ""; boolean result = ValidaCPF.isCpfValido(cpf); // add additional test code here assertEquals(false, result); } @Test public void testIsCpfValido_3() throws Exception { String cpf = ""; boolean result = ValidaCPF.isCpfValido(cpf); // add additional test code here assertEquals(false, result); } @Test public void testIsCpfValido_4() throws Exception { String cpf = "aaaaaaaaaaa"; boolean result = ValidaCPF.isCpfValido(cpf); // add additional test code here assertEquals(false, result); } }

Quadro 7 – Casos de teste gerados pelo CodePro Analytix.

Comparando-se os dois resultados, pode-se ver que os testes gerados pela ferramenta

desenvolvida proporcionam um ganho maior ao serem utilizados como testes de regressão, pois

testam dois casos importantes:

a) String contendo um CPF válido. Este é talvez o caso de teste mais importante de todos,

pois testa o “caminho feliz” deste método (embora “caminho feliz” seja um conceito

completamente subjetivo e nenhuma das duas ferramentas é capaz de determiná-lo). O que

levou a ferramenta a gerar este caso de teste foi a aplicação da técnica de particionamento

por equivalência. Com isto, para cada método do tipo boolean, o TestCaseValidator que

implementa esta técnica somente é “satisfeito” quando tiver gerado ao menos um caso de

teste que retorne false e outro que retorne true (as duas classes possíveis para o tipo

boolean).

Page 15: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

b) String contendo um CPF inválido que é composto por números, e com o mesmo tamanho

da String de CPF válido. Este é um valor que “poderia” ser um CPF válido mas não é,

devido à validação dos dígitos de verificação. O que levou a ferramenta a gerar este caso

de teste é que foi o caso que atingiu o maior percentual de cobertura (maior fluxo de

execução, passando pelo método inteiro até chegar na parte final da validação dos dígitos).

A ferramenta ordena os casos de teste gerados antes de gerar o código final utilizando o

percentual de cobertura e a complexidade dos valores de entrada como critério de ordenação.

Além disto, casos redundantes (que cobrem exatamente as mesmas instruções de outro caso já

gerado) são removidos. Para que seja capaz de gerar estes valores como entradas, a ferramenta

contém um grande conjunto de geradores de valores randômicos. Um subconjunto são os

geradores randômicos para Strings. Este conjunto contém uma classe para gerar Strings de

caracteres aleatórios, outra para gerar apenas Strings “numéricas” aleatórias, outra para gerar

apenas Strings alfabéticas aleatórias, entre outras.

Os “números” de CPF gerados como entrada para os testes foram gerados pelo gerador de

Strings “numéricas”, embora a ferramenta tenha tentado gerar outros tipos de Strings com outros

geradores - mas estes casos de teste foram ignoradas. Os geradores de Strings são utilizados

alternadamente em ciclo, e a ferramenta acaba retendo os casos de teste que geraram alta

cobertura e removendo os casos de teste de cobetura nula ou muito baixa. Com isto, os valores

escolhidos acabam sendo os que mais “se adaptaram” ao comportamento esperado pelo método:

por terem atingido cobertura mais alta, exercitaram um fluxo maior de execução dentro do

código fonte.

O quadro 8 mostra mais um exemplo de código alvo que foi submetido a geração de testes,

desta vez com parâmetros do tipo int e utilizando como gerador de valores uma classe

adaptadora para a ferramenta de execução simbólica de Eler, Endo e Durelli (2014).

package com.generator.core.res.input; public class IntOperationsSymbolic { public static String avaliaTriangulo(int a, int b, int c) { if (a < b + c && b < a + c && c < a + b) { if (a > 0 && b > 0 && c > 0) { if (a == b && b == c && c == a) { return "Equilátero"; } else if (a != b && b != c && c != a) { return "Escaleno"; } else { return "Isósceles"; } } } return "Não é triângulo"; } }

Quadro 8 – Exemplo de código alvo: função de verificação de triângulos.

Para este código alvo, a ferramenta desenvolvida gerou os casos de teste apresentados no

quadro 9. Nesta situação, o gerador de execução simbólica proporcionou bom desempenho

(geração em 3 segundos em um notebook Core i5 2.6GHz) por não utilizar valores randômicos e

sim diretamente os valores estratégicos para exercitar os diversos fluxos de execução. Além

disto, a cobertura atingida foi de 100% do método alvo.

package com.generator.core.res.expected; import org.junit.Assert; import org.junit.Test; import com.generator.core.res.input.IntOperationsSymbolic; public class IntOperationsSymbolicTest {

Page 16: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

/** * Coverage: 74,47% */ @Test public void testAvaliaTriangulo_1() { String actual = IntOperationsSymbolic.avaliaTriangulo(2, 1, 2); Assert.assertEquals("Isósceles", actual); } /** * Coverage: 74,47% */ @Test public void testAvaliaTriangulo_2() { String actual = IntOperationsSymbolic.avaliaTriangulo(2, 4, 3); Assert.assertEquals("Escaleno", actual); } /** * Coverage: 68,09% */ @Test public void testAvaliaTriangulo_3() { String actual = IntOperationsSymbolic.avaliaTriangulo(1, 1, 1); Assert.assertEquals("Equilátero", actual); } /** * Coverage: 36,17% */ @Test public void testAvaliaTriangulo_4() { String actual = IntOperationsSymbolic.avaliaTriangulo(1, 1, 2); Assert.assertEquals("Não é triângulo", actual); } }

Quadro 9 – Casos de teste gerados para a função de verificação de triângulos.

O quadro 10 mostra o código gerado pelo CodePro Analytix para o mesmo código alvo.

Novamente o tempo de geração foi de 1 segundo no mesmo hardware. A cobertura atingida foi

de 72,3% do código alvo. Pode-se ver que foram gerados 7 casos de teste redundantes (mesmos

valores de entrada) enquanto 2 dos 4 retornos possíveis não foram testados (“Escaleno” e

“Isóceles”).

package com.generator.core.res.input; import org.junit.*; import static org.junit.Assert.*; public class IntOperationsSymbolicTest { @Test public void testAvaliaTriangulo_1() throws Exception { int a = 1; int b = 1; int c = 1; String result = IntOperationsSymbolic.avaliaTriangulo(a, b, c); assertEquals("Equilátero", result); } @Test public void testAvaliaTriangulo_2() throws Exception { int a = 1; int b = 1; int c = 1; String result = IntOperationsSymbolic.avaliaTriangulo(a, b, c); assertEquals("Equilátero", result); }

Page 17: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

@Test public void testAvaliaTriangulo_3() throws Exception { int a = 1; int b = 1; int c = 1; String result = IntOperationsSymbolic.avaliaTriangulo(a, b, c); assertEquals("Equilátero", result); } @Test public void testAvaliaTriangulo_4() throws Exception { int a = 1; int b = 1; int c = 1; String result = IntOperationsSymbolic.avaliaTriangulo(a, b, c); assertEquals("Equilátero", result); } @Test public void testAvaliaTriangulo_5() throws Exception { int a = 1; int b = 1; int c = 1; String result = IntOperationsSymbolic.avaliaTriangulo(a, b, c); assertEquals("Equilátero", result); } @Test public void testAvaliaTriangulo_6() throws Exception { int a = 0; int b = 1; int c = 1; String result = IntOperationsSymbolic.avaliaTriangulo(a, b, c); assertEquals("Não é triângulo", result); } @Test public void testAvaliaTriangulo_7() throws Exception { int a = 1; int b = 0; int c = 1; String result = IntOperationsSymbolic.avaliaTriangulo(a, b, c); assertEquals("Não é triângulo", result); } @Test public void testAvaliaTriangulo_8() throws Exception { int a = 1; int b = 1; int c = 0; String result = IntOperationsSymbolic.avaliaTriangulo(a, b, c); assertEquals("Não é triângulo", result); } @Test public void testAvaliaTriangulo_9() throws Exception { int a = 1; int b = 1; int c = 1; String result = IntOperationsSymbolic.avaliaTriangulo(a, b, c); assertEquals("Equilátero", result); } @Test public void testAvaliaTriangulo_10() throws Exception { int a = 1; int b = 1; int c = 1; String result = IntOperationsSymbolic.avaliaTriangulo(a, b, c); assertEquals("Equilátero", result); } @Test public void testAvaliaTriangulo_11() throws Exception {

Page 18: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

int a = 1; int b = 1; int c = 1; String result = IntOperationsSymbolic.avaliaTriangulo(a, b, c); assertEquals("Equilátero", result); } }

Quadro 10 – Testes gerados para a função de triângulos pelo CodePro Analytix.

É importante citar que a distribuição correta da cobertura dos casos de teste ao gerar os testes

automatizados faz diferença no custo final de execução de testes. A prática da geração e/ou

implementação manual de testes automatizados geralmente leva à construção de suítes de testes

que podem chegar a ter milhares de casos de testes, dependendo do tamanho do programa. Sendo

assim, a geração de testes redundantes pode ocasionar em maior tempo total de execução de

testes, maior consumo de recursos de hardware e maior custo de manutenção do código de teste.

Por outro lado, suítes de testes otimizadas são capazes de testar mais código (maior cobertura)

em menos tempo.

Uma situação não mostrada nos testes acima são casos de teste onde o método alvo gera

exceção. Ambas as ferramentas realizam detecção de exceções em tempo de execução. É

possível configurar a ferramenta para que:

a) crie um caso de teste como exceção esperada (ou seja, considere que a exceção faz parte do

“contrato” do método e teste-a com assertivas para que garanta que tais entradas geram esta

exceção);

b) crie um caso de teste como exceção não esperada (ou seja, tratar como um bug encontrado,

gerando o teste de tal forma que falhe);

c) ignore o caso de teste que gerou exceção (ou seja, simplesmente não teste a situação).

d) considere a exceção como esperada apenas quando a classe da exceção esteja declarada na

cláusula throws do método alvo. Esta é a opção padrão.

A ferramenta mantém também uma lista configurável de classes de exceção para serem

tratadas sempre como bug. Exemplos são IllegalStateException, NullPointerException,

ArithmeticException, ArrayIndexOutOfBoundsException, entre outras.

Para finalizar os resultados obtidos, o quadro 11 mostra o resumo dos testes realizados,

comparando a ferramenta desenvolvida com o CodePro Analytix. Para o levantamento da

cobertura destes testes, foi utilizada a ferramenta Eclemma (MOUNTAINMINDS, 2014).

Atendendo ao requisito não-funcional RNF03 da ferramenta, em todas estas situações a

ferramenta gerou os casos de teste em menos de 30 minutos. Observação: nos testes da função

weekDayToStr, a cobertura máxima possível da função é de 90,5% por conta de um trecho de

código morto (inatingível) deixado propositalmente dentro da função.

Caso de código alvo Qtd. casos de teste gerados Cobertura atingida (%)

Ferramenta CodePro Ferramenta CodePro

Validação CPF 5 4 98,2% 62,0%

Verificação Triângulos 4 11 100% 72,3%

Verificação Números Primos 3 3 100% 58,8%

Divisão de valores em parcelas

(usa BigDecimal e arrays)

6 9 100% 46,5%

Operação com arrays: Max 3 4 100% 82,1%

Operação com arrays: Sum 3 2 100% 62,5%

Operação com arrays: Concat 6 9 100% 18,9%

Enumerações: weekDayToStr 7 7 90,5% 90,5%

Enumerações: strToWeekDay 8 3 100,0% 100,0% Quadro 11 – Resumo dos resultados obtidos, comparando a ferramenta com o CodePro Analytix.

Page 19: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

7. Considerações Finais

Este artigo apresentou algumas técnicas de testes automatizados, bem como o desenvolvimento

de uma ferramenta de geração de testes de regressão que utiliza tais técnicas.

Nos resultados exibidos a ferramenta apresentou em média ganho maior que o CodePro

Analytix, ao gerar testes de métodos públicos estáticos com os seguintes tipos de parâmetros:

String, arrays, enumerações, BigDecimal e os tipos primitivos do Java (byte, char, short, int,

long, boolean). Métodos não estáticos e outros tipos de dados não fizeram parte do escopo deste

trabalho, então não se pode afirmar o mesmo para estas situações.

Além disto, o CodePro Analytix permite utilizar a técnica de Anotações de Código para

melhorar os resultados gerados, podendo mudar o cenário dos testes realizados. Conforme

explicado na seção 5, decidiu-se não utilizar esta técnica na ferramenta desenvolvida porque o

objetivo é aplicá-la em testes de regressão de código legado, e esta técnica dependeria de alterar

o código legado incluindo as anotações.

As seguintes sugestões de melhoria são indicadas para esta ferramenta:

a) Suporte a geração de fixtures para suportar testes de métodos não estáticos de forma

semelhante ao CodePro Analytix;

b) Suporte a geração de valores para tipos de dados mais complexos como objetos POJO

(Plain Old Java Object), listas, sets, mapas e outros tipos de estruturas de dados;

c) Melhorias no analisador de execução simbólica para suportar mais tipos de dados como

byte, short, long, float, double, boolean, char e outros.

d) Suporte a testes de código alvo que interage com bancos de dados, tratando isolamento

dos dados entre os casos de teste;

e) Permitir geração dos testes diretamente a partir do ambiente de desenvolvimento. Neste

caso, através plug-in do Eclipse.

Referências

ATLASSIAN. Clover: Java and Groovy code coverage. Disponível em:

https://www.atlassian.com/software/clover/overview. Acesso em: 26/09/2014.

ATOLLIC. Trueanalyzer. Disponível em: http://www.atollic.com/index.php/trueanalyzer.

Acesso em: 26/09/2014.

BECK. K , GAMMA, E. Junit Framework. Disponível em: http://junit.org/. Acesso em:

26/09/2014.

CORNETT, S. Code Coverage Analysis. 2014. Disponível em:

http://www.bullseye.com/coverage.html. Acesso em: 26/09/2014.

CADAR, C, SEM, K. (2013). Symbolic execution for software testing: three decades later.

Communications of the ACM, 2008.

ECLIPSE FOUNDATION. Eclipse IDE. Disponível em:

http://www.eclipse.org/home/index.php. Acesso em: 26/09/2014.

ELER, M. M.; ENDO, A. T.; DURELLI, V. Covering User-Defined Data-flow Test

Requirements Using Symbolic Execution. XIII Simpósio Brasileiro de Qualidade de Software.

Blumenau, 2014.

Page 20: FERRAMENTA PARA GERAÇÃO DE TESTES AUTOMATIZADOS EM JAVA · samueldeschamps@gmail.com Resumo ... implementadas neste gerador: análise de cobertura de código, ... linguagem Java

FOWLER, M.. Test Coverage. Disponível em:

http://martinfowler.com/bliki/TestCoverage.html. Acesso em: 26/09/2014.

GESSER, J. Javaparser. Disponível em: https://code.google.com/p/javaparser/. Acesso em

06/10/2014.

GOOGLE DEVELOPERS. Codepro Analytix. Disponível em:

https://developers.google.com/java-dev-tools/codepro/doc/?hl=pt. Acesso em: 26/09/2014.

Acesso em: 06/10/2014.

HUSTON, T. What Is Regression Testing. 2014. Disponível em:

http://smartbear.com/products/qa-tools/what-is-regression-testing/. Acesso em: 26/09/2014.

KING, J. C. Symbolic Execution and Program Testing. Communications of the ACM, 1976.

MICSKEI, Z. Code-based test generation. Disponível em:

http://mit.bme.hu/~micskeiz/pages/code_based_test_generation.html. Acesso em 06/10/2014.

MOUNTAINMINDS GmbH & Co. KG and Contributors. Eclemma Java Code Coverage for

Eclipse. Disponível em: http://www.eclemma.org. Acesso em: 26/09/2014.

ORACLE. Netbeans, 2014. Disponível em: https://netbeans.org/. Acesso em 07/10/2014.

ORACLE. Hudson Extensible Continuous Integration Server, 2013. Disponível em:

http://www.hudson-ci.org/. Acesso em: 07/10/2014.

PFLEEGER, S. L. Engenharia de software: teoria e prática. 2 ed. São Paulo, SP: Prentice Hall,

2004.

PRESSMAN, Roger. S. Engenharia de software. 6 ed. São Paulo: SP: McGraw-Hill, 2006.

RAPPS, S, WEYUKER, E. J.. Selecting Software Test Data Using Data Flow Information.

IEEE Transaction on Software Engineering, 1985.

SOMMERVILLE, I. Engenharia de software. 8 ed. São Paulo, SP: Addison Wesley, 2007.

TAHCHIEV. et al. JUnit in Action. Manning publications 2011.

TEAM, T. C.. Choco: An Open Source Java Constraint Programming Library.

Workshop on Open-Source Software for Integer and Contraint Programming. ACM, 2008.