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.
|