Universidade do Estado de Santa Catarina
Centro de Educação Superior do Alto Vale do Itajaí
Departamento de Engenharia de Software
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.
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.
É 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.
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.
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
2
3
4
5
6
7
8
9
public static int factorial(int v) { // Nó 1
int f = 1; // Nó 1
int i = 2; // Nó 1
while (i <= v) { // arestas
f = f * i; // Nó 2
i = i + 1; // Nó 3
}
return f; // Nó 4
}
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.
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
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
Um intervalo
Um valor específico
O membro de um conjunto
Um valor booleano
Quais classes de equivalência existem
1 válida (dentro do intervalo);
2 inválidas (abaixo do intervalo, acima do intervalo).
1 válida (o valor);
2 inválidas (abaixo e acima do valor).
1 válida (valor pertencente ao conjunto);
1 inválida (valor fora do conjunto).
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;
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.
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.
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”.
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
Geradores de
Valores
Validadores de
Casos de Teste
Descrição
Geram os valores de entrada para os
testes. Especializam uma interface da
ferramenta para cada tipo de dado
versus técnica desejada.
Técnicas implementadas
1. Geração randômica;
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;
2. Execução simbólica;
3. Valores limites.
5. Particionamento por
Equivalência (parcial).
Quadro 4 – Técnicas implementadas pela ferramenta.
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.
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;
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.
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).
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 {
/**
* 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);
}
@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 {
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
Validação CPF
Verificação Triângulos
Verificação Números Primos
Divisão de valores em parcelas
(usa BigDecimal e arrays)
Operação com arrays: Max
Operação com arrays: Sum
Operação com arrays: Concat
Enumerações: weekDayToStr
Enumerações: strToWeekDay
Qtd. casos de teste gerados
Ferramenta
CodePro
5
4
4
11
3
3
6
9
3
3
6
7
8
4
2
9
7
3
Cobertura atingida (%)
Ferramenta
CodePro
98,2%
62,0%
100%
72,3%
100%
58,8%
100%
46,5%
100%
100%
100%
90,5%
100,0%
82,1%
62,5%
18,9%
90,5%
100,0%
Quadro 11 – Resumo dos resultados obtidos, comparando a ferramenta com o CodePro Analytix.
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.
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.
Download

Ferramenta para Geração de Testes Automatizados - Ceavi