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.