Uma Abordagem Evolucion ária para o Teste de Instru - INF

Propaganda
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;
}
...
...
Download