Todos los desarrolladores han sentido la frustración de recibir reportes de errores. ¿Por qué no descubrieron esos errores antes? ¿Qué paso con las pruebas unitarias y de integración? La respuesta a estas preguntas puede estar en los siguientes aspectos:
- Los usuarios ejecutaron código que no fue probado.
- El orden de la secuencia de comandos ejecutados fue diferente a lo probado.
- El usuario ingreso una serie de datos no validados.
- El sistema está instalado en un ambiente donde no fue probado.
Plan de pruebas y Tester
Para crear un plan de pruebas efectivo se deben considerar múltiples aspectos relacionados con el sistema que se está probando. Por ejemplo funcionalidades, ingreso de de datos, ambiente de ejecución, etc.
Por eso, un Tester de profesional debe tener Skills en múltiples áreas para poder diseñar un plan de pruebas efectivo.
El proceso de pruebas, al igual que las fases de desarrollo de RUP, tiene 4 fases. Estas fases son:
- Modelar el ambiente del sistema.
La primera tarea del Tester es simular como el sistema se relaciona con su ambiente. Para esto se deben identificar todas las interfaces del sistema y los datos que pasan por las mismas.
Existen 4 tipos de interfaces en los sistemas:
· Interfaces Humanas.
· Interfaces de software (API)
· Interfaces de Archivos (Datos)
· Interfaces de comunicaciones (Redes y Device)
El siguiente paso que el Tester debe hacer es encontrar las acciones que el usuario puede realizar y que harían que el software deje de comportarse de manera consistente. Por ejemplo:
· ¿Qué pasa si un cliente cambia los mismos registros que otro en el mismo tiempo? Esto es manejo de la concurrencia.
· ¿Qué pasa si se baja el sistema en medio de una transacción?
· ¿Qué pasa si dos sesiones del sistema acceden a la misma API?
· Etc….
Dentro de las consideraciones que debe tener en mente un Tester se encuentran la forma en que se elogien los valores de las variables de entrada y la secuencia cómo ellas son ingresadas.
En la selección de las variables existe un método llamado BOUNDARY VALUE PARTITIONING. Básicamente es para buscar los valores esperados y las condiciones de borde de cada variable. Además de lo anterior se debe considerar la entrada concurrente de datos y la correcta aislamiento entre cada sesión que está manipulando los valores de las variables.
La segunda consideración tiene que ver con el orden en que se ingresan las variables. Tres técnicas son usadas para apoyar la definición del orden de ingreso de los datos. La más común es usar diagramas de estados (UML) para modelar el ingreso de datos y el estado en que el sistema debe quedar en cada movimiento.
Otra técnica es la que nos brindan las herramientas basadas en la teoría del lenguaje. Por ejemplo expresiones regulares y gramaticales. Un ejemplo de esto sería:
Filemenu.Open filename* (ClickOpen | ClickCancel)
Por último, las más desconocidas y complejas son las técnicas estocásticas y de algoritmos genéticos. Estos modelos combinan los valores a ingresar y su secuencia para producir palabras y oraciones Sintacticamente correctas.
- Elegir los escenarios de pruebas.
Muchos modelos de dominio y grupos de variables representan un infinito número de escenarios de pruebas, cada uno con sus costos y plazos de pruebas. Siendo realistas, en un proyecto de software los presupuestos son limitados por lo que debemos discriminar en que escenarios se probaran y cuales no.
¿Cuál es el criterio que se debe usar? Existen muchas respuestas para esta pregunta, siendo la más común entre los Tester el uso del criterio de Cobertura. Este criterio dice que por lo menos una vez cada línea del código y las entradas del sistema han sido probadas. Esto es el criterio mínimo esperable para las pruebas de un sistema antes de ser entregado.
Si desarrollamos la idea de la cobertura del código, nos damos cuenta que esto es muy complejo porque además de considerar que todo el código haya sido ejecutado al menos una vez, caemos en el tema de las rutas de ejecución. Una ruta de ejecución es la secuencia en que las líneas de código son ejecutadas. Las rutas de ejecución para el mismo código pueden llegara a ser infinitas.
Para acotar estas infinitas posibilidades el Tester busca los escenarios más comunes de ejecución, lo típico que un usuario ejecutaría.
Existen tres criterios reconocidos para elegir las rutas de ejecución a ser probadas. El primer criterio es el que pone foco en la cobertura de las estructuras de control. La idea es que las pruebas hagan que la ruta de ejecución pase por todas las opciones que abren las estructuras de control. Por ejemplo IF, que en ambos casos se prueben.
El segundo criterio es DATAFLOW, esto es que todas las estructuras de datos sean iniciadas y usadas.
Por último, el criterio menos usado, en mi opinión, es FAULT SEEDING. La idea de está técnica es sembrar errores intencionalmente en el código los cuales son encontrados con los casos de pruebas, idealmente se encuentran también los errores originales.
Respecto a los criterios de selección de casos de pruebas para las entradas de datos son simplemente la cobertura de todas las entradas de datos del sistema y la técnica llamada DISCRIMINATION CRITERION, que consiste en probar aleatoriamente hasta llegar a cubrir las entradas posibles del sistema.
- Ejecutar y evaluar los escenarios de pruebas.
Después de tener definidos los escenarios de pruebas viene la labor de ejecutar lo que se definió. Hacer esto de manera manual es un trabajo arduo y puede generar nuevos errores. Existen herramientas que ayudan al Tester a obtener información interna del sistema para mejorar sus pruebas.
La evaluación del escenario de pruebas basado en las salidas del sistema no es fácil de automatizar. La evaluación pasa por el Tester que compara los valores esperados versus lo que realmente obtuvo. Para está sencilla tarea se asume que las especificaciones, valores esperados, son correctos. Actualmente esta evaluación es hecha por el Oráculo Humana, llamado Tester.
Dos aproximaciones para evaluar las pruebas
Existen dos opciones para ejecutar las pruebas:
· Formalismo: poco usado en la práctica porque requiere especificaciones formales de buena calidad. En las especificaciones pueden haber errores, que serían traspasados al sistema.
· Código embebido: Es software incrustado en el sistema que toma medidas de estados de objetos, valores de variables y datos internos. Esto ayuda al Tester porque entrega información desde el interior del sistema y no sólo de las salidas de pantalla.
Existen otro tipo de pruebas basadas en código embebido. Está segundo tipo de pruebas es mucho más sofisticado y son código de pruebas del código del sistema. Está técnica se llama SELF-TESTING PROGRAMS.
Pruebas de Regresión
En un equipo serio de desarrollo, una vez que el Tester ha reportado errores encontrados en el sistema, estos son asignados a los desarrolladores que correspondan para que los reparen. Los desarrolladores después de esto generan una nueva versión del sistema.
En ese momento se debe resolver la pregunta ¿Qué es lo que debo volver a probar? La respuesta a la pregunta no es trivial porque cada reparación de un error puede tener cuatro consecuencias posibles:
· Sólo repara el problema reportado.
· Falla en resolver el problema reportado.
· Resuelve el problema, pero genera otras fallas.
· No resuelve el problema y además genera otras fallas.
Lo razonable sería volver a probar todo para así hacerse cargo de todas las opciones que el FIX (código reparado) puede provocar. Esto es, cómo siempre, económicamente inviable para el proyecto de software.
Estas pruebas realizadas sucesivamente en cada versión del sistema se llaman pruebas de Regresión.
Además de tener que resolver que probar para validar el FIX, ocurre frecuentemente que en cada nueva versión del sistema se incorpora funcionalidades nuevas que deben ser probadas también. Muchas veces el Tester privilegia las pruebas sobre las nuevas funcionalidades en vez de hacer pruebas de regresión, esto es malo pero consistente con el criterio de cobertura expuesto en la fase de diseño de las pruebas.
- Medir el progreso de las pruebas.
Cuando uno juega el rol de Tester, el Project Manager siempre debe estarnos preguntando ¿Cómo vamos con las pruebas? No siempre tenemos una respuesta fundamentada para entregarle. Básicamente tenemos el número de veces que hemos probado, los casos, las fallas, etc. Es decir contamos eventos, pero esto no nos asegura que estemos avanzando hacia el objetivo del proyecto.
Para tener una idea del avance de las pruebas debemos preocuparnos de verificar la completitud estructural y funcional. Para poder comprobar los aspectos estructurales podemos formularnos las siguientes preguntas:
· ¿Hemos probado los errores de programación comunes?
· ¿Hemos ejercitado todo el código fuente?
· ¿Hemos forzado todos los datos internos para que sean inicializados y usados?
· ¿Hemos encontrado todos los errores sembrados?
Para la completitud estructural por su parte son:
· ¿Hemos pasado a través de las formas en que el sistema puede fallar? ¿Hemos realizado todos los Test que lo validan?
· ¿Hemos aplicado todas las entradas de datos?
· ¿Hemos explorado todos los estados del sistema?
· ¿Hemos ejecutado todos los escenarios que espero que el usuario use?
Estas preguntas ayudan a los Tester para poder estimar el número de fallas que quedan en el sistema por ser descubiertos y la probabilidad que estas ocurran en el ambiente productivo del sistema. Está es la medida cuantitativa que todos buscamos.
Excelente articulo, muy buen aporte !!
muy bueno, felicitaciones