1. Introducción
Según [20] las pruebas de software son muy costosas, por lo que se dejan para las últimas etapas del proyecto y, por ello, típicamente no se realizan con la calidad necesaria. No obstante, en el estado del arte existen múltiples propuestas que se centran en la planificación y cálculo de los medios indispensables para realizarlas [18,15], así como a la generación automática de escenarios [3] y valores de prueba [4]. Estas propuestas persiguen el objetivo fundamental de disminuir los tiempos asociados a este proceso; simplificar su ejecución por parte de desarrolladores y probadores; y alcanzar amplios grados de cobertura disminuyendo, a la vez, el tiempo empleado para su realización.
Las pruebas de software continúan ocupando espacios importantes en los trabajos científicos de múltiples investigadores; en particular, se mantienen como problemas abiertos la generación de caminos y valores de pruebas para apoyar el diseño de los casos de prueba [5,13, 21, 24,27], así como los procesos vinculados con las pruebas de software [27, 6, 8, 25, 30, 31].
La generación automática de escenarios y valores de prueba es un problema combinatorio, donde intervienen un gran número de variables, por lo que dificulta su solución si se aplican técnicas tradicionales, o si la cantidad de combinaciones es tan grande e inmanejable que no se puede decidir cuáles seleccionar.
En [12] se exploran algunas de las respuestas que brindan las temáticas de la “Ingeniería de Software Basada en Búsquedas” para dar solución a problemas combinatorios utilizando métodos de optimización [4, 11]. Además, existen trabajos recientes que automatizan la realización de las pruebas de software con respecto a la generación de escenarios y valores de prueba, utilizando técnicas para evadir la explosión combinatoria [12, 4,14].
En [4, 1] se describe el empleo de algoritmos de búsqueda para la generación de casos de prueba para programas orientados a objetos desarrollados en Java. Estas propuestas se centran en la generación de caminos independientes, no así de los valores. En [28] se hace un recorrido por las diversas técnicas de búsqueda que se han aplicado para la generación de datos de prueba estructural [9,19, 22, 29]. En [7] los autore4s proponen un modelo puro basado en el algoritmo “Búsqueda Tabú” para la generación automática de valores para casos de prueba. Mientras que en [16] se presenta una solución basada en la fusión de una metaheurística poblacional con una lista Tabú, lo que se conoce como un algoritmo memético, para gestionar el problema de la generación de caminos para casos de prueba. En [11, 4] se propone la generación de casos de pruebas a través del empleo de heurísticas y de técnicas de ingeniería de software basadas en la búsqueda. Estas alternativas se centran en el desarrollo de valores para alcanzar un nivel de cobertura particular de los ambientes [26].
Los aportes fundamentales de las propuestas antes mencionadas están dirigidos a la utilización de algoritmos metaheurísticos y diversas modificaciones a estos algoritmos, pero no tienen en cuenta la naturaleza propia de los métodos de diseño de casos de prueba [23]. Estos métodos que provienen de la disciplina de “Ingeniería de Software” se utilizan de forma empírica, pero no han sido incorporados a estas propuestas, lo que hace que el rango de valores que se utilizan como punto de partida para la generación de valores de prueba siga siendo grande, y por tanto el problema combinatorio continúa sin reducirse significativamente. Estos métodos de diseño tradicionales constituyen la base conceptual del diseño de los casos de prueba en la Ingeniería de Software, y debieran incorporarse a estas nuevas soluciones con el objetivo de reducir las combinaciones de valores a generar logrando cubrimientos similares de los escenarios de prueba.
Las propuestas mencionadas anteriormente van desde la utilización de algoritmos de optimización e inteligencia artificial para resolver el problema de la explosión combinatoria de los caminos y valores de pruebas, hasta propuestas de frameworks para la automatización de algunos elementos del proceso. En este último caso las propuestas son prototipos para validar la solución teórica, pero no se han incorporado a las soluciones comerciales los elementos de generación de los casos de prueba, de forma tal que puedan ser utilizados por desarrolladores y equipos de probadores, reduciendo así el esfuerzo vinculado con esta actividad de diseño que es altamente costosa.
Este artículo presenta una propuesta para ejecutar las pruebas de software en diferentes momentos del desarrollo del producto de software, que es independiente del enfoque o la metodología de desarrollo que se utilice en el proceso productivo: El Modelo de Ejecución de Pruebas Tempranas Basada en Búsquedas (MTest.search). En el trabajo se hace énfasis en la generación automática de las pruebas funcionales, por lo que se presenta un conjunto de herramientas automatizadas que permiten soportar el modelo en el entorno productivo.
2. Materiales y Métodos
El modelo, MTest.search, consta de Flujos de Trabajo para la ejecución de pruebas tempranas en el entorno de producción, Modelos de Optimización para reducción de casos de pruebas funcionales y unitarias, y Herramientas Automatizadas Integradas que dan soporte a la ejecución de los flujos de trabajo.
Para la ejecución de la propuesta se recomienda tener una estructura en el proyecto con al menos dos personas que desempeñen las funciones del ingeniero de prueba, que de conjunto con clientes y/o desarrolladores realicen el diseño de las pruebas y definan cuáles son los perfiles de prueba para cada proyecto. Además, quienes desempeñen este rol deben encargarse de mantener la implementación de las pruebas automáticas si fuera necesario.
El modelo contempla un grupo de flujos de trabajo que enfatizan las etapas de modelación y diseño de las pruebas, tanto funcionales como unitarias, para facilitar la integración de los modelos para reducción de casos de pruebas unitarias y funcionales con las propuestas establecidas en el mercado, a través de herramientas comerciales que en algunos casos se insertan en los ambientes de trabajo de los desarrolladores y probadores para la ejecución de pruebas tanto unitarias como funcionales. La contribución fundamental de MTest.search está en la propuesta de modelos de optimización que permiten diseñar casos de pruebas con combinaciones de valores reducidos y que tienen un soporte automatizado que puede ser fácilmente integrado a ambientes de trabajo. En este trabajo sólo se aborda lo relacionado con las pruebas funcionales.
En las Figuras 1 y 2 se muestran las actividades a seguir en la etapa de Captura de Requisitos para el diseño de pruebas funcionales.
Como parte de MTest.search se define un modelo de optimización que maximiza la cobertura de los escenarios en el proceso generación de combinaciones de valores de pruebas funcionales. Esta propuesta parte de la base de considerar que en la medida en que se prueben combinaciones de valores que cubran combinaciones de clases de equivalencia diferentes, se estará maximizando la cobertura de los escenarios. Por tanto, se propone un modelo de optimización, que maximiza la cobertura de los escenarios, maximizando las combinaciones de clases de equivalencias representadas en el conjunto de combinaciones de valores que se pretende obtener. Se trata, a su vez, de minimizar la cantidad de casos de prueba generados.
A continuación, se describe el modelo que será objeto de automatización:
Sea 𝛼̅ = (X 1, X 2, … , X n ) el vector que contiene las variables o atributos de entrada a la funcionalidad que se desea probar, 𝛽̅= (y 1, y 2, … , y n ) el vector que contiene la descripción del dominio de cada atributo perteneciente a 𝛼̅, C el nivel de cobertura de las combinaciones de valores que se desea lograr y n la cantidad de variables o atributos de entrada a la funcionalidad a la que se quiere generar valores de prueba.
Se define:
como el modelo de optimización por el cual se obtiene la matriz Mlxn con las combinaciones de valores de prueba posibles a partir de los criterios definidos en
l: es el número de combinaciones a obtener en la generación de combinaciones de valores de prueba y que es una función de la cobertura de los valores especificado como C.
Mlxn: es la matriz que contiene las combinaciones de valores generados y donde cada fila j se corresponde con un vector 𝛾̅, con 1 ≤ j ≤ l. Por tanto, cada elemento w ji se corresponde con un valor del atributo i en la combinación de valores j.
𝛾̅= (𝜑1, 𝜑2, … , 𝜑 n ): es el vector que contiene una combinación de valores generada, en la que cada elemento 𝜑 i es una triada ordenada 𝜑 i = (V i , E i , Z i ) donde E i es el valor generado para el atributo i, V i es el valor de la clase de equivalencia correspondiente y Z i contiene el valor de verdad (1 si pertenece, 0 si no pertenece) correspondiente a la pertenencia de V i al dominio y i definido para el atributo X i . Este vector constituye la codificación del problema de optimización. Cada elemento 𝜑 i se obtiene a partir de la función de transformación:
que aplica la transformación e k al dominio y i 𝜖𝛽̅ y obtiene l triadas ordenadas de la forma (V i , E i , Z i ).
A partir de esta transformación para un dominio de entrada se obtiene un conjunto discreto de valores basado en los criterios de diseño de casos de prueba:
es la función objetivo, que maximiza la cobertura de los escenarios para un nivel de cobertura de las combinaciones de valores especificado en C. fℎ(𝛾̅j): es la función heurística para cada una de las combinaciones de valores generadas. Esta considera la cantidad total de combinaciones de clases de equivalencia y la cantidad de filas de la matriz resultante que contienen la misma combinación de clases de equivalencia que el nuevo estado generado. Esto evita que se generen valores que cubran los mismos escenarios.
Es importante destacar que a diferencia de otras soluciones presentes en la bibliografía consultada, en las que el algoritmo propuesto para generar las combinaciones de valores, parte de rangos especificados para el dominio de cada una de las variables, la propuesta que se presenta en este modelo reduce los dominios de entrada a valores discretos utilizando el vector de transformaciones
A continuación se detallan las T ik (e k , y i ) transformaciones para reducir cada dominio y i a valores discretos aplicando como técnica de generación de valores: e k = Particiones_Equivalentes.
Para cada y i = "numérico" se obtienen los valores:
λ1 = (V i , 11,1),
λ2 = (V i , 12,0),
V i = add(MinIntervalo(y i ), −1)
λ3 = (V i , 12,0),
V i = add(MaxIntervalo(y i ), 1)
λ4 = (V i , 13, pertenencia(y i , V i )),
V i = decimal(MinIntervalo(y i ), MaxIntervalo(y i ))
λ5 = (V i , 14, aceptadoVacio(y i , V i )),
V i = vacio(y i )
λ6 = (V i , 15,1),
V i = MinIntervalo(y i )
λ7 = (V i , 15,1),
V i = MaxIntervalo(y i )
Para cada y i = "cadena" se obtienen los valores:
λ1 = (V i , 21,0),
V i = generarCadena(add(longitud(y i ), 1))
λ2 = (V i , 22, aceptadoVacio(y i , V i )),
V i = vacio(y i )
λ3 = (V i , 23, pertenencia(y i , V i )) o
λ3 = (V i , 24, pertenencia(y i , V i )) ,
V i = generarCadenaNumeros(longitud(y i ))
λ4 = (V i , 23, pertenencia(y i , V i )) o
λ4 = (V i , 24, pertenencia(y i , V i )),
V i = generarCadenaSimbolos(longitud(y i ))
λ5 = (V i , 23, pertenencia(y i , V i )) o
λ5 = (V i , 24, pertenencia(y i , V i )),
V i = generarCadenaLetras(longitud(y i ))
λ6 = (V i , 23, pertenencia(y i , V i )) o
λ6 = (V i , 24, pertenencia(y i , V i )),
V i = generarCadenaNumerosSimbolos (longitud(y i ))
λ7 = (V i , 23, pertenencia(y i , V i )) o
λ7 = (V i , 24, pertenencia(y i , V i )),
V i = generarCadenaNumerosLetras (longitud(y i ))
λ8 = (V i , 23, pertenencia(y i , V i )) o
λ8 = (V i , 24, pertenencia(y i , V i )),
V i = generarCadenaSimbolosLetras (longitud(y i ))
λ9 = (V i , 23, pertenencia(y i , V i )) o
λ9 = (V i , 24, pertenencia(y i , V i )),
V i = generarCadenaSimbolosLetrasNumeros (longitud(y i ))
Para cada y i = "enumerado" se obtienen los valores:
λ1 = (V i , 31, aceptadoVacio(y i , V i )),
V i = vacio(y i ) λ2 = (V i , 32,1),
V i = SeleccionarValor(y i )
λ3 = (V i , 33,0),
V i = GenerarNoPertenece(y i )
Para cada y i = "lógico" se obtienen los valores:
λ1 = (V i , 41, aceptadoVacio(y i , V i )),
V i = vacio(y i ) λ2 = (V i , 42,1),
V i = GenerarValorLogico(y i )
λ3 = (V i , 43,0),
V i = GenerarNoPertenece(y i )
Para cada y i = "fecℎa_ℎora" se obtienen los valores:
λ1 = (V i , 51,1),
V i = GenerarFecℎaValida(y i ) o
V i = GenerarHoraValida(y i )
λ2 = (V i , 52,0),
V i = GenerarFecℎaNoValida(y i ) o
V i = GenerarHoraNoValida(y i )
λ3 = (V i , 53, aceptadoVacio(y i , V i )),
V i = vacio(y i )
Para cada y i = "conjunto" se obtienen los valores:
λ1 = (V i , 61,1),
V i = generarConjunto(longitud(y i ), tipoElemento(y i ))
λ2 = (V i , 62,1),
V i = generarConjunto(0, tipoElemento(y i ))
λ3 = (V i , 63,1),
λ4 = (V i , 64,0),
V i = generarConjunto(add(longitud(y i ), 1), tipoElemento(y i ))
λ5 = (V i , 65,0),
Adicionalmente, en cada vector de transformaciones pueden ser incluidos valores que responden a la dependencia entre atributos Para ello se debe indicar si cada atributo es dependiente de otro. En el caso de los atributos dependientes estos son tratados como casos especiales en los que es necesario definir la relación de dependencia, que puede ser de: estructura, relatividad (mayor que, menor que, igual que) o existencia (pertenece o no a los elementos de un conjunto). Con esta información el vector de transformaciones del atributo dependiente es enriquecido.
3. Propuesta de solución
En el mercado existen diversas herramientas para la ejecución automática de pruebas, una vez que los desarrolladores o probadores han diseñado adecuadamente los casos de prueba. Por tal motivo, la propuesta de este modelo es que para dar soporte al modelo se utilice una combinación entre las herramientas de ejecución de pruebas existentes en el mercado y las herramientas que automatizan la generación de los casos de prueba, de forma tal que estas últimas se inserten en el entorno productivo de la empresa y se vinculen con las anteriores.
En el caso de las herramientas de generación de casos de prueba se ha implementado un componente que a partir de un Grafo de Control de Flujo obtiene los casos de pruebas. Este componente puede ser integrado a entornos de desarrollo productivo tanto para ejecutar pruebas funcionales como para ejecutar pruebas unitarias. En la Fig. 3 se muestra la relación de las aplicaciones propias del entorno productivo para la ejecución de pruebas con los componentes desarrollados como parte de este modelo, GeCaP, GeVaF [17] y GeVaU [10]. A la derecha aparecen las herramientas para pruebas funcionales y a la izquierda para pruebas unitarias.
Para el caso de las pruebas funcionales es necesario desarrollar aplicaciones clientes que se integren con el entorno de gestión del proyecto y de gestión de pruebas funcionales que utilice la empresa. Estas aplicaciones deben obtener la información de la funcionalidad que se pretende probar y una vez generados los escenarios y sus casos de prueba lo inserten en el entorno de pruebas funcionales.
El componente de generación automática de casos de pruebas (GeCaP) integra la generación de casos de pruebas unitarias y funcionales, para lo cual contiene componentes de generación de combinaciones de valores tanto para pruebas funcionales como para pruebas unitarias que utilizan metaheurística para optimizar el número de combinaciones de valores a generar.
En la Fig. 4 se muestran las funcionalidades del componente y su relación con los componentes que generan las combinaciones de valores para pruebas funcionales y unitarias, GeVaF y GeVaU respectivamente.
El componente GeVaF implementa el modelo de optimización, anteriormente definido, para la reducción de casos de pruebas funcionales haciendo uso de algoritmos heurísticos que maximizan la cobertura de los escenarios.
Para poder incorporar al entorno productivo la generación de los casos de pruebas funcionales utilizando el componente GeCaP se hace necesario desarrollar una aplicación de interfaz que capture los proyectos de desarrollo de la empresa, sus funcionalidades y los tipos de datos de cada una, y con esta información haga uso del componente. Se debe suministrar al componente el nivel de cobertura de los escenarios que se pretende alcanzar y la descripción de los atributos de entrada a cada funcionalidad.
Como resultado final el componente GeCaP genera un fichero texto con los valores de las variables para cada escenario. El fichero generado lo utiliza la aplicación cliente insertada en el entorno de gestión de pruebas funcionales.
4. Resultados y discusión
Con el objetivo de evaluar la viabilidad de introducir la solución propuesta en un entorno específico se desarrolló una aplicación cliente para el diseño de pruebas funcionales. La herramienta implementada para el entorno productivo de una empresa concreta hace uso del componente GeCaP para generar los casos de pruebas funcionales y unitarias. Ellas fueron probadas para un conjunto de proyectos reales de forma que se pudiera verificar la posible extensibilidad del modelo y el componente de software al entorno productivo de la empresa.
La evaluación del componente y el modelo estuvo guiada por las siguientes interrogantes:
¿Es posible cubrir el 100% de los escenarios con un mínimo de combinaciones de valores para cada escenario, sin que se generen combinaciones de valores que cubran el mismo escenario?
Si el porcentaje de cobertura es menor que 100, ¿se obtienen resultados similares a los de máxima cobertura sin que se generen combinaciones que satisfagan el mismo escenario?
Si los niveles de cobertura de entrada son superiores al 100%, ¿se garantiza un cubrimiento similar de los escenarios y se minimiza la cantidad de combinaciones de valores repetidos?
Se diseñaron 3 experimentos con diferentes niveles de cobertura de escenarios para dar respuesta a las interrogantes planteadas, en los que se utilizó el método de búsqueda aleatoria. Como entrada, además de la cobertura, se suministran los atributos de cada funcionalidad y una descripción de sus dominios. A partir de esta información de entrada, los dominios de cada atributo se reducen a un conjunto de valores significativos para la prueba, según se explicó en las secciones correspondientes a los modelos de optimización a través de aplicar el vector de transformaciones.
Para el primer experimento se fijó el número de iteraciones de los algoritmos metaheurísticos de acuerdo con el 100% de cobertura de los escenarios y el segundo para el 80% de cobertura de los escenarios. Por último, se hace una corrida para un 120% de cobertura.
Se utilizó como caso de estudio una aplicación con 6 funcionalidades que como promedio tiene 3 atributos por cada una ellas. Las variables incluidas cubren los tipos de datos: cadena, numérico, enumerado, lógico y fecha.
En la Tabla 2, se muestran las características de las funcionalidades con respecto a la cantidad de atributos de cada una, la cantidad máxima de escenarios sin redundancia y la cantidad máxima de combinaciones de valores que pueden generarse.
La Figura 5 muestra el gráfico con el resultado de las ejecuciones del modelo de optimización para las seis funcionalidades de la Tabla 1 en correspondencia con el 120%, 100% y 80% de cobertura de escenarios, en la que se puede apreciar que la curva de generación de combinaciones de valores sigue igual distribución que la curva de cantidad mínima de combinaciones necesarias para garantizar el 100% de cobertura de escenarios y que para valores altos de escenarios se aprecia una mayor reducción de combinaciones de valores en la generación final.
Adicionalmente se presenta en la Figura 6 un gráfico con la cantidad de combinaciones redundantes generadas en el peor caso para diferentes niveles de cobertura de escenarios. Este valor se obtiene para la funcionalidad 4 y 120% de cobertura de escenarios, en ese caso el 16,9% de las combinaciones generadas son redundantes. De forma general queda claro que hasta el 100% de cobertura de escenarios no se generan valores redundantes, por lo que la cantidad de combinaciones generadas es la mínima posible para el nivel de cobertura correspondiente.
A partir del análisis de los resultados se pudo comprobar que:
- Se garantiza cubrir los porcentajes de cobertura de los escenarios indicados en cada caso.
- Se generan cantidad de combinaciones de valores mínimas, en los casos de porcentajes de cobertura de hasta 100%, en particular se genera una única combinación para cada escenario.
- Para los porcentajes de cobertura superiores a 100% se generan combinaciones que satisfacen un mismo escenario, pero ningún escenario queda sin al menos una combinación de valores.
5. Conclusiones
El presente trabajo facilita el proceso de pruebas funcionales durante el desarrollo de productos de software, debido a que permite automatizar el diseño de casos de pruebas funcionales. Además, permite reducir el tiempo y esfuerzo dedicado por los probadores y diseñadores de casos de prueba en el diseño y ejecución de pruebas funcionales.
Debido a que los valores de prueba generados tienen en cuenta las técnicas de particiones equivalentes y valores límites, las combinaciones de valores generados satisfacen todos los escenarios generando una cantidad de combinaciones de valores reducida y se garantiza la obtención de los valores para la cobertura exigida en cada caso.