Clipper 5.x

Probando software y números de versión

 

Autor :
Diego Lucio D'Onofrio

Bibliografía :
The art of software testing by Glenford J. Myers
Software engineering: a practitioner's approach by Roger S. Pressman
Software engineering with student project guidance by Barbee Teasley Mynatt



Cuando aparecieron los primeros grandes sistemas informáticos se incluyo a nivel metódico e imprescindible un hasta entonces nuevo proceso en la confección de los mismos: el proceso de prueba.

Hoy en día se calcula que la fase o proceso de pruebas representa más de la mitad del coste de un programa, ya que requiere un tiempo similar al de la programación lo que obviamente acarrea un alto costo económico cuando estos no involucran vidas humanas, puesto que en este último caso el costo suele superar el 80% siendo esta etapa mas cara que el propio desarrollo y diseño de los distintos programas que conforman el sistema.

Un proceso de pruebas requiere mucho más que tiempo y dinero, necesita una verdadera metodología la cual exige herramientas y conocimientos destinados a dicha tarea, este texto trata de ser una pequeña guía para los programadores que aún no se han entrenado en este plano, no obstante recomiendo la lectura del libro The art of software testing del autor Glenford J. Myers en el cual se explican en un nivel muy superior muchos de los puntos que más abajo detallo, desconozco si existen traducciones al español, la copia que yo poseo está en inglés.
 

Hacer la prueba infalible de un programa implicaría ponerlo en todas las situaciones posibles, de esta manera aseguraríamos que el mismo se encuentra completamente libre de errores, como se imaginarán esto es imposible porque a pesar de que el número de líneas que lo conforman es finito, la prueba pasa a ser infinita cuando entran en juego los bucles con lo que hacer la prueba empírica exacta pasa de ser una enorme e incalculable cantidad de posibilidades a una cifra ciertamente infinita. Considerando este último punto lo que queda por hacer es buscar formas y métodos abordables para acercarse lo más posible a un resultado optimo.

La etapa de pruebas no debe ser posterior a la confección de un programa, tiene que ser paralela a la programación y como bien dice Glenford Myers en algunos casos deberá ser anterior, primero probar y después programar. Yo me sorprendí como lo habrán hecho ustedes al leer esto, pero luego leí algo que me aclaro un poco el tema, Glenford preguntaba, ¿O acaso nunca desarrollaste una rutina pensando, esto hay que probarlo?.

La versión es algo que nos ayudará mucho a la hora de encontrar fallas, es importante como un método de organización que cada vez que se modifique algo de un programa se modifique la versión del mismo y que esta figure en un lugar legible incluso para el cliente.

Particularmente utilizo el tipo de incremento de versión que plantea Roger Pressman, por haberlo encontrado muy útil para la clasificación de las mismas.

El método de Pressman es el siguiente: propone que el número de versión esté compuesta por cuatro partes, tres numéricas y una letra del alfabeto griego que puede no estar en las versiones finales.
El primer número antes del punto corresponde a la cantidad de veces que el programa ha sido reprogramado desde cero, es decir cada vez que se hace un programa nuevo que posea el mismo nombre y funciones que su antecesor.
Los número detrás del punto indican la cantidad de modificaciones realizadas a pedido y por corrección de fallas respectivamente.
Por ejemplo, una vez que se haya terminado el programa su versión será 1.0, si luego se le hace una modificación por pedido del cliente o para mejorar algún aspecto del sistema la versión será incrementada a 1.1.
Suponiendo que más tarde el programador detecta y corrige dos errores la versión será 1.12
Cuando algún numero de la sub-versión se vea incrementado por encima de 9 este será separado por puntos quedando así bien delimitada la versión principal, la cantidad de modificaciones por pedido y la cantidad de corrección de errores.
Ejemplo:
V1.39 - Representa 3 modificaciones a pedido y 9 correcciones de errores
V1.03.10 - Representa 3 modificaciones a pedido y 10 correcciones de errores

La letra que se coloque después de la versión indicará en que etapa de prueba este se encuentre, usando alfa para las pruebas en las que el cliente, sea este interno o externo, se encuentra en el área de desarrollo cosa muy común en las empresas que a pesar de no dedicarse a la comercialización de software tienen su propio equipo de desarrollo y programación.

La letra "Beta" se utilizará para indicar que el programa se encuentra en una etapa de prueba beta o "Beta test", esto será cuando el software se encuentre en el entorno del cliente sin el seguimiento del desarrollador, y será este quien oportunamente informe las fallas detectadas.

V1.03.10B - Representa 3 modificaciones a pedido y 10 correcciones de errores y que aún se encuentra en fase de prueba "Beta".

Cuando la versión carezca de letra del alfabeto griego indicará que la versión es final, o sea que fue probada y revisada y está libre de errores conocidos, a menos que el proveedor de software indique lo contrario.

Este último punto se presta a una confusión muy común, es obvio que cuando entregamos un programa terminado la idea es demostrar que funciona y cumple con los requisitos y necesidades del cliente, esto es muy distinto si el programa se encuentra en fase de pruebas beta, la idea de las pruebas beta no es demostrar que el sistema no tiene fallas, por el contrario trata de descubrir las fallas que no han sido detectadas por el equipo de desarrollo, este punto hay que tenerlo muy en claro.

Las pruebas beta como anteriormente mencioné son pruebas funcionales sobre el sistema completo y buscan una cobertura de la especificación de requisitos, es común que luego de los mas cuidadosos testeos por parte del desarrollador el software contenga "vicios ocultos" que solo serán notados cuando el o los clientes comiencen a darle el uso para el cual el programa ha sido pensado, en algunas oportunidades el cliente puede encontrar fallas causadas por usar el programa de una manera para la cual no había sido pensado, muchos de ustedes entenderán a que me refiero, en estos casos es normal decir cosas como "¿Como puede ser que a alguien se le ocurra hacer tal cosa?", en estos casos se puede decir que los requisitos no están claros o son ambiguos, cosa que puede salvar la cara pero que de ninguna manera dejará conforme al cliente. Decir que el usuario es la falla del sistema es otra tentación que conviene reprimir.


Las pruebas a realizar en tiempo de desarrollo.

Pruebas informales o fase de prueba informal, son aquellas pruebas que hace el desarrollador en su oficina, tienen como objetivo comprobar que el programa compile y ver que todo está yendo como debiera, normalmente se realizan varios cientos de estas pruebas que básicamente consisten en compilar periódicamente durante el desarrollo y ejecutar para ver el resultado.

Dentro de las pruebas en tiempo de desarrollo encontraremos las pruebas de unidades, estas son pruebas de menor escala y consisten en probar cada uno de los módulos que conforman el programa, cuando estos módulos son extensos o complejos se dividen para probar objetivamente partes mas pequeñas, este tipo de pruebas es la mas común.

Las pruebas de integración tienen por objetivo verificar el conjunto funcionamiento de dos o mas módulos, si bien se deben poner en práctica desde la creación de dos módulos que interactúen entre si, en el supuesto caso que se necesiten mas de dos módulos para efectuar las pruebas, deberán generarse simples emuladores de módulos que entreguen datos esperados para la prueba individual de cada uno.

También las pruebas de integración pueden ser realizadas en forma ascendente, esto evita tener que crear módulos emuladores, ya que a medida que se va creando la pirámide va siendo probada de abajo hacia arriba (Down to Top), como se imaginaran esto acarrea un trabajo simétricamente mayor lo que equipara o supera el tiempo que podría tomar el crear módulos para prueba.


Las pruebas después de la programacion.

Cuando se considera que un módulo está terminado se realizan las pruebas sistemáticas, el objetivo de estas es buscar fallos através de un criterio específico, estos criterios se denominan "pruebas de caja negra y de caja blanca".

Las pruebas de caja negra son aquellas que se enfocan directamente en el exterior del módulo, sin importar el código, son pruebas funcionales en las que se trata de encontrar fallas en las que este no se atiene a su especificación, como ser interfaz con el usuario, apariencia de los menús, control de las teclas, etcétera.
Este tipo de pruebas no es aplicable a los módulos que trabajan en forma transparente al usuario.
Para realizar estas pruebas existe una técnica algebraica llamada "clases de equivalencia", consiste en tratar a todos las posibles entradas y parámetros como un modelo algebraico, y utilizar las clases de este modelo para probar un amplio rango de posibilidades.
Para la generación de estas clases no se puede armar un modelo, pero se pueden seguir las siguientes pautas como guía utilizable para la creación de cada clase.
Por ejemplo:
Cuando una entrada es booleana, existen solo dos clases, verdadero o falso.
Para una entrada que está comprendida dentro de un rango, existen tres clases, por debajo, dentro, y por encima del rango.
Utilizando este ejemplo se pueden generar las distintas clases aplicables al módulo en cuestión, luego, se procede a ingresarle al módulo un valor de cada clase.

Las pruebas de caja blanca son mucho mas amplias, normalmente se denominan pruebas de cobertura o pruebas de caja transparente, al total de pruebas se caja blanca se le llama cobertura, la cobertura es un número porcentual que indica cuanto código del programa se ha probado.
Básicamente la idea de pruebas de cobertura consiste en diseñar un plan de pruebas en las que se vaya ejecutando sistemáticamente el código hasta que haya corrido todo o la gran mayoría de el, esto que parece complicado es mas aún cuando el programa contiene código de difícil alcance, como por ejemplo manejadores de errores o "código muerto".
Entiéndase por código muerto a aquellas funciones y/o procedimientos que hemos incluido por encontrarse en recopilaciones pero que estas nunca son ejecutadas por el programa, estas funciones no necesariamente deberán ser removidas pero si probadas por si algún día en revisiones futuras son incluidas.
Para los módulos que no poseen condiciones basta con ejecutar una vez el programa para asegurar una cobertura total.
Es importante que el diseño de cobertura sea eficiente y lo menos redundante posible, por ejemplo, en el siguiente código:

If Variable_Booleana
..Do Modulo_X
EndIf

Como no hay un "else", a simple vista con ejecutar una vez con éxito la condición bastaría, en términos de cobertura es así, pero entendiendo que el "Modulo_X" podría modificar variables o valores que afecten a la ejecución del resto del código habría que ejecutar 2 veces la condición, una satisfaciendo y otra no.
Respecto al siguiente ejemplo:

If Variable_Booleana1 .Or. Variable_Booleana2
..Do Modulo_X
EndI

O este otro:

If Variable_Booleana1 .And. Variable_Booleana2
..
Do Modulo_X
EndI


A simple vista y considerando que ambas variables pueden tener 2 valores se precisarían 4 pruebas para realizar la cobertura, pero esto no es así, solo es necesario 2 pruebas en el caso que el Modulo_X pueda interferir de alguna manera en el programa o una sola satisfaciendo la condición si el Modulo_X no alterara de ninguna manera con el resto de los procedimientos y condiciones a ejecutarse.
Probado las 4 posibilidades solo estaríamos probando que funcione el comando "IF" en sí, lo cual ya fue probado por el desarrollador del lenguaje de programación.

Con respecto a la cobertura en bucles el tema es un poco mas delicado, a simple vista un bucle no es mas que un salto condicional que se repite hasta que se cumpla o deje de cumplirse una o mas condiciones, en teoría esto es simple, pero en la práctica son una fuente inagotable de versátiles errores, que en su gran mayoría suelen ser catastróficos.
En primer lugar, la cantidad de veces que se ejecute un bucle debe ser precisa, y todos los programadores saben que no es difícil equivocarse y programar un bucle que se ejecute una vez de mas o una vez de menos, siempre que esto suceda los resultados serán indeseables, y muchas veces cuando se trate de manejos de datos complicados de calcular no será fácil advertir el error, el cual será caro cuando se trate de valores que se utilizan para tomar determinaciones a nivel empresarial o involucren vidas humanas.
Para realizar la cobertura total de un bucle se necesitan 3 pruebas, cero ejecuciones, una ejecución y mas de una ejecución.
Los bucles de tipo "for", parecerían ser mas sencillos, ya que la cantidad de ejecuciones es definida por su cabecera y controlada por el compilador, con una ejecución bastaría para una cobertura total, siempre y cuando no contengan código que altere el valor de la variable de control o comandos de salida (Exit), en este caso requiere un examen un poco mas detallado ya que el bucle deja de ser responsabilidad del lenguaje compilador y pasa a ser del programador.
Particularmente aconsejo que no se utilicen bucles "for" modificando su variable de control o incluyendo en ellos comandos de salida.
Los

En pocas palabras es muy importante diseñar lo mas precisamente posible las pruebas de cobertura, para que quede en lo posible la mayor parte del código probado con la mínima cantidad de pruebas realizadas.

Hay que tener en cuenta dos puntos importantes, en primer lugar las pruebas de caja blanca no reemplazan, solo complementan a las de caja negra y de aceptación, y en segundo lugar, las pruebas de cobertura deben ser realizadas una ves terminado el software y no deben ser confundidas con las pruebas informales que realiza el programador en momentos de desarrollo, dado que si bien estas van cubriendo distintos fragmentos de cada módulo, nunca son eficaces por no tener un diseño apropiado.

El valor porcentual de pruebas de cobertura de un sistema terminado nunca deberá ser inferior al 51%, y elevándose en función al coste que podría ocasionar las fallas posibles, ascendiendo a un 99% cuando estén involucradas vidas humanas o cuando la falla no da una segunda oportunidad.

El uso de un depurador es muy útil en las pruebas de cobertura, ya que se pueden ir viendo todas las líneas y ejecuciones paso a paso, esto no muy práctico y es bastante tedioso, pero es considerablemente efectivo.

Pruebas de aceptación, son las que hará el cliente, en esta fase de pruebas se determina que el sistema cumple con el objetivo deseado, determina la conformidad del cliente antes de que el programa le sea entregado como una versión final.

Las pruebas conocidas con el nombre de pruebas de rendimiento son aquellas que determinan los tiempos de respuesta, el espacio que ocupa el módulo en disco o en memoria, el flujo de datos que genera a través de un canal de comunicaciones, etc..

Pruebas de transformación, este método curioso y caro aún se pone en funcionamiento por diversas empresas, consiste en dividir el equipo de desarrollo en dos partes una vez realizadas todas las pruebas y corregidos todos los errores, luego una de las dos partes introduce pequeños errores en el sistema y la otra parte debe encontrarlos con los mismos procedimientos que se usaron para buscar los errores nativos.
Esto es muy costoso y consume grandes cantidades de tiempo.

Pruebas de robustez, comúnmente denominadas "robustness test" son las encargadas de verificar la capacidad del programa para soportar entradas incorrectas, por ejemplo en un sistema de facturación donde el usuario debe ingresar códigos de productos y luego cantidades es mas que factible que en algún momento ingrese un código en el campo de cantidad, si el programa fue sometido a pruebas de robustez este valor sería rechazado o grabado como una cantidad inmensa pero que no daría error por desbordamiento de datos.

Las denominadas pruebas de resistencia se utilizan para saber hasta donde puede soportar el programa condiciones extremas, por ejemplo los tiempos de respuesta con el procesador a un 95% de su utilidad o con muy poco espacio en disco.


El plan de pruebas

Un plan de pruebas deberá cumplir con ciertos puntos, en primer lugar se deberá tener en claro que tipo de pruebas se van a aplicar y su correcto orden y diseño.
Diseñar correctamente las pruebas de caja blanca y caja negra es un punto crucial.
Antes de que las pruebas comiencen deberá estar aclarado cual será el punto aceptable de cobertura como así también cual será el método para medir los resultados.
Las pruebas informales donde el resultado se aprecia muy sutilmente no deberán ser parte del plan de pruebas.
Recordar siempre que una prueba solo tiene éxito cuando el programa falla.

Como realizo las pruebas de un programa que me piden que esté terminado ayer.

En la empresa para la cual actualmente trabajo es muy común que se me solicite soluciones, nuevos programas, modificaciones a módulos, cambios de todo tipo, ya sea a nivel datos, código o interfaz con el usuario.
Estas solicitudes se realizan en el mimo momento en que surge la necesidad de cubrir un área que hasta entonces no era imprescindible, y normalmente deben estar en el día o en un lapso de horas, a menos que se trate de verdaderos desarrollos que se planifican con anterioridad y toman unos cuantos meses.
En estas oportunidades el único tipo de prueba que se puede realizar son las informales, las que voy haciendo a medida que confecciono el código, y cuando este aparenta estar listo tiene que ser puesto en funcionamiento.
Obviamente mis clientes, internos en este caso, no soportan ver un error y no se conforman con un "no tuve tiempo de probar en la totalidad", por lo que las fallas del programa no serán nada gratas para mi.
En este caso lo que hago es desarrollar y utilizar las pruebas informales y alguna que otra prueba de integración, y una vez puesto en funcionamiento el producto procedo con las pruebas de caja blanca, ya que las de caja negra se están haciendo simultáneamente por verdaderos clientes.
Por suerte poseo dos servidores uno funcional y otro que es una copia del mismo destinado a la realización de pruebas, esto acelera mucho los procesos de testeo, siempre es conveniente tener armada una estructura secundaria donde se puedan ingresar datos con ánimo de probar y que estos no se vean involucrados con los verdaderos.
Es muy útil también respetar siempre los números de versiones así siempre se sabrá que el software en ejecución es la última revisión del mismo.

Espero que este documento pueda ayudarlos a enfocar de otra manera las pruebas de sus programas, no obstante recomiendo que adquieran algo de bibliografía respecto al tema.
Y recuerden, hacer un sistema magistral cuesta mucho esfuerzo, probarlo cuesta mas.

Esta sección sobre CA-Clipper está coordinada íntegramente por Diego Lucio D'Onofrio


la Luna del Guille o... el Guille que está en la Luna... tanto monta...

Estadísticas desde el 01/Nov/2002 23:15