Universidade Federal de Goiás Instituto de Informática Ana Claudia Bastos Loureiro Monção Uma Abordagem Evolucionária para o Teste de Instruções SELECT SQL com o uso da Análise de Mutantes Goiânia 2013 Ana Claudia Bastos Loureiro Monção Uma Abordagem Evolucionária para o Teste de Instruções SELECT SQL com o uso da Análise de Mutantes Dissertação apresentada ao Programa de Pós– Graduação do Instituto de Informática da Universidade Federal de Goiás, como requisito parcial para obtenção do tı́tulo de Mestre em Computação. Área de concentração: Ciência da Computação. Orientador: Prof. Celso Gonçalves Camilo Júnior Co-Orientador: Prof. Cássio Leonardo Rodrigues Goiânia 2013 Dados Internacionais de Catalogação na Publicação (CIP) GPT/BC/UFG M751a Monção, Ana Claudia Bastos Loureiro Uma Abordagem Evolucionária para o Teste de Instruções SELECT SQL com o uso da Análise de Mutantes [manuscrito] / Ana Claudia Bastos Loureiro Monção. - 2013. 138 f. : il. Orientador: Prof. Dr. Celso Gonçalves Camilo Júnior; Coorientador: Prof. Dr. Cássio Leonardo Rodrigues. Dissertação (Mestrado) – Universidade Federal de Goiás, Instituto de Informática, 2013. Bibliografia. Inclui lista de figuras e tabelas. Apêndice. 1. Teste de software. 2. Algoritmos genéticos. I. Título. CDU: 004.415.53 Todos os direitos reservados. É proibida a reprodução total ou parcial do trabalho sem autorização da universidade, do autor e do orientador(a). Ana Claudia Bastos Loureiro Monção Graduou-se em Tecnólogo em Processamento de Dados pela Pontifı́cia Universidade Católica do Rio de Janeiro - PUC/RJ. Especializou-se em Engenharia Econômica e Administração da Produção pela Universidade Federal do Rio de Janeiro - UFRJ e em Governança de Tecnologia de Informação pela Pontifı́cia Universidade Católica de Goiás - PUC/GO. Atuou como Analista de Sistemas, na área de desenvolvimento, em empresas como IBM Brasil, Banco Brasileiro Comercial e Telecomunicações do Estado de Goiás e atualmente atua como Analista de Sistema, Administradora de Dados e Gerente de Projetos de desenvolvimento de software no Tribunal de Justiça do Estado de Goiás. Dedico este trabalho: Ao meu marido Vinı́cius Monção e aos meus filhos Vitor e Luiza, que se privaram da minha presença em vários momentos durante esta pesquisa. Agradecimentos Agradeço primeiramente à Deus que me deu mais uma grande oportunidade na vida de desenvolver este trabalho e conseguir concluir o mestrado. Agradeço à minha famı́lia pelo apoio e pela compreensão nos momentos em que estive ausente. Agradeço ao meu orientador Celso Gonçalves Camilo Júnior que soube conduzir as atividades de orientação possibilitando o alcance dos objetivos definidos, assim como motivar-me em relação ao objetivo final deste trabalho. Agradeço também aos professores Cássio Leonardo Rodrigues e Plı́nio de Sá Leitão Júnior que acompanharam e contribuı́ram no desenvolvimento deste trabalho, e aos demais professores do mestrado que me auxiliaram nos momentos em que precisei. Agradeço ainda aos alunos deste programa de pós-graduação com quem tive a oportunidade de estudar, pelo suporte que direta ou indiretamente deram-me durante este perı́odo. Agradeço aos meus colegas de trabalho que entenderam a minha ausência, me apoiaram e me motivaram em todos os momentos. Ninguém é tão grande que não possa aprender, nem tão pequeno que não possa ensinar. Esopo. Resumo Monção, Ana Claudia Bastos Loureiro. Uma Abordagem Evolucionária para o Teste de Instruções SELECT SQL com o uso da Análise de Mutantes. Goiânia, 2013. 136p. Dissertação de Mestrado. Instituto de Informática, Universidade Federal de Goiás. Teste de Software é uma área da Engenharia de Software de fundamental importância para a garantia da qualidade do software. São atividades que envolvem tempo e custos elevados, mas que precisam ser realizadas durante todo o processo de construção de um software. Assim como em outra áreas da Engenharia de Software, existem problemas nas atividades de Teste de Software cuja solução não é trivial. Para esses problemas, têm sido exploradas várias técnicas de busca e otimização tentando encontrar uma solução ótima ou perto da ótima, dando origem às linhas de pesquisa Search-Based Software Engineering (SBSE) e Search-Based Software Testing (SBST). O presente trabalho está inserido neste contexto e tem como objetivo solucionar o problema de seleção de dados de teste para execução de testes em instruções SQL. Dada a quantidade de soluções possı́veis para este problema, a abordagem proposta combina técnicas de Análise de Mutantes SQL com Computação Evolucionária para encontrar um conjunto de dados reduzido que seja capaz de detectar uma grande quantidade de defeitos em instruções SQL de uma determinada aplicação. Baseada em uma perspectiva heurı́stica, a proposta utiliza Algoritmos Genéticos (AG) para selecionar tuplas de um banco de dados existente (de produção) tentando reduzi-lo em um conjunto de dados relevante e efetivo. Durante o processo evolucionário, a Análise de Mutantes é utilizada para avaliação de cada conjunto de dados de teste selecionado pelo AG. Os resultados obtidos com a realização dos experimentos revelaram um bom desempenho utilizando a metaheurı́stica dos Algoritmos Genéticos e suas variações. Palavras–chave Teste de Software, Análise de Mutantes, Algoritmos Genéticos, SearchBased Software Testing, Search-Based Software Engineering. Abstract Monção, Ana Claudia Bastos Loureiro. S. Goiânia, 2013. 136p. MSc. Dissertation. Instituto de Informática, Universidade Federal de Goiás. oftware Testing is an important area of Software Engineering to ensuring the software quality. It consists of activities that involve long time and high costs, but need to be made throughout the process of building software. As in other areas of software engineering, there are problems in the activities of Software Testing whose solution is not trivial. For these problems, several techniques of optimization and search have been explored trying to find an optimal solution or near optimal, giving rise to lines of research textit Search-Based Software Engineering (SBSE) and textit Search-Based Software Testing (SBST). This work is part of this context and aims to solve the problem of selecting test data for test execution in SQL statements. Given the number of potential solutions to this problem, the proposed approach combines techniques Mutation Analysis for SQL with Evolutionary Computation to find a reduced data set, that be able to detect a large number of defects in SQL statements of a particular application. Based on a heuristic perspective, the proposal uses Genetic Algorithms (GA) to select tuples from a existing database (from production environment) trying to reduce it to a set of data relevant and effective. During the evolutionary process, Mutation Analysis is used to evaluate each set of test data selected by the AG. The results obtained from the experiments showed a good performance using meta-heuristic of Genetic Algorithms, and its variations. Keywords Software testing, Mutation Analisys, Genetic Algorithms, Search-Based Software Testing, Search-Based Software Engineering. Conteúdo Lista de Figuras 11 Lista de Tabelas 12 1 13 15 16 19 Introdução 1.1 1.2 1.3 2 Motivação e Objetivos Trabalhos Relacionados Organização da Dissertação Teste de Software 2.1 2.2 Conceitos Análise de Mutantes 2.3 2.4 2.5 Análise de Mutantes SQL Search-Based Software Engineering Meta-Heurı́sticas 2.2.1 2.5.1 2.5.2 2.5.3 2.6 2.7 3 Exemplos de Mutação Hill Climbing Simulated Annealing Algoritmos Genéticos Meta-heurı́stica aplicada na área de Teste de Software Considerações Finais Computação Evolucionária 3.1 3.2 Conceitos e Histórico Algoritmos Evolucionários 3.2.1 3.2.2 3.2.3 3.2.4 3.2.5 3.3 3.4 3.5 3.6 Representação População Inicial Função Objetivo Operadores Genéticos Seleção Cruzamento (Crossover ) Mutação Condição de Parada Algoritmos Genéticos Algoritmo Genético Elitista In Vitro Fertilization Genetic Algorithm - IVF/GA Considerações Finais 20 20 23 25 26 31 33 34 35 36 36 40 41 41 43 44 45 46 46 46 47 47 47 48 54 55 57 4 Aplicação da Abordagem 4.1 Utilizando Algoritmos Genéticos na Seleção de Dados de Teste para Instruções SQL 4.1.1 4.2 Projeto dos Algoritmos 4.2.1 4.2.2 4.2.3 4.3 5 Algoritmo Genético Canônico - AGCA Algoritmo Genético com Grupo de Eleitos - AGGE INVITRO Considerações Finais Experimentos e Resultados 5.1 5.2 5.3 5.4 5.5 5.6 5.7 6 Modelo de Representação Modelo de Mapeamento Cartesiano Direto Modelo de Mapeamento Cartesiano por Intervalos Modelo de Mapeamento Direto Ambiente Instruções Experimentos Aleatórios Experimentos com AGCA Experimentos com AGGE Experimentos com INVITRO Considerações Finais Conclusões e Trabalhos Futuros 6.1 6.2 Contribuições Trabalhos Futuros 58 58 62 64 67 68 69 69 71 71 72 74 74 77 80 86 91 94 98 100 101 102 Bibliografia 103 A Implementação do AGCA 109 B Implementação do AGGE 130 C Implementação do INVITRO 132 Lista de Figuras 2.1 2.2 Processo de Análise de Mutantes Elementos de uma solução de otimização na geração de dados de teste (adaptada de Harman et al. (2009) 24 3.1 3.2 3.3 3.4 3.5 3.6 3.7 3.8 Representação de um indivı́duo - Codificação binária Estrutura básica de um Algoritmo Genético Roleta para Seleção dos Indivı́duos da tabela 3.1 Operação de Cruzamento com 1 ponto de corte Operação de Cruzamento com 2 pontos de corte Operação de Cruzamento Uniforme Operação de Mutação em um gene do cromossomo Acoplamento do IVF/GA ao AG 45 50 51 52 52 52 53 56 4.1 4.2 Etapas do processo evolucionário do AGCA Diagrama de Classes do AG 61 70 5.1 5.2 5.3 Modelo de Dados do Banco de Dados COMPANY Modelo de Dados do Banco de Dados de Experimentos Cromossomo que mapeia um indivı́duo de tamanho 100 do Banco de Dados COMPANY Avaliação da Instrução 11 com AGCA Avaliação da Instrução 11 com AGGE Avaliação da Instrução 6 com INVITRO Avaliação da Instrução 10 com INVITRO Avaliação da Instrução 11 com INVITRO 75 76 5.4 5.5 5.6 5.7 5.8 39 76 91 94 95 96 96 Lista de Tabelas 2.1 Tabela de Operadores proposta por Tuya et al. (2007) 28 3.1 Exemplo de Indivı́duos e Aptidões 50 4.1 4.2 4.3 4.4 4.5 4.6 4.7 4.8 4.9 4.10 Tabela Usuario Tabela Local Tabela Grupo Tabela ProdutoCartesiano-Usuario X Local X Grupo Cromossomo - Modelo Mapeamento Cartesiano Direto Tabela Usuario BDT Tabela Local BDT Tabela Grupo BDT Cromossomo - Modelo Mapeamento Cartesiano por Intervalos Cromossomo - Modelo Mapeamento Direto 64 64 64 65 66 66 66 66 67 68 5.1 5.2 5.3 5.4 5.5 5.6 Configuração do Banco de Dados COMPANY Instruções Iniciais Tabela de Instruções com a Cobertura de Operadores e Categorias Resultados Iniciais de Experimentos Aleatórios (escores) Novas Instruções Derivadas Resultados Iniciais de Experimentos Aleatórios das Instruções Derivadas (Escores) Instruções Finais para os Experimentos usando AG Parâmetros AGCA Resultados dos Experimentos AGCA das Instruções Finais Selecionadas (Escores) Conjunto de resultados (AGCA) Resultados do AGCA com Variação de Tamanho de Cromossomo Instrução 11 Parâmetros AGGE Experimentos Aleatórios e usando AGGE Resultados do AGGE com Variação de Tamanho de Cromossomo Instrução 11 Resultados do INVITRO com Variação de Tamanho de Cromossomo Resultados Aleatórios, AGCA, AGGE, INVITRO 75 78 79 81 83 5.7 5.8 5.9 5.10 5.11 5.12 5.13 5.14 5.15 5.16 84 85 87 88 89 90 92 92 94 95 97 CAPÍTULO 1 Introdução Qualidade é uma caracterı́stica importante na produção de um software, sendo requerida no processo e no produto relacionado. A norma ISO9126 (ISO/IEC, 2001) define qualidade como“a totalidade de caracterı́sticas e critérios de um produto ou serviço que exercem suas habilidades para satisfazer às necessidades declaradas ou envolvidas”. Pressman (2009) define a qualidade de software como “conformidade com requisitos funcionais e de desempenho explicitamente declarados, normas de desenvolvimento explicitamente documentadas e caracterı́sticas implı́citas, que são esperadas em todo software desenvolvido profissionalmente”. Conforme Pressman (2009), a Engenharia de Software é uma disciplina de engenharia relacionada com todos os aspectos da produção de software, desde os estágios iniciais de especificação do sistema até sua manutenção. Teste de Software é uma das áreas da Engenharia de Software em que se busca a garantia da qualidade do software. Segundo Delamaro et al. (2007), o Teste de Software está inserido no contexto das atividades, coletivamente chamadas de “Validação, Verificação e Teste, ou VV&T”, que devem ser executadas durante a construção do software, com a finalidade de garantir que tanto o modo pelo qual o software está sendo construı́do quanto o produto que será entregue estejam em conformidade com o especificado. Dentre estas atividades, o teste é a mais utilizada e é considerada fundamental para a melhoria dos processos e produtos. Entretanto, é uma das atividades mais onerosas do processo de desenvolvimento de software (SOMMERVILLE, 2010). A qualidade dos testes é determinada pelos casos de teste utilizados. Para garantir um software livre de erros, ele deveria ser testado com todos os valores possı́veis do domı́nio de entrada, porém, a realização de testes desta forma (exaustivo) é inviável em razão do grande número de valores de entradas possı́veis (MCMINN, 2004). Diante dessa limitação, foram criadas as técnicas e os critérios de teste, que contribuem para geração, seleção e avaliação sistemática de um conjunto de casos de testes, garantindo que partes especı́ficas de um programa sejam exercitadas. Segundo Delamaro et al. (2007), a atividade de teste deve ser conduzida de maneira 17 sistemática, aplicando essas técnicas e critérios para aumentar as chances de revelar defeitos e reduzir os custos. As técnicas mais comuns são: • Técnica Estrutural: que utiliza o código fonte para estabelecer os casos de teste; • Técnica Funcional: que estabelece os casos de teste baseados nos requisitos funcionais; • Técnica Baseada em Erros: onde os elementos requeridos para caracterizar a atividade de teste são baseados em erros comuns que podem ocorrer durante a construção do software. Cada uma dessas técnicas possui um conjunto de critérios de teste, cuja diferença entre eles está na origem da informação utilizada para estabelecer os subdomı́nios e construir os casos de teste. Entre eles, o critério da Análise de Mutantes, da técnica de teste baseados em erros que, segundo Vincenzi (1998), tem se mostrado, através de estudos teóricos e empı́ricos, altamente eficaz em revelar a presença de erros, e com isso, avaliar o quanto um conjunto de casos de teste está adequado a um código em teste. A aplicação dessas técnicas e critérios é fundamental para uma seleção cuidadosa de dados de teste, porém, esta tarefa é muitas vezes difı́cil, demorada e propensa a erros. Assim, a necessidade de diminuir o tempo e os custos dos testes de software, e ao mesmo tempo aumentar a sua eficácia tem motivado a pesquisa de técnicas avançadas onde dados de teste são gerados automaticamente. É uma área de pesquisa ativa onde existem vários trabalhos com diferentes abordagens, como apresentados em McMinn (2004), Ali et al. (2010), Freitas et al. (2010) e McMinn (2011). Assim como qualquer código, instruções de consulta SQL (Structured Query Language) precisam ser testadas para garantir a qualidade do produto a ser entregue. Elas são amplamente utilizadas em aplicações que fazem uso de bancos de dados objeto-relacionais, sendo componentes importantes do software por tratar justamente da interface entre a aplicação e o repositório de dados. Para a realização de testes em instruções de consultas SQL é necessário a existência de um conjunto de tuplas que atenda aos requisitos da instrução, visto que o domı́nio de entrada para as consultas são as tuplas de um banco de dados. Encontrar um conjunto de tuplas otimizado, ou seja, pequeno e que consiga revelar grande parte dos erros, não é uma tarefa trivial. Pode ser considerada uma tarefa de otimização, onde estão envolvidos aspectos combinatórios que manualmente são inviáveis de serem tratados. Vários pesquisadores têm aplicado diferentes técnicas de busca e otimização tentando encontrar soluções para problemas deste tipo, ou seja, problemas pertinen- 1.1 Motivação e Objetivos 18 tes ao processo de construção de software, difı́ceis de serem resolvidos através das normas e metodologias convencionais. Normalmente são problemas envolvendo a seleção de uma solução em um conjunto muito grande de possibilidades (VERGILIO et al., 2012). Podem ser encontrados vários trabalhos com esse foco na literatura, onde os problemas da Engenharia de Software são reformulados como problemas de otimização e busca, linha de pesquisa referida, a partir de 2001, como Search-Based Software Engineering (SBSE ), de acordo com Harman e Jones (2001). E também para problemas especı́ficos da área de Teste de Software, linha de pesquisa conhecida como Search-Based Software Testing (SBST ). 1.1 Motivação e Objetivos Mesmo sendo uma linguagem relativamente simples e bem consolidada, é muito comum cometer erros durante a definição e/ou escrita de instruções SQL. Sejam erros sintáticos, que são aqueles que as regras da linguagem são infligidas e o próprio SGBD (Sistema de Gerenciamento de Banco de Dados) já consegue identificá-los, ou erros semânticos, que são aqueles que sintaticamente a instrução está correta porém o resultado gerado é diferente do esperado. Por serem um importante conjunto de componentes de softwares que utilizam bancos de dados objeto-relacional, as instruções SQL precisam passar por atividades de teste tentando garantir a qualidade do software antes de ser entregue. No contexto de testes de instruções SQL, o domı́nio de entrada para a execução dos testes possui um ou mais bancos de dados. O uso do banco de dados de produção para os testes, mesmo em um ambiente separado, pode ter um custo computacional muito elevado considerando a existência de bancos de dados gigantescos e a complexidade de algumas instruções SQL. Além disso, de acordo com as boas práticas da Engenharia de Software, testes devem ser executados em ambientes próprios, sendo que o uso do mesmo ambiente de produção para a realização dos testes representa um sério risco, podendo comprometer o funcionamento das aplicações em produção. Nesse cenário, é pertinente o emprego de bancos de dados de teste que sejam adequados ao teste das instruções SQL, com respeito à detecção de potenciais defeitos nessas instruções e à redução do custo associado ao teste. Estes bancos de dados podem ser gerados, através de alguma abordagem especı́fica ou selecionados, ou seja, extraı́dos de um banco de dados existente. Considerando que exista um banco de dados real no ambiente de produção, o problema atribuı́do a esta pesquisa é encontrar um banco de dados reduzido, 1.2 Trabalhos Relacionados 19 que tenha a mesma capacidade, ou muito próxima, do banco de produção no que diz respeito à revelar defeitos. Para isso, foi escolhida uma abordagem heurı́stica para seleção de um conjunto de tuplas desse banco de dados, que consiga revelar o maior número de defeitos possı́vel, ou seja, uma quantidade de defeitos igual ou bem próxima a que o banco de produção revelaria se fosse usado como dado de entrada para os testes. E para a avaliação da adequação dos dados selecionados às instruções em teste, foi utilizado o critério de Análise de Mutantes, da técnica de teste baseada em erros. O objetivo dessa dissertação é resolver o problema de seleção de dados de teste para execução dos testes em instruções SQL. Selecionar, dentro do domı́nio de tuplas de um banco de dados existente, um conjunto de tuplas de qualidade, que consiga auxiliar na detecção da maioria de defeitos das instruções SQL de uma aplicação. Para isso, foram utilizados os princı́pios da Computação Evolucionária, para o processo de seleção e evolução dos dados de teste, através de três algoritmos evolucionários, e a Análise de Mutantes SQL para a avaliação. 1.2 Trabalhos Relacionados Foram avaliados trabalhos relacionados à geração de dados de teste utilizando meta-heurı́sticas, aplicação de Análise de Mutantes com meta-heurı́sticas e geração de dados de testes para testes de instruções SQL, principalmente utilizando a Análise de Mutantes. Não foram encontrados trabalhos, na literatura, seguindo a mesma ideia desta dissertação, ou seja, seleção de dados de teste para testes de instruções SQL usando Algoritmos Evolucionários no processo de seleção e a Análise de Mutantes como método de avaliação dos dados selecionados. Como detalhado a seguir, foram encontrados alguns trabalhos relacionados com os seguintes aspectos: geração de dados de teste utilizando meta-heurı́sticas mas nenhum deles para instruções SQL; geração de dados de testes utilizando meta-heurı́sticas para matar mutantes, mas não relacionados com mutação de instruções SQL; geração de bancos de dados para teste de instruções SQL mas sem a utilização de meta-heurı́sticas; e redução de bancos de dados para aplicação de teste também sem a utilização de meta-heurı́sticas. Com relação à geração de dados de teste utilizando meta-heurı́sticas, foram encontrados vários trabalhos. Korel (1990) propõe um gerador de dados de teste para o qual o programa a ser testado é a entrada. O gráfico de controle de fluxo do programa é gerado, os caminhos possı́veis são percorridos, e então os dados necessários para percorrer os caminhos possı́veis são gerados. Mansour et al. (2001) utilizam meta-heurı́sticas para seleção de casos de teste e fazem uma comparação 1.2 Trabalhos Relacionados 20 entre cinco algoritmos: Simulated Annealing, Reduction, Slicing, Dataflow, e Firewall, tendo como função de avaliação a cobertura de código obtida pelos casos de teste. A comparação foi baseada em oito critérios quantitativos e qualitativos: número de casos de teste, tempo de execução, precisão, inclusividade (o quanto a técnica seleciona casos que cobrem falhas na nova versão), processamento dos requisitos, tipo de manutenção, nı́vel de teste e tipo de abordagem. Os resultados mostraram que as técnicas apresentam resultados diferentes dependendo do critério utilizado. Em relação ao algoritmo Simulated Annealing, por exemplo, os resultados indicaram boas soluções nos critérios de número de casos de teste e precisão (FREITAS et al., 2010). Michael et al. (2001) apresentam uma proposta de geração e evolução de dados de teste através de Algoritmos Genéticos. Bottaci (2001) descreve uma nova abordagem evolucionária, baseada na otimização de colônia de formigas, para geração automática de dados de testes no contexto de teste de mutação, com o objetivo de redução do custo desta técnica de teste. Hermadi (2003) mostra um gerador de dados de teste baseado em Algoritmos Genéticos. Khor e Grogono (2004) também aplicam esta mesma meta-heurı́stica na geração de dados de teste para determinada cobertura, e Louzada et al. (2012) fazem uso de um algoritmo genético com uma abordagem elitista para geração de dados de testes para programas desenvolvidos em linguagem java, cujo domı́nio de entrada são números reais. Também utilizam a Análise de Mutantes como método de avaliação dos dados gerados. O artigo de Ali et al. (2010) apresenta uma revisão sistemática de trabalhos na área de Search-Based Software Testing visando a avaliação de estudos empı́ricos, da aplicação de meta-heurı́sticas na geração de casos de teste, principalmente quanto ao custo e efetividade. Além desses, o survey de McMinn (2004) e o review elaborado por Vergilio et al. (2012) citam vários trabalhos realizados em geração de dados de teste usando meta-heurı́sticas. Entretanto, nenhum dos trabalhos encontrados, relacionados à otimização na geração de dados de teste usando meta-heurı́sticas, foi aplicado à instruções SQL, ou seja, à geração de instâncias de banco de dados. Com relação à geração de dados de teste, usando meta-heurı́sticas, e aplicação da Análise de Mutantes, foram encontrados os seguintes artigos relevantes: Masud et al. (2005), que trabalha com a divisão do programa em unidades menores e utiliza Algoritmos Genéticos para geração de dados de teste simples para matar mutantes, não tendo referência com bancos de dados; e May et al. (2007), que compara o Algoritmo Genético canônico com um algoritmo de bactérias (Immune Inspired) na geração de dados de teste, mostrando que este segundo trabalha com indivı́duos especialistas que matam mutantes não mortos por um conjunto de 1.2 Trabalhos Relacionados 21 indivı́duos, apresentando assim melhores resultados. Nenhum deles fez referência a banco de dados, apenas geração de dados de teste simples. Com relação a testes de bancos de dados, o artigo de Mannila e Raiha (1986) propõe a geração automática de um banco de dados adequado para testes de determinada instrução SQL, mas sem a utilização de meta-heurı́sticas. O mesmo acontece como o artigo de Chays et al. (2004), que propõe uma ferramenta para geração de casos de testes para aplicações com bancos de dados relacionais, baseados no esquema e nas restrições do banco de dados, usando instruções SQL simples. Gupta et al. (2010) trabalham a geração de dados de teste para matar mutantes SQL considerando as mutações nas cláusulas JOIN e nos operadores relacionais. Shah et al. (2011), além dos JOINS e dos operadores relacionais, acrescentam a geração de dados de teste para matar os mutantes dos comandos de agregação. Esses dois últimos trabalhos apresentam técnicas de geração de dados de teste, para avaliar a corretude de instruções SQL, baseada nos mutantes gerados pelas instruções, com o objetivo de matar a maior quantidade de mutantes usando os dados gerados. Os resultados foram considerados eficientes para as classes de mutantes envolvidas, porém não utilizam meta-heurı́sticas nem evolução dos dados de testes. Os dados são gerados seguindo as regras das instruções SQL e dos mutantes a serem mortos. Outro trabalho muito interessante é o de Tuya et al. (2009), onde apresentam uma estratégia de redução de um banco de dados de produção para realização de testes, através de regras de cobertura de algumas instruções SQL e usando a Análise de Mutantes como método de avaliação. Cria também uma ferramenta chamada QAShrink para automatizar a redução. Os resultados obtidos mostraram uma grande redução mantendo a cobertura bem próxima à do banco de dados de produção. Este trabalho está relacionado à ideia de redução do banco de dados, porém não utiliza meta-heurı́sticas, faz a redução em cima de regras das instruções SQL. Gupta et al. (2010) e Shah et al. (2011) são trabalhos muito semelhantes que tratam de geração de bancos de dados de teste, baseados em mutantes de determinadas cláusulas SQL. Já Tuya et al. (2009) consideram um banco de dados existente e trabalham com a redução deste, similar à proposta desta dissertação, mas sem a utilização de meta-heurı́sticas. Não utilizam seleção seguida de evolução dos dados, selecionam as tuplas de acordo com regras de cobertura das instruções SQL. Todos os trabalhos encontrados usando meta-heurı́sticas tratam de geração de dados de teste simples como valores numéricos, decimais, caracteres, etc, onde os programas testados não são relacionados a banco de dados. Como mencionado no Capı́tulo 2, Seção 2.3, e nos parágrafos anteriores, existem vários trabalhos 1.3 Organização da Dissertação 22 relacionados à geração de dados de teste para instruções SQL mas nenhum deles utilizando meta-heurı́sticas. Todos foram baseados em alguma regra de cobertura ou com o objetivo de matar tipos de mutantes. Não aplicaram evolução dos dados, ou seja, não utilizaram um processo evolucionário baseado em alguma meta-heurı́stica como é a proposta deste trabalho. Já para redução de bancos de dados, ou seja, seleção de tuplas de um banco de dados existente, os trabalhos avaliados utilizam métodos determinı́sticos que, dependendo do conjunto de soluções possı́veis para o problema, podem ser inviáveis. Como este trabalho trata deste assunto, e considerando que o banco de dados de produção pode ter uma dimensão muito grande, justifica-se o uso de meta-heurı́sticas para a seleção das tuplas. 1.3 Organização da Dissertação De acordo com a motivação e metodologia definida e visando a alcançar os objetivos, esta dissertação foi organizada da seguinte forma: • o Capı́tulo 2 apresenta alguns conceitos da área de Teste de Software, um estudo detalhado sobre Análise de Mutantes e sobre a Análise de Mutantes aplicada para SQL. Descreve também a aplicação de técnicas de otimização em teste de software, apresentando a linha de pesquisa Search-Based Software Engineering - SBSE, conceitos de meta-heurı́sticas e a aplicação destas na área de teste de software, conhecida como Search-Based Software Testing - SBST ; • o Capı́tulo 3 apresenta algumas definições sobre Computação Evolucionária, Algoritmos Evolucionários e Algoritmos Genéticos; • o Capı́tulo 4 define o problema, o ambiente criado e a abordagem proposta; • o Capı́tulo 5 apresenta os experimentos realizados e os resultados obtidos; • o Capı́tulo 6 apresenta as considerações finais, destacando as contribuições advindas deste trabalho à atividade de teste e os possı́veis desdobramentos em novos trabalhos futuros. • o Apêndice A apresenta o Algoritmo Genético Canônico (AGCA) implementado; • o Apêndice B apresenta as alterações realizadas para a implementação do Algoritmo Genético com Grupo de Eleitos (AGGE); • o Apêndice C apresenta as alterações realizadas para a implementação do Algoritmo Auxiliar Paralelo (INVITRO). CAPÍTULO 2 Teste de Software Este capı́tulo apresenta uma breve introdução sobre conceitos inerentes ao Teste de Software, principalmente no que diz respeito ao Teste de Mutação. É apresentada a importância desta atividade dentro da Engenharia de Software, os objetivos, os tipos de teste, e as técnicas e critérios existentes para aplicação de teste de forma sistematizada. Aborda em maiores detalhes o critério de Análise de Mutantes da técnica de teste baseado em defeitos, principalmente no contexto de instruções SQL. Posteriormente descreve a aplicação de técnicas de busca e otimização na área de Teste de Software. Apresenta a Search-Based Software Engineering (SBSE ), área onde métodos de pesquisa (algoritmos de busca) são utilizadas para a solução de problemas de Engenharia de Software. Apresenta conceitos de meta-heurı́sticas e o funcionamento de algumas, consideradas as mais utilizadas nesta área (HARMAN et al., 2009). Finalizando, aborda a aplicação dessas meta-heurı́sticas em problemas de Teste de Software, área de pesquisa conhecida como Search-Based Software Testing (SBST ). 2.1 Conceitos A complexidade no desenvolvimento de software tende a aumentar com a diversidade de ambientes tecnológicos e a necessidade de informação cada vez maior e mais rápida do ser humano. Por isso, e tentando garantir a qualidade do produto a ser entregue, as organizações têm investido na melhoria de processos e métodos de desenvolvimento, principalmente no que diz respeito a Verificação, Validação e Teste de Software que muitas vezes são atividades ignoradas durante o processo de software. Softwares são encontrados na maioria das atividades do nosso dia-a-dia, e o seu funcionamento correto é uma necessidade real. Uma falha pode ocasionar desde perdas de menor escala até danos muito relevantes, tais como, prejuı́zo financeiro e risco de vida. 2.1 Conceitos 24 A construção de software não é uma tarefa simples, pelo contrário, pode ser bastante complexa dependendo das caracterı́sticas e dimensões do sistema a ser criado. Como na maioria das atividades de engenharia, a construção de software depende, principalmente, da habilidade, da interpretação e da execução das pessoas que o constroem. Por isso, erros acabam surgindo, mesmo com a utilização de métodos e ferramentas de Engenharia de Software, afirma Delamaro et al. (2007). Segundo Pressman (2009), a Engenharia de Software é uma disciplina de engenharia relacionada com todos os aspectos da produção de software, desde os estágios iniciais de especificação do sistema até sua manutenção. Ela introduz várias atividades de garantia de qualidade no processo de desenvolvimento de software buscando minimizar os problemas. De acordo com Delamaro et al. (2007), para que os erros sejam descobertos antes do software ser liberado para utilização, existe uma série de atividades, coletivamente chamadas de “Validação, Verificação e Teste, ou VV&T”, com a finalidade de garantir que tanto o modo pelo qual o software está sendo construı́do quanto o produto que será entregue estejam em conformidade com o especificado. Atividades de VV&T, segundo Delamaro et al. (2007), podem ser divididas em estáticas e dinâmicas. As estáticas são aquelas que não requerem execução ou existência de um programa executável, já as dinâmicas são aquelas que se baseiam na execução de um programa ou modelo. Teste de Software é uma das áreas da Engenharia de Software em que se busca a garantia da qualidade do software, contribuindo continuamente para a melhoria dos processos e produtos. De acordo com Myers (1979), “teste é o processo de executar um programa com a intenção de encontrar defeitos”; e segundo Delamaro et al. (2007), se enquadram na categoria de atividades de VV&T dinâmicas pois executam um programa ou modelo com objetivo de verificar se o comportamento está de acordo com o esperado. Estas atividades não se restringem ao produto final, podem e devem ser conduzidas durante todo o processo de desenvolvimento do software, desde a sua concepção, e englobam diferentes técnicas. As atividades de testes são dividida, de uma forma geral, nas seguintes fases com objetivos distintos (DELAMARO et al., 2007): • Teste de unidade, aplicado nas menores unidades de um programa separadamente; • Teste de integração, realizado após serem testadas todas as unidades individualmente, com ênfase na construção da estrutura do sistema e integração entre as diversas partes do sistema; 2.1 Conceitos 25 • Teste de sistema, realizado após a integração de todas as partes do sistema com o objetivo de verificar se as funcionalidades especificadas foram corretamente implementadas. Além dessas três fases, destaca-se também o que se chama de teste de regressão. Esse tipo de teste não se realiza durante o processo normal de desenvolvimento, mas sim durante a manutenção do software, com o objetivo de verificar se os novos requisitos estão corretos e se os requisitos testados anteriormente continuam válidos (DELAMARO et al., 2007). O principal objetivo do teste é revelar a presença de defeitos no software para que possam ser corrigidos antes que causem algum dano, o que aumenta a confiabilidade do software. Idealmente, a atividade de teste deve ser conduzida de maneira sistemática, aplicando-se técnicas para balancear a redução de custo e o aumento das chances em revelar defeitos, caso existam. Tais técnicas definem elementos requeridos, que representam requisitos a serem cobertos durante o teste. Delamaro et al. (2007) identifica três técnicas para o teste de software: • Estrutural, também conhecida como teste caixa-branca, em que os elementos requeridos são derivados da estrutura do software; • Funcional, também conhecida como teste caixa-preta, em que os elementos requeridos são derivados da especificação funcional do software; • Baseado em Erros, que consiste na utilização de erros tı́picos do processo de implementação de software para que sejam derivados os requisitos de teste. Cada uma dessas técnicas possui critérios de teste, que podem ser usados na geração, seleção e avaliação de um conjunto de casos de testes. A utilização das técnicas ocorre por meio da aplicação de um desses critérios, que estabelece os elementos requeridos a serem exercitados no teste (GOODENOUGH; GERHART, 1975). A diferença entre eles está na origem da informação utilizada para estabelecer os subdomı́nios e construir os casos de teste. Alguns exemplos são: (i) o critério estrutural Todos-comandos, que requer que cada comando do programa seja executado pelo menos uma vez durante o teste; (ii) o critério funcional Análise do valor limite, que divide o domı́nio de entrada do software em partições e requer que os limites de cada partição sejam testados pelo menos uma vez durante o teste; (iii) o critério Análise de Mutantes ou Teste de Mutação que consiste em semear erros no programa original gerando programas mutantes e avaliar o comportamento dos mutantes com relação ao original. Uma grande dificuldade é encontrar casos de testes adequados, que garantam uma cobertura significativa, ou seja, que consigam abranger a maioria das situações 2.2 Análise de Mutantes 26 existentes no contexto e identificar grande parte dos defeitos. A utilização de abordagens e técnicas de testes procura atingir esse objetivo com custos computacional e operacional aceitáveis. A Análise de Mutantes é uma dessas abordagens, que funciona como uma maneira de mensurar o quanto um conjunto de testes está adequado e, consequentemente, ajuda a orientar na evolução e definição de novos casos de teste (DEMILLO et al., 1978). 2.2 Análise de Mutantes O sucesso da atividade de testes em revelar defeitos de um programa está diretamente relacionado com a qualidade do conjunto de casos de teste usado para testar o programa. Avaliar os casos de teste é um ponto crucial na atividade de teste. O objetivo é conseguir identificar e projetar casos de teste com maior poder em revelar uma grande quantidade de defeitos com o menor tempo e esforço possı́vel (BARBOSA et al., ). Para atingir este objetivo, é necessário aplicar técnicas e critérios de teste visando sistematizar a atividade de teste e auxiliar na geração dos casos de teste, bem como na avaliação da qualidade desses casos de teste. A Análise de Mutantes ou Teste de Mutação é um dos critérios usados para este fim. De acordo com DeMillo et al. (1978), o teste baseado na Análise de Mutantes, ou Teste de Mutação, é um critério da técnica de testes baseada em defeitos que tem como ideia básica a de que defeitos considerados no teste representam enganos que programadores cometem frequentemente. Sendo assim, tais defeitos são propositalmente inseridos no software original por meio de uma mudança sintática simples, feitas para criar um conjunto de programas defeituosos. Cada um desses programas – chamados de Mutantes – contém uma mudança sintática distinta. Para avaliar a qualidade de um conjunto de dados de teste, esses mutantes são executados com os mesmos dados de teste executados pelo programa original, verificando se os defeitos semeados podem ser detectados. Vários trabalhos relacionados a teste de mutação foram publicados por Jeff Offutt1 , e análises atuais de trabalhos nesta área podem ser encontradas em Delamaro et al. (2007) e em Jia e Harman (2011), onde fazem uma revisão geral sobre o desenvolvimento da Análise de Mutantes. A figura 2.1 representa um processo de teste de um programa P usando a Análise de Mutantes. Esse processo é realizado da seguinte forma: 1 http://www.cs.gmu.edu/~offutt/rsrch/mut.html 2.2 Análise de Mutantes 27 Figura 2.1: Processo de Análise de Mutantes Pequenas mudanças sintáticas no programa original P são efetuadas, a partir de operadores de mutação definidos de acordo com a sintaxe da linguagem. Com essas mudanças, é gerado um conjunto de programas mutantes P’. Após a geração dos programas mutantes, um conjunto de dados de teste T é executado no programa original P e as saı́das produzidas são checadas em relação ao esperado. Cada mutante P’ é então executado com cada dado de teste t do conjunto T. Se o resultado da execução de P’ é diferente do resultado da execução de P para algum dado de teste t, então o mutante P’ é considerado morto, caso contrário, P’ continua vivo (BARBOSA et al., ). O nı́vel de adequação de um conjunto de dados de teste, sob a perspectiva da Análise de Mutantes, é dado pelo chamado escore de mutação, que é uma medida de cobertura do critério de Análise de Mutantes. Em Demillo (1980) e Andrews et al. (2005), destaca-se a capacidade que a Análise de Mutantes tem em fornecer uma medida objetiva do nı́vel de confiança da adequação dos casos de teste analisados através da definição de um escore de mutação, que relaciona o número de mutantes mortos com o número de mutantes não equivalentes gerados. Um mutante é considerado equivalente quando sua modificação não influencia no comportamento do programa. Dado um programa P e um conjunto de dados de teste T, o cálculo do escore de mutação MS(P,T) é feito da seguinte forma: MS(P, T ) = DM(P, T ) M(P) − EM(P) Sendo: DM(P, T ) : quantidade de mutantes mortos pelos casos de teste em T M(P) : quantidade total de mutantes gerados. EM(P) : número de mutantes equivalentes a P. 2.2 Análise de Mutantes 28 O valor de MS(P, T ) varia entre 0 e 1, sendo que quanto maior este valor, mais adequado é o conjunto de casos de teste T para o programa sendo testado. Ou seja, quanto maior este valor, maior o número de programas mutantes (P’ ) mortos significando que o conjunto de casos de teste T conseguiu detectar uma grande quantidade de defeitos inseridos no programa. Segundo Jia e Harman (2011), a teoria da Análise de Mutantes é baseada em duas hipóteses fundamentais: a hipótese do programador competente e a hipótese do efeito de acoplamento. A primeira hipótese estabelece que os programadores são competentes e, portanto, tendem a desenvolver programas que são próximos da versão correta. Deste modo, embora os programas possam conter defeitos, assume-se que esses defeitos são simples e podem ser corrigidos com pequenas alterações sintáticas. Com base nesta hipótese a Análise de Mutantes aplica apenas mudanças sintáticas simples, simulando defeitos inseridos por “programadores competentes”. A hipótese do efeito de acoplamento estabelece que “dados de teste que distinguem todos os programas diferentes do programa correto apenas por erros simples são tão sensı́veis, que eles também distinguem erros mais complexos”. Portanto, defeitos complexos estão acoplados a defeitos simples de uma maneira tal que dados de teste que detectam os defeitos simples também irão detectar um alto percentual de defeitos complexos. 2.2.1 Exemplos de Mutação Operadores de Mutação são regras que definem as alterações que serão aplicadas em um programa P, dando origem aos programas mutantes. A seleção dos operadores a serem usados no processo depende, entre outros fatores, da sintaxe da linguagem, da abrangência do teste e do custo. Quanto maior o número de operadores utilizados, maior o número de mutantes gerados. Segundo Richard et al. (1989), para a linguagem C, por exemplo, foram definidos 75 operadores de mutação, divididos em 4 categorias : • • • • Mutação Mutação Mutação Mutação de de de de Comandos; Operadores; Variáveis; Constantes. Considerando o código na linguagem C, a seguir: while (a < 10) { 2.3 Análise de Mutantes SQL 29 c[a] = a+b; a++; } c[a] = 0; Um exemplo de um mutante gerado com um operador da categoria de mutação de comandos pode ser a alteração da posição de uma linha do código, como mostrado a seguir: while (a < 10) { c[a] = a+b; a++; c[a] = 0; } Outro exemplo usando um operador da categoria de mutação de operadores é a troca de operadores relacionais, como a troca do 0 <0 pelo 0 =0 , mostrado logo abaixo: while (a = 10) { c[a] = a+b; a++; } c[a] = 0; Assim como essas, várias outras alterações são realizadas de acordo com os operadores selecionados. 2.3 Análise de Mutantes SQL A Análise de Mutantes foi proposta inicialmente para ser usada em linguagens de programação como o Fortran e Ada. Posteriormente a técnica começou a ser aplicada em outras linguagens, interfaces, especificações e modelos. Para cada novo contexto, novos e especı́ficos operadores de mutação eram criados para adequação da técnica. Em tese, tendo definidos os operadores de mutação, o processo da Análise de Mutantes continua o mesmo independente da linguagem. Desta forma, existindo operadores especı́ficos, a técnica pode ser utilizada para testes de instruções SQL, e com isso avaliar a qualidade de casos de teste especı́ficos para aplicações de banco de dados. 2.3 Análise de Mutantes SQL 30 Chan et al. (2005) projetaram sete operadores de mutação para a linguagem SQL. Depois foi proposto um outro conjunto de operadores por Tuya et al. (2007). Este conjunto foi organizado em quatro categorias, sendo elas: • • • • SC - Mutação das principais cláusulas SQL; OR - Mutação dos operadores que estão presentes em condições e expressões; NL - Mutação relacionada a manipulação de valores nulos; IR - Mutações de identificadores: colunas, constantes e parâmetros; Cada categoria define vários operadores de mutação, classificados em tipos e subtipos, sendo que cada um refere-se a um determinado tipo de alteração aplicada nas instruções SQL. A tabela 2.1 apresenta os operadores, por categoria, propostos por Tuya et al. (2007). 10 11 12 13 14 15 16 17 18 19 20 21 22 Num 1 2 3 4 5 6 7 8 9 UOI ABS AOR BTW LKE NLF NLS NLI NLO IRC IRT IRP IRH OR IR NL SC Operador SEL JOI SUB GRU AGR UNI ORD ROR LCR Categoria Tabela 2.1: Tabela de Operadores proposta por Tuya et al. (2007) Descrição Troca do comando SELECT por SELECT DISTINCT Troca entre comando de JOIN (INNER JOIN, LEFT OUTER JOIN, RIGHT OUTER JOIN, FULL OUTER JOIN, CROSS JOIN) Mutação de predicados das subqueries Remoção da expressão GROUP BY Troca entre as funções de agregação (MIN, MAX, AVG, AVG(DISTINCT), SUM, SUM(DISTINCT), COUNT, COUNT(DISTINCT)) Troca entre os comandos UNION e UNION ALL ou remoção de queries com UNION Troca de parâmetros da ordenação ASC, DESC ou remoção da expressão de ordenação Troca entre operadores relacionais (=,<>,<,<=,>,=>) ou troca da expressão falso ou verdadeiro Troca entre operadores lógicos (AND, OR), troca a expressão pelos valores falso ou verdadeiro, troca a expressão para retornar o operando da direita ou da esquerda Cada expressão aritmética ou referência para um número n é trocada para -n,n+1 en-1 Cada expressão aritmética ou referência para um número n é trocada por ABS(n) e -ABS(n) Troca entre operadores aritméticos (+,-,*,/,%) ou troca pelo retorno no operando da esquerda ou da direira Troca das expressões z BETWEEN x AND y por z>x AND z<=y e por z>=x AND z<y Alterações nas condições usando LIKE (removendo, trocando e adicionando coringas) Troca entre os predicados IS NULL e IS NOT NULL Troca cada coluna c da lista de seleção por um valor fora do domı́nio de c quando o valor de c é nulo Força um valor verdadeiro da condição quando existe um valor nulo, cada atributo a em uma condição C é trocado por C OR a IS NULL Cada atributo a em uma condição C é trocado por NOT C OR a IS NULL, a IS NULL, a IS NOT NULL Troca de cada coluna da cláusula SELECT por outra coluna, constante ou parâmetro de tipo compatı́vel Troca de cada constante da cláusula SELECT por outra coluna, constante ou parâmetro de tipo compatı́vel Troca de cada parâmetro da cláusula SELECT por outra coluna, constante ou parâmetro de tipo compatı́vel Troca de cada referência a uma coluna por outra coluna da mesma tabela 2.3 Análise de Mutantes SQL 31 2.3 Análise de Mutantes SQL 32 Para ilustrar a geração de mutantes em instruções SQL, será apresentada, a seguir, uma instrução SQL original e alguns exemplos de mutantes de cada categoria: Instrução SQL original: SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 1500) AND DNO=5 Exemplos de mutantes: • Categoria SC - Operador SEL SELECT DISTINCT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 1500) AND DNO=5 • Categoria OR - Operador ABS SELECT * FROM EMPLOYEE WHERE (ABS(SALARY) BETWEEN 1000 AND 1500) AND DNO=5 • Categoria NL - Operador NLO SELECT * FROM EMPLOYEE WHERE ((SALARY IS NULL)) AND DNO=5 • Categoria IR - Operador IRT SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 5 AND 1500) AND DNO=5 Em 2006, no artigo de Tuya et al. (2006), foi criada uma ferramenta de geração de mutantes para instruções SQL, a SQLMutation, que implementa todos estes operadores. No mesmo artigo, também foi realizada uma avaliação dos resultados do uso desta ferramenta. Ela pode ser encontrada e utilizada no site do grupo de pesquisa de Engenharia de Software da Universidade de Oviedo2 através de uma interface Web. O artigo de Derezinska (2009) mostra uma avaliação desses operadores de mutação com relação à quantidade de mutantes gerados, à quantidade de mutantes mortos e ao escore de mutação para determinadas instruções SQL de um banco de dados de uma companhia de seguros. Vários outros estudos usando Análise de Mutação para instruções SQL foram encontrados, entre eles: Suárez-Cabal e Tuya (2009), que mostra um critério de cobertura estrutural para avaliação um conjunto de instruções SQL em teste e usa o escore de mutação como o método de avaliação; Tuya et al. (2009), que mostra a redução de uma base de dados direcionada a encontrar os requisitos para testar um determinado conjunto de instruções SQL; e Blanco et al. (2012), que propõe uma abordagem para geração de dados de teste para aplicações de bancos de dados, considerando não apenas instruções SQL, mas também as interfaces com os usuários. 2 http://in2test.lsi.uniovi.es/sqlmutation/ 2.3 Análise de Mutantes SQL 33 Casos de Teste para Análise de Mutantes SQL Além dos operadores de mutação, outro aspecto importante para a análise de mutantes SQL são os casos de teste. Cabeca et al. (2010) definem que no contexto de aplicações de banco de dados, um caso de teste é formado por: • Dados de entrada para o programa; • Conjunto de instâncias de bancos de dados; • Saı́da esperada e instância do banco de dados esperada. A qualidade das instâncias dos bancos de dados utilizadas nos teste é determinante para permitir uma maior revelação de defeitos. Os dados destas instâncias (dados de teste) podem ser gerados ou selecionados de bancos de dados existentes. Com relação à geração de dados de teste para instruções SQL, pode-se encontrar vários trabalhos na literatura. O artigo de Mannila e Raiha (1986) propõe a geração automática de um banco de dados adequado para testes de determinada instrução SQL. Uma ferramenta para geração de casos de testes para aplicações com bancos de dados relacionais foi proposta no artigo Chays et al. (2004). O artigo Gupta et al. (2010) trabalha a geração de dados de teste para matar mutantes SQL, considerando as mutações nas cláusulas JOIN e nos operadores relacionais. O artigo Shah et al. (2011), além dos JOINS e dos operadores relacionais, acrescenta a geração de dados de teste para matar os mutantes dos comandos de agregação. Os dois trabalhos consideraram os resultados eficientes para as classes de mutantes envolvidas. A seleção de dados de teste para instruções SQL, a partir de um banco de dados existente, seria a extração de tuplas deste banco de dados. Procura-se extrair um subconjunto de dados adequado que consiga garantir uma maior cobertura dos defeitos, com a menor quantidade de tuplas possı́vel. Nesse contexto, trabalhando com bancos de dados de grande escala, o espaço de busca para seleção dos dados pode ser considerado muito grande. Sendo assim, este problema pode ser caracterizado como um problema de otimização. Neste caso, o uso de métodos que buscam uma solução ótima é muitas vezes impraticável. Duas opções para realizar a seleção seriam: 1. Selecionar aleatoriamente um subconjunto de dados; 2. Realizar a seleção através de um processo, com critérios especı́ficos, que busque fazer a escolha de um bom subconjunto de dados. De acordo com nossos estudos, em muitos casos uma simples seleção aleatória é suficiente para proporcionar um bom subconjunto de dados. Porém, algumas 2.4 Search-Based Software Engineering 34 instruções SQL podem possuir caracterı́sticas que dependam de um subconjunto de dados com uma combinação muito especı́fica de tuplas, para conseguir revelar defeitos usando a Análise de Mutantes. Nesse contexto, foi encontrado o artigo de Tuya et al. (2009) que utiliza a regra de cobertura de determinada instrução SQL como critério para seleção de tuplas e redução de um banco de dados de produção. Justifica a redução principalmente para os testes de regressão e cria a ferramenta QAShrink para realizar o processo. Os experimentos mostram uma grande redução, mantendo a cobertura bem próxima à do banco de produção. São utilizados testes de mutação SQL para avaliação dos bancos reduzidos. Nesta dissertação também é apresentada uma abordagem para redução de um banco de dados de produção. Explora o uso de Algoritmos Genéticos, um campo da Computação Evolucionária, para selecionar dados, a partir de um banco de dados de produção que serão avaliados pela Análise de Mutantes. Essa abordagem proposta está relacionada com a área de pesquisa Search-Based Software Engineering, mas especificamente a Search-Based Sofware Testing, explicadas nas próximas seções. 2.4 Search-Based Software Engineering A otimização em Engenharia de Software é uma área de pesquisa recente que trata da aplicação de técnicas de busca e otimização matemática para a resolução de problemas complexos pertinentes ao processo de construção de software. São problemas, que acontecem nas sub-áreas da Engenharia de Software, difı́ceis de serem resolvidos através das normas e metodologias convencionais. Normalmente são problemas envolvendo a seleção de uma solução em um conjunto muito grande de possibilidades (VERGILIO et al., 2012). Apesar de já existirem trabalhos relacionados desde 1976, a área obteve maior atenção e abrangência a partir de 2001 com a publicação do artigo de Harman e Jones (2001) que deu origem à denominação Search-Based Software Engineering (SBSE ) para o campo de pesquisa. Desde então, diversos pesquisadores em parceria com engenheiros de software estão modelando e resolvendo problemas de Engenharia de Software utilizando técnicas de busca (ou algoritmos de busca), ou seja, reformulando problemas desta área como problemas de otimização baseados em busca. Soluções ótimas ou subótimas são procuradas em um espaço de soluções candidatas, tendo como guia, em geral, uma função objetivo, que distingue as soluções melhores das piores. De certo modo, a área SBSE é parte de um campo mais abrangente: a aplicação de mecanis- 2.4 Search-Based Software Engineering 35 mos de Inteligência Computacional para o tratamento de problemas em Engenharia de Software (PEDRYCZ; PETERS, 1998) (PEDRYCZ, 2002). Algoritmos de busca a serem usados podem ser classificados em dois principais grupos, segundo Vergilio et al. (2012). O primeiro são os algoritmos clássicos do campo da pesquisa operacional e programação linear, que são algoritmos determinı́sticos, ou seja, determinam uma única solução. O outro grupo inclui os algoritmos de busca estocásticas para solução de problemas complexos, onde nem sempre a solução ótima exata é encontrada, mas sim uma solução sub-ótima, normalmente próxima da ideal e que possa ser obtida em tempo hábil. As metaheurı́sticas tais como os Algoritmos Evolucionários, Otimização por Enxame de Partı́culas (PSO), Otimização baseada em Colônia de Formigas (ACO) e muitas outras estão incluı́das neste segundo grupo e são as técnicas mais utilizadas no campo da SBSE (HARMAN, 2007). Uma razão para isso é a natureza dos problemas da área: são problemas do mundo real geralmente relacionados a objetivos que não podem ser caracterizados por um conjunto de equações lineares, e assim, difı́ceis de serem tratados por métodos determinı́sticos (VERGILIO et al., 2012). O artigo de Harman et al. (2011) apresenta um tutorial, com um passo a passo, para a aplicação de técnicas de SBSE em problemas da Engenharia de Software. Mantere e Alander (2005), Harman (2007), Harman et al. (2009) e Freitas et al. (2009) apresentam revisões da literatura na área SBSE, classificando os trabalhos segundo o tipo de problema da Engenharia de Software tratado. Vergilio et al. (2011) e Vergilio et al. (2012) fazem uma análise do campo de pesquisa no Brasil e uma revisão de trabalhos de autores brasileiros na área SBSE. Algumas sub-áreas da Engenharia de Software têm se destacado na aplicação de técnicas de busca e otimização, principalmente com uso de meta-heurı́sticas, entre elas: a engenharia de requisitos, projeto de software, refatoração, estimativa de software e teste de software. Na revisão de Freitas et al. (2009) foram encontrados 23 trabalhos divididos nas seguintes sub-áreas: engenharia de requisitos(3), teste de software(8), estimativa de software(5), planejamento de projeto(2), otimização de código fonte(1), manutenção de software(1), engenharia de software orientada a serviço(1) e otimização de compilador(2). Na área de Teste de Software, diversos problemas têm sido modelados e resolvidos através das técnicas de busca e otimização, ganhando um novo campo de pesquisa denominado Search-Based Software Testing (SBST ). A primeira publicação sobre o uso de técnicas de otimização em teste de software foi o artigo de Miller e Spooner (1976), onde dois americanos sugeriram uma abordagem simples, e diferente das existentes, para geração de dados de teste 2.5 Meta-Heurı́sticas 36 para uma entrada em ponto flutuante, baseado em um método de maximização numérica. A partir de 1990, as pesquisas nesta área tiveram continuidade e vários trabalhos envolvendo geração de dados de teste, seleção de casos de teste, priorização de casos de teste, testes funcionais, testes não funcionais, testes de mutação, testes de regressão, entre outros, podem ser encontrados na literatura. Analisando o repositório de trabalhos da área de pesquisa SBSE 3 verifica-se um total de 1101 artigos, sendo mais da metade relacionados a teste de software. Segundo Vergilio et al. (2012), a área de teste de software tem recebido uma atenção especial dos pesquisadores e 67% dos trabalhos encontrados em SBSE foram dedicados a esta sub-área. Explica que este interesse pode ser baseado na natureza das atividades de teste, isto é, são atividades caras e trabalhosas. Como mencionado anteriormente, a ideia principal dos trabalhos encontrados nestas áreas de pesquisa (SBSE e SBST ) é a aplicação de meta-heurı́sticas para a otimização das buscas. Sendo assim, serão apresentados a seguir alguns conceitos e exemplos de meta-heurı́sticas. 2.5 Meta-Heurı́sticas Metaheurı́sticas são heurı́sticas de forma genérica, ou seja, que podem ser utilizadas em diferentes tipos de problemas. Estes métodos utilizam ideias de diversos domı́nios como inspiração para realizar o processo de busca da solução para problemas de otimização. O termo foi cunhado pela primeira vez em Glover (1970) como a representação do conjunto de algoritmos genéricos, sendo tais algoritmos estudados desde a década de 70. Contrariamente às heurı́sticas convencionais, as meta-heurı́sticas são de caráter geral e providas de mecanismos para tentar explorar melhor o espaço de busca. De acordo com Gendreau e Potvin (2010), o processo geral de execução de uma meta-heurı́stica trata da busca de soluções, a partir da visitação de regiões do espaço de busca, guiando-se pela função de avaliação, também conhecida como função objetivo, que é uma função matemática que atribui um valor a cada solução do espaço de busca, identificando o quanto a solução é adequada para o problema. A forma como o processo de busca segue é especı́fico de cada meta-heurı́stica sendo que todas tentam, de uma forma inteligente, encontrar boas soluções sem a 3 Repositório SBSE: http://crestweb.cs.ucl.ac.uk/resources/sbse_repository/ repository.html consultado em maio de 2013 2.5 Meta-Heurı́sticas 37 garantia da ótima solução. Gendreau e Potvin (2010) mostram que a utilização de meta-heurı́sticas pode ser justificada devido à alguns fatores, entre eles: • Complexidade interna do problema que impede a aplicação de técnicas exatas; • Quantidade muito grande de possı́veis soluções que impede a utilização de técnicas exaustivas. As meta-heurı́sticas diferenciam-se entre si basicamente pelo mecanismo usado para sair das armadilhas que concentram a exploração em determinados locais. Elas dividem-se em duas categorias, de acordo com o princı́pio usado para explorar o espaço de soluções: busca local e busca populacional. Nas meta-heurı́sticas baseadas em busca local, a exploração do espaço de soluções é feita por meio de movimentos, os quais são aplicados a cada passo sobre a solução corrente, gerando outra solução promissora em sua vizinhança. Busca Tabu, Simulated Annealing, Busca em Vizinhança Variável (Variable Neighborhood Search) e Iterated Local Search são exemplos de métodos que se enquadram nesta categoria, de acordo com Souza (2010). Os métodos baseados em busca populacional, por sua vez, consistem em manter um conjunto de boas soluções e combiná-las de forma a tentar produzir soluções ainda melhores. Segundo Glover (2002), exemplos clássicos de procedimentos desta categoria são os Algoritmos Genéticos, os Algoritmos Meméticos e o Algoritmo Colônia de Formigas. Segundo Harman et al. (2009), as meta-heurı́sticas que prevalecem na área SBSE e SBST são: Hill-Climbing, Simulated Annealing e Algoritmos Genéticos, apresentadas a seguir. 2.5.1 Hill Climbing A técnica Hill-Climbing é definida como um método de busca local de soluções (BLUM; ROLI, 2003). A atividade que é efetuada durante o processo de busca é representada pelo nome do método, traduzido como “Subida de Colina”. O processo segue os seguintes passos: 1. Selecione uma solução do espaço de busca e avalie o seu mérito, utilizando a função de avaliação. Defina esta solução como solução atual; 2. Aplique uma transformação na solução atual para gerar uma nova solução e avalie o seu mérito, utilizando a função de avaliação; 3. Se a nova solução é melhor do que a solução atual, torne esta solução a solução atual, caso contrário, descarte a nova solução; 4. Repita os passos 2 e 3 até que nenhuma transformação melhore a solução atual. 2.5 Meta-Heurı́sticas 38 O uso do termo “subida” é uma referência a problemas de maximização, onde o objetivo é encontrar soluções que apresentem o maior valor para a função de avaliação. Pode-se perceber que o método Hill-Climbing apresenta uma estratégia simples de busca sendo um dos mais simples algoritmos de otimização conhecidos. Dada a simplicidade do processo, o desempenho do algoritmo pode ser limitado. Isto acontece porque esta técnica é capaz apenas de encontrar a melhor solução localmente. Em outros termos, o método Hill-Climbing não explora o possı́vel grande espaço de busca (espaço global) que pode ter soluções melhores do que as encontradas apenas na vizinhança da solução atual. Normalmente é utilizado em conjunto com outros métodos. É importante destacar que o método Hill-Climbing pode ser utilizado tanto em problemas de maximização quanto em problemas de minimização. No caso de minimização, o algoritmo faria o mesmo processo de busca local, mas as soluções encontradas dariam menores valores para a função de avaliação. 2.5.2 Simulated Annealing A meta-heurı́stica Simulated Annealing, ou traduzida como Têmpera Simulada, é semelhante a Hill-Climbing. Seu procedimento de busca é baseado em um processo fı́sico real que ocorre na metalurgia de ligas e metais. Especificamente, o processo tomado como base é originalmente definido como annealing e traduzido como “têmpera”. Outro termo utilizado é “resfriamento”, tendo em vista a atividade que este processo efetua sobre o material. No processo de têmpera, um material é aquecido com altas temperaturas e, após, é resfriado de forma que ao final de todo o processo o material se encontre em um estado cristalizado de energia mı́nima. Na relação com a otimização matemática, o objetivo é minimizar o valor da função de avaliação a partir das soluções encontradas durante o processo de busca. Assim como o método Hill-Climbing, ela também pode ser aplicada tanto a problemas de maximização quanto a problemas de minimização. Se a função deve ser minimizada, o algoritmo já está pronto. Se o desejado for a maximização da função, basta considerar o oposto da mesma, pois a minimização do oposto de uma função corresponde à maximização da mesma. A diferença entre o algoritmo Simulated Annealing e o Hill-Climbing é que o primeiro permite a aceitação de soluções que não melhoram o valor da função de avaliação. Por exemplo, em problemas de minimização é possı́vel a aceitação para a próxima iteração de uma solução com valor maior que a solução corrente. Estas aceitações, contra o objetivo, são controladas a fim de definir um processo de busca 2.6 Meta-heurı́stica aplicada na área de Teste de Software 39 inteligente. A forma de controle de aceitação deriva de funções estatı́sticas definidas para o processo real. Em resumo, o algoritmo funciona da seguinte forma: seleciona uma solução na vizinhança da solução atual, se a solução encontrada é melhor que a solução corrente, então ela é aceita; caso contrário, se a nova solução encontrada piora o objetivo, então ela é aceita com certa probabilidade, definida em termos da diferença entre as soluções, do valor atual da variável de temperatura e de uma constante fı́sica. 2.5.3 Algoritmos Genéticos A meta-heurı́stica Algoritmos Genéticos, segundo Holland (1992), é um dos métodos mais utilizados para a resolução de problemas de otimização. Utiliza conceitos da Genética, como população, geração, reprodução e mutação. O funcionamento dessa meta-heurı́stica se resume, basicamente, no emprego de três operações: o cruzamento (crossover ), no qual as informações estruturais de duas soluções são cruzadas a fim de gerar duas novas soluções; a mutação, processo pelo qual algumas alterações aleatórias podem ser realizadas nas soluções geradas; e a seleção, que é responsável pela escolha dos indivı́duos que serão submetidos às operações de cruzamento e mutação. Após isso, as soluções atuais são avaliadas para a determinação de quais sobreviverão para a próxima iteração. Dessa forma, as soluções vão sendo selecionadas de acordo com o desempenho em relação à função de avaliação. Como as novas soluções são geradas a partir das soluções selecionadas, o processo evolui com o intuito de gerar soluções cada vez melhores. O Capı́tulo 3 (Computação Evolucionária), Seção 3.3, apresenta uma descrição detalhada sobre Algoritmos Genéticos, visto que foi a meta-heurı́stica escolhida para ser utilizada neste trabalho. 2.6 Meta-heurı́stica aplicada na área de Teste de Software Uma simples forma de um algoritmo de otimização e busca, normalmente fácil de implementar, é a busca aleatória. Na geração de dados de teste, entradas são geradas aleatoriamente até que o objetivo do teste seja atingido, como por exemplo, a cobertura de uma determinada linha de um programa. Porém, as buscas aleatórias são pobres quando as boas soluções ocupam uma pequena parte de todo o espaço de busca. Neste caso, um bom dado de teste pode ser mais rapidamente encontrado quando as buscas são guiadas por determinados parâmetros. Com a utilização de 2.6 Meta-heurı́stica aplicada na área de Teste de Software 40 meta-heurı́sticas no processo de busca de soluções, a função de avaliação especı́fica do problema é usada como uma forma de guiar as buscas. Como citado anteriormente, a aplicação de meta-heurı́sticas na área de Teste de Software tem tentado solucionar problemas de priorização de casos de teste, seleção de casos de teste e geração de dados de teste, sendo os últimos os que apresentam maior número de trabalhos relacionados. Os dois primeiros problemas são mais comuns em testes de regressão, onde é preciso priorizar e selecionar casos de teste a serem executados após a realização de uma manutenção no sistema já em uso. Neste caso, o teste de todo o sistema novamente, na maioria da vezes, torna-se inviável por questões de tempo e recursos insuficientes. Freitas et al. (2010) apresentam o estado da área de pesquisa SBST, mostrando seu potencial, as principais meta-heurı́sticas aplicadas e os problemas desta área já modelados com seus resultados. Foram identificados trabalhos relacionados à priorização de casos de teste, seleção de casos de teste, geração de dados de teste, testes não funcionais e testes funcionais. A atividade de priorização de casos de teste significa ordenar os casos de teste de modo que a cobertura atingida seja máxima quando for necessário parar a execução de testes, sem executar todos que estavam previstos. Assim, os conjuntos de casos de teste que oferecem maior cobertura são executados primeiro. Algoritmos gulosos, bem como técnicas meta-heurı́sticas, estão sendo utilizados para encontrar uma ordenação ótima, já que esses algoritmos encontram soluções ótimas ou próximas da ótima (GLOVER, 2002). Os trabalhos de Walcott et al. (2006) e de Yoo e Harman (2007) demonstram a efetividade da meta-heurı́stica Algoritmos Genéticos na priorização de casos de teste. Freitas et al. (2010) identifica trabalhos utilizando diferentes técnicas para priorização de casos de teste. Quanto à seleção de casos de teste, Mansour et al. (2001) compara cinco algoritmos: Simulated Annealing, Reduction, Slicing, Dataflow, e Firewall, tendo como função de avaliação a cobertura de código obtida pelos casos de teste. A comparação foi baseada em oito critérios quantitativos e qualitativos: número de casos de teste, tempo de execução, precisão, inclusividade (o quanto a técnica seleciona casos que cobrem falhas na nova versão processamento dos requisitos, tipo de manutenção, nı́vel de teste e tipo de abordagem. Os resultados mostraram que as técnicas apresentam resultados diferentes dependendo do critério utilizado. Em relação ao algoritmo Simulated Annealing, por exemplo, os resultados indicaram boas soluções nos critérios de número de casos de teste e precisão (FREITAS et al., 2010). No trabalho de Yoo e Harman (2007), foi introduzido o conceito de Pareto para seleção de casos de teste usando duas versões: uma combina duas funções de 2.6 Meta-heurı́stica aplicada na área de Teste de Software 41 avaliação (cobertura de código e custo) e a outra com três funções de avaliação (cobertura de código, custo e histórico de falhas). A eficiência das técnicas metaheurı́sticas foi comprovada na resolução desses problemas. Freitas et al. (2010) também apresentam trabalhos utilizando outras abordagens para seleção de casos de teste. A geração de dados de teste é o processo de identificação de dados de entrada válidos para um programa, de acordo com os critérios de teste. Quanto maior e mais complexo um programa, mais difı́cil é gerar tais dados de entrada. Em Korel (1990) é proposto um gerador de dados de teste para o qual o programa a ser testado é a entrada. O gráfico de controle de fluxo do programa é gerado, os caminhos possı́veis são percorridos, e então os dados necessários para percorrer os caminhos possı́veis são gerados. Mantere e Alander (1999) apresentam a aplicação de um Algoritmo Genético simples para o problema da geração de dados de teste, com o objetivo de exercitar caminhos completos em programas, baseado no teste estrutural. Michael et al. (2001), Hermadi (2003) e Khor e Grogono (2004) também apresentam a aplicação de Algoritmos Genéticos na geração de dados de teste. E Louzada et al. (2012) apresentam a aplicação de um algoritmo evolucionário elitista na geração automática de dados de teste. O survey de McMinn (2004) cita vários trabalhos da área SBST, relacionados a geração de dados de teste; e o artigo Ali et al. (2010) apresenta uma revisão sistemática de trabalhos desta área visando a avaliação de estudos empı́ricos da aplicação de meta-heurı́sticas na geração de casos de teste principalmente quanto ao custo e efetividade. A ideia geral dos trabalhos relacionados a geração de dados de teste é aplicar meta-heurı́sticas para buscar os dados que satisfaçam a um determinado critério, considerando o espaço de possı́veis entradas do programa. As meta-heurı́sticas funcionam como ferramentas para obter dados de teste que exercitem os elementos requeridos pelos critérios de teste, ou que avaliem certas caracterı́sticas do software. Nesse contexto então, o problema da geração de dados de teste é reformulado como um problema de otimização, ou seja, de busca por um conjunto “ótimo” de dados de teste, pertencentes ao espaço de todo o domı́nio de entrada do programa. De acordo com os artigos de Harman (2007), McMinn (2011) e Harman et al. (2012), dois requisitos devem ser satisfeitos para aplicar as buscas baseadas em técnicas de otimização na área de testes: • Representação : As soluções candidatas para o problema devem ser codificadas de tal forma que possam ser manipuladas pelos algoritmos de busca; • Função de Avaliação (fitness) : É responsável pela avaliação das soluções candidatas e com isso, o direcionamento para as áreas promissoras do espaço 2.6 Meta-heurı́stica aplicada na área de Teste de Software 42 de busca. A função de avaliação precisa ser definida especificamente para cada problema. Meta-heurı́sticas como Hill-Climbing, Simulated Annealing e Algoritmos Genéticos são aplicadas em diversos cenários, tais como o teste baseado em estrutura, o teste baseado em modelos e o teste baseado em defeitos, objeto deste trabalho. A Figura 2.2, adaptada de Harman et al. (2009), mostra uma abordagem SBST, em uma perspectiva ampla. Um engenheiro de software define um critério de adequação para o problema, baseado no objetivo do teste. Define também uma função objetivo ou função de avaliação, baseada nos elementos (ou caracterı́sticas) do software (ou do modelo) a serem exercitados. A meta-heurı́stica selecionada usa esta função para direcionar a busca de dados de teste. A execução do software ou dos modelos pode ser realizada para permitir a avaliação dos dados de teste. Figura 2.2: Elementos de uma solução de otimização na geração de dados de teste (adaptada de Harman et al. (2009) 2.7 Considerações Finais 2.7 43 Considerações Finais Este capı́tulo abordou informações sobre a área de Teste de Software com foco na Análise de Mutantes para instruções SQL e na aplicação de técnicas de busca e otimização na área teste. Iniciou com uma breve introdução sobre Teste de Software, conceitos, técnicas e critérios de teste. Apresentou com maiores detalhes o critério da Análise de Mutantes, da técnica de teste baseado em defeitos, com alguns exemplos de aplicação. Mostrou a Análise de Mutantes aplicada no contexto da linguagem SQL com conceitos, definição de operadores criados para permitir o uso da técnica nesta linguagem, exemplos de mutações e trabalhos relacionados. Abordou também informações sobre a área de pesquisa Search-Based Software Testing (SBST ) que trata do uso de técnicas de busca e otimização na solução de problemas da área de Teste de Software. Apresentou a Search-Based Software Engineering (SBSE ), área voltada para o estudo da solução de problemas difı́ceis em Engenharia de Software, através da aplicação de meta-heurı́sticas (algoritmos de busca). Mostrou alguns conceitos sobre meta-heurı́sticas assim como o funcionamento das mais utilizadas segundo os trabalhos encontrados na literatura. Finalizando, apresentou a aplicação de meta-heurı́stica na área de Teste de Software assim como alguns trabalhos já desenvolvidos nesta área. CAPÍTULO 3 Computação Evolucionária Este capı́tulo apresenta inicialmente alguns conceitos e um breve histórico da área de Computação Evolucionária. Descreve o funcionamento dos Algoritmos Evolucionários com seus parâmetros e principais elementos do processo de evolução. Apresenta em detalhes os conceitos e o processo básico dos Algoritmos Genéticos, que serão aplicados na abordagem proposta por este trabalho. E finalizando, apresenta uma breve descrição de um Algoritmo Genético com uma abordagem elitista; e de um In Vitro Fertilization Genetic Algorithm, para auxiliar nos resultados dos Algoritmos Genéticos. Tanto os Algoritmos Genéticos como as duas variações apresentadas serão utilizados, posteriormente, nos experimentos. 3.1 Conceitos e Histórico Na busca de soluções para os problemas de otimização, a complexidade das soluções computacionais é bastante relevante, sendo necessária uma classificação dos algoritmos propostos de modo a determinar o tipo de solução a ser empregada. Um dos métodos de análise da complexidade de um algoritmo é a análise assintótica, que retrata a ordem de crescimento do tempo de execução do algoritmo em função do tamanho da sua entrada. Segundo essa análise, a notação “O” é utilizada para exprimir complexidades (taxas de crescimento do tempo de execução) ou o limite superior mı́nimo do tempo de execução (CORMEN, 2002). Diante disso, os problemas foram classificados em relação à sua complexidade da seguinte forma: • P (Deterministic Polinomial Time): classe de problemas que podem ser resolvidos em tempo polinomial, ou seja, existem algoritmos para solução desse problema cujo tempo de execução no pior caso não ultrapassa a O(nk ), para alguma constante k, onde n é o tamanho da entrada do problema; • NP (Nondeterministic Polinomial Time): classe de todos os problemas de decisão que podem ser verificados em tempo polinomial, ou seja, tendo um 3.1 Conceitos e Histórico 45 certificado de uma solução para o problema, pode-se verificar se este certificado é correto em tempo polinomial. Dentro do contexto da teoria de complexidade dos algoritmos estão os problemas ditos NP-Completos, onde está a maioria dos problemas de otimização combinatória. Essa classe de problemas tem a propriedade de que, se qualquer problema NP-Completo pode ser resolvido em tempo polinomial, então todo problema em NP tem uma solução em tempo polinomial, isto é, P=NP. Entretando, ainda não foi descoberto nenhum algoritmo em tempo polinomial para qualquer problema NPCompleto (CORMEN, 2002). Diversas abordagens eficientes têm sido usadas para tratar esses tipos de problemas e encontrar soluções aproximadas com tempo computacional razoável. Segundo Engelbrecht (2007), um conjunto de algoritmos baseados em inteligência, sistemas biológicos e neurais, conhecidos também como bio-inspirados (LINDEN, 2008), vem exercendo um importante papel neste contexto, e resultou em uma área conhecida como Inteligência Computacional, onde estão inseridas, entre outras, a Computação Evolucionária, Redes Neurais, Inteligência de Enxames e Inferência Nebulosa. A Computação Evolucionária é uma área da Inteligência Computacional que propõe um novo paradigma para solução e otimização de problemas, baseado em mecanismos do processo de evolução natural (ENGELBRECHT, 2007). Estes mecanismos estão diretamente relacionados com a teoria da seleção de Darwin (DARWIN, 2007), onde ele afirma que em um mundo com recursos limitados e populações estáveis, os indivı́duos competem pela sobrevivência. Os mais adaptados e com as melhores caracterı́sticas têm mais chances de sobreviver e se reproduzirem, passando essas melhores caracterı́sticas para as próximas gerações. “Não é o mais forte que sobrevive, nem o mais inteligente, mas o que melhor se adapta às mudanças.” Charles Darwin Os primeiros passos na área de Computação Evolucionária foram de biólogos e geneticistas que tinham o interesse de simular os processos vitais de um ser humano em um computador. Segundo Engelbrecht (2007), o desenvolvimento da Computação Evolucionária iniciou com Algoritmos Genéticos, por volta de 1950, com um trabalho de Fraser, Bremmermann e Reed. Entretanto, John Holland, em Holland (1967) e Holland (1975) foi quem começou a desenvolver e refinar suas pesquisas no tema. Por isso, é considerado o pai da Computação Evolucionária, mais especificamente dos Algoritmos Genéticos. A Computação Evolucionária compreende um conjunto de técnicas estocásticas de busca e otimização de soluções, também conhecidos como Algoritmos 3.2 Algoritmos Evolucionários 46 Evolucionários. Segundo Linden (2008), são heurı́sticas que não garantem a obtenção do melhor resultado possı́vel em todas as suas execuções, e são aplicados em problemas onde não se conhece algoritmo com tempo de execução razoável para a solução. 3.2 Algoritmos Evolucionários Algoritmos Evolucionários são processos de busca estocástica para uma solução ótima, ou próxima da ótima, de um dado problema. Apesar de haver uma grande variedade de modelos propostos, todos eles têm em comum o conceito de simulação da evolução das espécies através de seleção, mutação e reprodução. Processos estes que dependem do desempenho dos indivı́duos da espécie dentro do ambiente (LINDEN, 2008). Basicamente, os Algoritmos Evolucionários funcionam mantendo uma população de estruturas que evoluem de forma semelhante à evolução das espécies, e a essas estruturas são aplicados os chamados operadores genéticos. Cada indivı́duo recebe uma avaliação que é uma quantificação da sua qualidade como solução do problema em questão. Baseado nessa avaliação, serão aplicados os operadores genéticos de forma a simular a sobrevivência do mais apto (LINDEN, 2008). Segundo Engelbrecht (2007), o processo de busca evolucionária é influenciado pelos seguintes componentes: • • • • • a codificação ou representação da solução; a função objetivo, usada para avaliar o fitness; a inicialização da população; os operadores de seleção; os operadores de reprodução. O Algoritmo Evolucionário genérico, onde encapsula o princı́pio da seleção natural (o melhor tem mais chance de ser selecionado para produzir filhos e para ir para a próxima geração) e onde as mutações são feitas de forma aleatória, pode ser codificado da seguinte forma, segundo Engelbrecht (2007): Inicializar o contador de gerações t=0; Inicializar uma população C(0) de d dimensões e n indivı́duos; while condição de parada não for verdadeira do Avaliar o fitness, f(xi(t)), de cada indivı́duo, xi(t); Executar a reprodução para criar os filhos; Selecionar a nova população, C(t + 1); Avançar para a nova geração, i.e. t = t + 1; 3.2 Algoritmos Evolucionários 47 end while As diferentes implementações dos Algoritmos Evolucionários (de acordo com representações, mecanismos de seleção e reprodução) resultam em diferentes paradigmas. Os mais conhecidos, segundo Engelbrecht (2007), são: • Algoritmos Genéticos, que modela a evolução genética; • Programação Genética, baseado nos Algoritmos Genéticos mas os indivı́duos são programas representados por árvores; • Programação Evolucionária, derivada da simulação de comportamento adaptativo em evolução; • Estratégias Evolucionárias, guiados pela modelagem de parâmetros estratégicos que controlam variações na evolução (evolução da evolução); • Evolução Diferencial, similar aos Algoritmos Genéticos, diferindo o mecanismo de reprodução; • Algoritmos Culturais, que usam a cultura da população como forma de influenciar o genótipo e o fenótipo dos indivı́duos; • Coevolução, que usa a cooperação e a competição entre os indivı́duos para adquirir caracterı́sticas para sobreviver. 3.2.1 Representação Na natureza, organismos têm caracterı́sticas que são representadas por cromossomos. Na Computação Evolucionária, os indivı́duos também são representados por cromossomos, que também são referenciados por genomas (LINDEN, 2008). As caracterı́sticas dos indivı́duos podem ser divididas em 2 classes de informações evolucionárias: • genótipos, que descrevem a composição genética do indivı́duo e • fenótipos, que expressam caracterı́sticas de comportamento. Um indivı́duo da população é uma solução para o problema. Este indivı́duo deve ser representado de maneira que seja possı́vel mapear a solução no espaço de busca e realizar os passos previstos do Algoritmo Evolucionário. Uma das formas de realizar esse mapeamento é através da representação cromossomial. Segundo Linden (2008), a representação cromossomial consiste basicamente em traduzir a informação do nosso problema em uma maneira viável de ser tratada pelo computador, onde cada parte do cromossomo, chamada de gene, é uma caracterı́stica (ou parte) da solução. Uma das primeiras decisões que se deve tomar no projeto de Algoritmos Evolucionários é a representação a ser usada. Essa escolha, segundo CamiloJunior (2010), é uma definição importante para o sucesso das buscas, pois influencia 3.2 Algoritmos Evolucionários 48 diretamente no espaço de busca a ser varrido pelo algoritmo e na definição dos operadores utilizados durante o processo. Segundo Engelbrecht (2007), algoritmos evolucionários de diferentes paradigmas usam modelos de representação diferentes. A maioria deles representa as soluções através de vetores de um tipo especı́fico. A representação mais utilizada na literatura é a binária por ser de fácil implementação e apresentar bons resultados (CAMILO-JUNIOR, 2010). Neste modelo, os cromossomos são formados por uma cadeia de bits (0,1) onde cada bit, ou conjunto de bits, representa a codificação de uma variável do problema. A figura 3.1 ilustra um indivı́duo representado através da codificação binária, onde os 4 primeiros genes representam a variável X e os 3 últimos a variável Y. Figura 3.1: Representação de um indivı́duo - Codificação binária 3.2.2 População Inicial Sendo Algoritmos Evolucionários, algoritmos baseados em população, o primeiro passo é inicializar a população. Normalmente, através de valores aleatórios dentro do domı́nio do problema, para uma representação uniforme de todo o espaço de busca. Uma boa população deve apresentar diversidade para dar aos AGs o maior número de matéria prima possı́vel e iniciar com o conhecimento amplo do espaço (CAMILO-JUNIOR, 2010). Outras formas de inicialização da população têm sido usadas dependendo do paradigma, conforme cita Camilo-Junior (2010). Segundo Engelbrecht (2007) e Linden (2008), o tamanho da população influencia na complexidade do algoritmo. Este deve ser grande o suficiente para gerar diversidade ao mesmo tempo em que não seja grande demais a ponto de tornar o programa demasiadamente lento. Quanto maior o número de indivı́duos, maior a complexidade do algoritmo por geração e mais tempo para encontrar uma resposta. 3.2 Algoritmos Evolucionários 3.2.3 49 Função Objetivo A função objetivo, também chamada de função de avaliação, é a função utilizada para avaliar o fitness da solução, ou seja, a habilidade do indivı́duo em relação aos demais indivı́duos da população. É uma função matemática que atribui um valor a cada solução do espaço de busca, identificando o quanto à solução é adequada para o problema. O tipo do problema de otimização (problemas com ou sem restrição, problemas multi-objetivos ou problemas dinâmicos) influenciam na formulação da função objetivo (ENGELBRECHT, 2007). Sendo assim, ela é responsável pela avaliação das soluções candidatas e, com isso, o direcionamento para as áreas promissoras do espaço de busca. 3.2.4 Operadores Genéticos Os operadores genéticos consistem em aproximações computacionais de fenômenos vistos na natureza, como a reprodução sexuada, a mutação genética e quaisquer outros que a imaginação dos programadores consiga reproduzir (LINDEN, 2008). Seleção É um dos principais operadores dos Algoritmos Evolucionários, relacionado diretamente com o conceito de sobrevivência de Darwin (DARWIN, 2007), cujo objetivo é selecionar as melhores soluções. Segundo Engelbrecht (2007), podem ser determinı́sticos ou probabilı́sticos e atuam em dois momentos: • Seleção de uma nova população ao final de cada geração; • Seleção de indivı́duos para a reprodução: melhores indivı́duos para o cruzamento e normalmente os mais fracos para a mutação. Segundo Engelbrecht (2007), existem diferentes estratégias de seleção que devem ser escolhidas de acordo com o paradigma e com o conhecimento sobre o assunto. Como exemplo, citam-se: • Seleção aleatória - indivı́duos são selecionados sem nenhum critério, de forma aleatória; • Seleção proporcional ou roleta - são atribuı́dos pesos proporcionais ao fitness dos indivı́duos, tendo os melhores mais probabilidade de serem selecionados; • Seleção por torneio - são selecionados alguns subconjuntos da população e destes subconjuntos, os melhores indivı́duos; • Seleção por rank - os indivı́duos são classificados de acordo com o fitness em um ranking; 3.2 Algoritmos Evolucionários 50 • Elitismo – mantem sempre um percentual dos melhores indivı́duos da geração atual na próxima geração. Cruzamento (Crossover ) Cruzamento é o processo de criar um ou mais indivı́duos através da combinação de materiais genéticos de dois ou mais pais. Existem vários tipos de operações de cruzamento sendo a mais simples, a escolha de um ponto de corte aleatório do cromossomo, ou seja, de uma posição aleatória entre 2 genes. Depois de selecionados os pais, na operação de seleção, e o ponto de corte, os filhos são gerados através da combinação do material genético dos pais. A primeira parte do cromossomo (até o ponto de corte) é formada pelo material genético de um dos pais e a segunda parte pelo material genético do outro pai. Engelbrecht (2007) e Linden (2008) apresentam outros operadores mais complexo de cruzamento. Mutação Mutação é o processo de alterar, aleatoriamente, genes do cromossomo com o objetivo de introduzir material genético novo para diversificar a população. Por isso, segundo Engelbrecht (2007), deve ser aplicada com cuidado para não distorcer o material genético bom de indivı́duos, sendo aplicada com baixa probabilidade ou proporcional ao fitness do indivı́duo. 3.2.5 Condição de Parada De acordo com Engelbrecht (2007), os operadores evolucionários são aplicados em um Algoritmo Evolucionário até uma condição de parada, que pode ser o número máximo de gerações atingido ou algum critério de convergência como: • Quando nenhuma melhora for observada durante um número de gerações consecutivas; • Quando não é mais possı́vel mudanças na população; • Quando uma solução aceitável for encontrada. As principais diferenças entre métodos clássicos de otimização e a Computação Evolucionária são: o processo de busca e as informações sobre o espaço de busca usadas para guiar o processo. De acordo com Engelbrecht (2007), enquanto a Computação Evolucionária utiliza regras de busca probabilı́sticas e paralelas, dentro do espaço de busca, para mover suas buscas através das gerações, os métodos clássicos usam regras determinı́sticas para mover de um ponto a outro no espaço de busca, de forma sequencial. Quanto às informações sobre o espaço de busca, os métodos 3.3 Algoritmos Genéticos 51 clássicos usam informações derivadas do ambiente para direcionar suas buscas, já na Computação Evolucionária é utilizada a função de avaliação. 3.3 Algoritmos Genéticos “A ideia por traz dos algoritmos genéticos, é fazer o que a natureza faz.” Zbigniew Michaelewicz Algoritmos Genéticos (AG) são um ramo dos Algoritmos Evolucionários, da área de Computação Evolucionária, que podem ser definidos como uma técnica de busca baseada numa metáfora do processo biológico de evolução natural (LINDEN, 2008). Foi um dos primeiros modelos de Algoritmos Evolucionários desenvolvidos. John Holland, em (HOLLAND, 1967), foi quem começou a desenvolver as principais pesquisas, popularizou a técnica e foi considerado o pai dos Algoritmos Genéticos (ENGELBRECHT, 2007). Em 1975, Holland publicou o livro “Adaptation in Natural and Artificial Systems” (HOLLAND, 1975), ponto inicial dos Algoritmos Genéticos. David E. Goldberg, aluno de Holland, nos anos 80 obteve seu primeiro sucesso em aplicação industrial com Algoritmos Genéticos. Desde então, os AGs são utilizados para solucionar problemas de otimização e aprendizado de máquinas. Baseiam-se nos mecanismos de seleção natural e genética. Combinam a sobrevivência entre os melhores com uma forma estruturada de troca de informação genética entre dois indivı́duos para formar uma estrutura heurı́stica de busca (LINDEN, 2008). Apesar de aleatórios, eles não são caminhadas sem direção pois exploram informações históricas para encontrar novos pontos de busca, onde são esperados melhores desempenhos. Isto é feito através de processos iterativos, onde cada iteração é chamada de geração. Assim como outros modelos de Algoritmos Evolucionários, os Algoritmos Genéticos operam com um conjunto de soluções, chamado de população, onde cada indivı́duo (solução) tem suas caracterı́sticas expressas através de genótipos (LINDEN, 2008). O ponto de partida para a utilização de Algoritmos Genéticos, como ferramenta para solução de problemas, é a representação destes problemas de maneira que os AGs possam trabalhar adequadamente sobre eles. Tradicionalmente, os indivı́duos são representados por vetores binários, onde cada elemento de um vetor denota a presença (1) ou ausência (0) de uma determinada caracterı́stica (genótipo). Os elementos podem ser combinados formando as caracterı́sticas reais do indivı́duo, ou o seu fenótipo. Segundo Holland (1967), outros tipos de AGs utilizam diferentes tipos de representação. 3.3 Algoritmos Genéticos 52 O princı́pio básico do funcionamento dos Algoritmos Genéticos é que um critério de seleção vai fazer com que, depois de muitas gerações, o conjunto inicial de indivı́duos gere indivı́duos mais aptos. A maioria dos métodos de seleção são projetados para escolher, preferencialmente, indivı́duos com maiores notas de aptidão, embora não exclusivamente, a fim de manter a diversidade da população (LINDEN, 2008). Em Ahn (2006), são listados as macro etapas que um Algoritmo Genético deve implementar para alcançar o objetivo proposto por essa técnica, também ilustrados na figura 3.2. • Passo 1. Inicialização: Gerar população inicial P de forma aleatória ou com conhecimento prévio. Neste momento são geradas n possı́veis soluções para o problema a ser resolvido. • Passo 2. Avaliação da aptidão: Avaliar a aptidão de todos os indivı́duos da população P. Esta análise é feita de acordo com uma função preestabelecida que determina a qualidade de um indivı́duo como solução do problema em questão. • Passo 3. Seleção: Selecionar de P um conjunto de candidatos promissores S. Na maioria das abordagens estes candidatos são os indivı́duos(soluções) com melhor aptidão da população. A partir deles serão geradas as novas populações. • Passo 4. Cruzamento (Crossover ): Aplicar o cruzamento entre indivı́duos de S para geração de um conjunto de descendentes O. Os descendentes serão os indivı́duos da nova população. Espera-se que eles tenham as boas caracterı́sticas dos indivı́duos da população anterior. • Passo 5. Mutação: Aplicar mutação no conjunto de descendente O para obter uma perturbação do conjunto O. Estas perturbações vão inserir novas caracterı́sticas aos indivı́duos modificados, levando a exploração de novas soluções possı́veis. • Passo 6. Substituição: Substituir a população corrente P com o conjunto de descendente O. A nova população será formada pelos novos indivı́duos gerados a partir dos cruzamentos e mutações. • Passo 7. Finalização: Se os critérios de parada não forem atingidos, o processo continua novamente a partir do passo 2. Porém, caso pelo menos um critério seja atingido, o algoritmo é interrompido e o melhor indivı́duo gerado é dado como solução para o problema. Um método de seleção muito utilizado é o Método da Roleta, onde indivı́duos de uma geração são escolhidos para fazer parte da próxima geração, através de um sorteio de roleta. Neste método, cada indivı́duo da população é representado na 3.3 Algoritmos Genéticos 53 Figura 3.2: Estrutura básica de um Algoritmo Genético roleta proporcionalmente ao seu ı́ndice de aptidão. Assim, aos indivı́duos com alta aptidão é dada uma porção maior da roleta, enquanto aos de aptidão mais baixa é dada uma porção relativamente menor da roleta. Finalmente, a roleta é girada um determinado número de vezes, dependendo do tamanho da população, e são escolhidos, como indivı́duos que participarão da próxima geração, aqueles sorteados na roleta (LINDEN, 2008). Como exemplo, a tabela 3.1 apresenta 4 indivı́duos representando os valores de uma variável X onde a função objetivo, que define a aptidão do indivı́duo, é dada por X 2 . Assim, a figura 3.3 ilustra uma roleta para este caso, com as proporções de cada indivı́duo, de acordo com a aptidão. Tabela 3.1: Exemplo de Indivı́duos e Aptidões Indivı́duo X Aptidão (X 2 ) A 01111 15 225 B 10100 20 400 C 01010 10 100 D 00101 5 25 A aptidão dos indivı́duos, ou forma de determinar a qualidade de um indivı́duo como solução para o problema em questão, é dada pela função de avaliação 3.3 Algoritmos Genéticos 54 Figura 3.3: Roleta para Seleção dos Indivı́duos da tabela 3.1 (fitness) aplicada a cada indivı́duo, que é o componente mais importante de qualquer Algoritmo Genético, segundo Linden (2008). Um conjunto de operadores é necessário para que, dada uma população inicial, se consiga gerar, com o passar do tempo, populações sucessivas de melhor aptidão. Esses operadores são: cruzamento (crossover ) e mutação. Eles são utilizados para assegurar a diversidade da população e manter caracterı́sticas de adaptação adquiridas pelas gerações anteriores. O cruzamento (crossover ) é o operador responsável pela recombinação de caracterı́sticas dos pais durante a reprodução, permitindo que as próximas gerações herdem essas caracterı́sticas. Segundo Engelbrecht (2007) e Linden (2008), ele é considerado o operador genético predominante nos AGs. Este operador pode ser utilizado de várias maneiras sendo as mais utilizadas: • Um ponto de cruzamento: é escolhido um ponto de corte e a partir deste ponto as informações genéticas dos pais serão trocadas. As informações anteriores a este ponto de um dos pais são ligadas às informações posteriores à este ponto do outro pai. A figura 3.4 ilustra um exemplo deste tipo de cruzamento para indivı́duos de representação numérica. • Muitos pontos de cruzamento: podem ser utilizados mais de um ponto de corte, generalizando a ideia anterior, como no exemplo da figura 3.5, que apresenta um cruzamento com 2 pontos de corte. 3.3 Algoritmos Genéticos 55 • Cruzamento uniforme: neste tipo de cruzamento não são utilizados pontos de corte, mas é determinada a probabilidade de cada variável ser trocada entre os pais, através de um parâmetro global, como ilustrado na figura 3.6. Figura 3.4: Operação de Cruzamento com 1 ponto de corte Figura 3.5: Operação de Cruzamento com 2 pontos de corte Figura 3.6: Operação de Cruzamento Uniforme Outro operador importante é o da mutação, que é responsável por introduzir novo material genético em um indivı́duos já existentes, ou seja, incluir arbitrariamente diversidade nas caracterı́sticas genéticas da população, visando controlar problemas de mı́nimos locais. A mutação é utilizada apoiando o operador de cruzamento, para assegurar que toda a gama de alelo é acessı́vel para cada gene (ENGELBRECHT, 2007), (HOLLAND, 1967) e (HOLLAND, 1975). Engelbrecht (2007) 3.3 Algoritmos Genéticos 56 apresenta vários tipos de mutação de acordo com modelo de representação utilizado. A figura 3.7 mostra uma mutação de um indivı́duo alterando apenas um gene do cromossomo. Figura 3.7: Operação de Mutação em um gene do cromossomo A cada passo, um novo conjunto de indivı́duos é gerado a partir da população anterior. A este novo conjunto, Engelbrecht (2007) dá o nome de Geração, e é através da criação de uma grande quantidade de gerações que é possı́vel obter bons resultados dos Algoritmos Genéticos. Alguns métodos, como a reprodução elitista, foram criados para prevenir que os melhores indivı́duos não desapareçam da população pela manipulação dos operadores genéticos, como apresentado por Holland (1967), Engelbrecht (2007) e Louzada et al. (2012). Os AGs trabalham com um conjunto de parâmetros que influenciam diretamente no funcionamento do mesmo. A escolha desses parâmetros é um problema não linear e depende do tipo de problema a ser tratado. Por isso, não é possı́vel encontrar uma boa configuração para generalizar a execução de qualquer tipo de problema (CAMILO-JUNIOR, 2010). Esses parâmetros são: • Tamanho da População: Influencia diretamente na exploração do espaço de busca, no tempo de execução e na demanda por recursos computacionais. Uma população pequena possui amostragem insuficiente do espaço de busca, já uma população grande leva uma convergência mais lenta com a necessidade de mais recursos computacionais ou de tempo. • Taxa de Cruzamento: Controla a frequência com a qual o operador de cruzamento é aplicado. Uma taxa baixa significa pouco aproveitamento da informação existente, já um valor alto pode provocar convergência prematura. • Taxa de Mutação: Define a probabilidade de um indivı́duo sofrer alterações em seus genes. Um valor baixo pode não satisfazer a necessidade de exploração e levar o algoritmo à estagnação, já uma valor alto conduz a uma busca aleatória, sem aproveitamento das informações existentes. 3.4 Algoritmo Genético Elitista 57 • Critério de Parada: Determina a finalização da execução do AG. Algumas formas de se determinar o momento de interromper o AG são: ao atingir um valor ótimo ou um valor conhecido, na ausência de melhorias por um determinado número de gerações, ao atingir um número de chamadas à função de avaliação e ao completar um número de gerações. Camilo-Junior (2010) cita vários trabalhos que apresentam sugestões para os parâmetros, mas conclui que, apesar das sugestões, normalmente a escolha se dá por tentativa e erro. Segundo Ahn (2006), Algoritmos Genéticos são estocásticos e a sua caracterı́stica mais singular é o fato de trabalharem com uma população (conjunto) de soluções, diferente de outros algoritmos de aproximação clássicos, que operam com uma única e simples solução a cada iteração. 3.4 Algoritmo Genético Elitista Outra abordagem a ser destacada, que será utilizada em alguns experimentos neste trabalho, é o uso do elitismo nos Algorı́timos Genéticos. Elitismo é umas das técnicas mais utilizadas durante a reprodução dos indivı́duos nos Algoritmos Genéticos (CAMILO-JUNIOR, 2010). Segundo Linden (2008), é uma alteração que quase não reflete no tempo de processamento do algoritmo, mas que garante que a performance do AG está sempre crescendo com o decorrer das gerações. A ideia básica do elitismo é a seguinte : os n melhores indivı́duos de cada geração não devem “morrer” junto com a sua geração, mas sim passar para a próxima geração, para garantir que seus genomas sejam preservados. Considerando que o melhor indivı́duo de uma geração é a solução mais adequada para o problema, pode-se garantir que, com o elitismo, os melhores indivı́duos das próximas gerações serão iguais ou melhores e nunca piores. Além da seleção dos melhores, uma outra abordagem elitista que pode ser usada, também trabalhada por Louzada et al. (2012), é a criação de um grupo de k indivı́duos eleitos. Esses indivı́duos normalmente não apresentam uma boa aptidão, porém têm caracterı́sticas que os diferenciam dos melhores indivı́duos da população. Essas caracterı́sticas dependem do problema que está sendo tratado. No caso em questão, o intuito é que os indivı́duos do grupo de eleitos sejam indivı́duos que consigam matar mutantes não mortos pelos melhores indivı́duos. Sendo assim, além dos n melhores indivı́duos, esse grupo de k indivı́duos eleitos também é mantido na próxima geração. 3.5 In Vitro Fertilization Genetic Algorithm - IVF/GA 3.5 58 In Vitro Fertilization Genetic Algorithm IVF/GA Com o intuito de melhorar os resultados dos Algoritmos Genéticos, foi proposto por Camilo-Junior (2010) e Camilo-Junior e Yamanaka (2011) um Algoritmo Auxiliar Paralelo (AAP), inspirado na Fertilização in Vitro. O objetivo do algoritmo é auxiliar os AGs com bons indivı́duos, a partir de um melhor tratamento das estruturas presentes nas populações de pais. O IVF/GA é baseado na população corrente do AG e em novos indivı́duos gerados. Recombina os cromossomos para melhor aproveitar as informações e seleciona os melhores indivı́duos, de forma análoga à Fertilização In Vitro. É executado em um fluxo paralelo ao fluxo do AG, recebendo uma parcela da população corrente e emitindo, como saı́da, um indivı́duo ou mais, melhores que o melhor corrente. Recebe uma quantidade de informações do AG, que poderiam ser perdidas, recombina e abastece o AG com bons indivı́duos, agilizando o processo evolutivo. Segundo Camilo-Junior (2010), o processo é divido em 3 fases : • Coleta : Existem várias estratégias para a coleta, mas a utilizada neste trabalho é a seleção dos N melhores indivı́duos da população corrente do AG, sendo o melhor, o Pai, e os demais, as Mães. • Manipulação Genética : Avalia, altera e recombina as estruturas selecionadas. Pode ser dividida em duas operações: alteração genética, onde parte de alguns cromossomos podem ser alteradas; e recombinação genética onde as combinações das informações são feitas e avaliadas. • Transferência : Caso o processo gere indivı́duos melhores, são transferidos para a população de acordo com alguma estratégia (substituição do melhor indivı́duo, substituição de qualquer indivı́duos, substituição do pior indivı́duos, entre outras). A estratégica usada neste trabalho é a substituição do melhor indivı́duo. A figura 3.8 ilustra o processo de execução do AG com o IVF/GA. Na fase de projeto do algoritmo genético deve-se definir a Divisão do Material Genético, onde o cromossomo é dividido em grupos de genes. Esta divisão depende da estratégia utilizada e pode ser definida de acordo com o conhecimento do problema. Na Manipulação Genética, as operações de alteração genética e recombinação são realizadas de acordo com a divisão do cromossomo. No trabalho de Camilo-Junior (2010) são propostos dois grupos de operadores: o primeiro, conhecido como AR - Assisted Recombination, que não trabalha com alterações genéticas na fase de manipulação genética, realiza apenas as combinações 3.5 In Vitro Fertilization Genetic Algorithm - IVF/GA 59 Figura 3.8: Acoplamento do IVF/GA ao AG do material genético existente; e o segundo, conhecido como EAR - Exploratory Assisted Recombination, que executa tanto a alteração genética quanto as combinações, procurando enriquecer a população corrente com novas informações. No grupo EAR existem 4 operadores distintos, representando tipos de mutações diferentes: o EAR-T altera todo o cromossomo, o EAR-P altera uma das parte originadas na divisão genética do cromossomo, o EAR-PA altera alguns genes de uma das partes do cromossomo e o EAR-N gera novos indivı́duos. A estratégia de recombinação, utilizada neste trabalho, considera uma das partes do cromossomo da Mãe e as demais partes do cromossomo do Pai, para gerar um filho. Sendo assim, serão gerados tantos filhos quanto a quantidade de partes em que o cromossomo foi dividido na etapa de Divisão do Material Genético. O fluxo básico do In Vitro Fertilization Genetic Algorithm segue os seguintes passos: 3.6 Considerações Finais 60 1. Aplicar coleta dos indivı́duos da população do AG de acordo com a estratégia selecionada; 2. Encontrar o indivı́duo mais apto, sendo o Pai e os demais as Mães; 3. Aplicar a Divisão do Material Genético (dividir o cromossomo em X partes); 4. Para cada indivı́duo Mãe selecionado: (a) Aplicar a Manipulação Genética: • Aplicar a Alteração Genética na Mãe gerando Mãe Alterada (se operadores do tipo EAR); • Aplicar Combinação entre o Pai e a Mãe Alterada gerando X Filhos (sendo X o número de partes do cromossomo); 5. Se o melhor indivı́duo Filho gerado for melhor que o Pai, o Filho substitui o Pai e repete-se a a partir do item 4; 6. Transferir o melhor indivı́duo encontrado para a população do AG, substituindo o melhor corrente. Este processo também será aplicado em alguns experimentos nesta dissertação. 3.6 Considerações Finais Este capı́tulo abordou um breve histórico da área de Computação Evolucionária e a justificativa para sua aplicação. Iniciou com a apresentação dos nı́veis de complexidade para a solução de alguns problemas, apresentou conceitos de Algoritmos Evolucionários com seu processo genérico e as várias implementações de algoritmos nesta área. Mostrou os principais elementos do processo de evolução com seus parâmetros e operadores. Apresentou em detalhes os conceitos e o processo básico de Algoritmos Genéticos, que serão aplicados na abordagem proposta por este trabalho; e finalizou com uma breve descrição de dois algoritmos que serão utilizados posteriormente nos experimentos para auxiliar nos resultados dos Algoritmos Genéticos. Um Algoritmo Genético usando uma abordagem elitista e um In Vitro Fertilization Genetic Algorithm, proposto por Camilo-Junior (2010). CAPÍTULO 4 Aplicação da Abordagem Este capı́tulo apresenta a abordagem proposta nesta dissertação, ou seja, o uso de Algoritmos Genéticos na seleção de dados para testes de instruções SQL, usando como método de avaliação dos dados selecionados a Análise de Mutantes. Inicialmente, apresenta as informações básicas para conhecimento da aplicação, a macro especificação do Algoritmo Genético implementado; mostra os modelos de representação avaliados com exemplos de utilização, e o modelo escolhido para representar um banco de dados como um indivı́duo, no contexto dos Algoritmos Genéticos. Finalizando, mostra o projeto de construção dos algoritmos utilizados nos experimentos: o Algoritmo Genético Canônico (AGCA) o Algoritmo Genético com Grupo de Eleitos (AGGE) e o INVITRO. 4.1 Utilizando Algoritmos Genéticos na Seleção de Dados de Teste para Instruções SQL Nos Capı́tulos 3 e 2 foram apresentados os macroprocessos dos Algoritmos Genéticos e da Análise de Mutantes respectivamente. Considerando o funcionamento e as caracterı́sticas destes processos, nesta seção é descrita, detalhadamente, a proposta de seleção de dados de testes de um banco de dados de produção, onde o mecanismo de seleção de dados é um Algoritmo Evolucionário, mais precisamente, um Algoritmo Genético. Ele tem por finalidade realizar a extração de parte de um banco de dados real e dele gerar uma instância reduzida para testes. Nesta proposta, o mecanismo utilizado para a avaliação dos dados selecionados é Análise de Mutantes para instruções SQL. Conforme já apresentado em seções anteriores, verificou-se a importância da execução de testes em instruções SQL, com o objetivo de garantir a corretude de tais instruções. Neste contexto, considerando a aplicação dos testes apenas em instruções de consulta SQL, tem-se como entrada de dados para os testes, instâncias de um banco de dados. 4.1 Utilizando Algoritmos Genéticos na Seleção de Dados de Teste para Instruções SQL 62 Também, como já citado anteriormente, executar testes em ambiente de produção não é considerado uma boa prática e representa um sério risco para as aplicações em execução neste ambiente. Além disso, o custo computacional também pode ser muito elevado, visto a existência de bancos de dados muito grandes e instruções SQL bastante complexas. Sendo assim, considerando que existe um banco de dados da aplicação em produção, deseja-se utilizar parte deste banco de dados para a aplicação dos testes de instruções de consulta SQL. Ou seja, um banco de dados reduzido, com uma quantidade suficiente de tuplas e que tenha um comportamento igual ou muito próximo ao comportamento do banco de dados de produção, para determinadas instruções SQL. Para isso, faz-se uso de Algoritmos Evolucionários, mais precisamente Algoritmos Genéticos, tentando, de forma heurı́stica, otimizar a seleção de tuplas a partir de um banco de dados de produção existente. Contextualizando a proposta, considera-se a existência de alguns componentes constantes durante o processo realizado pelo AG, sendo que tais componentes são entrada para o algoritmo. São eles: BDP (Banco de Dados de Produção): Representa um banco de dados real. Dele serão selecionadas os dados para formar a instância do Banco de Dados de Teste (BDT ). BDT (Banco de Dados de Teste): Conjunto de tuplas selecionadas a partir do BDP. Representa uma solução para o problema. iSQL (Instrução SQL): No contexto da Análise de Mutantes trata-se do programa a ser testado. iSQL’ (Mutantes de iSQL): Conjunto de mutantes gerados a partir da iSQL. Além destes componentes, outro aspecto a ser definido para o inı́cio do algoritmo é o conjunto de parâmetros que define o funcionamento do processo. São eles: i : Define a quantidade de indivı́duos por população, ou seja, quantas soluções (instâncias de bancos de dados) serão representadas na população. s : Define o tamanho do indivı́duo, ou seja, a quantidade de tuplas de cada solução. g : Define a quantidade máxima de gerações permitida durante o processo. Trata-se de um dos critérios de parada Uma observação importante é que neste trabalho optou-se por fixar o tamanho dos indivı́duos, com o objetivo de simplificar a implementação dos algoritmos 4.1 Utilizando Algoritmos Genéticos na Seleção de Dados de Teste para Instruções SQL 63 (parâmento s). Futuramente, pode-se pensar em implementações com tamanhos variáveis tentando chegar a um conjunto o mais reduzido possı́vel de tuplas. O problema, então, pode ser definido matematicamente conforme a equação: Maximizar f (x(s) ) = MM(x(s) ) , ∀x(s) ∈ Ω T MNE (4-1) Sujeito a uma quantidade de tuplas igual a s e 0 ≤ f (x(s) ) ≤ 1 onde: - x(s) : uma solução para o problema com s tuplas; - Ω : conjunto de soluções possı́veis para o problema; - f (x(s) ) : escore de mutação da solução x(s) , quando aplicada a Análise de Mutantes; - MM(x(s) ) : total de mutantes mortos pela solução x(s) ; - TMNE : total de mutantes não equivalente gerados para a instrução SQL. Dado que o total de tuplas do PDB é Y e que s representa a quantidade de tuplas da solução x(s) , a quantidade de soluções possı́veis para o problema pode ser definida conforme a equação: q= Y! s!(Y − s)! (4-2) que se refere à combinação de Y tuplas s a s. Macro Especificação do Algoritmo: Antes de iniciar as etapas do algoritmo, foi considerada a existência de um BDP, um iSQL a ser testado e que foram gerados os mutantes iSQL’, através da ferramenta SQLMutation (TUYA et al., 2006), utilizando os operadores mais adequados para a instrução em questão. Também foi considerado que a estrutura do BDT foi gerada de acordo com o BDP e que os parâmetros para o algoritmo já foram definidos. A figura 4.1 mostra as etapas do AGCA que são descritas mais adiante. Etapas do Algoritmo: 1 - A partir do BDP são gerados aleatoriamente i indivı́duos de tamanho s para formar a população inicial P. Cada indivı́duo representa um subconjunto de dados do BDP para gerar o BDT. 2 - Realizar os passos abaixo para todos os indivı́duos da população corrente P: 4.1 Utilizando Algoritmos Genéticos na Seleção de Dados de Teste para Instruções SQL 64 Figura 4.1: Etapas do processo evolucionário do AGCA 2.1 - Limpar os dados do BDT e instanciá-lo de acordo com as especificações do indivı́duo: este processo extrai do BDP as tuplas mapeadas no indivı́duo e popula as tabelas do BDT ; 2.2 - Definir a aptidão do indivı́duo: Executar a iSQL e seus mutantes iSQL’, utilizando o BDT, e comparar os resultados. Calcular e armazenar o escore de mutação do indivı́duo. O valor do escore determina a aptidão do indivı́duo. 2.3 - Se ainda existirem indivı́duos não avaliados na população corrente, ir para o passo 2. 3 - Finalizada a avaliação de todos os indivı́duos da população corrente P, verificar se algum critério de parada foi atendido. Caso tenha sido, o algoritmo é 4.1 Utilizando Algoritmos Genéticos na Seleção de Dados de Teste para Instruções SQL 65 interrompido e o melhor indivı́duo da população é usado para gerar a solução (instância BDT ) final. Se não, uma nova população deve ser gerada no passo 4. 4 - Uma nova população é gerada através das operações de seleção de indivı́duos pais, cruzamento destes gerando os filhos e mutação. Uma nova população corrente P é formada por indivı́duos filhos gerados e por sobreviventes da população anterior (indivı́duos com melhores aptidão). Todo o processo se reinicia a partir do passo 2. A ideia é usar essa abordagem em diferentes tipos de teste de instruções SQL mas, provavelmente, os maiores benefı́cios serão nos testes de regressão, que são aplicados a cada nova versão do software ou a cada ciclo, onde todos os testes que já foram aplicados em versões anteriores devem ser executados novamente. Testes de regressão têm o objetivo de verificar se um erro anteriormente identificado volta a ocorrer em uma nova versão do programa, medindo assim a qualidade do software produzido. Todo este processo apresentado só é factı́vel se for possı́vel representar cromossomialmente uma instância de banco de dados. Esta representação deve permitir a execução das etapas e operadores do Algoritmo Genético e o mapeamento para a solução. Além disso, a maneira de representar a solução vai determinar as regras de extração dos dados do BDP para instanciar o BDT. 4.1.1 Modelo de Representação Escolher uma representação, mais simples possı́vel, que seja adequada a este cenário, ou seja, que possibilite representar instâncias de bancos de dados como indivı́duos, e que facilite as etapas do processo de evolução (a avaliação, o cruzamento e a mutação) é uma decisão estratégica, fundamental para o sucesso do algoritmo. Como o processo de evolução aqui proposto é referente a dados de teste e os componentes de teste são sequências de comandos SQL, os indivı́duos do processo de evolução representam instâncias ou subconjuntos do banco de dados de produção, para a execução da SQL que está sendo testada. Neste caso, eles devem contemplar informações das relações (tabelas) presentes na especificação da SQL. Como não foram encontradas, na literatura, sugestões de representação cromossomial que atendam a este contexto, inicialmente foram estudadas e avaliadas duas propostas de representação que fazem uso do produto cartesiano das tabelas envolvidas na instrução SQL. As duas propostas utilizam um cromossomo, implementado como um vetor. Apesar de ser uma estrutura de dados que possibilita o 4.1 Utilizando Algoritmos Genéticos na Seleção de Dados de Teste para Instruções SQL 66 armazenamento de tipos diversos, o vetor(cromossomo) proposto não possui os valores contidos no subconjunto que ele representa, ou seja, não é armazenado no cromossomo valores contidos nas tabelas, ao invés disso, são armazenadas referências às tuplas que devem ser extraı́das do Banco de Dados de Produção (BDP ) para formar o Banco de Dados de Teste (BDT ). Considerando que um banco de dados tenha diversas tabelas e que o modelo deve permitir a representação de mais de uma tabela, a referência dos dados deve conter não apenas informações sobre as tuplas mas também sobre a tabela de origem de cada tupla. Porém, identificou-se que armazenar esta informação no cromossomo significaria criar mais uma dimensão, o que é ruim por aumentar a complexidade na execução do AG. Para evitar essa situação, as primeiras propostas utilizam cromossomos com uma única dimensão, fazendo referência às tuplas do produto cartesiano das tabelas envolvidas. Isso é possı́vel pois cada tupla do produto cartesiano tem informações das tuplas de cada tabela de origem referenciada. Para viabilizar esta estratégia, cada tabela envolvida deve ter um atributo identificador único e, com relação ao produto cartesiano, ele deve satisfazer às seguintes regras: 1. Ser gerado somente com os atributos identificadores (chaves primárias) das tabelas envolvidas; 2. Ter um identificador único para cada tupla; 3. Ser gerado e armazenado em uma estrutura acessı́vel pelo SGBD, através de consultas SQL simples. Uma VIEW é um tipo de estrutura, presente na maioria dos SGBDs, que permite a implementação destas caracterı́sticas. O cromossomo passa a referenciar as tuplas do produto cartesiano, através do identificador criado, e a partir destas tuplas é possı́vel instanciar o BDT. Isso é viável pois cada tupla do produto cartesiano contém o identificador da tupla de cada tabela envolvida. Para melhor entendimento do uso do produto cartesiano, considerar a instrução SQL do código 4.1 e as tabelas envolvidas: Usuario (4.1), Local (4.2) e Grupo (4.3). É um simples exemplo de um modelo de dados onde um Usuário está lotado em um Local e faz parte de um determinado Grupo. Listing 4.1: Instrução SQL SELECT a . nomeusuario , b . nomelocal , c . nomegrupo , c . t i p o g r u p o FROM u s u a r i o a INNER JOIN l o c a l b on a . i d l o c a l = b . i d l o c a l INNER JOIN grupo c on a . i d g r u p o = c . i d g r u p o 4.1 Utilizando Algoritmos Genéticos na Seleção de Dados de Teste para Instruções SQL 67 Tabela 4.1: Tabela Usuario idusuario nomeusuario idlocal idgrupo 1 Maria 1 1 2 Vitor 2 2 3 Luiza 1 3 4 João 2 1 5 Paula 1 3 Tabela 4.2: Tabela Local idlocal nomelocal 1 Matriz 2 Filial Centro Tabela 4.3: Tabela Grupo idgrupo nomegrupo tipogrupo 1 Aluno Normal 2 Professor Elite 3 Coordenador Vip O produto cartesiano gerado a partir das tabelas do exemplo mostrado possui 30 tuplas, e pode ser visualizado na tabela 4.4. Para criar uma instância do BDT, o cromossomo deve referenciar, de alguma forma, quais tuplas do produto cartesiano devem ser consideradas. Cada tupla no produto cartesiano indicada pelo cromossomo possui os identificadores para as tabelas de origem relacionadas. O processo de extração consiste então em: selecionar estas tuplas do BDP e inseri-las nas tabelas equivalentes do BDT. Tendo definido como estrutura principal o produto cartesiano, o próximo passo é definir uma maneira de referenciar, no cromossomo, quais tuplas devem ser consideradas. Para esse contexto, foram desenvolvidas duas propostas de representação: uma onde é feito um mapeamento direto entre as tuplas e os genes do cromossomo, que foi denominada de Modelo de Mapeamento Cartesiano Direto; e outra onde é feito um mapeamento em que cada gene representa um intervalo de tuplas, denominada de Modelo de Mapeamento Cartesiano por Intervalos. Modelo de Mapeamento Cartesiano Direto Nesta proposta é utilizada uma codificação numérica onde cada posição do vetor contém um valor inteiro maior que zero e menor que n, sendo que n é a 4.1 Utilizando Algoritmos Genéticos na Seleção de Dados de Teste para Instruções SQL 68 Tabela 4.4: Tabela ProdutoCartesiano-Usuario X Local X Grupo id idusuario idlocal idgrupo 1 1 1 1 2 1 1 2 3 1 1 3 4 1 2 1 5 1 2 2 6 1 2 3 7 2 1 1 8 2 1 2 9 2 1 3 10 2 2 1 11 2 2 2 12 2 2 3 13 3 1 1 14 3 1 2 15 3 1 3 16 3 2 1 17 3 2 2 18 3 2 3 19 4 1 1 20 4 1 2 21 4 1 3 22 4 2 1 23 4 2 2 24 4 2 3 25 5 1 1 26 5 1 2 27 5 1 3 28 5 2 1 29 5 2 2 30 5 2 3 quantidade de tuplas do produto cartesiano. Este valor é o número (identificador) da tupla no produto cartesiano. E o cromossomo tem um tamanho limitado m, definido de acordo com o parâmetro s, conforme apresentado na Seção 4.1. 4.1 Utilizando Algoritmos Genéticos na Seleção de Dados de Teste para Instruções SQL 69 Desta forma, durante a etapa 2.1 do algoritmo apresentado na Seção 4.1, a instância do BDT é construı́da lendo-se gene por gene do cromossomo (indivı́duo), extraindo do BDP a tupla a qual aquele gene está relacionado e incluindo-a na tabela equivalente do BDT. Ao final do processo o BDT está instanciado com todas as tuplas que foram indicadas no cromossomo. Para evidenciar essa etapa de extração e instanciação do BDT, considerar o cromossomo ilustrado na tabela 4.5 que indica quais tuplas do produto cartesiano, apresentado na tabela 4.4, devem ser selecionadas. Tabela 4.5: Cromossomo - Modelo Mapeamento Cartesiano Direto 3 7 13 15 27 O primeiro gene do cromossomo tem o valor 3, ou seja, ele faz referência à tupla 3 do produto cartesiano. Esta tupla, por sua vez, está indicando que devem ser consideradas as seguintes informações para montar o BDT : a tupla da tabela Usuario cuja coluna idusuario = 1; a tupla da tabela Local cuja coluna idlocal = 1; e a tupla da tabela Grupo cuja coluna idgrupo = 3. A instância do BDT representada por este cromossomo teria as mesmas tabelas do BDP (Usuario, Local e Grupo) preenchidas como indicadas nas tabelas 4.6, 4.7 e 4.8. Tabela 4.6: Tabela Usuario BDT idusuario nomeusuario idlocal idgrupo 1 Maria 1 1 3 Luiza 1 3 5 Paula 1 3 Tabela 4.7: Tabela Local BDT idlocal nomelocal 1 Matriz Tabela 4.8: Tabela Grupo BDT idgrupo nomegrupo tipogrupo 1 Aluno Normal 3 Coordenador Vip 4.1 Utilizando Algoritmos Genéticos na Seleção de Dados de Teste para Instruções SQL 70 Modelo de Mapeamento Cartesiano por Intervalos Este modelo de representação trabalha com a codificação binária indicando se um conjunto de tuplas está presente ou não no banco de dados a ser gerado com as informações do indivı́duo. Os genes dos cromossomos indicam intervalos de tuplas do produto cartesiano. Esta representação depende do tamanho definido para o cromossomo e da quantidade de tuplas do produto cartesiano. Definido o tamanho do cromossomo, divide-se a quantidade de tuplas da tabela do produto cartesiano pelo tamanho do cromossomo, encontrando os intervalos de tuplas que cada gene do cromossomo representa, de acordo com o número total de tuplas da tabela. Tendo, por exemplo, uma tabela de 30 tuplas e um cromossomo de tamanho 6, cada gene representará um intervalo de 5 tuplas (30/6). Neste caso, cada gene x, sendo x = 1..6, representaria o intervalo de tuplas de ((x − 1) ∗ 5) + 1 até (x ∗ 5). Na tabela 4.10 é ilustrado um exemplo de representação de um indivı́duo. Tabela 4.9: Cromossomo - Modelo Mapeamento Cartesiano por Intervalos 1a5 6a10 11a15 16a20 21a25 26a30 1 0 1 0 0 0 Este cromossomo tem tamanho 6 e indica que as tuplas de 1 a 5 e de 11 a 15 da tabela do produto cartesiano estarão presentes no BDT, pois o valor do gene é igual a 1. A instância correspondente deve ser montada com as informações das chaves primárias das tabelas originais contidas na tabela de produto cartesiano, da mesma forma que foi explicado no modelo de mapeamento cartesiano direto. Avaliando as duas propostas chegou-se a conclusão que, a segunda não deve ser considerada pois percebeu-se que ela não consegue ter uma abrangência uniforme do espaço de busca. Como as tuplas são representadas nos cromossomos através de intervalos, sempre existirão tuplas vizinhas juntas em um mesmo indivı́duo. Isso só não acontece se o tamanho do cromossomo for equivalente à quantidade de tuplas do produto cartesiano, o que não é viável computacionalmente. Com relação à primeira proposta, não foi encontrado o problema citado no parágrafo anterior mas, assim como na segunda, existe a dependência da geração do produto cartesiano das tabelas envolvidas da instrução SQL. Isto pode ser considerado um problema quando se trata de tabelas muito grandes, por ser um processo com custo operacional muito alto, o que inviabiliza a solução. Segundo Ramez e N. (2010), o produto cartesiano é gerado através de todas as combinações possı́veis entre as tuplas das tabelas, ou seja, cada tupla de uma tabela deve ser combinada com todas as tuplas das demais tabelas. Tendo uma 4.1 Utilizando Algoritmos Genéticos na Seleção de Dados de Teste para Instruções SQL 71 instrução SQL com 3 tabelas, a primeira com N tuplas, a segunda com M tuplas e a terceira com P tuplas, o produto cartesiano será uma tabela com N ∗ M ∗ P tuplas. É uma operação muito cara dependendo do tamanho e da quantidade de tabelas envolvidas. Sendo assim, partiu-se para o estudo de outro modelo de representação que não utiliza o produto cartesiano. A esse novo modelo foi dado o nome de Mapeamento Direto e é apresentado a seguir. Modelo de Mapeamento Direto Este modelo faz uso de um cromossomo, implementado como um vetor onde cada posição representa diretamente uma tupla de uma das tabelas do Banco de Dados de Produção (BDP ). Para isso, cada tabela deve ter uma identificação (ou um número), assim como cada tupla de cada tabela deve ter uma chave única numérica. Desta forma, a instância do BDT é construı́da lendo-se gene por gene do cromossomo (indivı́duo), extraindo do BDP a tupla que aquele gene representa e incluindo-a na tabela equivalente no BDT. Para tanto, foi necessário definir como um pré-requisito, a obrigatoriedade de que todas as tabelas do Banco de Dados possuam um atributo identificador sequencial e único. Considerando as tabelas utilizadas para esclarecer os modelos anteriores: • 1 - Usuario; • 2 - Local ; • 3 - Grupo; Um exemplo de um indivı́duo que representa um banco de dados de teste (BDT ), de 5 tuplas extraı́das do banco de dados de produção (BDP ), pode ser mostrado na tabela 4.10. Neste indivı́duo, o primeiro gene indica que a tupla com identificador 1 da tabela Grupo (3) deverá ser selecionada para constar no BDT, o segundo gene indica que a tupla com identificador 5 da tabela Usuario (1) também deverá ser selecionada, o terceiro gene representa a tupla 3 da tabela Usuario (1) e o quarto e quinto genes indicam que as tuplas 1 e 2 da tabela Local (2) também deverão ser selecionadas para constar no BDT. Tabela 4.10: Cromossomo - Modelo Mapeamento Direto 3:1 1:5 1:3 2:1 2:2 No modelo apresentado, como consequência dos operadores, um cromossomo pode ter valores repetidos em seus genes. Ou seja, uma mesma tupla pode ser 4.2 Projeto dos Algoritmos 72 referenciada dentro do mesmo indivı́duo mais de uma vez. Nestes casos, o processo de instanciação do BDT considera somente um dos valores, não existindo então a possibilidade de tuplas duplicadas na tabela. Desta forma, podem existir instâncias com diferentes tamanhos, porém, nunca maior do que o valor calculado de acordo com o parâmetro s. Este último modelo não apresenta os problemas encontrados com a utilização dos modelos anteriores. É possı́vel uma representação uniforme do espaço de busca e não é necessária a geração do produto cartesiano, como nos modelos anteriores, diminuindo bastante o custo computacional da solução. Ele permite a representação de um banco de dados, independente da quantidade de tabelas e tuplas. Também não foram encontrados problemas quanto aos operadores genéticos, podendo ser facilmente aplicados com a utilização deste modelo de representação. Sendo assim, ele foi o escolhido para a implementação dos Algoritmos Genéticos utilizados nos experimentos deste trabalho. 4.2 4.2.1 Projeto dos Algoritmos Algoritmo Genético Canônico - AGCA Foi implementado o Algoritmo Genético Canônico (AGCA) utilizando o macro-processo definido na Seção 3.3, considerando o modelo de representação selecionado, em linguagem JAVA com banco de dados MYSQL. O algoritmo contempla as seguintes classes, conforme o diagrama da figura 4.2: • Config - Classe responsável pela inicialização de todos os parâmetros utilizados nos experimentos; • Util - Classe utilitária, responsável pelos registros de logs em geral; • Ag - Classe principal para representar a execução do AG; • Populacao - Classe que representa cada geração do AG com um conjunto de indivı́duos; • Individuo - Classe que representa cada indivı́duo de cada geração/população, com um conjunto de genes e seu fitness; • Gene - Classe que representa cada gene de um indivı́duo que, conforme a representação utilizada, é composto por uma identificação da tabela e uma identificação da tupla. Foram implementadas as seguintes operações: • Seleção dos Pais - Operador : SL 4.2 Projeto dos Algoritmos 73 Figura 4.2: Diagrama de Classes do AG Esta operação seleciona pais para gerarem novos indivı́duos no processo de reprodução. É realizada através de um torneio: seleção aleatória de 2 indivı́duos sendo o melhor escolhido para pai; e da seleção aleatória de mais dois indivı́duos sendo o melhor escolhido para mãe; • Cruzamento - Operador : CZ Esta operação é realizada de acordo com a taxa de cruzamento, fornecida como parâmetro, que indica a probabilidade de haver ou não o cruzamento. Ela combina genes dos indivı́duos pais (pai e mãe) selecionados na operação anterior gerando um novo indivı́duo (filho). É selecionado aleatoriamente o ponto de corte do cromossomo, ou seja, o ponto onde o cromossomo será dividido mantendo os genes do pai na primeira parte do cromossomo e os genes da mãe, na segunda parte. • Mutação - Operador : MT Esta operação é realizada de acordo com a taxa de mutação, fornecida como parâmetro, que indica a probabilidade de haver ou não a alteração de cada 4.2 Projeto dos Algoritmos 74 gene do cromossomo. Sendo assim, um gene pode sofrer alteração ou não, e caso ocorra, é selecionada aleatoriamente uma nova tupla e o gene é modificado trazendo diversidade genética para o indivı́duo filho. • Substituição da População - Operador : SP Esta operação seleciona os indivı́duos para compor a nova geração e substituir a população. É utilizado o elitismo para manter os melhores indivı́duos sendo a quantidade de indivı́duos a serem mantidos fornecida como parâmetro. Além dos melhores indivı́duos mantidos, a nova geração é formada pelos filhos gerados nas operações anteriores. A implementação completa de todas as classes do Algoritmo Genético Canônico, construı́do nesse trabalho, está descrita no apêndice A. 4.2.2 Algoritmo Genético com Grupo de Eleitos - AGGE Este algoritmo é baseado na utilização de um grupo de indivı́duos eleitos, que não são os melhores indivı́duos mas apresentam caracterı́sticas que os destacam perante os melhores indivı́duos, conforme citado no Capı́tulo 3, Seção 3.4. Ele foi projetado para, dentro do contexto do problema apresentado, permitir que indivı́duos com baixo escore de mutação, mas que conseguem matar mutantes não mortos pelos melhores indivı́duos da população, continuem nas próximas gerações. Ele utiliza os mesmos operadores do processo de evolução do AGCA. A diferença é na etapa de substituição da população onde não apenas os n melhores indivı́duos são mantidos na próxima geração, mas também os indivı́duos que fazem parte do grupo de eleitos. Foram feitas alterações no AGCA para trabalhar com essa nova abordagem, como proposto por Louzada et al. (2012), que foi denominada de Algoritmo Genético com Grupo de Eleitos (AGGE). Além da função objetivo, que é o escore de mutação, também são avaliados os mutantes mortos pelo indivı́duo em relação aos mutantes mortos pelos n melhores indivı́duos da geração corrente. Caso o indivı́duo tenha conseguido matar um mutante não morto pelos n melhores indivı́duos, ele passa a compor o grupo de eleitos. No final, a nova geração será composta pelos n melhores indivı́duos, pelos indivı́duos do grupo de eleitos e pelos demais indivı́duos originados pelos operadores genéticos (SL, CZ e MT). O apêndice B descreve as alterações na implementação do AGCA realizadas para contemplar o grupo dos eleitos. 4.2.3 INVITRO Novas alterações no AGCA foram implementadas para aplicar o método IVF/GA, citado no Capı́tulo 3, Seção 3.5. O novo algoritmo foi denominado de 4.3 Considerações Finais 75 INVITRO e seu objetivo é auxiliar os AGs com bons indivı́duos, a partir de um melhor tratamento das estruturas presentes nas populações de pais (CAMILOJUNIOR, 2010). Conforme citado anteriormente, o INVITRO é um algoritmo auxiliar, executado em um fluxo paralelo aos Algoritmos Genéticos, que recombina cromossomos para maximizar o aproveitamento das informações presentes nos indivı́duos. Ele pode gerar indivı́duos mais aptos que serão inseridos na nova geração e manipulados pelos AGs na iteração seguinte. O seu fluxo segue a Coleta, Manipulação Genética, Seleção e Transferências de bons indivı́duos, conforme explicado no Capı́tulo 3, Seção 3.5. Foi definida a divisão do material genético (cromossomo) em 4 partes para auxiliar a variação genética das operações. Foi utilizado o operador EAR-P, definido em Camilo-Junior e Yamanaka (2011) e citado na Seção 3.5, que aplica a alteração genética de parte do cromossomo e também a recombinação. Foi implementado o método INVITRO com as seguintes etapas: • Coleta : Seleção do pai (melhor indivı́duo do AG) e das mães (os 20% melhores da população do AG); • Manipulação Genética: – Divisão do cromossomo em 4 partes; – Para cada mãe: ∗ Aplicação de alteração genética, ou seja, seleção aleatória de uma das 4 partes e mutação de todos os genes da parte selecionada; ∗ Recombinação do indivı́duo pai com a mãe gerando 4 filhos que irão competir, ou seja, seleção aleatória de uma parte da mãe e as demais do pai, para cada filho; – Seleção : Seleção do melhor filho gerado pela etapa anterior. Sendo o filho melhor que o pai, substituição do pai pelo filho e repetição do processo até que não hajam filhos melhores que o pai; • Transferência : O melhor indivı́duo encontrado é transferido para o AG no lugar do melhor corrente. O apêndice C descreve as alterações na implementação do AGCA, realizadas para contemplar o processo de Fertilização In Vitro (INVITRO). 4.3 Considerações Finais Este capı́tulo apresentou a abordagem proposta mostrando como utilizar Algoritmos Genéticos na seleção de dados de teste para instruções SQL. Definiu 4.3 Considerações Finais 76 as etapas para o processo evolucionário a ser utilizado na proposta, assim como seus parâmetros. Mostrou os modelos de representação avaliados, para utilização na implementação dos algoritmos, com suas caracterı́sticas e exemplos de utilização. Explicou o motivo da seleção do Modelo de Mapeamento Direto para representar um banco de dados como um indivı́duo no contexto da Computação Evolucionária. E finalizando, apresentou o projeto dos 3 algoritmos implementados e utilizados nos experimentos: o Algoritmo Genético Canônico, o Algoritmo Genético com Grupo de Eleitos e o INVITRO. CAPÍTULO 5 Experimentos e Resultados Este capı́tulo discorre sobre todas as etapas dos experimentos realizados neste trabalho. Inicia com a apresentação do ambiente para a execução dos experimentos: o modelo do banco de dados escolhido; as etapa de construção e carga da base de dados; e o modelo do banco de dados utilizado para armazenar os resultados dos experimentos e as demais informações. Apresenta várias instruções SQL que foram analisadas, as etapas e os critérios que levaram à escolha das instruções candidatas para a realização dos experimentos. Detalha a execução dos experimentos com cada abordagem: seleção aleatória, utilizando o AGCA, utilizando o AGGE e utilizando o INVITRO. E finaliza com uma avaliação dos resultados. 5.1 Ambiente Para iniciar os experimentos foi preciso montar um ambiente próprio onde fosse possı́vel a realização dos testes. Para isso, foram avaliadas algumas bases de dados existentes na literatura, assim como bases de dados de sistemas reais. Decidiuse por utilizar a base de dados COMPANY proposta por Navathe e Elsmari no livro Fundamentals of Database Systems (RAMEZ; N., 2010), utilizada em diversos trabalhos acadêmicos na área de banco de dados e apresentada na Figura 5.1. Após a escolha do modelo de dados, foi construı́do o ambiente com a estrutura do banco definido e foi criado um conjunto de scripts, com dados aleatórios, para a carga da base de dados a ser utilizada nos experimentos, considerando todas as restrições do modelo proposto por Ramez e N. (2010). Foi criada uma documentação para a execução dos scripts de criação e carga dos dados, com um passo a passo, para garantir que todas as regras e restrições propostas no modelo fossem seguidas durante a criação e alimentação das tabelas. Sendo assim, após a execução dos scripts de carga dos dados, o banco de dados apresentou um total de 241.000 tuplas, distribuı́das conforme indicado na Tabela 5.1. Cada tabela do banco de dados recebeu uma identificação com um valor numérico, 5.1 Ambiente 78 Figura 5.1: Modelo de Dados do Banco de Dados COMPANY como citado no Capı́tulo anterior, para possibilitar o mapeamento no modelo de representação escolhido. Tabela 5.1: Configuração do Banco de Dados COMPANY Identificação Tabela 1 employee 2 department 3 project 4 works on 5 dependent 6 dept locations Quantidade de Tuplas 100.000 1.000 5.000 75.000 50.000 10.000 Além do banco de dados de produção (BDP ), também foi construı́do o banco de dados de teste (BDT ), com a mesma estrutura do BDP, para receber as tuplas selecionadas durante os experimentos. O BDT é um banco vazio, que é alimentado a cada experimento com algumas tuplas selecionadas do BDP, e usado para a realização dos testes de mutação. Também foi construı́do um banco de dados, denominado Banco de Dados de Mutantes (BDM ), para registrar os resultados de todos os experimentos executados, possibilitando a geração de dados estatı́sticos e análise de resultados. O BDM é composto pelas seguintes tabelas: (i) instruções; (ii) mutantes de cada instrução; (iii) experimentos realizados; (iv) indivı́duos gerados em cada experimento e geração do AG; e (v) mutantes mortos por cada individuo. O modelo do banco de dados está apresentado na Figura 5.2. Considerando o modelo apresentado (COMPANY ), um exemplo de um indivı́duo de tamanho 100 pode ser mostrado na Figura 5.3. Este indivı́duo representa 5.1 Ambiente 79 Figura 5.2: Modelo de Dados do Banco de Dados de Experimentos a inclusão da tupla 23 da tabela department (2) no BDT, da tupla 1445 da tabela employee (1), da tupla 34 da tabela dependent (5), etc. E assim as 100 tuplas do cromossomo estarão presentes no subconjunto de dados a ser instanciado no BDT. Figura 5.3: Cromossomo que mapeia um indivı́duo de tamanho 100 do Banco de Dados COMPANY Com a base de dados criada e populada, deu-se inı́cio à escolha das instruções SQL a serem utilizadas nos experimentos. Inicialmente foram selecionadas instruções complexas que cobriam todas as tabelas da base de dados, mas verificouse que haveria problemas referentes à integridade referencial durante a evolução das instruções no processo do AG. Considerando a complexidade introduzida para o AG, já que seria necessário tratar esses problemas durante o processo de evolução, optou-se por simplificar o contexto e trabalhar com uma única tabela inicialmente, deixando para um momento futuro o tratamento de instruções que envolvam mais de uma tabela. 5.2 Instruções 5.2 80 Instruções Foi selecionada a tabela EMPLOYEE por ser uma tabela com informações relevantes de cadastro de funcionários e ter um número grande de tuplas (100.000). Foram elaboradas instruções SQL de vários nı́veis de complexidade, com relação à quantidade de cláusulas, condições e operações. Algumas foram retiradas do livro de Ramez e N. (2010) e outras foram geradas considerando os dados existentes na tabela e tentando aumentar a complexidade, introduzindo mais cláusulas e condições, no total de 35 instruções. Para cada instrução foram gerados os mutantes através da ferramenta SQLMutation, descrita por Tuya et al. (2006). Foram avaliadas as quantidades de mutantes gerados e a quantidade de categorias e tipos de operadores cobertos por cada instrução. A Tabela 5.2 mostra as instruções SQL com a quantidade total de mutantes gerados, e a Tabela 5.3 mostra a avaliação da cobertura dos mutantes por instrução, considerando todas as categorias e tipos de operadores que a ferramenta SQLMutation implementa. I35 I32 I33 I34 I26 I27 I28 I29 I30 I31 I7 I8 I9 I10 I11 I12 I13 I14 I15 I16 I17 I18 I19 I20 I21 I22 I23 I24 I25 Id I1 I2 I3 I4 I5 I6 Instrução Mutantes SELECT DNO, COUNT(1), MIN(SALARY), MAX(SALARY), AVG(SALARY) FROM EMPLOYEE GROUP BY DNO 61 SELECT * FROM EMPLOYEE ORDER BY DNO 1 SELECT CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME) AS NOME FROM EMPLOYEE ORDER BY NOME 28 SELECT CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME) AS NOME, SALARY FROM EMPLOYEE WHERE SALARY > 10000 ORDER BY NOME 52 SELECT CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME) AS NOME FROM EMPLOYEE WHERE DNO > 500 ORDER BY NOME 43 SELECT CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME) AS NOME, DNO, BDATE, SEX, SALARY, ADDRESS, SUPERSSN AS GERENTE FROM EMPLOYEE ORDER 74 BY NOME SELECT CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME) AS NOME, BDATE FROM EMPLOYEE ORDER BY NOME 28 SELECT CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME) AS NOME FROM EMPLOYEE WHERE SEX = ’F’ ORDER BY NOME 56 SELECT LNAME, COUNT(1) FROM EMPLOYEE GROUP BY LNAME 15 SELECT DNO, CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME) AS NOME, BDATE FROM EMPLOYEE ORDER BY DNO,NOME 40 SELECT DNO, CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME) AS NOME, SALARY FROM EMPLOYEE ORDER BY DNO,NOME 47 SELECT CONCAT(LNAME,’,’,FNAME,’ ’,MINIT) AS NOME, SALARY FROM EMPLOYEE ORDER BY LNAME 40 SELECT DNO, SEX, CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME) AS NOME FROM EMPLOYEE ORDER BY DNO,SEX,NOME 52 SELECT DNO, CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME) AS NOME, ADDRESS FROM EMPLOYEE ORDER BY DNO,NOME 50 SELECT DNO, CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME) AS NOME FROM EMPLOYEE WHERE SEX = ’F’ ORDER BY DNO,NOME 70 SELECT MONTHNAME(DATE(BDATE)), COUNT(1) FROM EMPLOYEE GROUP BY MONTHNAME(DATE(BDATE)) 9 SELECT DNO, MONTHNAME(DATE(BDATE)), COUNT(1) FROM EMPLOYEE GROUP BY DNO, MONTHNAME(DATE(BDATE)) 17 SELECT DNO, MONTHNAME(BDATE), COUNT(1) FROM EMPLOYEE WHERE MONTH(BDATE) = 12 GROUP BY DNO, MONTHNAME(BDATE) 30 SELECT * FROM EMPLOYEE WHERE ADDRESS LIKE ’%SP%’ 20 SELECT DNO, MIN(BDATE), MAX(BDATE) FROM EMPLOYEE GROUP BY DNO 22 SELECT FNAME, LNAME FROM EMPLOYEE WHERE ADDRESS LIKE ’%GOIANIA%’ 39 SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 2000) AND DNO=5 45 SELECT FNAME, LNAME FROM EMPLOYEE WHERE ADDRESS IS NULL 20 SELECT SSN, FNAME, MINIT, LNAME, BDATE, SEX, ADDRESS FROM EMPLOYEE WHERE SALARY > 1000 AND SALARY < 2000 AND SEX = ’F’ 109 SELECT T.SEX, MAX(T.SALARY), MIN(T.SALARY), AVG(T.SALARY) FROM (SELECT SSN, FNAME, SEX, SALARY FROM EMPLOYEE WHERE (SALARY 278 >= 800 AND SALARY < 10000 AND SEX = ’M’) OR (SALARY > 10000 AND SEX = ’F’) OR (SALARY > (0.7*(SELECT AVG(SALARY) FROM EMPLOYEE WHERE ADDRESS NOT LIKE ’%- SP%’ )))) T GROUP BY T.SEX ORDER BY T.SEX SELECT DNO, AVG(SALARY) FROM EMPLOYEE GROUP BY DNO 22 SELECT DNO, SEX, COUNT(SSN) FROM EMPLOYEE GROUP BY DNO, SEX 32 SELECT DNO, COUNT(SSN) FROM EMPLOYEE GROUP BY DNO 21 SELECT DNO, CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME) FROM EMPLOYEE WHERE DNO IN (1, 10, 100) ORDER BY DNO, FNAME 54 SELECT DNO, CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME), ADDRESS FROM EMPLOYEE WHERE ADDRESS LIKE ’%SP%’ ORDER BY DNO, FNAME 81 SELECT DNO, CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME), SALARY FROM EMPLOYEE WHERE SALARY > (SELECT AVG(SALARY) FROM EMPLOYEE) 73 ORDER BY DNO SELECT CONCAT(FNAME,,MINIT,,LNAME), ROUND(DATEDIFF(NOW(),DATE(BDATE))365,0) AS IDADE FROM EMPRESA.EMPLOYEE ORDER BY IDADE 40 SELECT DNO, CONCAT(FNAME,,MINIT,,LNAME), BDATE, SALARY FROM EMPRESA.EMPLOYEE WHERE SEX = ’M’ ORDER BY DNO, SALARY 79 SELECT CONCAT(FNAME,,MINIT,,LNAME), ROUND(DATEDIFF(NOW(),DATE(BDATE))365,0) AS IDADE FROM EMPLOYEE WHERE SEX = ’F’ AND 100 ROUND(DATEDIFF(NOW(),DATE(BDATE))/365,0) >= 40 ORDER BY IDADE SELECT CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME), ROUND(DATEDIFF(NOW(),DATE(BDATE))365,0) AS IDADE , SALARY, ADDRESS FROM EMPLOYEE 191 WHERE SEX = ’M’ AND ROUND(DATEDIFF(NOW(),DATE(BDATE))/365,0) >= 70 AND SALARY > 10000 AND ADDRESS LIKE ’%- SP%’ ORDER BY IDADE Tabela 5.2: Instruções Iniciais 5.2 Instruções 81 I1 I2 I3 I4 I5 I6 I7 I8 I9 I10 I11 I12 I13 I14 I15 I16 I17 I18 I19 I20 I21 I22 I23 I24 I25 I26 I27 I28 I29 I30 I31 I32 I33 I34 I35 Inst. X X X X X X X X X X X X X X X X X X X X X X X SEL JOI X SUB X X X X X X X X X SC GRU X X X X X X X X X X X AGR X UNI X X X X X X X X ORD X X X X X X X X X X X X ROR X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X Operadores: Categorias e Tipos OR LCR UOI ABS AOR BTW LKE X X NLF X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X NLO X X X X X X X X X NL NLS NLI X X X X X X X X X X X X X X X X X X X X X X X X X X X IRC X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X IR IRT IRP X Tabela 5.3: Tabela de Instruções com a Cobertura de Operadores e Categorias X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X IRH X Total Cobertura Operadores Tipos Categ. 8 4 1 1 2 1 9 4 5 3 7 4 4 2 7 4 3 2 5 2 8 4 7 4 6 3 6 3 8 4 2 1 3 2 7 4 7 4 3 2 8 4 12 4 4 3 11 3 15 4 7 4 8 4 6 3 7 2 9 4 11 4 8 3 12 4 12 4 14 4 5.2 Instruções 82 5.3 Experimentos Aleatórios 83 De acordo com os resultados apresentados na Tabela 5.2, que mostra as instruções e a quantidade de mutantes gerados, e na Tabela 5.3, que apresenta a cobertura de operadores por cada instrução, foram selecionadas 15 instruções levando em consideração esses dois critérios: quantidade de mutantes gerados e cobertura de operadores e de categorias de cada instrução. Procurou-se selecionar instruções de diferentes nı́veis de complexidade, para avaliar os comportamentos no que diz respeito a matar mutantes, e também aquelas que conseguem cobrir uma maior quantidade de operadores. Foram escolhidas, então, as instruções I1, I4, I21, I22, I24, I25, I26, I27, I29, I30, I31, I32, I33, I34, I35 que serviram de base para as próximas etapas. 5.3 Experimentos Aleatórios Com o objetivo de avaliar as instruções e identificar os mutantes difı́ceis de serem mortos, foram realizados os testes de mutação para cada uma das instruções selecionadas tendo como dado de teste o banco de dados de produção (BDP ). Os resultados foram registrados para servir como referência para as comparações com os resultados dos testes usando os bancos de dados de teste (BDT ). Inicialmente, foram realizados 1.100 experimentos aleatórios para cada instrução considerando BDTs com 0,1% e 1% do total de tuplas do BDP. A quantidade de experimentos com BDTs maiores foi menor devido ao tempo gasto nas execuções quando se aumenta a quantidade de tuplas. Os experimentos foram divididos da seguinte forma: • 1.000 experimentos com BDTs de 100 tuplas, ou seja, seleção aleatória de 100 tuplas do BDP, correspondendo a 0,1% do total das tuplas; • 100 experimentos com BDTs de 1.000 tuplas, ou seja, seleção aleatória de 1.000 tuplas, correspondendo a 1% do BDP. A Tabela 5.4 mostra os resultados por instrução: os escores de mutação do experimento usando o BDP, o maior escore de mutação usando os BDTs, a média e o desvio padrão. Avaliando os valores da Tabela 5.4, pode-se observar que algumas instruções já apresentaram bons resultados nos experimentos aleatórios, com escores de mutação dos BDT muito próximos aos valores encontrados usando o BDP como dado de teste. Exemplos dessa situação são as instruções I1, I27, I31 e I32 que foram avaliadas como fáceis de matar mutantes pois não apresentaram cláusulas restritivas sendo que qualquer seleção de dados do BDP consegue informações suficientes para matar os mutantes. Além dessas, as instruções I4, I30, I33 e I34 também conseguiram matar a maioria dos mutantes pois apresentaram condições pouco restritivas para 5.3 Experimentos Aleatórios 84 Tabela 5.4: Resultados Iniciais de Experimentos Aleatórios (escores) Instrução BDP I1 I4 I21 I22 I24 I25 I26 I27 I29 I30 I31 I32 I33 I34 I35 0,869 0,923 0,949 0,844 0,927 0,741 0,909 0,781 1 1 0,932 0,975 0,937 0,970 0,942 BDT Aleatórios - 100 tuplas Melhor Média Desvio Padrão 0,852 0,852 0 0,865 0,865 0 0,923 0,328 0,314 0,844 0,484 0,215 0,881 0,788 0,197 0,741 0,639 0,043 0,864 0,864 0 0,781 0,781 0 0,852 0,102 0,264 0,975 0,974 0,004 0,870 0,855 0,007 0,975 0,975 0 0,937 0,927 0,013 0,960 0,950 0,013 0,869 0,485 0,362 BDT Aleatórios - 1.000 tuplas Melhor Média Desvio Padrão 0,852 0,852 0 0,865 0,865 0 0,923 0,333 0,311 0,356 0,280 0,079 0,899 0,892 0,004 0,712 0,688 0,026 0,864 0,864 0 0,781 0,781 0 0,981 0,900 0,054 0,988 0,988 0 0,890 0,859 0,015 0,975 0,975 0 0,937 0,937 0 0,970 0,967 0,010 0,921 0,910 0,007 este BDP, ou seja, existem várias tuplas que satifazem às condições (SEX=’M’, SEX=’F’, ADDRESS LIKE ’%SP%’, SALARY > 10000). Logo, para esses casos, fica mais provável a seleção aleatória das tuplas necessárias para compor o BDT. Para essas instruções não se justifica a utilização de métodos heurı́sticos. Já outras instruções apresentaram valores inferiores, principalmente considerando a média e o desvio padrão, o que justificaria a utilização de uma heurı́stica. A partir dessas conclusões e avaliando os mutantes não mortos por cada instrução, foram selecionadas algumas instruções que justificam o uso de algoritmos evolucionários. Não foi encontrado nenhum estudo sobre operadores difı́ceis de matar mutantes para instruções SQL. Através de uma análise empı́rica dos resultados e dos mutantes não mortos, algumas instruções foram identificadas como mais difı́ceis de matar mutantes. A maior dificuldade para matar os mutantes, apresentada em todas essas instruções foi o fator da restritividade nas cláusulas where. Quanto mais restritiva é a instrução, mais difı́cil encontrar as tuplas que satisfazem àquelas condições para compor os dados de teste. A partir desta análise e das instruções selecionadas foram derivadas outras instruções: algumas semelhantes às iniciais com condições mais restritivas; outras também semelhantes às iniciais porém com mais predicados, para torná-las mais complexas; e outras criadas aleatoriamente. Todas elas foram criadas tentando cobrir mais operadores de mutação SQL, de todas as categorias. Chegou-se a um total de 11 instruções para avaliação dos primeiros experimentos com uso do AG. Foram selecionadas, como candidatas, as instruções I24, I26, I29, I31 e I35 e foram criadas novas instruções derivadas (I36, I37, I38, I39, I40, I41, I42, I43, I44, I45, I46) , tentando cobrir as 4 categorias de operadores (SC, OR, NL, IR). Para cada uma delas foram gerados os seus mutantes utilizando a ferramenta SQLMutation. 5.3 Experimentos Aleatórios 85 As novas instruções são apresentadas na Tabela 5.5, com a quantidade de mutantes gerados e a quantidade de operadores de mutação e categorias cobertos. I46 I45 I44 I42 I43 I41 I36 I37 I38 I39 I40 SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 8000) AND DNO=5 SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 8000) AND DNO=5 AND SEX=’M’ SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 1500) AND DNO=5 SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 2000) AND DNO IN (5,128,573,574,700,995) SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 2000) AND DNO IN (5,128,573,574,700,995) ORDER BY SALARY SELECT * FROM EMPLOYEE WHERE SALARY > (select avg(salary) from employee where address not like ’%- SP%’ AND SEX = ’F’) AND (DNO IN (573,574) OR DNO < 50) AND SEX=’M’ AND ADDRESS like ’%- SP’ SELECT SEX, COUNT(*), AVG(SALARY), MAX(SALARY), MIN(SALARY) FROM EMPLOYEE SELECT SEX, COUNT(*), AVG(SALARY), MAX(SALARY), MIN(SALARY) FROM EMPLOYEE WHERE DNO<10 AND ADDRESS LIKE ’%- GO’ AND SEX IS NOT NULL GROUP BY SEX SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 1500) AND DNO IN (5, 573, 995) AND ADDRESS NOT LIKE ’%- SP%’ ORDER BY SALARY SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 1500) AND DNO IN (5, 573, 995) AND ADDRESS NOT LIKE ’%- SP%’ UNION SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 5000 AND 8000) AND DNO IN (3,200,800) AND ADDRESS NOT LIKE ’%- RJ%’ ORDER BY dno, SALARY SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 3000) AND DNO IN (5, 573, 995) AND ADDRESS NOT LIKE ’%- SP%’ and sex = ’M’ union SELECT * FROM EMPLOYEE WHERE SALARY <= 3000 AND DNO IN (3,19,20,200,800) AND ADDRESS NOT LIKE ’%- RJ%’ and sex = ’M’ ORDER BY dno, SALARY Instrução Tabela 5.5: Novas Instruções Derivadas 296 236 84 50 105 189 45 67 45 99 101 Mutantes 14 13 12 6 14 13 Operadores Cobertos 11 11 11 11 11 4 4 4 4 4 4 Categorias Cobertas 4 4 4 4 4 5.3 Experimentos Aleatórios 86 5.3 Experimentos Aleatórios 87 Novamente, foram realizados experimentos aleatórios com as novas instruções derivadas tentando chegar a um conjunto menor de instruções para prosseguir os experimentos. Também foi realizado o teste de mutação, para cada instrução, usando o BDP como entrada de dados, e uma bateria de experimentos com BDTs construı́dos através de seleção aleatória. Foram 1.000 experimentos com BDTs de 100 tuplas, 100 experimentos com BDTs de 1.000 tuplas em um total de 1.100 experimentos para cada instrução. A Tabela 5.6 mostra os resultados das novas instruções. Tabela 5.6: Resultados Iniciais de Experimentos Aleatórios das Instruções Derivadas (Escores) Instrução BDP I36 I37 I38 I39 I40 I41 I42 I43 I44 I45 I46 0,844 0,851 0,844 0,949 0,941 0,746 0,840 0,905 0,893 0,928 0,939 BDT Aleatórios - 100 tuplas Melhor Média Desvio Padrão 0,666 0,217 0,169 0,134 0,123 0,037 0,356 0,169 0,082 0,111 0,104 0,114 0,119 0,061 0,035 0,503 0,188 0,118 0,820 0,820 0,000 0,219 0,179 0,034 0,155 0,113 0,044 0,097 0,061 0,018 0,078 0,064 0,019 BDT Aleatórios - 1.000 tuplas Melhor Média Desvio Padrão 0,800 0,360 0,243 0,701 0,246 0,171 0,311 0,202 0,075 0,232 0,124 0,038 0,446 0,189 0,137 0,608 0,415 0,176 0,840 0,824 0,008 0,819 0,501 0,282 0,536 0,232 0,163 0,407 0,213 0,115 0,409 0,198 0,143 Avaliando os resultados, tanto das instruções iniciais como das derivadas, verificou-se que algumas instruções justificam o uso de métodos heurı́sticos por apresentarem valores dos escores de mutação, nos experimentos com BDTs aleatórios, inferiores ao escore de mutação do BDP. Apesar das instruções do primeiro grupo não apresentarem valores tão distantes do valor do BDP, decidimos selecionar as 5 instruções com piores resultados deste grupo. Do segundo grupo, selecionamos as instruções que não conseguiram em nenhum dos experimentos encontrar um valor maior que 80% do BDP. Esse critério foi aleatório tentando reduzir o número de instruções para os próximos experimentos. Sendo assim, foram selecionadas, para os experimentos usando o AG, com o objetivo de melhorar os escores e chegar mais perto ao escore do BDP, as seguintes instruções do primeiro grupo: I24, I26, I29, I31, I35. Também foram selecionadas as instruções I38, I39, I40, I44, I45 e I46 do segundo grupo. A Tabela 5.7 sintetiza as 11 instruções selecionadas. Instrução 1 I24 SELECT SSN, FNAME, MINIT, LNAME, BDATE, SEX, ADDRESS FROM EMPLOYEE WHERE SALARY > 1000 AND SALARY < 2000 AND SEX = ’F’ 2 I26 SELECT DNO, AVG(SALARY) FROM EMPLOYEE GROUP BY DNO 3 I29 SELECT DNO, CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME) FROM EMPLOYEE WHERE DNO IN (1, 10, 100) ORDER BY DNO, FNAME 4 I31 SELECT DNO, CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME), SALARY FROM EMPLOYEE WHERE SALARY > (SELECT AVG(SALARY) FROM EMPLOYEE) ORDER BY DNO 5 I35 SELECT CONCAT(FNAME,’ ’,MINIT,’ ’,LNAME), ROUND(DATEDIFF(NOW(), DATE(BDATE)) / 365,0) AS IDADE , SALARY, ADDRESS FROM EMPLOYEE WHERE SEX = ’M’ AND ROUND(DATEDIFF(NOW(),DATE(BDATE)) / 365,0) >= 70 AND SALARY > 10000 AND ADDRESS LIKE ’%- SP%’ ORDER BY IDADE 6 I38 SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 1500) AND DNO=5 7 I39 SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 2000) AND DNO IN (5,128,573,574,700,995) 8 I40 SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 2000) AND DNO IN (5,128,573,574,700,995) ORDER BY SALARY 9 I44 SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 1500) AND DNO IN (5, 573, 995) AND ADDRESS NOT LIKE ’%- SP%’ ORDER BY SALARY 10 I45 SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 1500) AND DNO IN (5, 573, 995) AND ADDRESS NOT LIKE ’%- SP%’ UNION SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 5000 AND 8000) AND DNO IN (3,200,800) AND ADDRESS NOT LIKE ’%RJ%’ ORDER BY DNO, SALARY 11 I46 SELECT * FROM EMPLOYEE WHERE (SALARY BETWEEN 1000 AND 3000) AND DNO IN (5, 573, 995) AND ADDRESS NOT LIKE ’%- SP%’ AND SEX = ’M’ UNION SELECT * FROM EMPLOYEE WHERE SALARY <= 3000 AND DNO IN (3,19,20,200,800) AND ADDRESS NOT LIKE ’%- RJ%’ AND SEX = ’M’ ORDER BY DNO, SALARY Tabela 5.7: Instruções Finais para os Experimentos usando AG 296 236 45 99 101 84 191 22 54 73 Mutantes 109 5.3 Experimentos Aleatórios 88 5.4 Experimentos com AGCA 89 Além dos 1.100 experimentos aleatórios realizados para cada instrução, ainda foram realizados mais 10 experimentos para cada uma das 11 instruções, aumentando a quantidade de tuplas para 10.000 tuplas, que corresponde a 10% da tabela EMPLOYEE do BDP. Estes resultados serão usados em avaliações posteriores usando o AG. 5.4 Experimentos com AGCA Foram iniciados os experimentos com o AGCA considerando 0,1% da tabela original EMPLOYEE, ou seja, seleção de 100 tuplas de um total de 100.000 tuplas. Para este cenário, o espaço de busca corresponde à combinação de 100.000 tuplas, 100 a 100, conforme definido anteriormente na equação 4-2. Pode ser representado pela equação: 100.000! (5-1) q= 100!(100.000 − 100)! encorajando assim o uso de Algoritmos Genéticos ou de outra técnica baseada em heurı́sticas. Logo, considerando o espaço de busca e a equação 4-1, o problema de otimização em questão pode ser formulado matematicamente como: Maximizar f (x(100) ) = MM(x(100) ) , ∀x(100) ∈ Ω T MNE (5-2) Sujeito a uma quantidade de tuplas igual a 100 e 0 ≤ f (x(100) ) ≤ 1 onde: - x(100) : uma solução para o problema com 100 tuplas; - Ω : conjunto de soluções possı́veis para o problema no espaço de busca representado pela equação 5-1; - f (x(100) ) : escore de mutação da solução x(100) , quando aplicada a Análise de Mutantes; - MM(x(100) ) : mutantes mortos pela solução x(100) ; - TMNE : total de mutantes não equivalente gerados para a instrução SQL. Foram selecionadas aleatoriamente as instruções 6 (I38), 8 (I40), 10 (I45) para realização de experimentos, variando as taxas de cruzamento e mutação, com o objetivo de definir os melhores valores para os parâmetros do AGCA. Primeiro foi fixada a taxa de cruzamento em 80% e variadas as taxas de mutação em 1%, 5% e 10%, e realizados 10 experimentos para cada uma. Deste conjunto, os resultados usando a taxa de mutação de 10% foram os melhores. Sendo assim, fixou-se esta 5.4 Experimentos com AGCA 90 taxa de mutação (10%) e variou-se a taxa de cruzamento em 70% e 90%, realizando mais 10 experimentos para cada opção. Foram totalizados 150 experimentos sendo 50 por instrução, para avaliação das taxas do AGCA. Avaliando os resultados, foram selecionadas a taxa de mutação de 10% e a taxa de cruzamento de 80%. Definidas as taxas, foram realizados mais 10 experimentos usando AGCA para cada uma das 11 instruções selecionadas, com os parâmetros apresentados na Tabela 5.8. Tabela 5.8: Parâmetros AGCA Parâmetro Valor Taxa de Mutação 10% por gene Taxa de Cruzamento 80% Ponto de Cruzamento 1 ponto de cruzamento aleatório Quantidade de Gerações 50 Quantidade de Indivı́duos 100 Quantidade de Genes 100 Elitismo 2 melhores indivı́duos A Tabela 5.9 mostra os resultados dos experimentos usando AGCA para as 11 instruções, assim como os resultados dos experimentos aleatórios. Para cada conjunto de experimentos, foi destacado o melhor resultado, a média, o desvio padrão e a informação sobre o valor percentual da média em relação ao escore de mutação ideal (do BDP ). Esta informação representa o quanto, em percentual, aquele conjunto de experimentos atingiu, em média, o objetivo. Com relação ao tempo de execução do algoritmo, não foi possı́vel trabalhar com essa informação visto que os experimentos foram realizados em ambientes computacionais diferentes. O projeto do algoritmo foi feito de forma que indivı́duos que representam a mesma solução não tenham seus cálculos de fitness refeitos durante o mesmo experimento, tentando reduzir o custo de processamento do algoritmo. A Tabela 5.9 também destaca, no último campo, a informação da quantidade média de avaliações de indivı́duos realizadas nos experimentos usando AGCA, mostrando que houve uma redução de custo e tempo. Ou seja, sem a reutilização do fitness de indivı́duos que representam a mesma solução, a cada experimento o cálculo do fitness seria executado 5.000 vezes (100 indivı́duos em 50 gerações), caso não fosse encontrado o escore igual ao do BDP antes do término do experimento. Pode-se observar que, em nenhuma das instruções, a média de avaliações fitness atingiu o valor de 5.000, mostrando que os experimentos tiveram o reaproveitamento das informações de fitness já calculados. Instr. BDP BDTs Aleatórios-100 tuplas Aleatórios-1.000 tuplas Aleatórios-10.000 tuplas Melhor Média Desvio Melhor Média Desvio Melhor Média Desvio Padrão % Padrão % Padrão 1 (I24) 0,927 0,881 0,788 0,197 85 0,899 0,892 0,004 96,2 0,899 0,886 0,008 2 (I26) 0,909 0,864 0,864 0 95 0,864 0,864 0 95 0,864 0,864 0 3 (I29) 1 0,852 0,102 0,264 10,2 0,981 0,900 0,054 90 0,982 0,982 0 4 (I31) 0,932 0,870 0,855 0,007 91,7 0,890 0,859 0,015 92,2 0,904 0,885 0,017 5 (I35) 0,942 0,869 0,485 0,362 51,5 0,921 0,910 0,007 96,6 0,932 0,928 0,006 6 (I38) 0,844 0,356 0,169 0,082 20 0,311 0,202 0,075 23,9 0,800 0,404 0,142 7 (I39) 0,949 0,111 0,104 0,114 11 0,232 0,124 0,038 13,1 0,788 0,622 0,655 8 (I40) 0,941 0,119 0,061 0,035 6,5 0,446 0,189 0,137 20,1 0,852 0,593 0,630 9 (I44) 0,893 0,155 0,113 0,044 12,7 0,536 0,232 0,163 26 0,714 0,470 0,214 10 (I45) 0,928 0,097 0,061 0,018 6,6 0,407 0,213 0,115 23 0,746 0,553 0,138 11 (I46) 0,939 0,078 0,064 0,019 6,8 0,409 0,198 0,143 21,1 0,686 0,576 0,122 Média 0,928 0,477 0,333 36,1 0,733 0,508 54,3 0,833 0,706 % - Percentual da média em relação ao escore do BDP Média Avaliações - Quantidade média de avaliações de indivı́duos realizadas nos experimentos % 95,6 95 98,2 95 98,5 47,9 16,3 16,9 52,6 59,6 61,3 75,7 0,917 0,864 0,982 0,932 0,937 0,844 0,778 0,505 0,750 0,809 0,730 0,823 Melhor Tabela 5.9: Resultados dos Experimentos AGCA das Instruções Finais Selecionadas (Escores) Usando AGCA-100 tuplas Média Desvio Média Padrão % Avaliações 0,904 0,012 97,5 3976 0,864 0 95 3994 0,982 0 98,2 3943 0,906 0,021 97,2 3012 0,932 0,004 98,9 4001 0,662 0,217 78,4 3959 0,682 0,067 71,9 3968 0,405 0,106 43 3947 0,610 0,202 68,3 3982 0,736 0,071 79,3 3988 0,699 0,041 74,4 3971 0,762 82 3885 5.4 Experimentos com AGCA 91 5.4 Experimentos com AGCA 92 Apesar dos resultados obtidos usando o AGCA terem sido melhores do que a média dos resultados usando seleção aleatória, pode-se observar, comparando os melhores resultados obtidos de cada abordagem, 4 situações/grupos diferentes conforme Tabela 5.10. Tabela 5.10: Conjunto de resultados (AGCA) Situação Mostraram resultados semelhantes ao método de seleção aleatória de 100 tuplas Mostraram uma evolução comparado ao método de seleção aleatória de 100 tuplas Mostraram ganhos consideráveis conseguindo, a maioria, obter resultados melhores dos que os encontrados no método de seleção aleatória de 10.000 tuplas Mostraram bons resultados com AGCA alcançando o escore do BDP Instruções 2 1; 3 5; 7; 8; 9; 10; 11 4; 6 A instrução 2, usando o AGCA, chegou a resultados iguais aos aleatórios, o que pode ser certificado pela média e desvio padrão dos valores encontrados; as instruções 1 e 3 apresentaram uma melhora nos resultados usando o AGCA comparado à seleção aleatória de 100 tuplas, tanto nos melhores escores atingidos como nas médias dos valores; as instruções 5, 7, 8, 9, 10 e 11 conseguiram obter valores bem maiores do que os aleatórios com 100 tuplas, e a maioria ainda superou os resultados dos experimentos aleatórios usando 10.000 tuplas; e as instruções 4 e 6 também mostraram bons resultados usando o AGCA, ainda melhores do que os resultados usando seleção aleatória de 10.000 tuplas, que corresponde a 10% do BDP, e conseguiram alcançar o escore do BDP. Olhando sob a perspectiva da média dos valores encontrados e do percentual dessa média em relação ao BDP, pode-se observar que em todas as instruções conseguiu-se, com o AGCA, elevar esse percentual. Ou seja, com a utilização do AGCA as soluções encontradas (BDTs) conseguiram representar um percentual muito maior do BDP, em relação aos aleatórios, no que diz respeito a matar mutantes e revelar defeitos. Comparando também os métodos utilizados, em termos gerais, observase que a média do escore do BDP é de 0,920, considerando todas as instruções. Utilizando o método aleatório com 100 tuplas conseguiu-se alcançar 36,1% deste valor (considerado o valor ideal), com 1.000 tuplas, 54,3%, com 10.000 tuplas, 75,7% e usando o AGCA, 82%. Com esses resultados, percebeu-se que vale a pena continuar com a pesquisa visto que a maioria das instruções obteve melhora nos escores usando o AGCA. Para dar continuidade aos experimentos, restringiu-se o número de instruções. Três 5.4 Experimentos com AGCA 93 instruções foram selecionadas: a instrução 6 foi selecionada com o objetivo de verificar a estabilidade das outras abordagens, mesmo já tendo apresentado bons resultados com o AGCA; e as instruções 10 e 11 foram selecionadas por serem instruções mais complexas, apresentando uma maior quantidade de cláusulas e condições, e por mostrarem resultados distantes do escore do BDP, ou seja, foram consideradas instruções difı́ceis de matar mutantes. Trabalhando com a instrução 11, outras variações foram realizadas com relação a quantidade de tuplas, para avaliação do comportamento do AGCA. Tendo já os resultados do AGCA com indivı́duos de tamanho 100, foram realizados mais 10 experimentos para indivı́duos de tamanhos 50, 150 e 200, totalizando mais 30 experimentos. De acordo com a equação 4-2, os espaços de busca para esses experimentos 100.000! 100.000! 100.000! , 150!(100.000−150)! e 200!(100.000−200)! . foram, respectivamente, 50!(100.000−50)! Percebe-se, através da Tabela 5.11 e do Gráfico 5.4 que os resultados melhoram com o aumento da quantidade de genes/tuplas do indivı́duo. Tal comportamento, é esperado visto que quanto maior o número de tuplas do BDT, mais próximo ele está do BDP. Tabela 5.11: Resultados do AGCA com Variação de Tamanho de Cromossomo Instrução 11 Tamanho do Cromossomo 50 genes 100 genes 150 genes 200 genes Melhor 0,658 0,780 0,814 0,865 Média 0,623 0,724 0,762 0,778 Desvio Padrão 0,043 0,041 0,045 0,041 Apesar dos bons resultados encontrados usando o AGCA, observaram-se algumas situações que motivaram a implementação de alterações no Algoritmo Genético Canônico (AGCA). Através dos registros dos experimentos no banco de dados de mutantes (BDM ) verificou-se que alguns indivı́duos, considerados ruins, conseguem matar mutantes não mortos pelos melhores indivı́duos. Decidiu-se então, trabalhar com outra abordagem para manter estes indivı́duos, o Algoritmo Genético com Grupo de Eleitos (AGGE) explicado no Capı́tulo 4, Seção 4.2.2. A aplicação dessa nova proposta tenta atingir os seguintes objetivos: • (a) encontrar resultados (escores) iguais ou melhores que os encontrados pelos métodos aleatórios para as instruções do segundo grupo (Tabela 5.10); • (b) encontrar resultados melhores para as instruções do terceiro grupo (Tabela 5.10); • (c) manter os resultados das instruções do quarto grupo (Tabela 5.10). 5.5 Experimentos com AGGE 94 Figura 5.4: Avaliação da Instrução 11 com AGCA 5.5 Experimentos com AGGE Para avaliar as taxas de cruzamento e mutação a serem usadas como parâmetros, novamente foi selecionada a instrução 11 e realizados experimentos variando as taxas. Primeiro foi fixada a taxa de cruzamento em 80% e realizados 10 experimentos para cada uma das taxas de mutação 1%, 5% e 10%, totalizando 30 experimentos. Encontrada a melhor taxa de mutação (10%), foram realizados mais 10 experimentos para cada uma das taxas de cruzamento de 70% e 90%, totalizando 20 experimentos. Sendo assim, em um total de 50 experimentos, foi encontrada a taxa de mutação de 10% e a taxa de cruzamento de 90% como as melhores para o AGGE. Após esta etapa foram realizadas 10 experimentos usando AGGE para cada uma das outras instruções selecionadas (6 e 10), com as melhores taxas encontradas (taxa de mutação = 10% e taxa de cruzamento = 90%), totalizando mais 20 experimentos. Foram realizados então 10 experimentos usando AGGE, para cada instrução selecionada (6,10 e 11), com os parâmetros apresentados na Tabela 5.12 e seguindo o mesmo processo evolucionário mostrado no AGCA, diferenciando apenas a forma de realizar o elitismo e manter os indivı́duos durante as gerações. Para confirmar os resultados e atingir um número maior de testes, foi realizada uma nova bateria de experimentos aleatórios para as 3 instruções. Foram 5.000 BDTs de 100 tuplas e 500 BDTs de 1.000 tuplas selecionados aleatoriamente, totalizando 16.500 experimentos, sendo 5.500 por instrução. Estes novos resultados, assim como os resultados do AGGE são mostrados na Tabela 5.13. 5.5 Experimentos com AGGE 95 Tabela 5.12: Parâmetros AGGE Taxa de Mutação Taxa de Cruzamento Ponto de Cruzamento Quantidade de Gerações Quantidade de Indivı́duos Quantidade de Genes Elitismo 10% por gene 90% 1 ponto de cruzamento aleatório 50 100 100 O melhor indivı́duo e os indivı́duos que mataram mutantes não mortos pelos melhores Tabela 5.13: Experimentos Aleatórios e usando AGGE BDT Aleatórios Aleatórios Instrução BDP (100 tuplas) (1.000 tuplas) Melhor Média Desvio % Melhor Média Desvio 6 0,844 0,778 0,163 0,065 19,3 0,800 0,276 0,112 10 0,928 0,538 0,076 0,052 8,2 0,640 0,229 0,120 11 0,939 0,476 0,064 0,043 6,8 0,568 0,168 0,110 Média 0,904 0,597 0,101 11,4 0,669 0,224 % - Percentual da média em relação ao escore do BDP % 32,7 24,7 17,9 25,1 AGGE (100 tuplas) Melhor Média Desvio 0,844 0,576 0,204 0,839 0,791 0,044 0,807 0,758 0,036 0,830 0,708 - % 68,2 85,2 80,7 78,1 Avaliando os resultados na Tabela 5.13, pode-se observar uma melhora significativa com a aplicação do AGGE. Nos testes da instrução 6 conseguiu-se alcançar, mais de uma vez, o mesmo escore alcançado pelo BDP com apenas 0,1% da quantidade de tuplas do banco de produção. Assim, houve uma melhora na média dos resultados dos experimentos comparados aos aleatórios. Para as instruções 10 e 11, os resultados aplicando AGGE não alcançaram o escore de mutação do BDP mas foram muito superiores aos experimentos aleatórios, mesmo quando comparado com os de 1.000 tuplas. Os melhores resultados alcançados nos experimentos aleatórios para a instrução 10 foram: 0,538 usando um BDT de 100 tuplas; 0,640 usando um BDT de 1.000 tuplas; e 0,764 usando um BDT de 10.000 tuplas (nos experimentos anteriores). Com a aplicação do AGGE foi alcançado o valor de 0,839 com uma média de 0,791 e um baixo desvio padrão de 0,044. Sendo assim, considerando a média e o desvio padrão, pode-se concluir que todos os valores obtidos nos experimentos com AGGE foram melhores que os aleatórios, mesmo os que usaram BDTs de 10.000 tuplas, que representa 10% do BDP. Esses resultados podem ser confirmados, também, sob a perspectiva do percentual da média dos valores em relação ao escore do BDP que foi de 85,2%, ou seja, em média, os experimentos conseguiram chegar a 85,2% do escore ideal. O mesmo aconteceu com a instrução 11 cujos melhores resultados dos experimentos aleatórios foram: 0,476 usando um BDT de 100 tuplas; 0,568 usando um BDT de 1.000 tuplas; e 0,686 usando um BDT de 10.000 tuplas (nos experimentos anteriores). Já com a aplicação do AGGE conseguiu-se alcançar um escore 5.5 Experimentos com AGGE 96 de mutação igual a 0,807 usando um BDT com apenas 100 tuplas. Também apresentou uma média maior que os experimentos aleatórios e um baixo desvio padrão (0,038) significando que todos os resultados alcançaram valores aproximados e maiores que os aleatórios. Observando também o campo de percentual (%), na Tabela 5.13, podemos ver que, em média, os experimentos usando AGGE para a instrução 11 conseguiram chegar a 80,7% do escore ideal, o do BDP. Analisando novamente, de forma generalizada, pode-se observar que: considerando a média geral de escore do BDP de 0,904 para as 3 instruções, o método aleatório de 100 tuplas alcançou apenas 11,4% deste valor, o de 1.000 tuplas alcançou 25,1% e o método utilizando AGGE chegou a 78,1%, confirmando o ganho na aplicação desta abordagem em todas as instruções. Uma outra informação levantada foi com relação à quantidade de indivı́duos que constituiam o grupo de eleitos ao final de cada experimento. O comportamento das instruções foi o seguinte: • Instrução 6 : finalizou os experimentos com uma média de 0,9 indivı́duo no grupo de eleitos; • Instrução 10 : finalizou os experimentos com uma média de 3,1 indivı́duo no grupo de eleitos; • Instrução 11 : finalizou os experimentos com uma média de 3,8 indivı́duo no grupo de eleitos; Com esta observação pode-se perceber que uma outra opção seria retornar como solução, ao final do experimento, não só o melhor indivı́duo mas o conjunto de todas as tuplas do melhor indivı́duo e dos demais indivı́duos do grupo de eleitos. Desta forma, aumentaria a quantidade de mutantes mortos e o escore de mutação. Esta informação justifica o estudo de outras formas de avaliações multi-objetivas em trabalhos futuros. Também tentando avaliar o comportamento do AGGE quanto à quantidade de tuplas selecionadas, e trabalhando com a instrução 11, foram realizados mais 10 experimentos para indivı́duos de tamanhos 50, 150 e 200, totalizando mais 30 experimentos. O comportamento, de acordo com o Gráfico 5.5, foi semelhante ao AGCA quando variados os tamanhos dos indivı́duos, ou seja, os resultados melhoram com o aumento da quantidade de genes/tuplas do indivı́duo. No entanto a melhoria foi menor entre 50 genes e 200 genes, especificamente entre 100 genes e 200 genes. Através de uma análise empı́rica, pode-se concluir que o AGGE conseguiu, com menos quantidade de tuplas, resultados melhores que os experimentos aleatórios. Isso pode ser justificado pela utilização do material genético dos indivı́duos do grupo de eleitos visto que eles são mantidos nas próximas gerações, levando informações 5.6 Experimentos com INVITRO 97 de como matar mutantes não mortos pelos melhores indivı́duos. Sendo assim, eles ajudam a melhorar os descendentes das gerações seguintes. Tabela 5.14: Resultados do AGGE com Variação de Tamanho de Cromossomo Instrução 11 Tamanho do Cromossomo Melhor Média Desvio Padrão 50 genes 0,807 0,702 0,051 100 genes 0,865 0,786 0,051 150 genes 0,855 0,813 0,028 200 genes 0,865 0,830 0,029 Figura 5.5: Avaliação da Instrução 11 com AGGE Apesar dos bons resultados obtidos usando o AGGE, foi aplicado um terceiro método, inspirado na Fertilização in Vitro, que foi denominado de INVITRO, para as mesmas 3 instruções. O objetivo foi enriquecer ainda mais o trabalho e novamente tentar melhorar os resultados. A próxima seção mostra os experimentos e os resultados utilizando o método INVITRO. 5.6 Experimentos com INVITRO Utilizou-se, como parâmetros para os experimentos com INVITRO, os melhores valores das taxas de cruzamento e mutação encontrados usando AGCA, assim como o mesmo parâmetro do elitismo. Foram realizados 10 experimentos, para cada uma das variações de tamanho do cromossomo (50,100,150 e 200), para as três instruções selecionadas (6,10 e 11), 5.6 Experimentos com INVITRO 98 totalizando 120 experimentos. Os resultados são apresentados na Tabela 5.15 e nos gráficos referentes a cada instrução 5.6, 5.7 e 5.8. Tabela 5.15: Resultados do INVITRO com Variação de Tamanho de Cromossomo Instrução 6 Instrução 10 Instrução 11 Tamanho do Melhor Média Desvio Melhor Média Desvio Melhor Média Desvio Cromossomo Padrão Padrão Padrão 50 genes 0,8 0,431 0,132 0,784 0,691 0,07 0,723 0,686 0,030 100 genes 0,844 0,764 0,129 0,848 0,792 0,033 0,814 0,761 0,032 150 genes 0,844 0,689 0,196 0,886 0,803 0,036 0,848 0,806 0,035 200 genes 0,844 0,727 0,18 0,881 0,798 0,04 0,862 0,824 0,027 Figura 5.6: Avaliação da Instrução 6 com INVITRO Nos três casos pode-se verificar que existe uma melhora nos resultados a medida que se aumenta o tamanho do cromossomo, similar ao comportamento do AGCA e do AGGE. Isso se justifica pois quanto maior o número de tuplas mais informações existem no BDT para atender os requisitos especı́ficos (restrições) para execução das instruções SQL. A Tabela 5.16 apresenta os resultados das três abordagens, utilizando BDTs de 100 tuplas, para uma melhor visualização e comparação. 5.6 Experimentos com INVITRO Figura 5.7: Avaliação da Instrução 10 com INVITRO Figura 5.8: Avaliação da Instrução 11 com INVITRO 99 BDP Aleatórios Melhor Média Desvio % 6 0,844 0,778 0,163 0,065 19,3 10 0,928 0,538 0,076 0,052 8,2 11 0,939 0,476 0,064 0,043 6,8 Média 0,904 0,597 0,101 11,4 % - Percentual da média em relação ao escore do BDP Instrução Melhor 0,844 0,809 0,730 0,794 BDT - 100 tuplas AGCA Média Desvio % Melhor 0,662 0,217 78,4 0,844 0,736 0,071 79,3 0,839 0,699 0,041 74,4 0,807 0,699 77,4 0,830 AGGE Média Desvio 0,576 0,204 0,791 0,044 0,758 0,036 0,708 - Tabela 5.16: Resultados Aleatórios, AGCA, AGGE, INVITRO % 68,2 85,2 80,7 78,1 Melhor 0,844 0,848 0,814 0,835 INVITRO Média Desvio 0,764 0,129 0,792 0,033 0,761 0,032 0,772 - % 90,5 85,3 81,0 85,6 5.6 Experimentos com INVITRO 100 5.7 Considerações Finais 101 Com relação à instrução 6 pode-se observar que em todas as três abordagens os resultados atingiram o escore do BDP. Todas tiveram resultados semelhantes quando comparados os melhores valores encontrados, sendo que as médias e os desvios tiveram pequenas variações que podem ser justificadas pelas etapas aleatórias das abordagens. Na abordagem INVITRO, comparando com a mesma quantidade de genes dos experimentos AGCA e AGGE (100 genes), observa-se uma melhora na média e no desvio padrão, que pode ser justificado pelo uso do algoritmo paralelo que visa melhorar os resultados encontrados no AG. As instruções 10 e 11, apesar dos resultados terem sido bem parecidos nas três abordagens, pode-se observar, comparando o uso de BDTs de 100 genes, que o AGGE conseguiu resultados um pouco melhores que o AGCA, e que o INVITRO conseguiu resultados ainda melhores que o AGGE. Isto pode ser observado tanto na melhor solução quanto na média e no desvio padrão. Tal comportamento pode ser justificado pelas caracterı́sticas dos algoritmos: o AGGE mantém indivı́duos com algumas informações relevantes para as próximas gerações melhorando os resultados; e o INVITRO também melhora o resultado encontrado no AGCA. Avaliando de forma generalizada a aplicação das 3 abordagens, e considerando o valor médio de escore do BDP de 0,904 para as 3 instruções, pode-se concluir, empiricamente, que o método INVITRO apresentou melhores resultados, conseguindo alcançar, em média, 85,6% deste valor. O AGGE alcançou 78,1% e o AGCA, 77,4%. 5.7 Considerações Finais Este capı́tulo apresentou o ambiente criado para execução dos experimentos: o modelo do banco de dados escolhido, as etapa de construção e carga da base de dados; o modelo do banco de dados utilizado para armazenar os experimentos e as demais informações. Discorreu sobre todas as etapas que levaram à escolha das instruções candidatas, a realização dos experimentos utilizando o Algoritmo Genético Canônico, as avaliações dos resultados e as alterações na implementação do AG gerando dois novos algoritmos: um novo algoritmo que considerou um grupo de indivı́duos eleitos, que matam mutantes não mortos pelos melhores, chamado de Algoritmo Genético com Grupo de Eleitos (AGGE); e um algoritmo inspirado na Fertilização In Vitro, que foi denominado de INVITRO. Foram realizados diversos experimentos aleatórios para cada instrução, e experimentos utilizando as 3 abordagens heurı́sticas. Os dados estatı́sticos dos experimentos mostram que as abordagens heurı́sticas tiveram melhores resultados, no geral, comparadas aos métodos aleatórios. De acordo com a Tabela 5.16, e 5.7 Considerações Finais 102 considerando o valor médio dos escores de mutação do BDP, o AGCA apresentou um aumento de 66% (77,4% - 11,4%), na média, dos valores atingidos pelos últimos experimentos aleatórios usando o mesmo número de tuplas (100 tuplas). Com o AGGE houve um aumento de 66,7% (78,1% - 11,4%), considerando apenas as 3 instruções selecionadas, e com o INVITRO houve um aumento de 74,2% (85,6% 11,4%). Pode-se concluir, empiricamente, que os resultados são bem melhores que os aleatórios, para essas instâncias, e que o método INVITRO, de uma forma geral, atingiu valores mais próximos do valor ótimo (escore do BDP ). De acordo com as médias gerais dos experimentos, considerando as 3 instruções finais, o método INVITRO obteve a melhor média, ou seja, chegou mais próximo do valor considerado como referência para o escore de mutação (0,904). Conseguiu atingir 85,6% deste valor, aumentando em 8,2% o resultado do AGCA que foi de 77,4%. A aplicação do AGGE conseguiu alcançar 78,1%, aumentando bem pouco o resultado do AGCA, na média, mas conseguiu resultados (soluções) melhores. Isso pode ser observado pelos melhores valores encontrados e pela redução do desvio padrão dos experimentos. Todas essas avaliações estão diretamente ligadas à adequação dos dados de teste selecionados (dos BDTs). Quanto maior o valor do escore de mutação do BDT, mais adequado são os dados selecionados para a realização dos testes e mais eficiente é a abordagem. CAPÍTULO 6 Conclusões e Trabalhos Futuros Este trabalho apresentou a aplicação de uma abordagem que visa selecionar um conjunto de dados, de um banco de dados de produção, que seja adequado para testes de determinadas instruções de consulta SQL. Durante o processo, foram identificadas instruções que justificam o uso da abordagem, principalmente as que apresentaram cláusulas com condições mais restritivas com relação aos dados existentes no BDP. Foi proposto a utilização de Algoritmos Genéticos para a seleção dos dados e da Análise de Mutantes para a avaliação dos dados selecionados, sendo o escore de mutação usado como função objetivo, para guiar o processo de evolução dos Algoritmos Genéticos. Primeiro foi realizado um estudo dos assuntos envolvidos com o tema: Teste de Software, Análise de Mutantes para SQL, Search-Based Software Testing e Computação Evolucionária. Foi introduzida a ideia da pesquisa com apresentação dos principais trabalhos encontrados na literatura, relacionados com o tema. Foram apresentadas informações sobre a área de Teste de Software, importância, aplicações, tipos de teste, técnicas e critérios. O critério de Análise de Mutantes, da técnica de teste baseada em defeitos foi detalhado, mostrando também a aplicação deste na linguagem SQL, assim como foram apresentados vários trabalhos relacionados, encontrados na literatura. Os conceitos e aplicações de meta-heurı́sticas na área de teste de software foram apresentados além de uma fundamentação sobre os algoritmos evolucionários com ênfase nos Algoritmos Genéticos e algumas variações de implementação relevantes para esta dissertação. Após a fase de fundamentação, no Capı́tulo 4 foi mostrada toda a aplicação da abordagem proposta: a explicação da abordagem; as etapas do processo evolucionário utilizado; os modelos de representação avaliados e o projeto dos algoritmos. No Capı́tulo 5 foi apresentado: o ambiente criado; o banco de dados usado para os experimentos; o banco de dados criado para armazenar o resultado dos experimentos; as instruções selecionadas e todas as etapas e critérios que levaram a 6.1 Contribuições 104 esta seleção; e finalmente os experimentos realizados com todos os métodos propostos e a avaliação dos resultados. Para a avaliação dos resultados, foram realizados experimentos aleatórios e experimentos com 3 implementações de algoritmos diferentes: o Algoritmo Genético Canônico (AGCA), o Algoritmo Genético com Grupo de Eleitos (AGGE) e o INVITRO. A implementação destas variações foram originadas, durante a pesquisa, a partir da análise dos resultados e da identificação de problemas que poderiam ser solucionados com alterações na abordagem inicial. De acordo com as análises estatı́sticas dos resultados, realizadas no Capı́tulo 5, pode-se observar que as propostas apresentadas nesta dissertação conseguiram resultados relevantes na área de teste de instruções SQL. Mostraram que, através da utilização de métodos heurı́sticos na seleção de dados de teste, é possı́vel obter conjuntos de tuplas mais adequados para os testes de instruções SQL do que através de seleções aleatórias. Os escores de mutação obtidos com a Análise de Mutantes SQL foram usados para avaliar os conjuntos de tuplas selecionados e através deles, é possı́vel concluir que os algoritmos evolucionários conseguiram chegar mais perto dos valores considerados ótimos para os dados de teste. 6.1 Contribuições Algumas contribuições deste trabalho estão elencadas abaixo: 1. Publicação do artigo completo Moncao et al. (2013b) no evento CEC 2013; 2. Publicação do resumo Moncao et al. (2013a), como poster, no evento GECCO 2013; 3. Identificação de trabalhos relacionados à área de Análise de Mutantes para instruções SQL; 4. Identificação de trabalhos relacionados à área Search-Based Software Testing; 5. Os resultados dos experimentos realizados contribuı́ram para: • Encontrar modelos de representação para instâncias de banco de dados no contexto da Computação Evolucionária; • Mostrar que é possı́vel a aplicação dos Algoritmos Evolucionários na seleção de dados de teste para instruções SQL; • Mostrar que a aplicação dos Algoritmos Evolucionários neste contexto apresentou resultados bem melhores comparados aos experimentos com seleção aleatória; 6.2 Trabalhos Futuros 105 • Mostrar que a utilização da implementação do método INVITRO e do Algoritmo Genético com Grupo de Eleitos (AGGE) obteve melhores resultados comparados ao Algoritmo Genético Canônico (AGCA); 6.2 Trabalhos Futuros De acordo com os resultados obtidos e considerando as crı́ticas e sugestões dos revisores dos artigos submetidos e publicados, identificamos alguns desdobramentos para a pesquisa: • Estender os experimentos para instruções SQL mais complexas, que utilizam mais de uma tabela; • Desenvolver estudos visando uma estratégia incremental de testes baseados em matar mutantes mais difı́ceis; • Providenciar um benchmarking para realização dos experimentos, visto a complexidade e tempo gastos para a preparação do ambiente; • Estender os experimentos para outro ambiente, um banco de dados real de grande escala; • Aplicar outros tipos de algoritmos evolucionários no mesmo contexto; • Explorar mais a implementação INVITRO, com outros operadores, tentando obter melhores resultados; • Usar outro critério de avaliação diferente do escore de mutação ou combinado a ele, procurando explorar funções multi-objetivos; • Caracterizar melhor os tipos instruções que justificam ou não o uso de métodos heurı́sticos; • Estudar outros tipos de operadores genéticos especı́ficos para o problema; • Desenvolver implementações com um tamanho variável do indivı́duo, possibilitando uma otimização melhor para o problema; • Estudar a utilização das informações dos indivı́duos que sobraram no grupo de eleitos como uma forma de indicar a quantidade de tuplas necessária para matar mais mutantes, através de uma avaliação multi-objetiva. Bibliografia AHN, C. W. Advances in Evolutionary Algorithms. 1nd. ed. [S.l.]: Springer, 2006. ISBN 978-3-540-31758-6. ALI, S.; BRIAND, L.; HEMMATI, H.; PANESAR-WALAWEGE, R. K. A systematic review of the application and empirical investigation of search-based test-case generation. IEEE Transactions on Software Engineering, v. 36, n. 6, p. 742–762, 2010. ANDREWS, J. H.; BRIAND, L. C.; LABICHE, Y. Is mutation an appropriate tool for testing experiments? In: Proceedings of the 27th international conference on Software engineering. New York, NY, USA: ACM, 2005. (ICSE ’05), p. 402–411. ISBN 1-58113-963-2. Disponı́vel em: <http://doi.acm.org/10.1145/1062455.1062530>. BARBOSA, E. F.; MALDONADO, J. C.; VINCENZI, A. M. R. Introduction to Software Testing. BLANCO, R.; TUYA, J.; SECO, R. V. Test adequacy evaluation for the user-database interaction: A specification-based approach. In: ICST. [S.l.: s.n.], 2012. p. 71–80. BLUM, C.; ROLI, A. Metaheuristics in combinatorial optimization: Overview and conceptual comparison. ACM Comput. Surv., ACM, New York, NY, USA, v. 35, n. 3, p. 268–308, set. 2003. ISSN 0360-0300. Disponı́vel em: <http://doi.acm.org/10.1145/937503.937505>. BOTTACI, L. A genetic algorithm fitness function for mutation testing. In: Proceedings of the SEMINALL-workshop at the 23rd international conference on software engineering, Toronto, Canada. [S.l.: s.n.], 2001. CABECA, A. G.; JINO, M.; LEITAO-JUNIOR, P. S. Análise de mutantes em aplicações sql de banco de dados. VII Simpósio Brasileiro de Qualidade de Software, 2010. CAMILO-JUNIOR, C. G. Um Algoritmo Auxiliar Paralelo inspirado na Fertilização in Vitro para melhorar o desempenho dos Algoritmos Genéticos. Tese (Doutorado) — Universidade Federal de Uberlândia, 2010. CAMILO-JUNIOR, C. G.; YAMANAKA, K. In vitro fertilization genetic algorithm. In: Evolutionary Algorithms. InTech, 2011. (Prof. Eisuke Kita (Ed.)). ISBN 978-953-307-171-8. Disponı́vel em: <http://www.intechopen.com/books/evolutionaryalgorithms/in-vitro-fertilization-genetic-algorithm>. CHAN, W. K.; CHEUNG, S.; TSE, T. H. Fault-based testing of database application programs with conceptual data model. Quality Software, International Conference on, IEEE Computer Society, Los Alamitos, CA, USA, v. 0, p. 187–196, 2005. ISSN 1550-6002. Bibliografia 107 CHAYS, D.; DENG, Y.; FRANKL, P. G.; WEYUKER, E. J. An agenda for testing relational database applications. In: Software Testing, Verification and Reliability. [S.l.: s.n.], 2004. p. 2004. CORMEN, T. H. Algoritmos: Teoria e Prática. 2nd. ed. [S.l.]: Editora Campus, Rio de Janeiro, 2002. DARWIN, C. On the Origin of Species: By Means of Natural Selection Or the Preservation of Favored Races in the Struggle for Life. COSIMO CLASSICS, 2007. (Cosimo classics science). ISBN 9781602061446. Disponı́vel em: <http://books.google.com.br/books?id=bFXDg6-IxlcC>. DELAMARO, M. E.; MALDONADO, J. C.; JINO, M. Introdução ao Teste de Software. Rio de Janeiro, RJ, Brasil: Elsevier, 2007. ISBN 9788535226348. DEMILLO, R.; LIPTON, R.; SAYWARD, F. Hints on test data selection: Help for the practicing programmer. Computer, v. 11, n. 4, p. 34 –41, april 1978. ISSN 0018-9162. DEMILLO, R. A. Mutation analysis as a tool for software quality assurance. In: COMPSAC80. Chicago, IL: [s.n.], 1980. p. 390–393. DEREZINSKA, A. An Experimental Case Study to Applying Mutation Analysis for SQL Queries. 2009. ENGELBRECHT, A. P. Computational Intelligence: An Introduction. 2nd. ed. [S.l.]: Wiley Publishing, 2007. ISBN 0470035617. FREITAS, F. G.; MAIA, C. L. B.; CAMPOS, G. A. L.; SOUZA, J. T. Optimization in software testing using metaheuristics. Revista de Sistemas de Informação da FSMA, p. 3–13, 2010. Disponı́vel em: <http://www.fsma.edu.br/si/sistemas.html>. FREITAS, F. G.; MAIA, C. L. B.; COUTINHO, D. P.; CAMPOS, G. A. L.; SOUZA, J. T. Aplicação de metaheurı́sticas em problemas da engenharia de software: Revisão de literatura. II Congresso Tecnológico Infobrasil, 2009. GENDREAU, M.; POTVIN, J.-Y. Handbook of Metaheuristics. 2nd. ed. [S.l.]: Springer Publishing Company, Incorporated, 2010. ISBN 1441916636, 9781441916631. GLOVER, F. Future Paths for Integer Programming and Links to Artificial Intelligence. [S.l.]: Computer Operational Research, 1970. GLOVER, G. A. K. F. W. Handbook Of Metaheuristics. 1st edition. ed. [S.l.]: Springer-Verlag New York, LLC, 2002. GOODENOUGH, J. B.; GERHART, S. L. Towards a theory of test data selection. IEEE Transactions on Software Engineering, v. 2, p. 156–173, 1975. GUPTA, B. P.; VIRA, D.; SUDARSHAN, S. X-data: Generating test data for killing sql mutants. In: LI, F. et al. (Ed.). Proceedings of the 26th International Conference on Data Engineering, ICDE 2010, March 1-6, 2010, Long Beach, California, USA. [S.l.]: IEEE, 2010. p. 876–879. ISBN 978-1-4244-5444-0. Bibliografia 108 HARMAN, M. The current state and future of search based software engineering. In: Proceedings of International Conference on Software Engineering / Future of Software Engineering 2007 (ICSE/FOSE ’07). Minneapolis, Minnesota, USA: IEEE, 2007. p. 342–357. HARMAN, M.; JONES, B. F. Search-based software engineering. Information & Software Technology, v. 43, n. 14, p. 833–839, 2001. HARMAN, M.; MANSOURI, S. A.; ZHANG, Y. Search Based Software Engineering: A Comprehensive Analysis and Review of Trends Techniques and Applications. 2009. HARMAN, M.; MANSOURI, S. A.; ZHANG, Y. Search-based software engineering: Trends, techniques and applications. ACM Comput. Surv., ACM, New York, NY, USA, v. 45, n. 1, p. 11:1–11:61, dez. 2012. ISSN 0360-0300. Disponı́vel em: <http://doi.acm.org/10.1145/2379776.2379787>. HARMAN, M.; MCMINN, P.; SOUZA, J. T. de; YOO, S. Search-based software engineering: Techniques, taxonomy, tutorial. In: MEYER, B.; NORDIO, M. (Ed.). Empirical Software Engineering and Verification. Springer, 2011, (Lecture Notes in Computer Science, v. 7007). p. 1–59. Disponı́vel em: <http://philmcminn.staff.shef.ac.uk/publications/pdfs/2011-lncs.pdf>. HERMADI, I. Genetic algorithm based test data generator. In: Proceedings of the 2003 Congress on Evolutionary Computation CEC2003. [S.l.]: IEEE Press, 2003. p. 85–91. HOLLAND, J. H. Nonlinear Environments Permitting Efficient Adaptation. [S.l.]: Computer and Information Siences II, New York: Academic, 1967. HOLLAND, J. H. Adaptation in Natural and Artificial Systems. University of Michigan Press. 2a . ed. [S.l.]: MIT Press, 1975. HOLLAND, J. H. Adaptation in natural and artificial systems. Cambridge, MA, USA: MIT Press, 1992. ISBN 0-262-58111-6. ISO/IEC. ISO/IEC 9126. Software engineering – Product quality. [S.l.]: ISO/IEC, 2001. JIA, Y.; HARMAN, M. An analysis and survey of the development of mutation testing. Software Engineering, IEEE Transactions on, v. 37, n. 5, p. 649 –678, sept.-oct. 2011. ISSN 0098-5589. KHOR, S.; GROGONO, P. Using a genetic algorithm and formal concept analysis to generate branch coverage test data automatically. In: Proceedings of the 19th IEEE international conference on Automated software engineering. Washington, DC, USA: IEEE Computer Society, 2004. (ASE ’04), p. 346–349. ISBN 0-7695-2131-2. Disponı́vel em: <http://dx.doi.org/10.1109/ASE.2004.71>. KOREL, B. Automated software test data generation. IEEE Trans. Softw. Eng., IEEE Press, Piscataway, NJ, USA, v. 16, n. 8, p. 870–879, ago. 1990. ISSN 0098-5589. Disponı́vel em: <http://dx.doi.org/10.1109/32.57624>. LINDEN, R. Algoritmos Genéticos. 2nd. ed. BRASPORT, 2008. ISBN 9788574523736. Disponı́vel em: <http://books.google.com.br/books?id=it0kv6UsEMEC>. Bibliografia 109 LOUZADA, J.; CAMILO-JUNIOR, C. G.; VINCENZI, A.; RODRIGUES, C. L. An elitist evolutionary algorithm for automatically generating test data. In: IEEE Congress on Evolutionary Computation. IEEE, 2012. p. 1–8. ISBN 978-1-4673-1510-4. Disponı́vel em: <http://dblp.uni-trier.de/db/conf/cec/cec2012.html/LouzadaCVR12>. MANNILA, H.; RAIHA, K. J. Test data for relational queries. In: Proceedings of the fifth ACM SIGACT-SIGMOD symposium on Principles of database systems. New York, NY, USA: ACM, 1986. (PODS ’86), p. 217–223. ISBN 0-89791-179-2. Disponı́vel em: <http://doi.acm.org/10.1145/6012.15415>. MANSOUR, N.; BAHSOON, R.; BARADHI, G. Empirical comparison of regression test selection algorithms. Journal of Systems and Software, v. 57, n. 1, p. 79–90, 2001. Disponı́vel em: <http://dblp.uni-trier.de/db/journals/jss/jss57.html>. MANTERE, T.; ALANDER, J. T. Geração automática de dados e tratamento de não executabilidade no teste estrutural de software. In Anais do XIII Simpósio Brasileiro de Engenharia de Software,(SBES), Florianópolis, SC, p. 307–322, out. 1999. MANTERE, T.; ALANDER, J. T. Evolutionary software engineering, a review. Appl. Soft Comput., Elsevier Science Publishers B. V., Amsterdam, The Netherlands, The Netherlands, v. 5, n. 3, p. 315–331, mar. 2005. ISSN 1568-4946. Disponı́vel em: <http://dx.doi.org/10.1016/j.asoc.2004.08.004>. MASUD, M.; NAYAK, A.; ZAMAN, M.; BANSAL, N. Strategy for mutation testing using genetic algorithms. In: IEEE. Electrical and Computer Engineering, 2005. Canadian Conference on. [S.l.], 2005. p. 1049–1052. MAY, P.; TIMMIS, J.; MANDER, K. Immune and evolutionary approaches to software mutation testing. Artificial Immune Systems, Springer, p. 336–347, 2007. MCMINN, P. Search-based software test data generation: a survey: Research articles. Softw. Test. Verif. Reliab., John Wiley and Sons Ltd., Chichester, UK, v. 14, n. 2, p. 105–156, jun. 2004. ISSN 0960-0833. Disponı́vel em: <http://dx.doi.org/10.1002/stvr.v14:2>. MCMINN, P. Search-based software testing: Past, present and future. In: Proceedings of the 2011 IEEE Fourth International Conference on Software Testing, Verification and Validation Workshops. Washington, DC, USA: IEEE Computer Society, 2011. (ICSTW ’11), p. 153–163. ISBN 978-0-7695-4345-1. Disponı́vel em: <http://dx.doi.org/10.1109/ICSTW.2011.100>. MICHAEL, C. C.; MCGRAW, G.; SCHATZ, M. A. Generating software test data by evolution. IEEE Trans. Softw. Eng., IEEE Press, Piscataway, NJ, USA, v. 27, n. 12, p. 1085–1110, dez. 2001. ISSN 0098-5589. Disponı́vel em: <http://dx.doi.org/10.1109/32.988709>. MILLER, W.; SPOONER, D. L. Automatic generation of floating-point test data. IEEE Trans. Software Eng., v. 2, n. 3, p. 223–226, 1976. MONCAO, A. C. B. L.; CAMILO-JUNIOR, C. G.; QUEIROZ, L. T.; RODRIGUES, C.; VINCENZI, A. M. R.; LEITAO-JUNIOR, P. Applying genetic algorithms to data Bibliografia 110 selection for sql mutation analysis. In: ACM. Genetic and Evolutionary Computation GECCO 2013, Amsterdan. [S.l.], 2013. MONCAO, A. C. B. L.; CAMILO-JUNIOR, C. G.; QUEIROZ, L. T.; RODRIGUES, C.; VINCENZI, A. M. R.; LEITAO-JUNIOR, P. S. Shrinking a database to perform sql mutation tests using an evolutionary algorithm. In: IEEE. IEEE Congress on Evolutionary Computation - CEC 2013, Cancun. [S.l.], 2013. MYERS, G. J. The Art of Software Testing. New York: John Wiley & Sons, 1979. PEDRYCZ, W. Computational intelligence as an emerging paradigm of software engineering. In: Proceedings of the 14th international conference on Software engineering and knowledge engineering. New York, NY, USA: ACM, 2002. (SEKE ’02), p. 7–14. ISBN 1-58113-556-4. Disponı́vel em: <http://doi.acm.org/10.1145/568760.568763>. PEDRYCZ, W.; PETERS, J. F. Computational Intelligence in Software Engineering. River Edge, NJ, USA: World Scientific Publishing Co., Inc., 1998. ISBN 9810235038. PRESSMAN, R. S. Software Engineering: A Practitioner’s Approach. Nova York: McGraw-Hill, 2009. RAMEZ, E.; N., S. B. Fundamentals of Database Systems. 6a . ed. [S.l.]: Pearson, 2010. ISBN 978-0136086208. RICHARD, H. A.; DEMILLO, R. A.; HATHAWAY, B.; HSU, W.; HSU, W.; KRAUSER, E. W.; MARTIN, R. J.; MATHUR, A. P.; SPAFFORD, E. H. Design Of Mutant Operators For The C Programming Language. [S.l.], 1989. SHAH, S.; SUDARSHAN, S.; KAJBAJE, S.; PATIDAR, S.; GUPTA, B.; VIRA, D. Generating test data for killing sql mutants: A constraint-based approach. In: Data Engineering (ICDE), 2011 IEEE 27th International Conference on. [S.l.: s.n.], 2011. p. 1175 –1186. ISSN 1063-6382. SOMMERVILLE, I. Software Engineering. 9nd. ed. [S.l.]: Addison-Wesley, 2010. ISBN 0137035152. SOUZA, M. J. F. Manual de Computação Evolutiva e Heurı́stica. [S.l.]: Universidade Federal de Ouro Preto, 2010. SUáREZ-CABAL, M. J.; TUYA, J. Structural coverage criteria for testing sql queries. Journal of Universal Computer Science, v. 15, n. 3, p. 584–619, feb 2009. Disponı́vel em: <http://www.jucs.org/jucs 15 3/structural coverage criteria for>. TUYA, J.; CABAL, M. J. S.; RIVA, C. de la. Query-aware shrinking test databases. In: Proceedings of the 2nd International Workshop on Testing Database Systems, DBTest 2009, Providence, Rhode Island, USA, June 29, 2009. [S.l.]: ACM, 2009. ISBN 978-1-60558-706-6. TUYA, J.; SUAREZ-CABAL, M. J.; RIVA, C. de la. Sqlmutation: A tool to generate mutants of sql database queries. Mutation Analysis, Workshop on, IEEE Computer Society, Los Alamitos, CA, USA, v. 0, p. 1, 2006. Bibliografia 111 TUYA, J.; SUáREZ-CABAL, M. J.; RIVA, C. de la. Mutating database queries. Information and Software Technology, v. 49, n. 4, p. 398 – 417, 2007. Disponı́vel em: <http://www.sciencedirect.com/science/article/pii/S0950584906000814>. VERGILIO, S. R.; COLANZI, T. E.; POZO, A. T. R.; ASSUNCAO, W. K. G. Search based software engineering: A review from the brazilian symposium on software engineering. In: Proceedings of the 2011 25th Brazilian Symposium on Software Engineering. Washington, DC, USA: IEEE Computer Society, 2011. (SBES ’11), p. 50–55. ISBN 978-0-7695-4603-2. Disponı́vel em: <http://dx.doi.org/10.1109/SBES.2011.13>. VERGILIO, S. R.; COLANZI, T. E.; POZO, A. T. R.; ASSUNCAO, W. K. G. Search based software engineering: Review and analysis of the field in brazil. The Journal of Systems and Software, 2012. VINCENZI, A. Subsı́dios para o estabelecimento de estratégias de teste baseadas na técnica de mutação. [s.n.], 1998. Disponı́vel em: <http://books.google.com.br/books?id=PSRvHAAACAAJ>. WALCOTT, K. R.; SOFFA, M. L.; KAPFHAMMER, G. M.; ROOS, R. S. Timeaware test suite prioritization. In: Proceedings of the 2006 international symposium on Software testing and analysis. New York, NY, USA: ACM, 2006. (ISSTA ’06), p. 1–12. ISBN 1-59593-263-1. Disponı́vel em: <http://doi.acm.org/10.1145/1146238.1146240>. YOO, S.; HARMAN, M. Pareto efficient multi-objective test case selection. In: Proceedings of the 2007 international symposium on Software testing and analysis. New York, NY, USA: ACM, 2007. (ISSTA ’07), p. 140–150. ISBN 978-1-59593-734-6. Disponı́vel em: <http://doi.acm.org/10.1145/1273463.1273483>. APÊNDICE A Implementação do AGCA Este apêndice apresenta os códigos fontes do Algoritmo Genético Canônico (AGCA) implementado. • Classe Ag.java Classe principal da aplicação, para representar a execução do AG. import java.io.File; import java.io.FileWriter; import java.io.IOException; public class Ag extends Util { double totalMaior = 0.0, totalMenor = 0.0, totalTempo = 0.0, totalFitness = 0.0; double mediaMaior = 0.0, mediaMenor = 0.0, mediaTempo = 0.0, mediaFitness = 0.0, desvioMaior = 0.0, desvioMenor = 0.0, desvioTempo = 0.0, desvioFitness = 0.0; public double getValorMaiorFitness(double[] fitness) { double maior = fitness[0]; for (int i = 1; i < fitness.length; i++) { if (fitness[i] > maior) maior = fitness[i]; } return maior; } public int getMaiorFitness(double[] t) { int posicao = 0; double aux = this.getValorMaiorFitness(t); for (int i = 0; i < t.length; i++) { if (t[i] == aux) { posicao = i; } } return posicao; } public static void main(String[] args) throws IOException { Ag ag = new Ag(); Apêndice A 113 arquivo = new File("c:/AmbienteJava/Experimentos/Experimento.txt"); fr = new FileWriter(arquivo); new Config(); for (int exp = 1; exp <= Config.qtdeExp; exp++) { ag.iniciarExperimento(exp); } ag.mediaMaior = ag.totalMaior / Config.qtdeExp; ag.mediaMenor = ag.totalMenor / Config.qtdeExp; ag.mediaFitness = ag.totalFitness / Config.qtdeExp; ag.mediaTempo = ag.totalTempo / Config.qtdeExp; ag.registrarLogBasico("------------------------------------------------------------------"); ag.registrarLogBasico("Dados Gerais dos Experimentos."); ag.registrarLogBasico("------------------------------------------------------------------"); ag.registrarLogBasico("Média Maior fitness : " + ag.mediaMaior); ag.registrarLogBasico("Média Menor fitness : " + ag.mediaMenor); ag.registrarLogBasico("Média Qtde Fitness : " + ag.mediaFitness); ag.registrarLogBasico("Média Tempo : " + ag.mediaTempo + " ms"); fr.close(); } public void iniciarExperimento(int exp){ registrarExperimento(’i’); /* registra informaç~ oes do experimento no banco */ registrarLogBasico("-------------------------------------------------------"); registrarLogBasico("INICIANDO EXPERIMENTO...AGCA" + Config.experimento); registrarLogBasico("-------------------------------------------------------"); registrarLogBasico("Quantidade de Geraç~ oes : " + Config.numGer); registrarLogBasico("Quantidade de Indivı́duos : " + Config.tamPop); registrarLogBasico("Quantidade de Genes(tuplas) : " + Config.tamCrom); registrarLogBasico("Ponto de corte cruzamento : " + Config.cruzamento + "(0-par e impar, 1-aleatorio)"); registrarLogBasico("Taxa de cruzamento : " + Config.txCruzamento + "(0-par e impar, 1-aleatorio)"); registrarLogBasico("Taxa de Mutaç~ ao : " + Config.mutacao + " probabilidade de mutaç~ ao por gene"); registrarLogBasico("Elitismo : " + Config.elitismo); registrarLogBasico("Instruç~ ao SQL sendo avaliada : " + Config.sql); registrarLogBasico("Banco utilizado : " + Config.banco); registrarLogBasico("-------------------------------------------------------"); long t = System.currentTimeMillis(); Populacao p = new Populacao(Config.tamPop); p.avaliarIndividuos(); double valorMaiorFitness = p.valorMaiorFitness; double valorMenorFitness = p.valorMenorFitness; int individuoComMaiorFitness = p.individuoComMaiorFitness; int individuoComMenorFitness = p.individuoComMenorFitness; int totFitness = p.qtdeFitness; while ((p.valorMaiorFitness < Config.escoreBDP) && (p.contadorGeracoes < Config.numGer) ) { p.criarNovaGeracao(); p.avaliarIndividuos(); Apêndice A 114 /* pega o maior fitness do experimento */ if (p.valorMaiorFitness > valorMaiorFitness) { valorMaiorFitness = p.valorMaiorFitness; individuoComMaiorFitness = p.individuoComMaiorFitness; } /* pega o menor fitness do experimento */ if (p.valorMenorFitness < valorMenorFitness) { valorMenorFitness = p.valorMenorFitness; individuoComMenorFitness = p.individuoComMenorFitness; } totFitness = totFitness + p.qtdeFitness; /*acumula total de fitness para o experimento */ } t = System.currentTimeMillis() - t; registrarLogBasico("------------------------------------------------------------------"); registrarLogBasico("FIM DO EXPERIMENTO."); registrarLogBasico("------------------------------------------------------------------"); registrarLogBasico("Id do individuo com maior fitness : " + individuoComMaiorFitness); registrarLogBasico("Maior fitness : " + valorMaiorFitness); registrarLogBasico("Id do individuo com menor fitness : " + individuoComMenorFitness); registrarLogBasico("Menor fitness : " + valorMenorFitness); registrarLogBasico("Total fitness : " + totFitness); registrarLogBasico("Tempo : " + t + " ms"); registrarExperimento(’f’); //registra fim do experimento /* acumula totais this.totalMaior = this.totalMenor = this.totalFitness this.totalTempo = } } de todos os experimentos */ this.totalMaior + valorMaiorFitness; this.totalMenor + valorMenorFitness; = this.totalFitness + totFitness; this.totalTempo + t; • Classe Populacao.java Classe que representa a população, ou seja, cada geração do AG com um conjunto de indivı́duos. import import import import import import java.sql.Connection; java.sql.DriverManager; java.sql.ResultSet; java.sql.SQLException; java.sql.Statement; java.util.*; public class Populacao extends Util { public public public public public ArrayList<Individuo> individuos; ArrayList<Double> fitness; ArrayList<Individuo> candidatos; double totalFitness = 0.0; double mediaFitness = 0.0; Apêndice A public public public public public public 115 int individuoComMaiorFitness = 0; int individuoComMenorFitness = 0; double valorMaiorFitness = 0.0; double valorMenorFitness = 1.0; int contadorGeracoes = 1; int qtdeFitness = 0; public int tamanhoPopulacao; public Individuo populacaoInteira; /*popInteira - criado para representar a populacao Inteira*/ public Populacao(int tamanho) { this.tamanhoPopulacao = tamanho; individuos = new ArrayList<Individuo>(tamanho); fitness = new ArrayList<Double>(tamanho); candidatos = new ArrayList<Individuo>(tamanho); inicializaPopulacao(tamanho); } private void inicializaPopulacao(int tamanho) { for (int i = 0; i < tamanho; i++) { individuos.add(new Individuo()); fitness.add((double) 0.0); } } public void avaliarIndividuos() { double fit; valorMaiorFitness = 0.0; individuoComMaiorFitness = 0; valorMenorFitness = 1.0; individuoComMenorFitness = 0; totalFitness = 0; qtdeFitness = 0; for (int k = 0; k < tamanhoPopulacao; k++) { //Se individuo ainda n~ ao foi armazenado no banco individuos.get(k).instanciar(); int opcao = 2; if (Config.banco.equals("bdp")) { opcao = 0; } fit = individuos.get(k).getFitness(contadorGeracoes,opcao); if (individuos.get(k).novoIndividuo) { qtdeFitness++; /* só mede o fitness para novos indivı́duos */ } fitness.set(k, (double) fit); if (fit > valorMaiorFitness) { valorMaiorFitness = (double) fit; individuoComMaiorFitness = individuos.get(k).getSerial(); } Apêndice A 116 /* pega o pior individuo da geraç~ ao */ if (fit < valorMenorFitness) { valorMenorFitness = (double) fit; individuoComMenorFitness = individuos.get(k).getSerial(); } totalFitness = totalFitness + (double) fit; if (Config.numGer == 1) { registrarLogBasico("Individuo } } " + k + " : " + fit); /* ordena os indivı́duos pelo melhor fitness */ for (int i = 0; i < tamanhoPopulacao-1; i++) { for (int j = i+1; j < tamanhoPopulacao; j++) { Individuo indAux = new Individuo(); Double fitAux = 0.0; if (fitness.get(i) < fitness.get(j)) { indAux = individuos.get(j); fitAux = fitness.get(j); fitness.set(j,fitness.get(i)); individuos.set(j,individuos.get(i)); fitness.set(i,fitAux); individuos.set(i,indAux); } } } mediaFitness = totalFitness / tamanhoPopulacao; registrarLogBasico("==================================================================================="); registrarLogBasico("Maior fitness da geraç~ ao " + contadorGeracoes + " : " + valorMaiorFitness); registrarLogBasico("Menor fitness da geraç~ ao " + contadorGeracoes + " : " + valorMenorFitness); registrarLogBasico("Média do fitness da geraç~ ao " + contadorGeracoes + " : " + mediaFitness); registrarLogBasico("==================================================================================="); } public void criarNovaGeracao() { /* adiciona o de maior fitness na lista de candidatos */ /* selecionar pai e mae - entre 2 o de menor fitness */ contadorGeracoes++; candidatos.clear(); Random r = new Random(); int pai = 0; int mae = 0; // mantem os melhor indivı́duo - numero de individuo = Config.elitismo for (int i=0; i < Config.elitismo; i++) { candidatos.add(individuos.get(i)); } // Completa a lista de candidatos com filhos (seleç~ ao de pais por torneio) Apêndice A 117 while (candidatos.size() < tamanhoPopulacao) { int limiteSelecao; limiteSelecao = tamanhoPopulacao -1; /* cruza qualquer um */ /* escolhe 2 aleatoriamente e o melhor será o pai - Abordagem do Torneio */ pai = r.nextInt(limiteSelecao); int aux = r.nextInt(limiteSelecao); if (fitness.get(aux) > fitness.get(pai)) { pai = aux; } do { /* escolhe dois aleatoriamente e o melhor é a m~ ae */ mae = r.nextInt(limiteSelecao); aux = r.nextInt(limiteSelecao); if (fitness.get(aux) > fitness.get(mae)) { mae = aux; } } while (mae == pai); /* cruzar pai e mae */ double mr; mr = Math.random(); if (mr <= Config.txCruzamento) { /*cruza e adiciona filhos aos candidatos */ Individuo[] filhos = null; filhos = individuos.get(pai).cruzar(individuos.get(mae)); candidatos.add(filhos[0]); candidatos.add(filhos[1]); } else { /* n~ ao cruza e mantem os pais */ candidatos.add(individuos.get(pai)); candidatos.add(individuos.get(mae)); } } /* Substitui geraç~ ao */ individuos.clear(); fitness.clear(); for (int j = 0; j < tamanhoPopulacao; j++) { individuos.add(candidatos.get(j)); fitness.add((double) 0.0); } candidatos.clear(); } public String toString() { StringBuffer saida = new StringBuffer("Populacao: \n"); Iterator<Individuo> it = individuos.iterator(); while (it.hasNext()) { Apêndice A 118 saida.append("\n"); saida.append(it.next().toString()); } return saida.toString(); } } • Classe Individuo.java Classe que representa cada indivı́duo de cada geração/população, com um conjunto de genes e seu fitness. import import import import import import import import import import import import import import import java.io.BufferedReader; java.io.File; java.io.FileReader; java.sql.Connection; java.sql.DriverManager; java.sql.ResultSet; java.sql.ResultSetMetaData; java.sql.SQLException; java.sql.Statement; java.sql.Types; java.text.SimpleDateFormat; java.util.ArrayList; java.util.Arrays; java.util.Date; java.util.Random; public class Individuo extends Util { private Gene[] genes; private int serial; public String sql_individuo; public boolean novoIndividuo; public Individuo(Gene[] genes, int serial) { this.genes = genes; this.serial = serial; } public Individuo() { genes = new Gene[Config.tamCrom]; for (int i = 0; i < genes.length; i++) { genes[i] = new Gene(); /* gera gene da segunda opç~ ao de representaç~ ao - tupla e tabela */ } } public Individuo(String valor) { genes = new Gene[Config.tamCrom]; for (int i = 0; i < genes.length; i++) { genes[i] = new Gene(valor); /* gera gene com um determinado valor */ } } /** * construtor para Individuo que representa toda a populacao. */ popInteira Apêndice A 119 public Individuo(ArrayList<Individuo> individuos) { genes = new Gene[Config.tamPop*Config.tamCrom]; int contGenes = 0; for (int i=0; i<Config.tamPop;i++){ for (int j=0; j<Config.tamCrom;j++){ this.genes[contGenes] = new Gene(individuos.get(i).getGenes()[j].getValor()); contGenes++; } } } /* pega o array de genes do cromossomo. */ public Gene[] getGenes() { return genes; } /* seta um array de genes do cromossomo. */ public void setGenes(Gene[] genes) { this.genes = genes; } public int getSerial() { return serial; } public void setSerial (int serial) { this.serial = serial; } /** * faz o cruzamento entre cromossomos com um ponto de corte * exemplo * pai : 01011100 * mae : 11100010 * filho[0] : 01010010 e filho[1] : 11101100 * * @param cromossomo cromossomo para cruzar. * @return cromossomo filho. */ public Individuo[] cruzar(Individuo individuo) { Individuo[] novoIndividuo = new Individuo[2]; novoIndividuo[0] = new Individuo(" "); novoIndividuo[1] = new Individuo(" "); if (Config.cruzamento == 1) { Random r = new Random(); /* escolhe aleatoriamente o ponto de corte dentro da qtde de genes */ int pontoCorte = r.nextInt(Config.tamCrom); for (int i = 0; i < genes.length; i++) { Gene novoGene; if (i <= pontoCorte) { /* até o ponto de corte pega os genes do pai , depois os da m~ ae */ novoGene = new Gene(this.getGenes()[i].getValor()); } else { novoGene = new Gene(individuo.getGenes()[i].getValor()); Apêndice A 120 } novoIndividuo[0].getGenes()[i] = novoGene; /* cria o primeiro filho */ } for (int i = 0; i < genes.length; i++) { Gene novoGene; if (i <= pontoCorte) { /* até o ponto de corte pega os genes da m~ ae , depois os do pai */ novoGene = new Gene(individuo.getGenes()[i].getValor()); } else { novoGene = new Gene(this.getGenes()[i].getValor()); } novoIndividuo[1].getGenes()[i] = novoGene; /* cria o segundo filho */ } } novoIndividuo[0].mutar(); /* faz a mutaç~ ao de um gene */ novoIndividuo[1].mutar(); /* faz a mutaç~ ao de um gene */ return novoIndividuo; } /* Faz a mutaç~ ao de uma quantidade de genes - qtde_mutacao */ public void mutar() { double mr; for (int i = 0; i < genes.length; i++) { mr = Math.random(); if (mr < Config.mutacao) { Gene novoGene = new Gene(); /* atualiza o gene do cromossomo com o valor do novo gene gerado */ genes[i].setValor(novoGene.getValor()); } } } public static String hash(String string) { int hash = 1; int prime = 31; char[] array = string.toCharArray(); for (int i = 0; i < array.length; i++) { hash = prime * hash + array[i]; } return Integer.toHexString(prime + hash).toUpperCase(); } public void instanciar() { try { Connection conp = DriverManager.getConnection(Config.nomeBdp, Config.usuarioBdp, Config.senhaBdp); Connection cont = DriverManager.getConnection(Config.nomeBdt, Config.usuarioBdt,Config.senhaBdt); Statement stmp = conp.createStatement(); Statement stmt = cont.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); Apêndice A 121 ResultSet rsp; ResultSet rst; String sqlt= ""; String sqlp; String nome_tabela; String info_gene[]; int tuplaInconcisente = 0; for (int k = 0; k < Config.nomeTabelas.size(); k++) { nome_tabela = Config.nomeTabelas.get(k); try { sqlt = "DELETE FROM " + nome_tabela + ";" ; stmt.executeUpdate(sqlt); sqlt = "ALTER TABLE " + nome_tabela + " AUTO_INCREMENT = 1;"; stmt.executeUpdate(sqlt); } catch (SQLException e){ registrarErro(sqlt + " : " + e.getMessage()); } } /* pegar todos os genes do cromossomo e para cada um, pegar o nome da tabela, a tupla, dar o select no bdp e insert no bdt */ for (int j = 0; j < genes.length; j++) { info_gene = (genes[j].toString().split(":")); int id_tupla = Integer.parseInt(info_gene[0]); nome_tabela = Config.nomeTabelas.get(Integer.parseInt(info_gene[1])); String nome_id_tabela = Config.idTabelas.get(Integer.parseInt(info_gene[1])); try{ sqlp = "SELECT * FROM " + nome_tabela + " WHERE " + nome_id_tabela + " = " + id_tupla + ";" ; /* executar o select no bdt para ver se já foi criada a tupla */ rst = stmt.executeQuery(sqlp); rsp = stmp.executeQuery(sqlp); ResultSetMetaData rsmd = rsp.getMetaData(); int cols = rsmd.getColumnCount(); String colunas = " "; for (int c = 1 ; c <= cols; c++){ if (colunas == " ") { colunas = rsmd.getColumnName(c) ; } else { colunas = colunas + ", " + rsmd.getColumnName(c) ; } } sqlt = ""; if ( !rst.next() && rsp.next()) { /* se tem no bdp e n~ ao tem no bdt */ Apêndice A 122 sqlt = "INSERT INTO " + nome_tabela + "( " + colunas + ") values ("; try { for (int c = 1 ; c <= cols; c++){ /* só foi considerado String e int */ if ( c > 1) { sqlt = sqlt + ","; } if ((rsmd.getColumnType(c) == Types.CHAR) || (rsmd.getColumnType(c) == Types.VARCHAR)){ if (String.valueOf(rsp.getString(c)).equals("null")) { sqlt = sqlt + "null" ; } else if (rsp.getString(c).isEmpty()) { sqlt = sqlt + " ’’ "; } else { sqlt = sqlt + " ’" + rsp.getString(c) + "’ "; } } else { if (rsmd.getColumnType(c) == Types.DECIMAL) { if (String.valueOf(rsp.getDouble(c)).equals("null")) { sqlt = sqlt + "null" ; } else { sqlt = sqlt + " " + rsp.getDouble(c) + " "; } } else { if (rsmd.getColumnType(c) == Types.INTEGER){ if (String.valueOf(rsp.getInt(c)).equals("null")) { sqlt = sqlt + "null" ; } else { sqlt = sqlt + " " + rsp.getInt(c) + " "; } } else { if (rsmd.getColumnType(c) == Types.DATE){ if (String.valueOf(rsp.getDate(c)).equals("null")) { sqlt = sqlt + "null" ; } else { sqlt = sqlt + "’" + rsp.getDate(c) + "’"; } } } } } } sqlt = sqlt + ");"; } catch (Exception e) { e.printStackTrace(); } try{ stmt.executeUpdate(sqlt); Apêndice A 123 } catch (SQLException e){ tuplaInconcisente++; } } } catch (Exception e){ registrarErro("PREENCHIMENTO DAS TABELAS DO BDT : " + sqlt + e.getMessage()); } } } catch (SQLException e) { registrarErro("INSTANCIACAO DO BDT" + e.getMessage()); } catch (Exception e) { e.printStackTrace(); } } public String toString() { StringBuffer saida = new StringBuffer("Individuo: \n"); saida.append(genes.toString() + "\n"); return saida.toString(); } public String toString2() { String texto = " "; for (int j = 0; j < genes.length; j++) { texto = texto.trim() + genes[j]+ "|"; } return texto; } public double getFitness(int geracao, int opcao) { double score = 0; int totMutMortos = 0; String arquivoResultadoOriginal; String arquivoResultadoMutante; arquivoResultadoOriginal = "c:/AmbienteJava/ag/resultadoOriginal.txt"; arquivoResultadoMutante = "c:/AmbienteJava/ag/resultadoMutante.txt"; ArrayList<String> arqOriginal = new ArrayList<String>(); ArrayList<String> arqMutante = new ArrayList<String>(); try { Class.forName(Config.classe); Connection cont; Statement stmt; if (Config.banco.equals("bdp")) { cont = DriverManager.getConnection(Config.nomeBdp, Config.usuarioBdp, Config.senhaBdp); stmt = cont.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); Apêndice A 124 } else { cont = DriverManager.getConnection(Config.nomeBdt, Config.usuarioBdt, Config.senhaBdt); stmt = cont.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); } Connection con_mut = DriverManager.getConnection(Config.nomeBdm, Config.usuarioBdm, Config.senhaBdm); Statement stmt_mut = con_mut.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); Statement stmt_mut2 = con_mut.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rst, rst_mut; String sqlt; File f = new File(arquivoResultadoOriginal); f.delete(); rst_mut = stmt_mut.executeQuery("select * from instrucoes where id = " + Config.sql + ";"); rst_mut.next(); int id_instrucao = rst_mut.getInt("id"); String sqlt_orig = rst_mut.getString("comando"); sqlt = sqlt_orig + " into outfile ’" + arquivoResultadoOriginal + "’;"; rst = stmt.executeQuery(sqlt); /* roda o original para gravar o resultado em arquivo */ String sqlt_mut, sqlt_mut2; if (opcao == 2) { /* grava o indivı́duo no banco */ sqlt_mut = "insert into individuos (descricao, geracao, id_experimento) values (’" + this.toString2() + "’, " + geracao + ", " + Config.experimento + ");" ; stmt_mut.executeUpdate(sqlt_mut); rst_mut = stmt_mut.executeQuery("SELECT LAST_INSERT_ID() as ID from individuos"); rst_mut.next(); this.serial = rst_mut.getInt(1); } /* recupera todos os mutantes do banco */ rst_mut = stmt_mut.executeQuery("Select * from mutantes where id_instrucao = " + id_instrucao + ";"); int contMutante = 0; int id_mutante; while (rst_mut.next()) { contMutante ++; sqlt_mut = rst_mut.getString("sql_mutante"); registrarLogDetalhe("..... Mutante " + contMutante + " : " + sqlt_mut ); id_mutante = rst_mut.getInt("id"); arquivoResultadoMutante = "c:/AmbienteJava/Workspace/m" + id_mutante + ".txt"; File fmut = new File (arquivoResultadoMutante); fmut.delete(); sqlt_mut = sqlt_mut + " into outfile ’" + arquivoResultadoMutante + "’;"; Apêndice A 125 stmt = cont.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); boolean diferente = false; stmt.setQueryTimeout(20); try { rst = stmt.executeQuery(sqlt_mut); } catch (SQLException e) { diferente = true; /* matou o mutante */ } stmt.close(); if (! diferente) { /* verifica se os arquivos tem o mesmo tamanho */ File arquivo1 = new File(arquivoResultadoOriginal); File arquivo2 = new File(arquivoResultadoMutante); long tam1 = arquivo1.length(); long tam2 = arquivo2.length(); if (tam1 != tam2) { diferente = true; } } if (! diferente) { /* se n~ ao passou no time out e os arquivos tem o mesmo tamanho*/ /* compara arquivos de saida */ BufferedReader f1 = new BufferedReader(new FileReader(arquivoResultadoOriginal),1024); BufferedReader f2 = new BufferedReader(new FileReader(arquivoResultadoMutante),1024); String original; String mutante; original=f1.readLine(); mutante=f2.readLine(); arqOriginal.clear(); arqMutante.clear(); while (original != null) { arqOriginal.add(original); original=f1.readLine(); } while (mutante != null) { arqMutante.add(mutante); mutante=f2.readLine(); } if (arqMutante.size() != arqOriginal.size()) { diferente = true; } else { int k = 0; while ((k < arqOriginal.size()) && !diferente) { /* quando entra aqui os tamanhos s~ ao iguais */ if ( ! arqOriginal.get(k).equals(arqMutante.get(k)) ){ diferente = true; } k++; } Apêndice A 126 } f1.close(); f2.close(); } if (diferente) { /* se diferente, matou mutante */ registrarLogDetalhe("..... Bang!!! Mutante " + contMutante + " morto!! "); if (opcao == 2) { /* insere mutantes mortos no banco */ sqlt_mut2 = "insert into mutantes_mortos (id_individuo, id_mutante) values (" + this.serial + "," + rst_mut.getInt("id") + ");"; stmt_mut2.executeUpdate(sqlt_mut2); } totMutMortos++; } } if (contMutante == 0) { score = 0; } else { score = (double) totMutMortos / (double) contMutante; } if (opcao == 2) { /* grava fitness dos individuos no banco */ sqlt_mut = "update individuos set fitness = " + score + " where id = " + this.serial + ";"; stmt_mut.executeUpdate(sqlt_mut); } con_mut.close(); cont.close(); } catch (SQLException e){ e.printStackTrace(); registrarErro("Executou SQL com erro"); } catch (Exception e){ e.printStackTrace(); registrarErro("Executou SQL original com erro"); } finally{ } return score; } } • Classe Gene.java Classe que representa cada gene de um indivı́duo que, conforme a representação utilizada, é composto por uma identificação da tabela e uma identificação da tupla. import java.util.Random; /** * Esta classe tem o objetivo de prover a abstraç~ ao necessaria para o funcionamento * de um gene. */ public class Gene { private String[] tuplaTabela = new String[2] ; /* guarda a tupla e a tabela correspondente */ public String gene = " "; String valor; Apêndice A 127 public Gene() { /* Considerando 3 tabelas A:100 linhas, B:20 linhas e C:30 linhas, temos ao todo 150 tuplas - Config.total_tuplas */ /* O valor selecionado aleatoriamente será entre 150, sendo que existe um vetor com os nomes das tabelas e outro relacionado com a quantidade de tuplas da tabela anterior mais as desta tabela. ex: vetor nome_tabelas - 0:A 1:B 2:C, vetor qtd_tuplas - 0:100 1:120 2:150 Se for selecionado o valor 110, ele é a tupla 10 da tabela B. O 20 é a tupla 20 da tabela A. O 145 é a 25 da tabela C */ Random r = new Random(); /* escolhe aleatoriamente dentro do total de tuplas - geral */ int valor = r.nextInt(Config.totalTuplas); int i = 0; while (valor > Config.qtdTuplas.get(i)) { i++; } if (i == 0) { tuplaTabela[0] = String.format("%09d", valor); /* formatar com nove zeros antes 0000000001 */ } else { tuplaTabela[0] = String.format("%09d", (valor - Config.qtdTuplas.get(i-1))); } tuplaTabela[1] = String.valueOf(i); gene = tuplaTabela[0] + ":" + tuplaTabela[1]; } /* seta um valor para o gene 0 ou 1 */ public Gene(String valor) { setValor(valor); } /* retorna o valor do gene. */ public String getValor() { return gene; } /* seta um valor no gene. */ public void setValor(String valor) { this.gene = valor; } public String toString() { return gene; } } • Classe Config.java Classe responsável pela inicialização de todos os parâmetros utilizados nos experimentos. import java.io.BufferedReader; Apêndice A import import import import import import import import import 128 java.io.FileReader; java.io.IOException; java.sql.Connection; java.sql.DriverManager; java.sql.ResultSet; java.sql.SQLException; java.sql.Statement; java.text.DecimalFormat; java.util.*; ublic class Config { static int representacao; /* 1-primeira opcao (tuplas das tabelas juntas em um gene) , 2-segunda opcao (tabela e tupla) */ static int cruzamento; /* 0-par e impar, 1-ponto de corte aleatório, 2-mascara*/ static double txCruzamento; static double mutacao; static int tamPop; static int tamCrom; static int numGer; static int elitismo; static double escoreBDP; static String banco; static String nomeBdp, nomeBdt, usuarioBdp, usuarioBdt, senhaBdp, senhaBdt, nomeBdm, usuarioBdm, senhaBdm, nomeBancoBdm; static String classe; static ArrayList<String> nomeTabelas = new ArrayList<String>(); static ArrayList<String> idTabelas = new ArrayList<String>(); static ArrayList<Integer> tamTabelas = new ArrayList<Integer>(); String [] linha; String [] detalhe; static ArrayList<Integer> qtdTuplas = new ArrayList<Integer>(); static int totalTuplas = 0; DecimalFormat dff = (DecimalFormat) DecimalFormat.getInstance(); static int sql; static int experimento; static int qtdeExp; static int qtdeMutantes, primeiroMutante, ultimoMutante; /* L^ e o arquivo de configuraç~ ao onde cada linha tem o total de tuplas de cada tabela do SQL ** usado para saber a quantidade de tuplas de cada tabela para gerar os gene aleatóriamente */ public Config(){ try { BufferedReader in = new BufferedReader(new FileReader("config.txt")); String str; while (in.ready()) { str = in.readLine(); String [] linha = (str.split("#")); if (linha[0].equalsIgnoreCase("representacao")) { representacao = Integer.parseInt(linha[1]); } if (linha[0].equalsIgnoreCase("txCruzamento")) txCruzamento = Double.parseDouble(linha[1]); if (linha[0].equalsIgnoreCase("cruzamento")) cruzamento = Integer.parseInt(linha[1]); Apêndice A 129 if (linha[0].equalsIgnoreCase("mutacao")) mutacao = Double.parseDouble(linha[1]); if (linha[0].equalsIgnoreCase("tamPop")) tamPop = Integer.parseInt(linha[1]); if (linha[0].equalsIgnoreCase("tamCrom")) tamCrom = Integer.parseInt(linha[1]); if (linha[0].equalsIgnoreCase("numGer")) numGer = Integer.parseInt(linha[1]); if (linha[0].equalsIgnoreCase("elitismo")) elitismo = Integer.parseInt(linha[1]); if (linha[0].equalsIgnoreCase("escoreBDP")) escoreBDP = Double.parseDouble(linha[1]); if (linha[0].equalsIgnoreCase("banco")) { String [] detalhe = (linha[1].split(" ")); banco = detalhe[0]; if (banco.equals("bdp")){ Config.numGer = 1; Config.tamCrom = 1; Config.tamPop = 1; } } if (linha[0].equalsIgnoreCase("bdp")) { /* dados do banco de produç~ ao */ String [] detalhe = (linha[1].split(";")); nomeBdp = detalhe[0]; usuarioBdp = detalhe[1]; senhaBdp = detalhe[2]; } if (linha[0].equalsIgnoreCase("bdt")) { /* dados do banco de produç~ ao */ String[] detalhe = (linha[1].split(";")); nomeBdt = detalhe[0]; usuarioBdt = detalhe[1]; senhaBdt = detalhe[2]; } if (linha[0].equalsIgnoreCase("bdm")) { /* dados do banco de produç~ ao */ String[] detalhe = (linha[1].split(";")); nomeBdm = detalhe[0]; usuarioBdm = detalhe[1]; senhaBdm = detalhe[2]; nomeBancoBdm = detalhe[3]; } if (linha[0].equalsIgnoreCase("tabela")) { String[] detalhe = (linha[1].split(";")); nomeTabelas.add(detalhe[0]); idTabelas.add(detalhe[1]); tamTabelas.add(Integer.parseInt(detalhe[2])); totalTuplas = totalTuplas + Integer.parseInt(detalhe[2]); qtdTuplas.add(totalTuplas); } if (linha[0].equalsIgnoreCase("classe")) { classe = linha[1]; /* classe de conex~ ao */ } Apêndice A 130 if (linha[0].equalsIgnoreCase("sql")) { sql = Integer.parseInt(linha[1]); } if (linha[0].equalsIgnoreCase("qtdeExperimentos")) { qtdeExp = Integer.parseInt(linha[1]); if (Config.banco.equals("bdp")) { qtdeExp = 1; } } } in.close(); } catch (IOException e) { System.out.println("nao encontrou"); } } } • Classe Util.java Classe utilitária, responsável pelos registros dos experimentos e dos logs em geral. import import import import import import import import import import java.io.File; java.io.FileWriter; java.sql.Connection; java.sql.DriverManager; java.sql.ResultSet; java.sql.SQLException; java.sql.Statement; java.text.SimpleDateFormat; java.util.Date; java.io.IOException; public class Util { public static File arquivo; public static FileWriter fr; public void registrarLogBasico(String texto){ System.out.println("["+ new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(new Date()) + "]" + " " + texto) try { fr.write(texto + "\n"); } catch (IOException e){ System.out.println("Erro"); } } public void registrarLogDetalhe(String texto){ System.out.println("["+new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(new Date()) + "]" + " " + texto); } public void registrarErro(String texto){ System.out.println("["+new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(new Date()) + "]" + " *** ERRO *** " + " " + texto); try { fr.write(texto + "\n"); Apêndice A 131 } catch (IOException e){ System.out.println("Erro"); } } public void registrarExperimento(char inicio_fim) { try{ Connection con_mut = DriverManager.getConnection(Config.nomeBdm, Config.usuarioBdm, Config.senhaBdm); Statement stmt_mut = con_mut.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); ResultSet rst, rst_mut; String sqlt; if (inicio_fim == ’i’) { String parametros = "Banco : " + Config.banco + ", " + "Quantidade de Geraç~ oes : " + Config.numGer + ", "Quantidade de Indivı́duos : " + Config.tamPop + ", " + "Quantidade de Genes(tuplas) : " + Config.tamCrom + ", " + "Ponto de corte cruzamento : " + Config.cruzamento + "(0-par e impar, 1-aleatorio)" + ", " + "Taxa de Mutaç~ ao : " + Config.mutacao + " probabilidade de mutaç~ ao por gene" + ", " + "Elitismo : " + Config.elitismo; sqlt = "insert into experimentos (data_inicio, id_instrucao, parametros) values (’" + new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(new Date()).toString() + "’, " + Config.sql + ",’" + par stmt_mut.executeUpdate(sqlt); rst_mut = stmt_mut.executeQuery("SELECT LAST_INSERT_ID() as id_experimento from experimentos"); rst_mut.next(); Config.experimento = rst_mut.getInt(1); } else { sqlt = "update experimentos set data_fim = ’" + new SimpleDateFormat("dd/MM/yyyy HH:mm:ss").format(ne "’ where id_experimento = " + Config.experimento + ";"; stmt_mut.executeUpdate(sqlt); } con_mut.close(); } catch (SQLException e){ registrarErro(e.getMessage()); } } } Exemplo de um arquivo de configuração com os parâmetros necessários para a realização dos experimentos, onde apenas a tabela employee é utilizada. Arquivo nomeado como Config.txt e utilizado pela classe Config.java. representacao#2 txCruzamento#1 cruzamento#1 mutacao#0.03 tamPop#1 tamCrom#1 Apêndice A numGer#1 elitismo#2 classe#com.mysql.jdbc.Driver bdp#jdbc:mysql://localhost:3306/empresa;root;123456 bdt#jdbc:mysql://localhost:3306/empresa_teste;root;123456 bdm#jdbc:mysql://localhost:3306/mutantes;root;123456;mutantes tabela#department;dnumber;0; tabela#dependent;id;0; tabela#employee;ssn;100000; tabela#project;pnumber;0; tabela#dept_locations;id;0; tabela#works_on;id;0; sql#108 escoreBDP#1 banco#bdp qtdeExperimentos#10 132 APÊNDICE B Implementação do AGGE Este apêndice apresenta as alterações implementadas para o Algoritmo Genético com Grupo de Eleitos (AGGE), no código do AGCA. • Classe Populacao.java Foi alterado o código do método criarNovaGeração() para manter os indivı́duos que mataram mutantes não mortos pelos melhores indivı́duos. public void criarNovaGeracao() { ... ... ... /* mantem os melhor indivı́duo - numero de individuo = Config.elitismo */ for (int i=0; i < Config.elitismo; i++) { candidatos.add(individuos.get(i)); } /* manter os indivı́duos que mataram mutantes que o melhor n~ ao matou - grupo dos eleitos */ try { Connection conm = DriverManager.getConnection(Config.nomeBdm, Config.usuarioBdm,Config.senhaBdm); Statement stmm = conm.createStatement(); ResultSet rsm; String sqlm; for (int i=1; i< tamanhoPopulacao; i++) { /* monta clausula sql com todos os mutantes mortos pelo melhor e pelos candidatos */ String clausula = " id_individuo = " + candidatos.get(0).getSerial(); for (int w=1; w < candidatos.size(); w++) { clausula = clausula + " or id_individuo = " + candidatos.get(w).getSerial(); } /* conta mutantes mortos por esse individuo e n~ ao mortos pela clausula montada */ sqlm = "select count(1) as total from (select id_mutante from " + Config.nomeBancoBdm + ".mutantes_mortos where id_individuo = " + individuos.get(i).getSerial() + " and id_mutante not in (select id_mutante from " + Config.nomeBancoBdm + ".mutantes_mortos where " Apêndice B 134 + clausula + ")) as sel1 ;"; rsm = stmm.executeQuery(sqlm); rsm.next(); int qtde = rsm.getInt("total"); if (qtde > 0) { candidatos.add(individuos.get(i)); } } } catch (SQLException e){ e.printStackTrace(); System.out.println("Executou SQL com erro"); } // Completa a lista de candidatos com filhos (seleç~ ao de pais por torneio) .... .... } APÊNDICE C Implementação do INVITRO Este apêndice apresenta as alterações no AGCA para implementar o Algoritmo Auxiliar Paralelo, que chamamos como INVITRO nos experimentos. • Classe Ag.java Foi alterado o método iniciarExperimento(int exp) da seguinte forma: foi acrescentada a chamada para o método inVitro() da classe Populacao.java para executar o algoritmo auxiliar paralelo implementado; e foi acrescentada a chamada para um novo método criado na classe Populacao.java, substituiGeracao(), que faz a substituição da geração sendo o pai do AG, o melhor indivı́duo encontrado no INVITRO. public void iniciarExperimento(int exp){ ... ... while ((p.valorMaiorFitness < Config.escoreBDP) && (p.contadorGeracoes < Config.numGer) ) { p.inVitro(); //busca um filho melhor que o melhor da geraç~ ao p.criarNovaGeracao(); //faz os cruzamentos e mutaç~ oes normais p.substituiGeracao(); ... ... ... • Classe Populacao.java Foram acrescentados atributos para manter o pai, as mães selecionadas no processo INVITRO e os seus fitness. Foi alterado o método construtor para inicializar esses atributos e foram criados os novos métodos substituiGeracao(), que faz a substituição da geração do AG trocando o pai pelo melhor indivı́duo do INVITRO; e o inVitro(), que executa todo o processo do algoritmo auxiliar paralelo tentando melhorar os resultados do AG. Apêndice C ... ... public public public public ... ... 136 ArrayList<Individuo> maesInvitro; ArrayList<Double> fitMaesInvitro; Individuo paiInvitro; Double fitPaiInvitro; public Populacao(int tamanho) { ... ... maesInvitro = new ArrayList<Individuo>(tamanho*20/100); fitMaesInvitro = new ArrayList<Double>(tamanho*20/100); ... ... } public void substituiGeracao() { /* Substitui geraç~ ao */ //substitui o pai pelo melhor invitro // o melhor individuo vai ser ou o melhor da geraç~ ao anterior (pai do invitro) ou o novo individuo gerado pelo // nesse momento, o array de candidatos já esta com a nova geraç~ ao, o melhor, será substituı́do if ((double) fitPaiInvitro > (double) fitness.get(0) ) { candidatos.set(0, paiInvitro); registrarLogBasico("Trasferiu filho Invitro: " + (double) fitPaiInvitro); } individuos.clear(); fitness.clear(); for (int j = 0; j < tamanhoPopulacao; j++) { individuos.add(candidatos.get(j)); fitness.add((double) 0.0); } candidatos.clear(); } public void inVitro() { // --- antes desta chamada já avaliou todos os indivı́duos e est~ ao em ordem sendo o primeiro, o melhor // Coleta pai e m~ aes do array ordenado e adiciona em outro array // Faz a manipulaç~ ao genética // Avalia os indivı́duos filhos e guarda o melhor // pai = individuos.get(0) e fitnessPai = fitness.get(0) fitMaesInvitro.clear(); maesInvitro.clear(); fitPaiInvitro = (double) fitness.get(0); paiInvitro = individuos.get(0); Apêndice C 137 Individuo melhorFilho = null; Double fitnessMelhorFilho = (double) 0.0; Individuo maeInvitro = new Individuo(" "); String p1 = paiInvitro.toString2(); maesInvitro.clear(); fitMaesInvitro.clear(); for (int i = 0; i < (Config.tamPop * 20 / 100); i++) { maesInvitro.add(new Individuo(" ")); fitMaesInvitro.add((double) 0.0); } for (int j = 1; j < (Config.tamPop * 20 / 100) + 1 ; j++) { // pega 20% dos melhores fitMaesInvitro.set(j-1, (double) fitness.get(j)); maesInvitro.set(j-1, individuos.get(j)); } while (true) { registrarLogBasico("Executou Invitro"); for (int w = 0; w < (Config.tamPop * 20 / 100) ; w++) { // para cada m~ ae maeInvitro = (Individuo) maesInvitro.get(w).manipular(); //manipula uma parte do cromossomo if (! p1.equals(paiInvitro.toString2())) { p1 = paiInvitro.toString2(); } Individuo[] filhos = null; filhos = paiInvitro.recombinar(maeInvitro); // gera 4 filhos recombinando com o pai for (int k=0; k<4;k++) { // guarda o melhor filho filhos[k].instanciar(); Double fitFilho = (double) filhos[k].getFitness(0,1); if ((double) fitFilho > (double)fitnessMelhorFilho) { fitnessMelhorFilho = (double) fitFilho; melhorFilho = filhos[k]; } } } if ((double) fitnessMelhorFilho > (double) fitPaiInvitro) { // se o melhor filho é melhor que o pai - substitu fitPaiInvitro = (double) fitnessMelhorFilho; paiInvitro = melhorFilho; } else { Apêndice C 138 break; } } } ... ... • Classe Individuo.java Foram acrescentados dois métodos próprios do processo INVITRO: manipular(), que faz a manipulação genética das mães e o recombinar(Individuo mae), que recombina os indivı́duos pai e mãe gerando 4 filhos. ... ... /** * faz a manipulaç~ ao genética do processo Invitro da m~ ae */ public Individuo manipular() { // troca uma das 4 partes da mae Individuo ind = new Individuo(" "); ind.genes = this.genes; Random r = new Random(); /* escolhe aleatoriamente dentro da 4 partes do cromossomo*/ int parte = 0; while (parte==0) { parte = r.nextInt(4); // qual parte substituir } // de acordo com o valor da parte a ser substituida calcula o gene inicial e final int geneInicial = (parte-1) * (Config.tamCrom)/4; int geneFinal = (parte * Config.tamCrom/4) - 1; for (int m = geneInicial; m < geneFinal + 1; m++) { Gene novoGene = new Gene(); /* atualiza o gene do cromossomo com o valor do novo gene gerado */ ind.genes[m].valor = novoGene.getValor(); } return ind; } /* * Faz a recombinaç~ ao entre os indivı́duos para gerar os filhos */ public Individuo[] recombinar(Individuo mae) { // gera 4 filhos Apêndice C Individuo[] filhos[0] = filhos[1] = filhos[2] = filhos[3] = 139 filhos = new Individuo[4]; new Individuo(" "); new Individuo(" "); new Individuo(" "); new Individuo(" "); for (int i = 0; i < Config.tamCrom; i++) { if ( i < 1*Config.tamCrom/4 ) { // filho 0 - primeira parte do mae filhos[0].getGenes()[i] = mae.getGenes()[i]; filhos[1].getGenes()[i] = this.getGenes()[i]; filhos[2].getGenes()[i] = this.getGenes()[i]; filhos[3].getGenes()[i] = this.getGenes()[i]; } else { if ( i < 2*Config.tamCrom/4) { // filho 1 segunda parte do mae filhos[0].getGenes()[i] = this.getGenes()[i]; filhos[1].getGenes()[i] = mae.getGenes()[i]; filhos[2].getGenes()[i] = this.getGenes()[i]; filhos[3].getGenes()[i] = this.getGenes()[i]; } else { if (i < 3*Config.tamCrom/4) { //filho 2 terceira parte do mae filhos[0].getGenes()[i] = this.getGenes()[i]; filhos[1].getGenes()[i] = this.getGenes()[i]; filhos[2].getGenes()[i] = mae.getGenes()[i]; filhos[3].getGenes()[i] = this.getGenes()[i]; } else { //filho 3 ultima parte do pai filhos[0].getGenes()[i] = this.getGenes()[i]; filhos[1].getGenes()[i] = this.getGenes()[i]; filhos[2].getGenes()[i] = this.getGenes()[i]; filhos[3].getGenes()[i] = mae.getGenes()[i]; } } } } return filhos; } ... ...