Actas de los Talleres de las Jornadas de Ingeniería del Software y Bases de Datos, Vol. 2, No. 4, 2008 Generación de mutantes con algoritmos genéticos Domínguez Jiménez, J. J., Estero Botaro, A., Medina Bulo, I. Departamento de Lenguajes y Sistemas Informáticos, Universidad de Cádiz C/ Chile, 1, CP 11003, Cádiz {Juanjose.dominguez,Antonia.estero,inmaculada.medina}@uca.es Resumen. Las técnicas de caja blanca y, más precisamente, las técnicas de prueba de mutaciones dependen del lenguaje de programación empleado. El trabajo que se expone presenta un generador de mutantes independiente del lenguaje de programación empleado, mediante el empleo de algoritmos genéticos. Una de las características del generador es que no se necesitan generar todos los mutantes posibles, sino que la heurística aplicada permitirá realizar una selección de los mejores mutantes. El generador es capaz de detectar los posibles mutantes equivalentes. Palabras Clave: Prueba de mutaciones, Generador de mutantes, Algoritmos genéticos. 1 Introducción Las técnicas de caja blanca y, más en concreto, la prueba de mutaciones, tienen un alto grado de dependencia del lenguaje de programación, por lo que no podemos reutilizar herramientas empleadas en otros lenguajes. En este sentido, es necesario el desarrollo de herramientas que, de forma automática, generen mutantes para cualquier lenguaje de programación. Los algoritmos genéticos (AG) han demostrado ser una estrategia de optimización heurística eficaz para optimizar funciones con numerosos óptimos locales. Sin embargo, dentro del ámbito de pruebas del software, su uso se ha limitado a la generación de casos de prueba [8]. Este artículo presenta un generador de mutantes independiente del lenguaje de programación, empleando AG. Nuestro trabajo presenta un uso novedoso de éstos al aplicarlos a la generación de mutantes. El generador será capaz de detectar los posibles mutantes equivalentes. La estructura del artículo es la siguiente: en la sección 2 se resumen brevemente las conceptos teóricos sobre los que subyace el generador; en la sección 3 describimos brevemente la situación actual en la generación automática de mutantes; la sección 4 expone la generación de mutantes propuesta y, finalmente, en la sección 5 se resumen las conclusiones y las futuras líneas de trabajo. 2 Conceptos teóricos En esta sección se resumen los conceptos principales sobre los que se apoya el generador de mutantes propuesto: la prueba de mutaciones y los AG. 2.1 Prueba de Mutaciones La prueba de mutaciones es una técnica de prueba del software de caja blanca basada en errores [14], que consiste en introducir fallos simples en el programa original, aplicando para ello operadores de ISSN 1988-3455 SISTEDES, 2008 27 Actas de los Talleres de las Jornadas de Ingeniería del Software y Bases de Datos, Vol. 2, No. 4, 2008 mutación. Los programas resultantes reciben el nombre de mutantes. Cada operador de mutación se corresponde con una categoría de error típico que puede cometer el programador. Si un caso de prueba es capaz de distinguir al programa original del mutante, es decir, la salida del mutante y la del programa original son diferentes, se dice que mata al mutante. Si por el contrario ningún caso de prueba es capaz de diferenciar al mutante del programa original, se habla de un mutante vivo para el conjunto de casos de prueba empleado. Una de las principales dificultades de aplicar la prueba de mutaciones es la existencia de mutantes equivalentes. Éstos presentan el mismo comportamiento que el programa original. Estos mutantes no deben confundirse con los mutantes difíciles de matar (stubborn non-equivalent mutants), originados porque el conjunto de casos de prueba no es suficiente para poder detectarlos. Uno de los principales inconvenientes de la prueba de mutaciones es el alto coste computacional que implica. Esto es debido a que disponemos normalmente de un número grande de operadores de mutación que generan un elevado número de mutantes. Existen diversas estrategias para reducir el elevado coste computacional de la prueba de mutaciones [11]. Una de ellas es la realización de una mutación selectiva, donde se realizan pocas mutaciones sin incurrir en una pérdida de información. Por lo tanto, con objeto de determinar qué mutaciones son las más apropiadas, es interesante la aplicación de técnicas de optimización, como son los AG. 2.2 Algoritmos Genéticos Los AG [9] son técnicas de búsqueda probabilística basadas en la teoría de la evolución y la selección natural, principalmente en la supervivencia de los mejores y el carácter hereditario de las características. Los puntos fuertes de los AG son su flexibilidad, simplicidad y capacidad de hibridación. Entre sus puntos débiles están su naturaleza heurística y el manejo de restricciones. Los AG trabajan con una población de soluciones, denominadas individuos, y procesan toda la información que ésta contiene de forma paralela. A lo largo de las distintas generaciones de la población, los AG realizan un proceso de selección y mejora de los individuos, de manera que son ideales para la resolución de problemas de optimización. El esquema de codificación empleado depende de las características del problema que se trate. Cada individuo tendrá una aptitud que medirá la calidad de la solución que representa respecto al problema que se está resolviendo. La aptitud de los individuos será objeto de un proceso de maximización a lo largo de las distintas generaciones del algoritmo. En un AG existen dos tipos de operadores: de selección y de reproducción. Los operadores de selección se encargan de seleccionar individuos de una población para la reproducción. Los operadores de reproducción permiten la generación de nuevos individuos en la población. Podemos distinguir dos tipos: el cruce y la mutación. El operador de cruce genera dos individuos nuevos, denominados hijos, a partir de dos individuos seleccionados previamente, denominados padres. Los hijos heredan parte de la información almacenada en cada uno de los dos padres. El operador de mutación tiene como finalidad alterar la información almacenada en un individuo. El diseño de estos operadores depende del esquema de codificación empleado. 3 Antecedentes La generación automática de mutantes ha permitido evaluar la efectividad de las diferentes técnicas de prueba del software, y así mejorar su eficiencia. Existen diversos trabajos en la bibliografía que tratan sobre el desarrollo de herramientas para automatizar la generación de mutantes, tales como Mothra [5] para Fortran, MuJava [6] para Java, Proteum [3] para C, Insure [12] para C++, SQLMutation [13] para SQL. Todos estos generadores se caracterizan porque producen de forma automática todos los mutantes posibles para los operadores de mutación proporcionados, sin realizar ninguna mutación selectiva en la generación. 28 SISTEDES, 2008 ISSN 1988-3455 Actas de los Talleres de las Jornadas de Ingeniería del Software y Bases de Datos, Vol. 2, No. 4, 2008 Existen en la bibliografía numerosas aplicaciones de los AG a las pruebas del software [7], aunque la mayor parte de éstas se han limitado a la generación de casos de prueba. En cuanto a la aplicación de AG a la generación de mutantes, existen pocas referencias. Así, en la búsqueda bibliográfica realizada, sólo Adamopoulos y col. [1] describe un sistema para la generación de mutantes y casos de prueba mediante AG en combinación con Mothra. En ese sistema, los individuos del AG están formados por conjuntos de mutantes generados previamente con Mothra, a diferencia de nuestro trabajo, donde los mutantes se generan con el propio AG. 4 Generación de Mutantes con Algoritmos Genéticos El generador de mutantes que se presenta está basado en AG. Además de la generación automática de mutantes, se pretende resolver la detección de los mutantes equivalentes al programa original. La figura 1 muestra el esquema del generador. Podemos distinguir dos elementos: un analizador del programa original y el generador de mutantes. El sistema tiene como entrada un programa original, un conjunto inicial de casos de prueba, así como los parámetros de configuración del AG. Fig. 3. Esquema del generador de mutantes Antes de ejecutar el generador de mutantes, el sistema analizará el programa original, de manera que se identificarán las instrucciones que pueden sufrir mutación de acuerdo a los operadores de mutación definidos para el lenguaje. El componente analizador será el único que dependa del lenguaje. A continuación se ejecutará el generador de mutantes, cuyo objetivo es generar de forma automática mediante un AG los mutantes del programa original. 4.1 Analizador Esta fase consiste en la identificación de las distintas instrucciones o elementos del programa original que pueden sufrir modificación de acuerdo al conjunto de operadores de mutación definidos para el lenguaje. De esta manera, al final obtendremos un fichero de texto que contiene para cada operador de mutación un recuento de las instrucciones a las que se puede aplicar. ISSN 1988-3455 SISTEDES, 2008 29 Actas de los Talleres de las Jornadas de Ingeniería del Software y Bases de Datos, Vol. 2, No. 4, 2008 4.2 Generador de Mutantes La generación de mutantes se realiza mediante un AG, en el que cada individuo representa a un mutante. A continuación pasamos a detallar los distintos aspectos del algoritmo. Representación de los Individuos. Cada individuo codifica la mutación a realizar al programa original mediante tres campos: uno para identificar el operador, otro para referenciar la instrucción donde se aplicará, y un tercero que contiene información para la aplicación del operador. El campo Operador identifica al operador de mutación que se aplica. Se codifica con un valor entero entre 1 y OM, donde OM es el número de operadores de mutación definidos. El campo Instrucción representa el número de instrucción del programa original donde se aplicará el operador. Con objeto de realizar una distribución uniforme entre todos los operadores independientemente del número de instrucciones asociadas a cada uno, el campo se codifica con un valor entero comprendido en el rango de 1 a I, donde I = mcm {mi, 1 ≤ i ≤ OM}, siendo mi el número de instrucciones que existen en el programa original a las que se puede aplicar el operador de mutación i-ésimo. Posteriormente, es necesario realizar una traducción, de manera que el valor Ii representará una operación de mutación en la instrucción (I i × mi ) / I El campo Atributo representa la información necesaria para la aplicación del operador de mutación. Dado que una mutación consiste en la alteración de un elemento del programa, este campo especifica cuál será el nuevo valor que tomará el elemento afectado por la mutación. Así, los operadores de mutación podemos clasificarlos en: • Aquellos que cambian un elemento por otro de un conjunto. En este caso, el atributo indica el nuevo elemento que se quiere poner. Consideremos, por ejemplo, los operadores de mutación para las expresiones relacionales que aparecen en la tabla 1 con sus respectivos valores. El individuo que indicaría cambiar la instrucción a>2500 por a<2500, se codificaría con un 3. Tabla 1. Operadores relacionales y su correspondiente valor Operador Valor > 1 = 2 < 3 >= 4 <= 5 != 6 • Aquellos que eliminan un elemento. En este caso, el atributo no tendría significado. Se mantiene en la codificación con valor 1. Un ejemplo de este tipo de operadores pueden ser aquellos que eliminan una rama else de una construcción if-else. • Aquellos que suponen un cambio de posición de la instrucción. El atributo indicará cuál es la nueva posición donde se ubicará. Si se intercambia con la instrucción anterior se codifica con 1, y si es con la instrucción posterior se codifica con 2. Al igual que en el campo Instrucción, con objeto de realizar una distribución uniforme entre todos los individuos, el campo Atributo contiene un valor entero dentro del rango 1 a V, donde V=mcm{ vi ,1 ≤ i ≤ OM}, siendo vi el número de valores que puede tomar el operador de mutación i-ésimo. De forma análoga se realiza una traducción para la obtención del desplazamiento. Este esquema de codificación de individuos permite que la arquitectura pueda adaptarse fácilmente a cualquier lenguaje de programación. Simplemente habría que numerar los distintos operadores de mutación disponibles y, para cada uno de ellos, sus posibles valores. Aptitud de los Individuos. Para poder evaluar la aptitud de un individuo (fig.1, paso M2) necesitamos ejecutar el mutante al que representa frente a los casos de prueba hasta que encontremos uno que lo mate. Al finalizar la ejecución de todos los mutantes, dispondremos de una matriz de dimensiones M×T, siendo M el número máximo de mutantes y T el número máximo de casos de prueba de que disponemos, donde cada elemento mij de la matriz valdrá 1 ó 0, dependiendo de si el mutante i-ésimo ha sido matado o no con el caso de prueba j-ésimo. En dicha matriz, se cumplen las siguientes relaciones: • Un mutante puede ser matado por un caso de prueba o seguir vivo: T ∑ m ∈ [0,1]∀i ij j =1 • Un caso de prueba puede matar a ninguno o a varios mutantes: M ∑ m ∈ [0, M ], ∀j ij i =1 30 SISTEDES, 2008 ISSN 1988-3455 Actas de los Talleres de las Jornadas de Ingeniería del Software y Bases de Datos, Vol. 2, No. 4, 2008 Cuando un caso de prueba no mata a ningún mutante puede indicarnos dos cosas: el caso de prueba tiene baja calidad, o bien los mutantes generados no son adecuados para ese caso de prueba. La función de aptitud representa al número de mutantes que son matados con el mismo caso de prueba. Dado que el algoritmo genético requiere siempre una función a maximizar, se establece una cota superior a dicho valor. Así, la función de aptitud para un individuo I: T M Aptitud ( I ) = M − ∑ mIj ⋅ ∑ mij j =1 i =1 (2) Esta función de aptitud de un individuo tendrá en cuenta si éste es muerto o no por los casos de prueba y cuántos mutantes son también matados por el mismo caso de prueba. De este modo, se intenta penalizar aquellos grupos de mutantes que son matados por el mismo caso de prueba, y favorecer a aquellos que son matados por un caso de prueba que sólo mata a dicho mutante, y por tanto, difíciles de matar. Sin embargo, cuando un mutante no es matado por ningún caso de prueba, es decir, no es detectado (tiene una aptitud de valor M), será almacenado en una memoria del algoritmo (fig. 1, paso M3), considerándose como un posible mutante equivalente. Generaciones. La primera población del AG será generada aleatoriamente (fig. 1, paso M1). El tamaño de la población constituye un parámetro de entrada del algoritmo. Se emplea un AG generacional, donde los individuos de las siguientes generaciones provienen tanto de la población anterior, como de las operaciones de reproducción. Así, el algoritmo tendrá un parámetro de entrada, porcentaje de repetidores, que determina cuántos mutantes de la generación antigua pasan a la nueva. La selección de estos mutantes se realiza mediante elitismo, escogiendo aquellos individuos con mejor aptitud (fig. 1, paso M4). Por otro lado, el algoritmo genera nuevos individuos mediante la realización de operaciones de cruce y mutación sobre los individuos de la población anterior. La selección de los individuos participantes se realiza mediante la técnica de la ruleta [4]. Se realizan operaciones de cruce o mutación, ambas excluyentes, de acuerdo a las probabilidades de estas operaciones, que mantendrán la siguiente regla: pm=1-pc, donde pm es la probabilidad de realizar una operación de mutación y pc la probabilidad de cruce, siendo éste un parámetro de entrada del algoritmo. Operadores Genéticos. El AG para la generación de nuevos individuos (fig. 1, paso M5) aplica los dos operadores genéticos: el cruce y la mutación. La selección de los individuos participantes se realiza con probabilidad uniforme, sin tener en cuenta la aptitud de los mismos. El operador de cruce consiste en realizar un cruce básico de un punto. El objetivo de éste es realizar un intercambio de elementos de los individuos participantes, de manera que el punto de cruce se escoge aleatoriamente entre los posibles campos que componen los individuos. El operador de mutación consiste en cambiar el valor de un elemento del individuo. De este modo existen 3 tipos de operadores de mutación, según donde se apliquen: mutación de instrucción, mutación de operador y mutación de atributo. Dado que las tres mutaciones cambian elementos codificados como enteros, se empleará una mutación consistente en generar una perturbación gaussiana aleatoria alrededor de uno de los valores del individuo seleccionado. La magnitud de la perturbación que se realiza se controla a través de la desviación estándar θ cuyo valor se mantiene constante en todo el proceso de funcionamiento del algoritmo. Esta magnitud está relacionada con la probabilidad de mutación pm, de forma que si las mutaciones son muy frecuentes, el tamaño de la magnitud de éstas decrece, y viceversa, es decir, θ = 1 - p m. Criterio de Parada. El AG finalizará cuando se alcance un número máximo de generaciones. 5 Conclusiones y Trabajo Futuro Este artículo presenta un generador de mutantes basado en AG para la realización de prueba de mutaciones, independiente del lenguaje de programación. La principal aportación es una propuesta heurística para la generación heurística de mutantes, sin necesidad de generar todos los mutantes ISSN 1988-3455 SISTEDES, 2008 31 Actas de los Talleres de las Jornadas de Ingeniería del Software y Bases de Datos, Vol. 2, No. 4, 2008 posibles, reduciendo el alto coste computacional de la prueba de mutaciones. Otra característica importante del generador propuesto es su independencia del lenguaje de programación para el que se crean los mutantes. Para ello, los individuos del AG codifican la instrucción a mutar y el operador de mutación que se empleará. De este modo, es fácilmente adaptable a cualquier otro lenguaje de programación. El generador tiene además como objetivo secundario la detección de posibles mutantes equivalentes. Para ello, el generador de mutantes dispone de una memoria donde almacena aquellos que son difíciles de matar. Nuestro trabajo se centra ahora en la realización de las primeras pruebas con el generador. Hemos escogido el lenguaje WS-BPEL 2.0 [10] al no disponer actualmente de generadores automáticos de mutantes. Para ello estamos elaborando un conjunto de operadores de mutación, e identificando los atributos que tiene cada operador. Una vez realizada esta labor, podremos realizar las primeras pruebas de su funcionamiento con composiciones de servicios web en WS-BPEL [2]. Posteriormente, podremos comparar la efectividad de la herramienta empleándola en lenguajes que ya disponen de otros generadores de mutación. El generador presentado se convierte en el primer paso del desarrollo de una herramienta para la prueba de mutaciones donde, conectado con un generador de casos de prueba, permita generar casos de prueba de calidad. Por oro lado, el generador de mutantes proporcionará información de la evolución a lo largo de las distintas generaciones. De este modo, podremos analizar qué operadores de mutación son más efectivos, y así determinar un subconjunto de operadores óptimo. Agradecimientos Este trabajo ha sido financiado por el Programa Nacional de I+D+I del Ministerio de Educación y Ciencia y fondos FEDER mediante el proyecto SOAQSim (TIN2007-67843-C06-04). Referencias 1. Adamopoulos, K., Harman, M., Hierons, R.M.: How to Overcome the Equivalent Mutant Problem and Achieve Tailored Selective Mutation Using Co-evolution. Proc. of the GECCO’04, pp. 1338–1349 (2004) 2. Canfora, G., Di Penta, M.: Testing services and service-centric systems: challenges and opportunities. IT Professional, vol. 8(2), pp. 10–17 (2006) 3. Delamaro, M., Maldonado, J.: Proteum–A Tool for the Assessment of Test Adequacy for C Programs. Proc. of the Conference on Performability in Computing System, pp. 79–95 (1996) 4. Goldberg, D.E.: Genetic algorithms in search, optimization and machine learning. Addison-Wesley (1989) 5. King, K.N., Offutt, A.J.: A Fortran Language System for Mutation-based Software Testing. Software - Practice and Experience, vol. 21(7), pp. 685–718 (1991) 6. Ma, Y.S., Offutt, J., Kwon, Y.R.: MuJava: an automated class mutation system. Soft. Test., Verif. & Rel., vol. 15(2), pp. 97–133 (2005) 7. Mantere, T., Alaner, J.T.: Evolutionary Software Engineering, a review. Applied Soft Computing, vol. 5, pp. 315–331 (2005) 8. Mcminn, P.: Search-based software test data generation: A survey. Soft. Test., Verif. & Rel., vol. 14(2), pp. 105–156 (2004) 9. Michalewicz, Z.: Genetic algorithms + data structures = Evolution programs. Springer-Verlag (1992) 10. OASIS: WS-BPEL 2.0 (2007), http://docs.oasis-open.org/wsbpel/2.0/OS/wsbpel-v2.0-OS.html 11. Offutt, A.J., Untch, R.H.: Mutation 2000: uniting the orthogonal. Mutation testing for the new century, pp. 34–44, Kluwer Academic Publishers, Norwell, MA, USA (2001) 12. Parasoft: Insure++ (2008), http://www.parasoft.com/jsp/products/home.jsp?product=Insure 13. Tuya, J., Suárez-Cabal, M.J., de la Riva, C., SQLMutation: a Tool to Generate Mutants of SQL Database Queries. Proc. of the Second Workshop on Mutation Analysis, Raleigh, (2006). 14. Woodward, M.R.: Mutation testing —its origin and evolution. Information and Software Technology, vol. 35(3), pp.. 163–169 (1993) 32 SISTEDES, 2008 ISSN 1988-3455