Estrategias De Prueba Del Software

30.01.2013 16:37

 

 

ESTRATEGIAS DE PRUEBA DEL SOFTWARE

 

Una estrategia de prueba del software integra las técnicas de diseño de casos de prueba en una serie de pasos bien planificados que dan como resultado una correcta construcción del software. Y lo que es más importante, una estrategia de prueba del software proporciona un mapa a seguir para el responsable del desarrollo del software, a la organización de control de calidad y al cliente: un mapa que describe los pasos que hay que llevar a cabo como parte de la prueba, cuándo se deben planificar y realizar esos pasos, y cuánto esfuerzo, tiempo y recursos se van a requerir. Por tanto, cualquier estrategia de prueba debe incorporar la planificación de la prueba, el diseño de casos de prueba, la ejecución de las pruebas y la agrupación y evaluación de los datos resultantes.

Una estrategia de prueba del software debe ser suficientemente flexible para promover la creatividad y la adaptabilidad necesarias para adecuar la prueba a todos los grandes sistemas basados en software. Al mismo tiempo, la estrategia debe ser suficientemente rígida para promover un seguimiento razonable de la planificación y la gestión a medida que progresa el proyecto. Shooman trata estos puntos:

En muchos aspectos, la prueba es un proceso individualista y el número de tipos diferentes de pruebas varía tanto como los diferentes enfoques de desarrollo. Durante muchos años, nuestra única defensa contra los errores de programación era un cuidadoso diseño y la propia inteligencia del programador. Ahora nos encontramos en una era en la que las técnicas modernas de diseño (y las revisiones técnicas formales) nos ayudan a reducir el número de errores iniciales que se encuentran en el código de forma inherente. De forma similar, los diferentes métodos de prueba están empezando a agruparse en varias filosofías y enfoques diferentes.

 

 

ENFOQUE ESTRATÉGICO DE PRUEBA

 

La prueba es un conjunto de actividades que se pueden planificar por adelantado y llevar a cabo sistemáticamente. Por esta razón se debe definir en el proceso de la ingeniería del software una plantilla para la prueba del software: un conjunto de pasos en los que podamos situar los métodos específicos de diseño de casos de prueba.

Se han propuesto varias estrategias de prueba del software en distintos libros. Todas proporcionan al desarrollador de software una plantilla para la prueba y todas tienen las siguientes características generales:

·         La prueba comienza en el nivel de módulo y trabaja «hacia fuera», hacia la integración de todo el sistema basado en computadora.

·         Según el momento son apropiadas diferentes técnicas de prueba.

·         La prueba la lleva a cabo el responsable del desarrollo del software y (para grandes proyectos) un grupo independiente de pruebas.

·         La prueba y la depuración son actividades diferentes, pero la depuración se debe incluir en cualquier estrategia de prueba.

 

Una estrategia de prueba del software debe incluir pruebas de bajo nivel que verifiquen que todos los pequeños segmentos de código fuente se han implementado correctamente, así como pruebas de alto nivel que validen las principales funciones del sistema frente a los requisitos del cliente. Una estrategia debe proporcionar una guía al profesional y proporcionar un conjunto de hitos para el jefe de proyecto. Debido a que los pasos de la estrategia de prueba se dan a la vez cuando aumenta la presión de los plazos fijados, se debe poder medir el progreso y los problemas deben aparecer lo antes posible.

 

Verificación y validación

La prueba del software es un elemento de un tema más amplio que, a menudo, se le conoce como verificación y validación (V&V). La verificación se refiere al conjunto de actividades que aseguran que el software implementa correctamente una función específica. La validación se refiere a un conjunto diferente de actividades que aseguran que el software construido se ajusta a los requisitos del cliente. Bohem lo define de otra forma:

Verificación:«¿Estamos construyendo el producto correctamente?»

Validación:«¿Estamos construyendo el producto correcto?»

 

Organización para la prueba del software

En cualquier proyecto de software existe un conflicto de intereses inherente que aparece cuando comienza la prueba. Se pide a la gente que ha construido el software que lo pruebe.

Esto parece totalmente inofensivo: después de todo, ¿quién puede conocer mejor un programa que los que lo han desarrollado? Desgraciadamente, los mismos programadores tienen un gran interés en demostrar que el programa está libre de errores, que funciona de acuerdo con las especificaciones del cliente y que estará listo de acuerdo con los plazos y el presupuesto. Cada uno de estos intereses se convierte en inconveniente a la hora de encontrar errores a lo largo del proceso de prueba.

Desde un punto de vista psicológico, el análisis y el diseño del software (junto con la codificación) son tareas constructivas. El ingeniero de software crea un programa de computadora, su documentación y sus estructuras de datos asociadas. Al igual que cualquier constructor, el ingeniero de software está orgulloso del edificio que acaba de construir y se enfrenta a cualquiera e intente sacarle defectos. Cuando comienza la prueba, aparece una sutil, aunque firme intención de «romper» lo que el ingeniero del software ha construido. Desde el punto de vista del constructor, la prueba se puede considerar (psicológicamente) destructiva. Por lo tanto, el constructor anda con cuidado, diseñando y ejecutando pruebas que demuestren que el programa funciona, en lugar de detectar errores. Desgraciadamente, los errores seguirán estando. Y si el ingeniero de software no los encuentra, el cliente si lo hará.

A menudo, existen ciertos malentendidos que se pueden deducir equivocadamente de la anterior discusión:

(1) el responsable del desarrollo no debería entrar en el proceso de prueba;

(2) el software debe ser «puesto a salvo» de extraños que puedan probarlo de forma despiadada;

(3) los encargados de la prueba sólo aparecen en el proyecto cuando comienzan las etapas de prueba.

Todas estas frases son incorrectas.

El responsable del desarrollo del software siempre es responsable de probar las unidades individuales (módulos) del programa, asegurándose de que la una lleva a cabo la función para la que fue diseñada. En muchos casos, también se encargará de la prueba de integración, el paso de prueba que lleva a la construcción (y prueba) de la estructura total del sistema. Sólo una vez que la arquitectura del software esté completa entra en juego un grupo independiente de prueba.

El papel del grupo independiente de prueba (GIP) es eliminar los inherentes problemas asociados con el hecho de permitir al constructor que pruebe lo que ha construido. Una prueba independiente elimina el conflicto de intereses que, de otro modo, estaría presente. Después de todo, al personal del equipo que forma el grupo independiente se le paga para que encuentre errores.

Sin embargo, el responsable del desarrollo del software no entrega simplemente el programa al GIP y se desentiende. El responsable del desarrollo y el GIP trabajan estrechamente a lo largo del proyecto de software para asegurar que se realizan pruebas exhaustivas. Mientras se realiza la prueba, el desarrollador debe estar disponible para corregir los errores que se van descubriendo.

El GIP es parte del equipo del proyecto de desarrollo de software en el sentido de que se ve implicado durante el proceso de especificación y sigue implicado (planificación y especificación de los procedimientos de prueba) a lo largo de un gran proyecto. Sin embargo, en muchos casos, el GIP informa a la organización de garantía de calidad del software, consiguiendo de este modo un grado de independencia que no sería posible si fuera una parte de la organización de desarrollo del software. 

Una estrategia de prueba del software

El proceso de ingeniería del software se puede ver como una espiral, Inicialmente, la ingeniería del sistema define el papel del software y conduce al análisis de los requisitos del software, donde se establece el dominio de información, la función, el comportamiento, el rendimiento, las restricciones y los criterios de validación del software.

Al movernos hacia el interior de la espiral, llegamos al diseño y, por último, a la codificación. Para desarrollar software de computadora, damos vueltas en espiral a través de una serie de flujos o líneas que disminuyen el nivel de abstracción en cada vuelta.

La prueba de unidad comienza en el vértice de la espiral y se centra en cada unidad del software, tal como está implementada en código fuente. La prueba avanza, al movernos hacia fuera de la espiral, hasta llegar a la prueba de integración, donde el foco de atención es el diseño y la construcción de la arquitectura del software. Dando otra vuelta por la espiral hacia fuera, encontramos la prueba de validación, donde se validan los requisitos establecidos como parte del análisis de requisitos del software, comparándolos con el sistema que ha sido construido. Finalmente, llegamos a la prueba del sistema, en la que se prueban como un todo el software y otros elementos del sistema. Para probar software de computadora nos movemos hacia fuera por una espiral que, a cada vuelta, aumenta el alcance de la prueba.

Si consideramos el proceso desde el punto de vista procedimental, la prueba, en el contexto de la ingeniería del software, realmente es una serie de cuatro pasos que se llevan a cabo secuencialmente. Inicialmente, la prueba se centra en cada módulo individualmente, asegurando que funcionan adecuadamente como una unidad. De ahí el nombre de prueba de unidad. La prueba de unidad ejercita caminos específicos de la estructura de control del módulo para asegurar un alcance completo y una detección máxima de errores. A continuación se deben ensamblar o integrar los módulos para formar el paquete de software completo. La prueba de integración se dirige a todos los aspectos asociados con el doble problema de verificación y de construcción del programa. Después de que el software se ha integrado (construido), se dirigen un conjunto de pruebas de alto nivel. Se deben comprobar los criterios de validación (establecidos durante el análisis de requisitos). La prueba de validación proporciona una seguridad final de que el software satisface todos los requisitos funcionales, de comportamiento y de rendimiento.

El último paso de prueba de alto nivel queda fuera de los límites de la ingeniería del software, entrando en el más amplio contexto de la ingeniería de sistemas de computadora. El software, una vez validado, se debe combinar con otros elementos del sistema (p.ej.: hardware, gente, bases de datos). La prueba del sistema verifica que cada elemento encaja de forma adecuada y que se alcanza la funcionalidad y el rendimiento del sistema total.

Criterios para completar la prueba

Cada vez que se trata la prueba del software surge una pregunta clásica: ¿Cuándo hemos terminado la prueba, cómo sabemos que hemos probado lo suficiente? Desgraciadamente, no hay una respuesta definitiva a esta pregunta, pero hay algunas respuestas prácticas y nuevos intentos de base empírica.

Una respuesta a la pregunta anterior es: «La prueba nunca termina, ya que el responsable del desarrollo del software carga o pasa el problema al cliente». Cada vez que el cliente/usuario ejecuta un programa de computadora, dicho programa se está probando con un nuevo conjunto de datos. Otra respuesta (algo cínica, pero sin embargo cierta) es: «Se termina la prueba cuando se agota el tiempo o el dinero disponible para tal efecto».

Aunque algunos profesionales se sirvan de estas respuestas como argumento, un ingeniero de software necesita un criterio más riguroso para determinar cuándo se ha realizado la prueba suficiente. Musa y Ackerman sugieren una respuesta basada en un criterio estadístico: «No, no podemos tener la certeza absoluta de que el software nunca fallará, pero en base a un modelo estadístico de corte teórico y validado experimentalmente, hemos realizado las pruebas suficientes para decir, con un 95 por ciento de certeza, que la probabilidad de funcionamiento libre de fallo de 1.000 horas de CPU, en un entorno definido de forma probabilística, es al menos 0,995».

Mediante el modelado estadístico y la teoría de fiabilidad del software, se pueden desarrollar modelos de fallos del software (descubiertos durante la prueba) como una función del tiempo de ejecución. Una versión del modelo de fallos, denominado modelo logarítmico de Poisson de tiempo de ejecución, adquiere la siguiente forma:

f(t) =(1/p)ln(l0pt+ 1) (1)

donde f(t) =número acumulado de fallos que se espera que se produzcan una vez que se ha probado el software durante una cierta cantidad de tiempo de ejecución t,

l0 = la intensidad de fallos inicial del software (fallos por unidad de tiempo) al principio de la prueba

p = la reducción exponencial de intensidad de fallo a medida que se encuentran los errores y se van haciendo las correcciones.

 

La intensidad de fallos instantánea, l(t) se puede obtener mediante la derivada de f(t):

l(t) = l0/ (l0pt+1) (2)

Mediante la relación de la ecuación (2), los que realizan la prueba pueden predecir la disminución de errores a medida que avanza la prueba. La intensidad de error real se puede trazar junto a la curva predecida (Figura 10.3). Si los datos reales recopilados durante la prueba y el modelo logarítmico de Poisson de tiempo de ejecución están razonablemente cerca unos de otros, sobre un número de puntos de datos, el modelo se puede usar para predecir el tiempo de prueba total requerido para alcanzar una intensidad de fallos aceptablemente baja.

Mediante la agrupación de métricas durante la prueba del software y haciendo uso de los modelos de fiabilidad del software existentes, es posible desarrollar directrices importantes para responder a la pregunta: ¿cuándo terminamos la prueba? No hay duda de que todavía queda mucho trabajo por hacer antes de que se puedan establecer reglas cuantitativas para la prueba, pero los enfoques empíricos que existen actualmente son considerablemente mejores que la pura intuición.

 

 

Aspectos Estratégicos

Más adelante exploramos una estrategia sistemática para la prueba del software. Pero incluso la mejor estrategia fracasará si no se tratan una serie de aspectos invalidantes.

Tom Gilb plantea que se deben abordar los siguientes puntos si se desea implementar con éxito una estrategia de prueba del software:

Especificar los requisitos del producto de manera cuantificable mucho antes de que comiencen las pruebas. Aunque el objetivo principal de la prueba es encontrar errores, una buena estrategia de prueba también evalúa otras características de la calidad tales como la transportabilidad, facilidad de mantenimiento y facilidad de uso. Todo esto debería especificarse de manera que sea medible para que los resultados de la prueba no sean ambiguos.

Establecer los objetivos de la prueba de manera explícita. Se deberían establecer en términos medibles los objetivos específicos de la prueba. Por ejemplo la efectividad de la prueba, la cobertura de la prueba, tiempo medio de fallo, el coste para encontrar y arreglar errores, densidad de fallos remanente o frecuencia de ocurrencia, y horas de trabajo por prueba de regresión deberían establecerse dentro de la planificación de la prueba.

Comprender qué usuarios va a tener el software y desarrollar un perfil para cada categoría de usuario. Usar casos, que describan el escenario de interacción para cada clase de usuario pudiendo reducir el esfuerzo general de prueba concentrando la prueba en el empleo real del producto.

Desarrollar un plan de prueba que haga hincapié en la «prueba de ciclo rápido». Gilb recomienda que un equipo de ingeniería del software «aprenda a probar en ciclos rápidos (2 por ciento del esfuerzo del proyecto) de incrementos de funcionalidad y/o mejora de la calidad útiles para el cliente y que se puedan probar sobre el terreno». La realimentación generada por estas pruebas de ciclo rápido puede usarse para controlar los niveles de calidad y las correspondientes estrategias de prueba.

Construir un software «robusto» diseñado para probarse a sí mismo. El software debería diseñarse de manera que use técnicas de depuración anti-errores. Es decir, el software debería ser capaz de diagnosticar ciertas clases de errores. Además, el diseño debería incluir pruebas automatizadas y pruebas de regresión.

Usar revisiones técnicas formales efectivas como filtro antes de la prueba. Las revisiones técnicas formales pueden ser tan efectivas como las pruebas en el descubrimiento de errores. Por este motivo, las revisiones pueden reducir la cantidad de esfuerzo de prueba necesaria para producir software de alta calidad.

Llevar a cabo revisiones técnicas formales para evaluar la estrategia de prueba y los propios casos de prueba. Las revisiones técnicas formales pueden descubrir inconsistencias, omisiones y errores claros en el enfoque de la prueba. Esto ahorra tiempo y también mejora la calidad del producto.

Desarrollar un enfoque de mejora continua al proceso de prueba. Debería medirse la estrategia de prueba. Las métricas agrupadas durante la prueba deberían usarse como parte de un enfoque estadístico de control del proceso para la prueba del software.

 

PRUEBA DE UNIDAD

La prueba de unidad centra el proceso de verificación en la menor unidad del diseño del software: el módulo. Usando la descripción del diseño procedimental como guía, se prueban los caminos de control importantes, con el fin de descubrir errores dentro del límite del módulo.

La complejidad relativa de las pruebas y de los errores descubiertos está limitada por el alcance estricto establecido por la prueba de unidad.

 

Consideraciones sobre la prueba de unidad

Las pruebas que se dan como parte de la prueba de unidad. Se prueba la interfaz del módulo para asegurar que la información fluye de forma adecuada hacia y desde la unidad de programa que está siendo probada. Se examinan las estructuras de datos locales para asegurar que los datos que se mantienen temporalmente conservan su integridad durante todos los pasos de ejecución del algoritmo. Se prueban las condiciones límite para asegurar que el módulo funciona correctamente en los límites establecidos como restricciones de procesamiento. Se ejercitan todos los caminos independientes (caminos básicos) de la estructura de control con el fin de asegurar que todas las sentencias del módulo se ejecutan por lo menos una vez. Y, finalmente, se prueban todos los caminos de manejo de errores.

Antes de iniciar cualquier otra prueba es preciso probar el flujo de datos de la interfaz del módulo. Si los datos no entran correctamente, todas las demás pruebas no tienen sentido. Myers, en su libro sobre prueba del software, propone una lista de comprobaciones para la prueba de interfaces:

1.    ¿Es igual el número de parámetros de entrada al número de argumentos?

2.    ¿Coinciden los atributos de los parámetros y los argumentos?

3.    ¿Coinciden los sistemas de unidades de los parámetros y de los argumentos?

4.    ¿Es igual el número de argumentos transmitidos a los módulos de llamada que el número de parámetros?

5.    ¿Son iguales los atributos de los argumentos transmitidos a los módulos de llamada y los atributos de los parámetros?

6.    ¿Son iguales los sistemas de unidades de los argumentos transmitidos a los módulos de llamada y de los parámetros?

7.    ¿Son correctos el número de los atributos y el orden de los argumentos de las funciones incorporadas?

8.    ¿Existen referencias a parámetros que no estén asociados con el punto de entrada actual?

9.    ¿Entran sólo argumentos alterados?

10. ¿Son consistentes las definiciones de variables globales entre los módulos?

11. ¿Se pasan las restricciones como argumentos?

 

Cuando un módulo lleve a cabo E/S externa, se deben llevar a cabo pruebas de interfaz adicionales. Nuevamente, de Myers:

 

1.    ¿Son correctos los atributos de los archivos?

2.    ¿Son correctas las sentencias ABRIR/CERRAR?

3.    ¿Coinciden las especificaciones de formato con las sentencias de E/S?

4.    ¿Coincide el tamaño del buffer con el tamaño del registro?

5.    ¿Se abren los archivos antes de usarlos?

6.    ¿Se tienen en cuenta las condiciones de fin-de-archivo?

7.    ¿Se manejan los errores de E/S?

8.    ¿Hay algún error textual en la información de salida?

 

Las estructuras de datos locales de cada módulo son una fuente potencial de errores. Se deben diseñar casos de prueba para descubrir errores de las siguientes categorías:

 

1.    Tipificación impropia o inconsistente.

2.    Inicialización o valores implícitos erróneos.

3.    Nombres de variables incorrectos (mal escritos o truncados).

4.    Tipos de datos inconsistentes.

5.    Excepciones de desbordamiento por arriba o por abajo, o de direccionamiento.

 

Además de las estructuras de datos locales, durante la prueba de unidad se debe comprobar (en la medida de lo posible) el impacto de los datos globales sobre el módulo.

Durante la prueba de unidad, la comprobación selectiva de los caminos de ejecución es una tarea esencial. Se deben diseñar casos de prueba para detectar errores debidos a cálculos incorrectos, comparaciones incorrectas o flujos de control inapropiados. Las pruebas del camino básico y de bucles son técnicas muy efectivas para descubrir una gran cantidad de errores en los caminos.

Entre los errores más comunes en los cálculos están:

1.            precedencia aritmética incorrecta o mal interpretada;

2.            operaciones de modo mezcladas;

3.            inicializaciones incorrectas;

4.            falta de precisión;

5.            incorrecta representación simbólica de una expresión.

Las comparaciones y el flujo de control están fuertemente emparejadas (p. ej.: el flujo de control cambia frecuentemente tras una comparación).

Los casos de prueba deben descubrir errores como:

1.      comparaciones ente tipos de datos distintos;

2.      operadores lógicos o de precedencia incorrectos;

3.      igualdad esperada cuando los errores de precisión la hacen poco probable; (4) variables o comparaciones incorrectas;

4.      terminación de bucles inapropiada o inexistente;

5.      fallo de salida cuando se encuentra una iteración divergente y

6.      bucles que manejan variables modificadas de forma inapropiada.

Un buen diseño exige que las condiciones de error sean previstas de antemano y que se dispongan unos caminos de manejo de errores que redirijan o terminen de una forma limpia el proceso cuando se dé un error. Yourdon llama a este enfoque antipurgado. Desgraciadamente, existe una tendencia a incorporar la manipulación de errores en el software y así no probarlo nunca. Como ejemplo, sirve una historia real:

Mediante un contrato se desarrolló un importante sistema de diseño interactivo. En un módulo de proceso de transacciones un bromista puso el siguiente mensaje de manipulación de error que aparecía tras una serie de pruebas condicionales que invocaban varias ramificaciones del flujo de control: ¡ERROR! NO HAY FORMA DE QUE UD. LLEGUE HASTA AQUÍ. ¡Este «mensaje de error» fue descubierto por un cliente durante la fase de puesta a punto!

 

Entre los errores potenciales que se deben comprobar cuando se evalúa la manipulación de errores están:

 

1.      Descripción ininteligible del error.

2.      El error señalado no se corresponde con el error encontrado

3.      La condición de error hace que intervenga el sistema antes que el mecanismo de manejo de errores

4.      El proceso de la condición excepcional es incorrecto

5.      La descripción del error no proporciona suficiente información para ayudar en la localización de la causa del error.

 

La prueba de límites es la última (y probablemente, la más importante) tarea del paso de la prueba de unidad. El software falla frecuentemente en sus condiciones límite. Es decir, con frecuencia aparece un error cuando se procesa el elemento n-ésimo de un array n-dimensional, cuando se hace la i-ésima repetición de un bucle de pasos o cuando se encuentran los valores máximo o mínimo permitidos. Los casos de prueba que ejerciten las estructuras de datos, el flujo de control y los valores de los datos por debajo, en y por encima de los máximos y los mínimos son muy apropiados para descubrir estos errores.

 

Procedimientos de la prueba de unidad

Normalmente, se considera a la prueba de unidad como algo adjunto al paso de codificación. El diseño de casos de prueba de unidad comienza una vez que se ha desarrollado, revisado y verificado en su sintaxis el código a nivel de fuente. Un repaso de la información del diseño proporciona directrices para el establecimiento de los casos de prueba que más probablemente descubrirán errores de las categorías previamente mencionadas. Cada caso de prueba debe ir acompañado de un conjunto de resultados esperados.

Debido a que un módulo no es un programa independiente, se debe desarrollar para cada prueba de unidad un software que controle y/o resguarde. En la mayoría de las aplicaciones, un conductor no es más que un «programa principal» que acepta los datos del caso de prueba, pasa estos datos al módulo (a ser probado) e imprime los resultados importantes.

Los resguardos sirven para reemplazar módulos que están subordinados (llamados por) al módulo que hay que probar. Un resguardo o un «subprograma simulado» usan la interfaz del módulo subordinado, lleva a cabo una mínima manipulación de datos, imprime una verificación de entrada y vuelve.

Los conductores y los resguardos son una sobrecarga de trabajo. Es decir, ambos son software que debe desarrollarse pero que no se entrega con el producto de software final. Si se mantienen simplificados los conductores y los resguardos, el trabajo adicional real es relativamente pequeño. Desgraciadamente, muchos módulos no se pueden probar en una unidad de forma adecuada con un «simple» software adicional. En tales casos, la prueba completa se pospone hasta que se llegue al paso de prueba de integración (donde también se usan conductores o resguardos).

La prueba de unidad se simplifica cuando se diseña un módulo con un alto grado de cohesión. Cuando un módulo sólo se dirige a una función, se reduce el número de casos de prueba y los errores se pueden predecir y descubrir más fácilmente.

 

PRUEBA DE INTEGRACIÓN

Un neófito del mundo del software podría, una vez que se les ha hecho la prueba de unidad a todos los módulos, cuestionar de forma aparentemente legítima lo siguiente: «Si todos funcionan bien por separado, ¿por qué dudar de que funcionen todos juntos?». Por supuesto, el problema es «ponerlos juntos» (interacción). Los datos se pueden perder en una interfaz; un módulo puede tener un efecto adverso e inadvertido sobre otro; las subfunciones, cuando se combinan, pueden no producir la función principal deseada; la imprecisión aceptada individualmente puede crecer hasta niveles inaceptables; y las estructuras de datos globales pueden presentar problemas; desgraciadamente, la lista sigue y sigue.

La prueba de integración es una técnica sistemática para construir la estructura del programa mientras que, al mismo tiempo, se llevan a cabo pruebas para detectar errores asociados con la interacción. El objetivo es coger los módulos probados en unidad y construir una estructura de programa que esté de acuerdo con lo que dicta el diseño.

A menudo hay una tendencia a intentar una integración no incremental; es decir, a construir el programa mediante un enfoque de «big bang». Se combinan todos los módulos por anticipado. Se prueba todo el programa en conjunto. ¡Normalmente se llega al caos! Se encuentra un gran conjunto de errores. La corrección se hace difícil, puesto que es complicado aislar las causas al tener delante el programa entero en toda su extensión. Una vez que se corrigen esos errores aparecen otros nuevos y el proceso continúa en lo que parece ser un ciclo sin fin.

La integración incremental es la antítesis del enfoque del «big bang». El programa se construye y se prueba en pequeños segmentos en los que los errores son más fáciles de aislar y de corregir, es más probable que se puedan probar completamente las interfaces y se puede aplicar un enfoque de prueba sistemática. A continuación se tratan varias estrategias de integración incremental diferentes.

 

Integración descendente

La integración descendente es un planteamiento incremental a la construcción de la estructura de programas. Se integran los módulos moviéndose hacia abajo por la jerarquía de control, comenzando por el módulo de control principal (programa principal). Los módulos subordinados (de cualquier modo subordinados) al módulo de control principal se van incorporando en la estructura, bien de forma primero-en-profundidad o bien de formaprimero-en-anchura.

La integración primero-en-profundidad integra todos los módulos de un camino de control principal de la estructura. La selección del camino principal es, de alguna manera, arbitraria y dependerá de las características específicas de la aplicación. Por ejemplo, si se elige el camino a mano izquierda, se integrarán primero los módulos M1, MM5.

A continuación se integrará MM(si es necesario para un funcionamiento adecuado de M2). Acto seguido se construyen los caminos de control central y derecho. La integración primero-en-anchura incorpora todos los módulos directamente subordinados a cada nivel, moviéndose por la estructura de forma horizontal. Según la figura, los primeros módulos que se integran son M2, MM4A continuación sigue el siguiente nivel de control, M5, M6etc.

 

 

El proceso de integración se realiza en una serie de cinco pasos:

1.  Se usa el módulo de control principal como controlador de la prueba, disponiendo de resguardos para todos los módulos directamente subordinados al módulo de control principal.

2.  Dependiendo del enfoque de integración elegido (es decir, primero-en-profundidad o primero-en-anchura) se van sustituyendo los resguardos subordinados uno a uno por los módulos reales.

3.  Se llevan a cabo pruebas cada vez que se integra un nuevo módulo.

4.  Tras terminar cada conjunto de pruebas, se reemplaza otro resguardo con el módulo real.

5.  Se hace la prueba de regresión para asegurarse de que no se han introducido errores nuevos.

 

El proceso continúa desde el paso 2 hasta que se haya construido la estructura del programa entero.

La estrategia de integración descendente verifica los puntos de decisión o de control principales al principio del proceso de prueba. En una estructura de programa bien fabricada, la toma de decisiones se da en los niveles superiores de la jerarquía y, por tanto, se encuentran antes. Si existen problemas generales de control, es esencial reconocerlos cuanto antes. Si se selecciona la integración primero-en-profundidad, se puede ir implementando y demostrando las funciones completas del software. Por ejemplo, considere una estructura de transacción clásica en la que se requiere una compleja serie de entradas interactivas, obtenidas y validadas por medio de un camino de entrada. Ese camino de entrada puede ser integrado en forma descendente. Así, se puede demostrar todo el proceso de entradas (para posteriores operaciones de transacción) antes de que se integren otros elementos de la estructura. La demostración anticipada de las posibilidades funcionales es un generador de confianza tanto para el desarrollador como para el cliente.

La estrategia descendente parece relativamente fácil, pero en la práctica, pueden surgir algunos problemas logísticos. El más común de estos problemas se da cuando se requiere un proceso de los niveles más bajos de la jerarquía para poder probar adecuadamente los niveles superiores. Al principio de la prueba descendente, los módulos de bajo nivel se reemplazan por resguardos; por tanto, no pueden fluir datos significativos hacia arriba por la estructura del programa.

 

 

El responsable de la prueba tiene tres opciones:

1.  retrasar muchas de las pruebas hasta que los resguardos sean reemplazados por los módulos reales;

2.  desarrollar resguardos que realicen funciones limitadas que simulen los módulos reales; o

3.  integrar el software desde el fondo de la jerarquía hacia arriba.

El primer enfoque (retrasar pruebas hasta reemplazar los resguardos por los módulos reales) hace que perdamos cierto control sobre la correspondencia de ciertas pruebas específicas con la incorporación de determinados módulos. Esto puede dificultar la determinación de las causas del error y tiende a violar la naturaleza altamente restrictiva del enfoque descendente. El segundo enfoque es factible pero puede llevar a un significativo incremento del esfuerzo a medida que los resguardos se hagan más complejos. El tercer enfoque, denominado prueba ascendente, se estudia a continuación.

 

Integración ascendente

La prueba de la integración ascendente, como su nombre indica, empieza la construcción y la prueba con los módulos atómicos (es decir, módulos de los niveles más bajos de la estructura del programa). Dado que los módulos se integran de abajo hacia arriba, el proceso requerido de los módulos subordinados a un nivel dado siempre está disponible y se elimina la necesidad de resguardos.

Se puede implementar una estrategia de integración ascendente mediante los siguientes pasos:

1.  Se combinan los módulos de bajo nivel en grupos (a veces denominados construcciones) que realicen una subfunción específica del software.

2.  Se escribe un controlador (un programa de control de la prueba) para coordinar la entrada y la salida de los casos de prueba.

3.  Se prueba el grupo.

4.  Se eliminan los controladores y se combinan los grupos moviéndose hacia arriba por la estructura del programa.

 

Se combinan los módulos para formar los grupos 1, 2 y 3. Cada uno de los grupos se somete a prueba mediante un controlador (mostrado como un bloque punteado). Los módulos de los grupos 1 y 2 son subordinados de Ma. Los controladores Dy D2 se eliminan y los grupos interaccionan directamente con Ma. De forma similar, se elimina el controlador Ddel grupo 3 antes de la integración con el módulo Mb Tanto Ma como Mb se integrarán finalmente con el módulo Mc y así sucesivamente. En la Figura 9 se muestran diferentes categorías de controladores.

A medida que la integración progresa hacia arriba, disminuye la necesidad de controladores de prueba diferentes. De hecho, si los dos niveles superiores de la estructura del programa se integran de forma descendente, se puede reducir sustancialmente el número de controladores y se simplifica enormemente la integración de grupos.

 

Prueba de regresión

Cada vez que se añade un nuevo módulo como parte de una prueba de integración, el software cambia. Se establecen nuevos caminos de flujo de datos, pueden ocurrir nuevas E/S y se invoca una nueva lógica de control.

Estos cambios pueden causar problemas con funciones que antes trabajaban perfectamente. En el contexto de una estrategia de prueba de integración, la prueba de regresión es volver a ejecutar un subconjunto de pruebas que se han llevado a cabo anteriormente para asegurarse que los cambios no han propagado efectos colaterales no deseados.

En un contexto más amplio, las pruebas con éxito (de cualquier tipo) dan como resultado el descubrimiento de errores, y los errores hay que corregirlos. Cuando se corrige el software, se cambia algún aspecto de la configuración del software (el programa, su documentación o los datos que lo soportan). La prueba de regresión es la actividad que ayuda a asegurar que los cambios (debidos a las pruebas o por otros motivos) no introducen un comportamiento no deseado o errores adicionales.

La prueba de regresión se puede hacer manualmente, volviendo a realizar un subconjunto de todos los casos de prueba o utilizando herramientas automáticas de reproducción de captura. Las herramientas de reproducción de captura permiten al ingeniero del software capturar casos de prueba y los resultados para la subsiguiente reproducción y comparación. El conjunto de pruebas de regresión (el subconjunto de pruebas a realizar) contiene tres clases diferentes de casos de prueba:

  • Una muestra representativa de pruebas que ejercite todas las funciones del software.
  • Pruebas adicionales que se centran en las funciones del software que se van a ver probablemente afectadas por el cambio.
  • Pruebas que se centran en los componentes del software que se han cambiado.

Comentarios sobre la prueba de integración

Ha habido muchos estudios sobre las ventajas y desventajas de la prueba de integración ascendente frente a la descendente. En general, las ventajas de una estrategia tienden a convertirse en desventajas para la otra estrategia. La principal desventaja del enfoque descendente es la necesidad de resguardos y dificultades de prueba que pueden estar asociados con ellos. Los problemas asociados con los resguardos pueden quedar compensados por la ventaja de poder probar de antemano las principales funciones de control. La principal desventaja de la integración ascendente es que "el programa como entidad no existe hasta que se ha diseñado el último módulo". Este inconveniente se resuelve con la mayor facilidad de diseño de casos de prueba y con la falta de resguardos.

La selección de una estrategia de integración depende de las características del software y, a veces, de la planificación del proyecto. En general, el mejor compromiso puede ser un enfoque combinado (a veces denominado "prueba de sandwich") que use la descendente para los niveles superiores de la estructura del programa, junto con la ascendente para los niveles subordinados.

A medida que progresa la prueba de integración, el responsable de las pruebas debe identificar los módulos críticos.

Un módulo crítico es aquel que tiene una o más de las siguientes características:

1.    está dirigido a varios requisitos del software;

2.    tiene un mayor nivel de control (está relativamente alto en la estructura del programa);

3.    es complejo o propenso a errores; o

4.    tiene unos requisitos de rendimiento muy definidos.

Los módulos críticos deben probarse lo antes posible. Además, las pruebas de regresión se deben centrar en el funcionamiento de los módulos críticos.

Documentación de la prueba de integración

Un plan global para la integración del software y una descripción de las pruebas específicas deben quedar documentados en una especificación de prueba. La especificación es un resultado del proceso de ingeniería de software y forma parte de la configuración del software. La Figura 1 presenta un esquema de una especificación de prueba que puede usarse como marco de trabajo para este documento.

Esquema de la especificación de la prueba

  1. Ámbito de la prueba
  2. Plan de la prueba
  1. Fases de la prueba y construcciones
  2. Planificación
  3. Software adicional
  4. Entorno y recursos
  1. Procedimiento de prueba n (descripción de la prueba para la construcción de n)
  1. Orden de integración
  1. propósito
  2. módulos que hay que probar
  1. Pruebas de unidad para los módulos en construcción
  1. descripción de las pruebas para el módulo n
  2. descripción del software adicional
  3. resultados esperados
  1. Entorno de la prueba
  1. herramientas o técnicas especiales
  2. descripción del software adicional
  1. Datos de los casos de prueba
  2. Resultados esperados para la construcción de n
  1. Resultados de prueba obtenidos
  2. Referencias
  3. Apéndices
 

Figura 1

El «Ámbito de la prueba» resume las características funcionales, de rendimiento y de diseño interno específicas que van a probarse. Se delimita el esfuerzo de prueba, se describen los criterios de terminación de cada fase de prueba y se documentan las restricciones de planificación.

La sección «Plan de prueba» describe la estrategia general para la integración. La prueba se divide en fases y construcciones que tratan características específicas funcionales y de comportamiento del software. Por ejemplo, la prueba de integración para un sistema de computadora CAD orientado a gráficos podría dividirse en las siguientes fases de prueba:

  • interacción con el usuario (selección de órdenes; creación de dibujos; representación visual; proceso y representación de errores)
  • manipulación y análisis de datos (creación de símbolos; dimensionamiento; rotación; cálculo de propiedades físicas).
  • proceso y generación de información visual (visualización bidimensional; visualización tridimensional; gráficos y diagramas)
  • gestión de bases de datos (acceso; actualización; integridad; rendimiento).

Cada una de estas fases y subfases (presentadas entre paréntesis) define una categoría funcional principal del software y puede generalmente estar asociada a un dominio específico de la estructura del programa.

Por lo tanto, las construcciones del programa (grupos de módulos) se crean para corresponder a cada fase.

En todas las fases de prueba se siguen los siguientes criterios con sus correspondientes pruebas.

Integridad de interfaz. Se prueban las interfaces internas y externas a medida que se incorpora cada módulo (o grupo) a la estructura.

Validez funcional. Se llevan a cabo pruebas diseñadas para descubrir errores funcionales.

Contenido de la información. Se llevan a cabo pruebas diseñadas para descubrir errores asociados con las estructuras de datos globales o locales.

Rendimiento. Se llevan a cabo pruebas diseñadas para verificar los límites de rendimiento establecidos durante el diseño del software.

 

Como parte de la sección «Plan de prueba» también se incluye el estudio de una planificación temporal de la integración, del software adicional y temas asociados. Se establecen fechas de comienzo y finalización para cada fase y se definen «ventanas de disponibilidad» para las unidades de módulo probadas. Una breve descripción del software adicional (resguardos y controladores) se centrará en aquellas características que puedan requerir un esfuerzo especial. Por último, se describe el entorno y los recursos de prueba.

En la sección «Procedimiento de prueba» se describe de forma detallada el procedimiento de prueba requerido para llevar a cabo el plan de prueba. De acuerdo con el punto III de la especificación de prueba, se describe el orden de integración y las pruebas correspondientes a cada fase de integración. Asimismo se incluye una lista con todos los casos de prueba (anotados para una posterior referencia) y los resultados esperados.

En la cuarta sección de la especificación de prueba se registra la historia de los resultados reales de la prueba, de los problemas y de las peculiaridades. La información contenida en esta sección puede ser vital para el mantenimiento del software. En las dos últimas secciones se presentan unas referencias y apéndices apropiados.

Como todos los demás elementos de la configuración del software, el formato de la especificación de prueba se puede ajustar a las necesidades particulares de cada organización de desarrollo del software. Sin embargo, es importante resaltar que la estrategia de integración, contenida en el plan de prueba, y los detalles de la prueba, descritos en el procedimiento de prueba, son ingredientes esenciales que deben estar siempre presentes.

 

PRUEBA DE VALIDACIÓN

 

Tras la culminación de la prueba de integración, el software está completamente ensamblado como un paquete; se han encontrado y corregido los errores de interfaz y puede comenzar una serie final de pruebas del software: la prueba de validación. La validación puede definirse de muchas formas, pero una simple (aunque vulgar) definición es que la validación se consigue cuando el software funciona de acuerdo con las expectativas razonables del cliente. En este punto, un desarrollador de software estricto podría protestar: «¿Qué o quién es el árbitro de las expectativas razonables?».

Las expectativas razonables están definidas en la especificación de requisitos del software —un documento que describe todos los atributos del software visibles para el usuario. La especificación contiene una sección denominada «Criterios de validación». La información contenida en esa sección forma la base del enfoque a la prueba de validación.

Criterios de la prueba de validación

La validación del software se consigue mediante una serie de pruebas que demuestran la conformidad con los requisitos. Un plan de prueba traza las clases de pruebas que se han de llevar a cabo, y un procedimiento de prueba define los casos de prueba específicos en un intento por descubrir errores de acuerdo con los requisitos. Tanto el plan como el procedimiento estarán diseñados para asegurar que se satisfacen todos los requisitos funcionales, que se alcanzan todos los requisitos de rendimiento, que la documentación es correcta e inteligible y que se alcanzan otros requisitos (p ej.: transportabilidad, compatibilidad, recuperación de errores, facilidad de mantenimiento).

Una vez que se procede con cada caso de prueba de validación, puede darse una de las dos condiciones siguientes:

1.  Las características de funcionamiento o de rendimiento están de acuerdo con las especificaciones y son aceptables o

2.  Se descubre una desviación de las especificaciones y se crea una lista de deficiencias. Las desviaciones o errores descubiertos en esta fase del proyecto raramente se pueden corregir antes de la terminación planificada. A menudo es necesario negociar con el cliente un método para resolver las deficiencias.

 

Revisión de la configuración

Un elemento importante del proceso de validación es la revisión de la configuración. La intención de la revisión es asegurarse de que todos los elementos de la configuración del software se han desarrollado apropiadamente, se han catalogado y están suficientemente detallados para soportar la fase de mantenimiento durante el ciclo de vida del software.

 

Pruebas alfa y beta

Es virtualmente imposible que un constructor de software pueda prever cómo usará realmente el programa el usuario. Se pueden malinterpretar las instrucciones de uso, se pueden utilizar habitualmente extrañas combinaciones de datos y una salida que puede parecer clara para el responsable de las pruebas puede ser ininteligible para el usuario.

Cuando se construye software a medida para un cliente, se llevan a cabo una serie de pruebas de aceptación para permitir que el cliente valide todos los requisitos. Las realiza el usuario final en lugar del responsable del desarrollo del sistema, una prueba de aceptación puede ir desde un informal «paso de prueba» hasta la ejecución sistemática de una serie de pruebas bien planificadas. De hecho, la prueba de aceptación puede tener lugar a lo largo de semanas o meses, descubriendo así errores acumulados que pueden ir degradando el sistema.

Si el software se desarrolla como un producto que se va a usar por muchos clientes, no es práctico realizar pruebas de aceptación formales para cada uno de ellos. La mayoría de los constructores de productos de software llevan a cabo un proceso denominado prueba alfa y beta para descubrir errores que parezca que sólo el usuario final puede descubrir.

La prueba alfa se lleva a cabo en el lugar de desarrollo pero por un cliente. Se usa el software de forma natural con el desarrollador como observador del usuario y registrando los errores y los problemas de uso. Las pruebas alfa se llevan a cabo en un entorno controlado.

La prueba beta se lleva a cabo por los usuarios finales del software en los lugares de trabajo de los clientes. A diferencia de la prueba alfa, el desarrollador no está presente normalmente. Así, la prueba beta es una aplicación «en vivo» del software en un entorno que no puede ser controlado por el desarrollador. El cliente registra todos los problemas (reales o imaginarios) que encuentra durante la prueba beta e informa a intervalos regulares al desarrollador. Como resultado de los problemas informados durante la prueba beta, el desarrollador del software lleva a cabo modificaciones y así prepara una versión del producto de software para toda la clase de clientes.

 

PRUEBA DEL SISTEMA

 

Al principio, pusimos énfasis en el hecho de que el software es sólo un elemento de un sistema mayor basado en computadora. Finalmente, el software es incorporado a otros elementos del sistema (p. ej.: nuevo hardware, información) y realizan una serie de pruebas de integración del sistema y de validación. Estas pruebas caen fuera del ámbito del proceso de ingeniería del software y no las realiza únicamente el desarrollador del software. Sin embargo, los pasos dados durante el diseño del software y durante la prueba pueden mejorar enormemente la probabilidad de éxito en la integración del software en el sistema.

Un problema típico de la prueba del sistema es la «delegación de culpabilidad». Esto ocurre cuando se descubre un error y cada uno de los creadores de cada elemento del sistema echa la culpa del problema a los otros. En vez de verse envuelto en esta absurda situación, el ingeniero del software debe anticiparse a los posibles problemas de interacción y: (1) diseñar caminos de manejo de errores que prueben toda la información procedente de otros elementos del sistema; (2) llevar a cabo una serie de pruebas que simulen la presencia de datos en mal estado o de otros posibles errores en la interfaz del software; (3) registrar los resultados de las pruebas como «evidencia» en el caso de que se le señale con el dedo; (4) participar en la planificación y el diseño de pruebas del sistema para asegurarse de que el software se prueba de forma adecuada.

La prueba del sistema, realmente, está constituida por una serie de pruebas diferentes cuyo propósito primordial es ejercitar profundamente el sistema basado en computadora. Aunque cada prueba tiene un propósito diferente, todas trabajan para verificar que se han integrado adecuadamente todos los elementos del sistema y que realizan las funciones apropiadas.

 

Prueba de recuperación

Muchos sistemas basados en computadora deben recuperarse de los fallos y continuar el proceso en un tiempo previamente especificado. En algunos casos, un sistema debe ser tolerante con los fallos; es decir, los fallos del proceso no deben hacer que cese el funcionamiento de todo el sistema.

En otros casos, se debe corregir un fallo del sistema en un determinado periodo de tiempo para que no se produzca un serio daño económico.

La prueba de recuperación es una prueba del sistema que fuerza el fallo del software de muchas formas y verifica que la recuperación se lleva a cabo apropiadamente. Si la recuperación es automática (llevada a cabo por el propio sistema) hay que evaluar la corrección de la inicialización, de los mecanismos de recuperación del estado del sistema, de la recuperación de datos y del proceso rearranque. Si la recuperación requiere la intervención humana, hay que evaluar los tiempos medios de reparación para determinar si están dentro de unos límites aceptables.

 

Prueba de seguridad

Cualquier sistema basado en computadora que maneje información sensible o lleve a cabo acciones que puedan perjudicar (o beneficiar) impropiamente a las personas es un posible objetivo para entradas al sistema impropias o ilegales. Este acceso al sistema incluye un amplio rango de actividades: «piratas informáticos» que intentan entrar en los sistemas por deporte, empleados disgustados que intentan penetrar por venganza e individuos deshonestos que intentan penetrar para obtener ganancias personales ilícitas.

La prueba de seguridad intenta verificar que los mecanismos de protección incorporados en el sistema lo protegerán, de hecho, de accesos impropios.

Durante la prueba de seguridad, el responsable de la prueba desempeña el papel de un individuo que desea entrar en el sistema. ¡Todo vale! Debe intentar conseguir las claves de acceso por cualquier medio, puede atacar al sistema con software a medida, diseñado para romper cualquier defensa que se haya construido, debe bloquear el sistema, negando así el servicio a otras personas, debe producir a propósito errores del sistema, intentando acceder durante la recuperación o debe curiosear en los datos sin protección, intentando encontrar la clave de acceso al sistema, etc.

Con tiempo y recursos suficientes, una buena prueba de seguridad terminará por acceder en el sistema. El papel del diseñador del sistema es hacer que el coste de la entrada ilegal sea mayor que el valor de la información obtenida.

 

Prueba de resistencia

Las pruebas de resistencia están diseñadas para enfrentar a los programas con situaciones anormales. En esencia, el sujeto que realiza la prueba de resistencia se pregunta: ¿A qué potencia puedo ponerlo a funcionar antes de que falle?

La prueba de resistencia ejecuta un sistema de forma que demande recursos en cantidad, frecuencia o volúmenes anormales. Por ejemplo: (1) diseñar pruebas especiales que generen diez interrupciones por segundo, cuando las normales son una o dos; (2) incrementar las frecuencias de datos de entrada en un orden de magnitud con el fin de comprobar cómo responden las funciones de entrada; (3) ejecutar casos de prueba que requieran el máximo de memoria o de otros recursos; (4) diseñar casos de prueba que puedan dar problemas en un sistema operativo virtual o (5) diseñar casos de prueba que produzcan excesivas búsquedas de datos residentes en disco. Esencialmente, el responsable de la prueba intenta romper el programa.

Una variante de la prueba de resistencia es una técnica denominada prueba de sensibilidad. En algunas situaciones (la más común se da con algoritmos matemáticos), un rango de datos muy pequeño dentro de los límites de una entrada válida para un programa puede producir un proceso exagerado e incluso erróneo o una profunda degradación del rendimiento. Esta situación es análoga a una singularidad en una función matemática. La prueba de sensibilidad intenta descubrir combinaciones de datos dentro de una clase de entrada válida que pueda producir inestabilidad o un proceso incorrecto.

 

Prueba de rendimiento

Para sistemas de tiempo real y sistemas empotrados, es inaceptable el software que proporciona las funciones requeridas pero no se ajusta a los requisitos de rendimiento. La prueba de rendimiento está diseñada para probar el rendimiento del software en tiempo de ejecución dentro del contexto de un sistema integrado. La prueba de rendimiento se da durante todos los pasos del proceso de la prueba. Incluso al nivel de unidad, se debe asegurar el rendimiento de los módulos individuales a medida que se llevan a cabo las pruebas de caja blanca. Sin embargo, hasta que no están completamente integrados todos los elementos del sistema no se puede asegurar realmente el rendimiento del sistema.

Las pruebas de rendimiento, a menudo, van emparejadas con las pruebas de resistencia y, frecuentemente, requieren instrumentación tanto de software como de hardware. Es decir, muchas veces es necesario medir la utilización de recursos (p. ej.: ciclos de procesador), de un modo exacto. La instrumentación externa puede monitorizar los intervalos de ejecución, los sucesos ocurridos (p. ej.: interrupciones) y muestras de los estados de la máquina en un funcionamiento normal. Instrumentando un sistema, el encargado de la prueba puede descubrir situaciones que lleven a degradaciones y posibles fallos del sistema.

 

DEPURACIÓN

La prueba del software es un proceso que puede planificarse y especificarse sistemáticamente. Se puede llevar a cabo el diseño de casos de prueba, se puede definir una estrategia y se pueden evaluar los resultados en comparación con las expectativas prescritas.

La depuración ocurre como consecuencia de una prueba efectiva. Es decir, cuando un caso de prueba descubre un error, la depuración es el proceso que provoca la eliminación del error. Aunque la depuración puede y debe ser un proceso ordenado, sigue teniendo mucho de arte. Un ingeniero del software, al evaluar los resultados de una prueba, se encuentra frecuentemente con una indicación «sintomática» de un problema en el software. Es decir, la manifestación externa de un error, y la causa interna del error pueden no estar relacionadas de una forma obvia. El proceso mental, apenas comprendido, que conecta un síntoma con una causa es la depuración.

 

El proceso de depuración

La depuración no es una prueba, pero siempre ocurre como consecuencia de la prueba. El proceso de depuración comienza con la ejecución de un caso de prueba. Se evalúan los resultados y aparece una falta de correspondencia entre los esperados y los encontrados realmente. En muchos casos, los datos que no concuerdan son un síntoma de una causa subyacente que todavía permanece oculta. El proceso de depuración intenta hacer corresponder el síntoma con una causa, llevando así a la corrección del error.

El proceso de depuración siempre tiene uno de los dos resultados siguientes: (1) se encuentra la causa, se corrige y se elimina; o (2) no se encuentra la causa. En este último caso, la persona que realiza la depuración debe sospechar la causa, diseñar un caso de prueba que ayude a confirmar sus sospechas y el trabajo vuelve hacia atrás a la corrección del error de una forma iterativa.

¿Por qué es tan difícil la depuración? Todo parece indicar que la respuesta tiene más que ver con la psicología humana que con la tecnología del software. Sin embargo, varias características de los errores nos dan algunas pistas:

  1. El síntoma y la causa pueden ser geográficamente remotos entre sí. Es decir, el síntoma puede aparecer en una parte del programa, mientras que la causa está localizada en otra parte muy alejada. Las estructuras de programa fuertemente acopladas resaltan esta situación.
  2. El síntoma puede desaparecer (temporalmente) al corregir otro error.
  3. El síntoma puede realmente estar producido por algo que no es un error (p.ej.: inexactitud en los redondeos).
  4. El síntoma puede estar causado por un error humano que no sea fácilmente detectado.
  5. El síntoma puede ser el resultado de problemas de temporización en vez de problemas de proceso.
  6. Puede ser difícil reproducir exactamente las condiciones de entrada (p.ej.: una aplicación de tiempo real en la que el orden de la entrada no está determinado).
  7. El síntoma puede aparecer de forma intermitente. Esto es particularmente corriente en sistemas empotrados que acoplan el hardware y el software de manera confusa.
  8. El síntoma puede ser debido a causas que se distribuyen por una serie de tareas ejecutándose en diferentes procesadores.

Durante la depuración encontramos errores que van desde lo ligeramente inesperado (p. ej.: un formato de salida incorrecto) hasta lo catastrófico (p.ej.: el sistema falla, produciéndose serios daños económicos o físicos). A medida que las consecuencias de un error aumentan, crece la presión por encontrar su causa. A menudo la presión fuerza a un ingeniero de software a corregir un error introduciendo dos más.

 

Consideraciones psicológicas

Desgraciadamente, todo parece indicar que la habilidad en la depuración es un rasgo innato del ser humano. A ciertas personas se les da bien y a otras no. Aunque las manifestaciones experimentales de la depuración están abiertas a muchas interpretaciones, se han detectado grandes variaciones en la destreza para la depuración de distintos programadores con el mismo bagaje de formación y de experiencia.

Hablando de los aspectos humanos de la depuración, Shneiderman manifiesta:

La depuración es una de las partes más frustrantes de la programación. Contiene elementos de resolución de problemas o de rompecabezas, junto con el desagradable reconocimiento de que se ha cometido un error. La enorme ansiedad y la no inclinación a aceptar la posibilidad de cometer errores hace que la tarea sea extremadamente difícil. Afortunadamente, también se da un gran alivio y disminuye la tensión cuando el error es finalmente... corregido.

 

Aunque puede resultar difícil «aprender» a depurar, se pueden proponer varios enfoques del problema. En la siguiente sección los examinamos.

 

Enfoques de la depuración

Independientemente del enfoque que se utilice, la depuración tiene un objetivo primordial: encontrar y corregir la causa de un error en el software. El objetivo se consigue mediante una combinación de una evaluación sistemática, de intuición y de suerte. Bradley describe el enfoque de la depuración de la siguiente forma:

La depuración es una aplicación directa del método científico desarrollado hace 2500 años. La base de la depuración es la localización de la fuente del problema (la causa) mediante partición binaria, manejando hipótesis que predigan nuevos valores a examinar.

 

Tomemos un sencillo ejemplo que no tiene que ver con el software: en mi casa no funciona una lámpara. Si no funciona nada en la casa, la causa debe estar en el circuito principal de fusibles o fuera de la casa; miro fuera para ver si hay un apagón en todo el vecindario. Conecto la sospechosa lámpara a un enchufe que funcione y una aparato que funcione en el circuito sospechoso. Así se sigue la secuencia de hipótesis y de pruebas.

En general, existen tres enfoques que se pueden proponer para la depuración:

  • Fuerza bruta
  • Vuelta atrás
  • Eliminación de causas

La categoría de depuración por la fuerza bruta es probablemente la más común y menos eficiente a la hora de aislar la causa del error en el software. Aplicamos los métodos de depuración por fuerza bruta cuando todo lo demás falla. Mediante una filosofía de «dejar que la computadora encuentre el error», se hacen volcados de memoria, trazas de ejecución y se cargan multitud de sentencias WRITE en el programa. Esperamos que en algún lugar de la gran cantidad de información generada encontremos alguna pista que nos lleve a la causa de un error. Aunque la gran cantidad de información producida nos puede llevar finalmente al éxito, lo más frecuente es que se desperdicie tiempo y esfuerzo. ¡Primero se debe usar la inteligencia!

La vuelta atrás es un enfoque más normal para la depuración, que se puede usar con éxito para pequeños programas. Partiendo del lugar donde se descubre el síntoma, se recorre hacia atrás (manualmente) el código fuente hasta que se llega a la posición de error. Desgraciadamente, a medida que aumenta el número de líneas del código, el número de posibles caminos de vuelta se hace difícilmente manejable.

El tercer enfoque para la depuración—eliminación de causa— se manifiesta mediante inducción o deducción e introduce el concepto de partición binaria. Los datos relacionados con la ocurrencia del error se organizan para aislar las posibles causas. Se llega a una «hipótesis de causa» y se usan los datos anteriores para probar o revocar la hipótesis. Alternativamente, se desarrolla una lista de todas las posibles causas y se llevan a cabo pruebas para eliminar cada una. Si alguna prueba inicial indica que determinada hipótesis de causa en particular parece prometedora, se refinan los datos con el fin de intentar aislar el error.

Cada uno de los enfoques anteriores puede complementarse con herramientas de depuración. Podemos usar una gran cantidad de compiladores de depuración, ayudas dinámicas para la depuración («trazadores»), generadores automáticos de casos de prueba, volcados de memoria y mapas de referencias cruzadas. Sin embargo, las herramientas no son un sustituto de la evaluación cuidadosa basada en un completo documento del diseño del software y un código fuente claro.

Cualquier discusión sobre los enfoques para la depuración y sus herramientas no estaría completa sin mencionar un poderoso aliado: ¡otras personas! Cualquiera de nosotros podrá recordar haber estado dando vueltas en la cabeza durante horas o días a un error persistente. Desesperados, le explicamos el problema a un colega con el que damos por casualidad y le mostramos el listado. Instantáneamente (parece), se descubre la causa del error. Nuestro colega se aleja sonriendo ladinamente. Un punto de vista fresco, no embotado por horas de frustración, puede hacer maravillas. Una máxima final para la depuración puede ser: «¡Cuando todo lo demás falle, pide ayuda!».