Diseño de las pruebas.
Calidad, errores y pruebas
La calidad no es algo que se pueda
agregar al software después de desarrollado si no se hizo todo el desarrollo
con la calidad en mente. Muchas veces parece que el software de calidad es aquél
que brinda lo que se necesita con adecuada velocidad de procesamiento. En realidad,
es mucho más que eso. Tiene que ver con la corrección, pero también con usabilidad,
costo, consistencia, confiabilidad, compatibilidad, utilidad, eficiencia y
apego a los estándares.
Todos estos aspectos de la calidad
pueden ser objeto de tests o pruebas que determinen el grado de calidad.
Incluso la documentación para el usuario debe ser probada.
Como en todo proyecto de cualquier
índole, siempre se debe tratar que las fallas sean mínimas y poco costosas,
durante todo el desarrollo. Y además, es sabido que cuanto más tarde se encuentra
una falla, más caro resulta eliminarla. Es claro que si un error es descubierto
en la mitad del desarrollo de un sistema, el costo de su corrección será mucho
menor al que se debería enfrentar en caso de descubrirlo con el sistema
instalado y en funcionamiento.
Desde el punto de vista de la
programación, nos interesa la ausencia de errores (corrección), la
confiabilidad y la eficiencia. Dejando de lado las dos últimas, nos
concentraremos en este capítulo en las pruebas que determinen que un programa
está libre de errores.
Un error es un comportamiento distinto del que espera un usuario
razonable. Puede haber errores aunque se hayan seguido todos los pasos
indicados en el análisis y en el diseño, y hasta en los requisitos aprobados
por el usuario. Por lo tanto, no necesariamente un apego a los requisitos y un
perfecto seguimiento de las etapas nos lleva a un producto sin errores, porque
aún en la mente de un usuario pudo haber estado mal concebida la idea desde el
comienzo. De allí la importancia del desarrollo incremental, que permite ir
viendo versiones incompletas del sistema.
Por lo tanto, una primera fuente de
errores ocurre antes de los requerimientos o en el propio proceso de análisis.
Pero también hay errores que se introducen durante el proceso de desarrollo
posterior. Así, puede haber errores de diseño y errores de implementación.
Finalmente, puede haber incluso errores en la propia etapa de pruebas y
depuración.
Categorías de pruebas:
Según la naturaleza de lo que se esté
controlando, las pruebas se pueden dividir en dos categorías:
- Pruebas centradas en la verificación.
- Pruebas centradas en la validación.
Las primeras sirven para determinar la
consistencia entre los requerimientos y el programa terminado. Soporta
metodologías formales de testeo, de mucho componente matemático. De todas maneras,
hay que ser cuidadoso, porque no suele ser fácil encontrar qué es lo que hay
que demostrar. La verificación consiste en determinar si estamos construyendo
el sistema correctamente, a partir de los requisitos.
En general a los informáticos no les
gustan las pruebas formales, en parte porque no las entienden y en parte porque
requieren un proceso matemático relativamente complejo.
La validación consiste en saber si
estamos construyendo el sistema correcto. Las tareas de validación son más
informales. Las pruebas suelen mostrar la presencia de errores, pero nunca demuestran
su ausencia.
Las pruebas y el desarrollo de software.
La etapa de pruebas es una de las
fases del ciclo de vida de los proyectos. Se la podría ubicar después del
análisis, el diseño y la programación, pero dependiendo del proyecto en cuestión
y del modelo de proceso elegido, su realización podría ser en forma paralela a
las fases citadas o inclusive repetirse varias veces durante la duración del
proyecto.
La importancia de esta fase será mayor
o menor según las características del sistema desarrollado, llegando a ser
vital en sistemas de tiempo real u otros en los que los errores sean irrecuperables.
Las pruebas no tienen el objeto de
prevenir errores sino de detectarlos5. Se efectúan sobre el trabajo realizado y
se deben encarar con la intención de descubrir la mayor cantidad de errores posible.
Para realizar las pruebas se requiere
gente que disfrute encontrando errores, por eso no es bueno que sea el mismo
equipo de desarrollo el que lleve a cabo este trabajo. Además, es un principio
fundamental de las auditorías. Por otro lado, es bastante común que a quien le
guste programar no le guste probar, y viceversa.
A veces se dan por terminadas las
pruebas antes de tiempo. En las pruebas de caja blanca (ver más adelante) no es
mala idea probar un 85% de las ramas y dar por terminado luego de esto.
Otra posibilidad es la siembra de
errores y seguir las pruebas hasta que se encuentren un 85% de los errores
sembrados, lo que presumiblemente implica que se encontró un 85% de los no
sembrados6. Otros métodos se basan en
la estadística y las comparaciones, ya sea por similitud con otro sistema en
cantidad de errores o por el tiempo de prueba usado en otro sistema.
En un proyecto ideal, podríamos
generar casos de prueba para cubrir todas las posibles entradas y todas las
posibles situaciones por las que podría atravesar el sistema. Examinaríamos así
exhaustivamente el sistema para asegurar que su comportamiento sea perfecto.
Pero hay un problema con esto: el número de casos de prueba para un sistema
complejo es tan grande que no alcanzaría una vida para terminar con las
pruebas. Como consecuencia, nadie realiza una prueba exhaustiva de nada salvo
en sistemas triviales.
En un sistema real, los casos de
prueba se deben hacer sobre las partes del sistema en los cuales una buena
prueba brinde un mayor retorno de la inversión o en las cuales un error
represente un riesgo mayor.
Las pruebas cuestan mucho dinero. Pero
para ello existe una máxima: “pague por la prueba ahora o pague el doble por el
mantenimiento después”.
Todo esto lleva a que se deban
planificar bien las pruebas, con suficiente anticipación, y determinar desde el
comienzo los resultados que se deben obtener.
La idea de extreme programming es más radical: propone primero escribir los
programas de prueba y después la aplicación, obligando a correr las pruebas
siempre antes de una integración.
Se basa en la idea bastante acertada
de que los programas de prueba son la mejor descripción de los requerimientos.
Esto lo analizamos en un ítem especial.
Tipos de pruebas:
Analizaremos 5 tipos de pruebas:
- Revisiones de código
- Pruebas unitarias
- Pruebas de integración
- Pruebas de sistema
- Pruebas de aceptación
- No son tipos de pruebas
intercambiables, ya que testean cosas distintas. En el ítem siguiente
analizamos cada tipo.
Otra posible clasificación de las
pruebas es:
- De caja blanca o de código.
- De caja negra o de especificación.
En las primeras se evalúa el contenido
de los módulos, mientras en las segundas se trata al módulo como una caja
cerrada y se lo prueba con valores de entrada, evaluando los valores de salida.
Vistas de este modo, las pruebas de
caja negra sirven para verificar especificaciones.
Las pruebas unitarias suelen ser de
caja blanca o de caja negra, mientras que las de integración, sistema y
aceptación son de caja negra. Las tareas de depuración luego de encontrar errores
son más bien técnicas de caja blanca, así como las revisiones de código. En
todos los casos, uno de los mayores desafíos es encontrar los datos de prueba:
hay que encontrar un subconjunto de todas las entradas que tengan alta
probabilidad de detectar el mayor número de errores.
Revisiones de código:
Las revisiones de código son las
únicas que se podrían omitir de todos los tipos de pruebas, pero tal vez sea
buena idea por lo menos hacer alguna de ellas:
- Pruebas de escritorio
- Recorridos de código
- Inspecciones de código
La prueba de escritorio rinde muy
poco, tal vez menos de lo que cuesta, pero es una costumbre difícil de desterrar.
Es bueno concentrarse en buscar anomalías típicas, como variables u objetos no
inicializados o que no se usan, ciclos infinitos y demás.
Los recorridos rinden mucho más. Son
exposiciones del código escrito frente a pares. El programador, exponiendo su
código, encuentra muchos errores. Además da ideas avanzadas a programadores
nuevos que se lleva a recorrer.
Las llamadas inspecciones de código
consisten en reuniones en conjunto entre los responsables de la programación y
los responsables de la revisión. Tienen como objetivo revisar el código escrito
por los programadores para chequear que cumpla con las normas que se hayan
fijado y para verificar la eficiencia del mismo. Se realizan siguiendo el
código de un pequeño porcentaje de módulos seleccionados al azar o según su
grado de complejidad. Las inspecciones se pueden usar en sistemas grandes, pero
con cuidado para no dar idea de estar evaluando al programador.
Suelen servir porque los revisores
están más acostumbrados a ver determinados tipos de errores comunes a todos los
programadores. Además, después de una inspección a un programador, de la que
surge un tipo de error, pueden volver a inspeccionar a otro para ver si no cayó
en el mismo error.
El concepto de extreme programming propone programar de a dos, de modo que uno escribe
y el otro observa el trabajo. Si el que está programando no puede avanzar en
algún momento, sigue el que miraba. Y si ambos se traban pueden pedir ayuda a
otro par. Esta no sólo es una forma más social de programación, sino que
aprovecha las mismas ventajas de los recorridos e inspecciones de código, y
puede prescindir de ellos.
Pruebas unitarias:
Las pruebas unitarias se realizan para
controlar el funcionamiento de pequeñas porciones de código como ser
subprogramas (en la programación estructurada) o métodos (en POO).
Generalmente son realizadas por los
mismos programadores puesto que al conocer con mayor detalle el código, se les
simplifica la tarea de elaborar conjuntos de datos de prueba para testearlo.
Si bien una prueba exhaustiva sería impensada teniendo en cuenta recursos,
plazos, etc., es posible y necesario elegir cuidadosamente los casos de prueba
para recorrer tantos caminos lógicos como sea posible. Inclusive procediendo de
esta manera, deberemos estar preparados para manejar un gran volumen de datos
de prueba.
Los métodos de cobertura de caja
blanca tratan de recorrer todos los caminos posibles por lo menos una vez, lo
que no garantiza que no haya errores pero pretende encontrar la mayor parte.
El tipo de prueba a la cual se someterá a cada uno de los módulos dependerá de su complejidad. Recordemos que nuestro objetivo aquí es encontrar la mayor cantidad de errores posible. Si se pretende realizar una prueba estructurada, se puede confeccionar un grafo de flujo con la lógica del código a probar. De esta manera se podrán determinar todos los caminos por los que el hilo de ejecución pueda llegar a pasar, y por consecuente elaborar los juegos de valores de pruebas para aplicar al módulo, con mayor facilidad y seguridad.
Documentación del sistema.
En general se habla mucho de la
documentación, pero no se la hace, no se le asigna presupuesto, no se la
mantiene y casi nunca está al día en los proyectos de desarrollo de software.
Lo importante es la disponibilidad de
la documentación que se necesita en el momento en que se la necesita.
Muchas veces se hace porque hay que
hacerla y se escribe, con pocas ganas, largos textos, a la vez que se está
convencido de estar haciendo un trabajo inútil. A veces se peca por exceso y
otras por defecto. Ocurre mucho en la Web y con productos RAD. En ocasiones se
olvida que el mantenimiento también debe llegar a la documentación.
La documentación se suele clasificar
en función de las personas o grupos a los cuales está dirigida:
- Documentación para los
desarrolladores.
- Documentación para los usuarios.
- Documentación para los
administradores o soporte técnico.
La documentación para desarrolladores
es aquélla que se utiliza para el propio desarrollo del producto y, sobre todo,
para su mantenimiento futuro. Se documenta para comunicar estructura y comportamiento
del sistema o de sus partes, para visualizar y controlar la arquitectura del
sistema, para comprender mejor el mismo y para controlar el riesgo, entre otras
cosas.
Obviamente, cuanto más complejo es el
sistema, más importante es la documentación.
En este sentido, todas las fases de un
desarrollo deben documentarse: requerimientos, análisis, diseño, programación,
pruebas, etc... Una herramienta muy útil en este sentido es una notación
estándar de modelado, de modo que mediante ciertos diagramas se puedan
comunicar ideas entre grupos de trabajo.
Hay decenas de notaciones, tanto
estructuradas como orientadas a objetos. Un caso particular es el de UML, que
analizamos en otra obra. De todas maneras, los diagramas son muy útiles, pero
siempre y cuando se mantengan actualizados, por lo que más vale calidad que
cantidad.
La documentación para desarrolladores
a menudo es llamada modelo, pues
es una simplificación de la realidad para comprender mejor el sistema como un
todo.
Otro aspecto a tener en cuenta cuando
se documenta o modela, es el del nivel de detalle.
Así como cuando construimos planos de
un edificio podemos hacer planos generales, de arquitectura, de instalaciones y
demás, también al documentar el software debemos cuidar el nivel de detalle y
hacer diagramas diferentes en función del usuario de la documentación,
concentrándonos en un aspecto a la vez.
De toda la documentación para los
desarrolladores, nos interesa especialmente en esta obra aquella que se utiliza
para documentar la programación, y en particular hemos analizado la que se usa
para documentar desarrollos orientados a objetos en el capítulo respectivo.
La documentación para usuarios es todo
aquello que necesita el usuario para la instalación, aprendizaje y uso del
producto. Puede consistir en guías de instalación, guías del usuario, manuales
de referencia3 y guías de mensajes.
En el caso de los usuarios que son
programadores, verbigracia los clientes de nuestras clases, esta documentación
se debe acompañar con ejemplos de uso recomendados o de muestra y una reseña de
efectos no evidentes de las bibliotecas.
Más allá de todo esto, debemos tener
en cuenta que la estadística demuestra que los usuarios no leen los manuales a
menos que nos les quede otra opción. Las razones pueden ser varias, pero un
análisis de la realidad muestra que se recurre a los manuales solamente cuando
se produce un error o se desconoce cómo lograr algo muy puntual, y recién
cuando la ayuda en línea no satisface las necesidades del usuario. Por lo
tanto, si bien es cierto que debemos realizar manuales, la existencia de un
buen manual nunca nos libera de hacer un producto amigable para el usuario, que
incluso contenga ayuda en línea. Es incluso deseable proveer un software
tutorial que guíe al usuario en el uso del sistema, con apoyo multimedia, y que
puede llegar a ser un curso online.
Buena parte de la documentación para
los usuarios puede empezar a generarse desde que comienza el estudio de
requisitos del sistema. Esto está bastante en consonancia con las ideas de extreme programming y con
metodologías basadas en casos de uso.
La documentación para administradores
o soporte técnico, a veces llamada manual de operaciones, contiene toda la
información sobre el sistema terminado que no hace al uso por un usuario final.
Es necesario que tenga una descripción de los errores posibles del sistema, así
como los procedimientos de recuperación. Como esto no es algo estático, pues la
aparición de nuevos errores, problemas de compatibilidad y demás nunca se puede
descartar, en general el manual de operaciones es un documento que va
engrosándose con el tiempo.