Informática


Diseño de la interfaz de usuario: Pruebas del software


Diseño de la interfaz de usuario. Pruebas del software.

Sumario:

1. Introducción.

2. Diseño de la interfaz de usuario.

2.1. Evolución histórica.

2.2. Producción de prototipos preliminares y diálogos.

2.3. Ergonomía del diseño de la interfaz.

3. Pruebas del software.

3.1. Definiciones.

3.2. El proceso de prueba.

3.3. Técnicas de diseño de casos de prueba.

3.4. Pruebas estructurales. (Prueba de la caja blanca)

3.4.1. Utilización de la complejidad ciclomática de McCabe.

3.5. Prueba funcional. (Prueba de la caja negra)

3.5.1. Particiones o clases de equivalencia.

3.5.2. Análisis de valores límite (AVL).

3.5.3. Conjetura de errores.

3.6. Pruebas aleatorias.

3.7. Enfoque práctico recomendado para el diseño de casos.


1. INTRODUCCIÓN

Hasta ahora, se ha realizado el análisis del sistema (generando los DFD y el modelo E/R) y el diseño del sistema (generando el diagrama de estructura y el diseño lógico de la base de datos que, generalmente, es el modelo relacional). Además, es necesario definir la interfaz entre el usuario y el ordenador.

2. DISEÑO DE LA INTERFAZ DE USUARIO

2.1. Evolución histórica

La tecnología de interfaz de usuario, al igual que el hardware, ha pasado por una serie de generaciones [TESLER, 1991]. Estas generaciones contienen o parecen contener a las anteriores, y se pueden clasificar cronológicamente como sigue [NIELSEN, 1993]:

  • Hasta 1945: no existía ningún paradigma de interfaz de usuario, y se hacía acceso directo de forma manual al hardware.

  • 1945-1955: programación en modo batch o por lotes. La primera generación de interfaces no era interactiva, ya que la interacción entre el sistema y el usuario se restringía a un único punto en el tiempo. Todos los mandatos del usuario tenían que ser especificados antes de que el usuario conociera el resultado de cualquiera de ellos. Se recomienda que tales modos batch proporcionen alguna opción al usuario para controlar continuamente el progreso del trabajo batch, de forma que pueda interrumpir o modificar el trabajo. Es muy frustante tener un trabajo grande ejecutándose y que, cuando vaya a finalizar, tenga que descartarse porque se debería haber modificado el último mandato. Actualmente estas interfaces han tenido un renacimiento en los sistemas de acceso por medio del intercambio de mensajes de correo electrónico.

  • 1955-1965: lenguajes de mandatos. También denominadas interfaces en línea. Los sistemas de tiempo compartido se inventaron alrededor de 1960 como un medio para permitir a varios usuarios tener acceso simultáneo interactivo a un único servidor [LEE et al., 19921. Uno de los problemas principales de estos sistemas es la pequeña cantidad de recursos de ordenador disponibles para soportar la interfaz de cualquier usuario, por lo que, a menudo, se utilizan interfaces en línea. Éstas eran básicamente interfaces de una dimensión, en las que el usuario sólo podía interactuar con el ordenador en una línea que servía como línea de mandato. Cuando el usuario pulsaba la tecla de intro (Return o Enter), no se podía modificar la entrada. De forma similar, cuando el ordenador presentaba una salida al usuario, no se podía modificar para reflejar cambios en los datos. Estas interfaces se implementaron originalmente en las máquinas teletipos, aunque las últimas versiones utilizan pantallas tipo tern-únal. Debido a que las interfaces en línea no permitían a los usuarios navegar por la pantalla, la inte­racción se limitaba a diálogos pregunta-respuesta y a la introducción de mandatos con parámetros. La mayoría de las interfaces de usuario en línea se implementaban en lenguajes de mandatos y, aunque algunos de ellos son muy poderosos y permiten la construcción de secuencias de mandatos muy com­plicadas, desafortunadamente es normal que se olviden de los errores del usuario, ya que requieren que éste especifique el mandato deseado exacta­mente en el formato requerido.

  • 1965-1980: pantallas completas con menús estrictamente jerárquicos. El espacio de diseño de la interfaz es de dos dimensiones. Un uso clásico de la pantalla completa es el de los diálogos para rellenar informes, en los que el usuario tiene un número de campos etiquetados que puede editar en la secuencia que desee. Estos diálogos todavía existen en las interfaces modernas en forma de cajas de diálogo, las cuales son más dinámicas que los informes tradicionales, ya que contienen menús desplegables y ayuda para que el usuario rellene el informe. Además de los menús, muchas de las interfaces de pantalla completa utilizan también las teclas de función como una forma de interacción. Estas teclas aceleran la interacción y existen tan pocas que los usuarios, generalmente, se las aprenden de memoria.

  • 1980-1995: ventanas, iconos, menús y ratón. También denominadas interfaces gráficas de usuario. La mayoría de las interfaces actuales de usuario pertenecen a esta categoría, a veces denominada sistemas WIMP (Windows, Icons, Menus and Pointing device). Las interfaces windows añaden casi una tercera dimensión a las dos dimensiones inherentes a cada ventana debido a la posibilidad de superponer ventanas (está claro que superponer ventanas no es verdaderamente una tercera dimensión, ya que no es posible ver el contenido de la ventana que está debajo, por tanto, podríamos decir que tienen “dos dimensiones y media”). El estilo de interacción utilizado en muchas interfaces gráficas de usuario es la manipulación directa [SHNEIDERMAN, 1983], la cual se basa en la representación visual de los objetos del diálogo que tengan interés para el usuario. Esto permite al usuario controlar el diálogo con sólo mover los objetos por la pantalla y manipularlos con el ratón. Sin embargo, las interfaces de manipulación directa pueden resultar más difíciles de utilizar que las tradicionales, debido a que son más dependientes de un control fino sobre el ratón.

  • 1995-Futuro: interfaces no basadas en mandatos. La próxima generación de interfaces ya se está desarrollando [NIELSEN, 1993b]. Es probable que la tendencia de las generaciones previas continúe, y que la dimensión de las interfaces de usuario aumente de 2.5 a 3 o más dimensiones. Las formas comunes de añadir una dimensión a las interfaces de usuario consisten en añadir tiempo (en forma de animación), sonido o voz, así como una verdadera tercera dimensión espacial en forma de sistemas de realidad virtual. Las dos predicciones más fáciles con respecto a la siguiente generación de interfaces de usuario son que incluirán una dimensionalidad más alta con más tipos de medios y que serán altamente portables y personales, a la vez que se utiliza tecnología de comunicaciones para conseguir una gran conectividad. Otro estilo de diálogo para las interfaces de usuario de la próxima generación puede ser el de las interfaces de usuario no basadas en mandatos. Todos los estilos de interfaz hasta ahora han tenido en común, al menos, el concepto de mandato, y se basaban en el principio de un diálogo explícito entre el usuario y el ordenador en el que el usuario ordenaba al ordenador que hiciese ciertas acciones específicas. Por el contrario, muchos esfuerzos actuales de investigación apuntan a sistemas que permitan al usuario centrarse en el dominio en lugar de tener que controlar al ordenador explícitamente. En estos sistemas futuros, el ordenador se encargará de tomar la responsabilidad de la interacción, basando sus acciones en sus observaciones del usuario, utilizando tecnologías como el seguimiento del ojo, reconocimiento de gestos y análisis semi-inteligente de las acciones del usuario.

2.2. Producción de prototipos preliminares y diálogos

El propósito de la interfaz es muy simple: recoger de los usuarios la información del sistema y ponerla a disposición de otros usuarios. La información recogida se llama entrada del sistema y se almacena en la base de datos para ponerla a disposición de los demás usuarios. La información suministrada se llama salida del sistema. Es decir, el diseño de interfaces cubre tanto las entradas como las salidas.

Las entradas y salidas pueden ser interactivas o por lotes. En una interfaz interactiva, el usuario se comunica directamente con el ordenador y la salida se obtiene muy poco tiempo después de la entrada. En el caso de entradas o salidas no interactivas, es decir, por lotes, las transacciones se reúnen en formularios en el punto de recepción y después se introducen en el ordenador al mismo tiempo. Estas transacciones se procesan y un tiempo después se producen las salidas, las cuales se pasan al usuario. Así, el tiempo transcurrido desde la introducción de los datos hasta que se obtiene una respuesta puede ser considerable.

El diseño de interfaz interactivo provoca un diálogo hombre-máquina que permite un intercambio rápido de información entre los ordenadores y sus usuarios humanos, mientras que la interfaz no interactiva utiliza un soporte de papel para contener la información en el que las entradas normalmente se realizan en formularios especialmente diseñados y las salidas se producen en un listado impreso.

Será necesario definir los formatos individuales de las pantallas utilizadas. En el caso de utilizar un paquete estándar, habrá que evaluar la posibilidad de adoptar el tipo de formato del producto. Entre los aspectos a considerar en los formatos de pantalla se destacan: encabezamiento, cuerpo principal, pie, teclas de función, teclas de ayuda y líneas de visualización de los mensajes de ayuda.

También hay que describir, de forma detallada, los diálogos entre pantallas y su encadenamiento. Para ello, es útil representarlas jerárquicamente, de forma que en los niveles superiores se representen los menús, y en los niveles inferiores las pantallas de diálogos, representativas de funciones o procesos concretos del sistema. En esta representación jerárquica nos interesa identificar los menús o diálogos concretos en función de los grupos de usuarios que los utilicen.

Será también necesario determinar los diálogos que se consideran críticos para un funcionamiento correcto del nuevo sistema. Los diálogos críticos se determinan en función de factores como: tipo de proyecto, grado de cambio con respecto al sistema actual, complejidad de los trabajos del sistema. Para ello, es útil tener en cuenta los siguientes criterios: frecuencia de uso de estos diálogos, acceso a gran número de entidades de datos del sistema, gran número de elementos de datos asociados con el diálogo, cambio en el modo habitual de trabajo en el sistema actual, diálogos comunes a diferentes grupos de usuarios, diálogos que contienen opciones complejas de navegación, etc.

Por último, habrá que realizar un prototipo dinámico que permita la simulación (introducción y validación de datos por pantalla) para los diálogos considerados como críticos. Como recomendaciones para realizar este prototipo se tendrá en cuenta: la determinación del punto de entrada a cada pantalla y sus posibilidades de navegación a otras, la especificación de los datos asociados a cada pantalla (longitud, reglas de validación, mensajes de ayuda, etc.), la evaluación de la consistencia del diseño, asegurando que toda la información necesaria para el usuario está contemplada en las pantallas, y la confirmación con el usuario de la validez de los diseños de pantalla realizados.

2.3. Ergonomía del diseño de la intefaz

El viaje al mundo del diseño de pantallas comienza con una discusión con el usuario, la parte más importante de cualquier sistema informatizado. Como hemos comentado, es de aceptación general que el sistema sería más fácil de utilizar si lo hubiera diseñado el usuario, por tanto, el diseño de la interfaz debe estimular al usuario haciéndolo cómplice del sistema.

El ser humano es un organismo complejo con una variedad de atributos que tienen una influencia importante en el diseño de pantallas [GALITZ, 1989]. Entre estos atributos, destacamos la percepción, la memoria, el aprendizaje, la capacidad y diferencias individuales. Por tanto, debe cultivarse la satisfacción personal y, consecuentemente, mejorar la aceptación del sistema, permitiendo que las personas mejoren sus conocimientos mientras utilizan el sistema.

Para realizar un buen trabajo, la interfaz con el ordenador debe ser, entre otras cosas, lo más fácil, amigable y agradable posible, y se debe usar un diálogo que se acerque al lenguaje natural en vez de la jerga informática. Entre las consideraciones a tener cuenta a la hora de diseñar pantallas se encuentran las siguientes:

  • Características deseadas: simplicidad, claridad y fácil de comprender. Será necesario tener claridad visual, de forma que los elementos estén agrupados de forma comprensible y con significado en vez de al azar y de forma confusa.

  • Saber dónde situar la información en la pantalla. Será necesario indicar un punto de partida obvio en la esquina superior izquierda de la pantalla, reservar áreas específicas de pantalla para diferentes tipos de información (como, por ejemplo, mandatos, mensajes de error, títulos y campos de datos, de forma que esta consistencia se mantenga en todas las pantallas) y proporcionar una composición que guste visualmente (es decir, que esté balanceada, sea simétrica, sea predecible, secuencial, simple, con agrupamientos, etc.).

  • Saber qué información situar en la pantalla. Para ello, hay que poner sólo la información que es esencial para la toma de una decisión o para la ejecución de una acción (¡No inundar al usuario con información!) y poner todos los datos relacionados con una tarea en una única pantalla (así el usuario no tiene que recordar datos de una pantalla a otra).

  • Saber cómo situar la información en la pantalla. Así, en cuanto a las fuentes de letras, se recomienda utilizar minúsculas para el texto con la letra inicial de la frase en mayúsculas; para las etiquetas, encabezamientos o subtítulos utilizar mayúsculas. En cuanto a las palabras, se recomienda no usar jerga, sino utilizar palabras cortas, familiares, etc. También es necesario saber como alinear y/o resaltar el texto y las palabras, dónde situar las ilustraciones, los campos de datos, etc.

  • La interfaz de entrada debe recoger todos los datos necesarios, sin introducir errores, para el sistema. De esta forma, la interfaz contiene una protección contra errores de entrada. Así mismo, también debe recoger los datos minimizando el número de teclas pulsadas por el usuario. Las entradas deben estar bien estructuradas y ser fáciles de comprender y utilizar. Se deben usar nombres precisos y permitir abreviaturas cuando se necesiten introducciones rápidas de datos. Se deben evitar las entradas repetitivas. Igualmente, el diseño de la salida asegura que se extraen todos los datos suministrados por el sistema y que esas salidas están estructuradas de forma que sean fáciles de leer.

  • El color añade una nueva dimensión a la facilidad de uso de la pantalla, ya que atrae la atención del usuario. Si se utiliza de forma adecuada, puede resaltar la organización lógica de una pantalla, facilitar la separación de componentes y acentuar las diferencias. Por el contrario, si se usa inadecuadamente, puede distraer y fatigar la visión debilitando la facilidad de uso del sistema. Por ejemplo, en las pantallas gráficas se recomienda no utilizar más de seis colores a la vez, evitar colores extremos (rojo y azul, amarillo y púrpura), evitar colores que no tengan contraste (blanco y amarillo, rojos, azules).

Para finalizar, diremos que el diseño de pantallas es un proceso ordenado que empieza en los requisitos y finaliza con la implementación.

3. PRUEBAS DEL SOFTWARE

Una de las características típicas del desarrollo de software basado en el ciclo de vida es la realización de controles periódicos, normalmente coincidiendo con los hitos del proyectos o la terminación de documentos. Estos controles pretenden una evaluación de la calidad de los productos generados (especificación de requisitos, documentos de diseño, etc.) para poder detectar posibles defectos cuanto antes. Sin embargo, todo sistema o aplicación, independientemente de estas revisiones, debe ser probado mediante su ejecución controlada antes de ser entregado al cliente. Estas ejecuciones o ensayos de funcionamiento, posteriores a la terminación del código del software, se denominan habitualmente pruebas.

Las pruebas constituyen un método más para poder verificar y validar el software. Se puede definir la verificación como [IEEE, 1990] “el proceso de evaluación de un sistema o de uno de sus componentes para determinar si los productos de una fase dada satisfacen las condiciones impuestas al comienzo de dicha fase”. Por ejemplo, verificar el código de un módulo significa comprobar si cumple lo marcado en la especificación de diseño donde se describe. Por otra parte, la validación es “el proceso de evaluación de un sistema o de uno de sus componentes durante o al final del proceso de desarrollo para determinar si satisface los requisitos especificados”. Así, validar una aplicación implica comprobar si satisface los requisitos marcados por el usuario. Podemos recurrir a la clásica explicación informal de Boehm de estos conceptos:

  • Verificación: ¿estamos construyendo correctamente el producto?

  • Validación: ¿estamos construyendo el producto correcto?

Como hemos dicho, las pruebas permiten verificar y validar el software cuando ya está en forma de código ejecutable. A continuación, expondremos algunas definiciones de conceptos relacionados con las pruebas.

3.1. Definiciones

Las siguientes definiciones son algunas de las recogidas en el diccionario IEEE en relación a las pruebas [IEEE, 1990], que complementamos con otras más informales:

  • Pruebas (test): “una actividad en la cual un sistema o uno de sus componentes se ejecuta 2 en circunstancias previamente especificadas, los resultados se observan y registran y se realiza una evaluación de algún aspecto”. Para Myers [MYERS, 1979], probar (o la prueba) es el “proceso de ejecutar un programa con el fin de encontrar errores”. El nombre “prueba”, además de la actividad de probar, se puede utilizar para designar “un conjunto de casos y procedimientos de prueba” [IEEE, 1990].

  • Caso de prueba (test case): “un conjunto de entradas, condiciones de ejecución y resultados esperados desarrollados para un objetivo particular como, por ejemplo, ejercitar un camino concreto de un programa o verificar el cumplimiento de un determinado requisito”. También se puede referir a la documentación en la que se describen las entradas, condiciones y salidas de un caso de prueba.

  • Defecto (defect, fault, “bug”): “un defecto en el software como, por ejemplo, un proceso, una definición de datos o un paso de procesamiento incorrectos en un programa”.

  • Fallo (failure): «La incapacidad de un sistema o de alguno de sus componentes para realizar las funciones requeridas dentro de los requisitos de rendimiento especificados».

  • Error (error): tiene varias acepciones:

  • La diferencia entre un valor calculado, observado o medido y el valor verdadero, especificado o teóricamente correcto. Por ejemplo, una diferencia de dos centímetros entre el valor calculado y el real.

  • Un defecto. Por ejemplo, una instrucción incorrecta en un programa.

  • Un resultado incorrecto. Por ejemplo, un programa ofrece como resultado de la raíz cuadrada de 36 el valor 7 en vez de 6.

  • Una acción humana que conduce a un resultado incorrecto (una metedura de pata). Por ejemplo, que el operador o el programador pulse una tecla equivocada.

  • Nosotros desecharemos las acepciones 2 y 3, ya que coinciden con las definiciones de defecto y fallo, para evitar equívocos. La relación entre error, fallo y defecto queda más clara en la Figura 12-1.

    Diseño de la interfaz de usuario: Pruebas del software

    Figura 1. Relación entre error, defecto y fallo.

    3.2. El proceso de prueba

    El proceso de prueba comienza con la generación de un plan de pruebas en base a la documentación sobre el proyecto y la documentación sobre el software a probar. A partir de dicho plan, se entra en detalle diseñando pruebas específicas basándose en la documentación del software a probar. Una vez detalladas las pruebas (especificaciones de casos y de procedimientos), se toma la configuración del software (revisada, para confirmar que se trata de la versión apropiada del programa) que se va a probar para ejecutar sobre ella los casos. En algunas situaciones, se puede tratar de reejecuciones de pruebas, por lo que es conveniente tener constancia de los defectos ya detectados aunque aún no corregidos. A partir de los resultados de salida, se pasa a su evaluación mediante comparación con la salida esperada. A partir de ésta, se pueden realizar dos actividades:

    • La depuración (localización y corrección de defectos).

    • El análisis de la estadística de errores.

    La depuración puede corregir o no los defectos. Si no consigue localizarlos, puede ser necesario realizar pruebas adicionales para obtener más información. Si se corrige un defecto, se debe volver a probar el software para comprobar que el problema está resuelto.

    Por su parte, el análisis de errores puede servir para realizar predicciones de la fiabilidad del software y para detectar las causas más habituales de error y mejorar los procesos de desarrollo.

    3.3. Técnicas de diseño de casos de prueba

    El diseño de casos de prueba está totalmente mediatizado por la imposibilidad de probar exhaustivamente el software. Pensemos en un programa muy sencillo que sólo suma dos números enteros de dos cifras (del 0 al 99). Si quisiéramos probar, simplemente, todos los valores válidos que se pueden sumar, deberíamos probar 10.000 combinaciones distintas de cien posibles números del primer sumando y cien del segundo. Pensemos que aún quedarían por probar todas las posibilidades de error al introducir los datos (por ejemplo, teclear una letra en vez de un número). La conclusión es que, si para un programa tan elemental debemos realizar tantas pruebas, pensemos en lo que supondría un programa medio.

    En consecuencia, las técnicas de diseño de casos de prueba tienen como objetivo conseguir una confianza aceptable en que se detectarán los defectos existentes (ya que la seguridad total sólo puede obtenerse de la prueba exhaustiva, que no es practicable) sin necesidad de consumir una cantidad excesiva de recursos (por ejemplo, tiempo para probar o tiempo de ejecución). Toda la disciplina de pruebas debe moverse, por lo tanto, en un equilibrio entre la disponibilidad de recursos y la confianza que aportan los casos para descubrir los defectos existentes. Este equilibrio no es sencillo, lo que convierte a las pruebas en una disciplina difícil que está lejos de parecerse a la imagen de actividad rutinaria que suele sugerir.

    Ya que no se pueden probar todas las posibilidades de funcionamiento del software, la idea fundamental para el diseño de casos de prueba consiste en elegir algunas de ellas que, por sus características, se consideren representativas del resto. Así, se asume que, si no se detectan defectos en el software al ejecutar dichos casos, podemos tener un cierto nivel de confianza (que depende de la elección de los casos) en que el programa no tiene defectos. La dificultad de esta idea es saber elegir los casos que se deben ejecutar. De hecho, una elección puramente aleatoria no proporciona demasiada confianza en que se puedan detectar todos los defectos existentes. Por ejemplo, en el caso del programa de suma de dos números, elegir cuatro pares de sumandos al azar no aporta mucha seguridad a la prueba (una probabilidad de 4/10.000 de cobertura de posibilidades). Por eso es necesario recurrir a ciertos criterios de elección que veremos a continuación.

    Existen tres enfoques principales para el diseño de casos:

  • El enfoque estructural o de caja blanca (véase la figura 2). Consiste en centrarse en la estructura interna (implementación) del programa para elegir los casos de prueba. En este caso, la prueba ideal (exhaustiva) del software consistiría en probar todos los posibles caminos de ejecución, a través de las instrucciones del código, que puedan trazarse.

  • El enfoque funcional o de caja negra (véase la figura 2). Consiste en estudiar la especificación de las funciones, la entrada y la salida para derivar los casos. Aquí, la prueba ideal del software consistiría en probar todas las posibles entradas y salidas del programa.

  • El enfoque aleatorio consiste en utilizar modelos (en muchas ocasiones estadísticos) que representen las posibles entradas al programa para crear a partir de ellos los casos de prueba. La prueba exhaustiva consistiría en probar todas las posibles entradas al programa.

  • Estos enfoques no son excluyentes entre sí, ya que se pueden combinar para conseguir una detección de defectos más eficaz. Veremos a continuación una presentación de cada uno de ellos.

    Figura 2. Los enfoques de diseño de pruebas de caja blanca y de caja negra.

    3.4. Pruebas estructurales. (Prueba de la caja blanca)

    Como hemos dicho, las pruebas exhaustivas son impracticables. Podemos recurrir al clásico ejemplo de Myers [MYERS, 1979] de un programa de 50 líneas con 25 sentencias IF en serie, en el que el número total de caminos contiene 33,5 millones de secuencias potenciales (contando dos posibles salidas para cada IF tenemos 225 posibles caminos). El diseño de casos tiene que basarse en la elección de caminos importantes que ofrezcan una seguridad aceptable en descubrir defecto, y para ello se utilizan los llamados criterios de cobertura lógica. Antes de pasar a examinarlos, conviene señalar que estas técnicas no requieren el uso de ninguna representación gráfica específica del software, aunque es habitual tomar como base los lla­mados diagramas de flujo de control (flowgraph charts o flowcharts). Como ejemplo de diagrama de flujo junto al código correspondiente, véase la Figura 3.

    En el recuadro adjunto pueden consultarse algunas recomendaciones para dibujar los grafos de flujo de los programas para poder generar los casos de prueba correspondientes. También existen herramientas que dibujan el grafo de flujo de un programa sólo con facilitar el código fuente del mismo como entrada.

    Figura 3. Grafo de flujo de un programa (pseudocódigo).

    Una posible clasificación de criterios de cobertura lógica es la que se ofrece abajo. Hay que destacar que los criterios de cobertura que se ofrecen están en orden de exigencia y, por lo tanto, de coste económico. Es decir, el criterio de cobertura de sentencias es el que ofrece una menor seguridad de detección de defectos, pero es el que cuesta menos en número de ejecuciones del programa.

  • Cobertura de sentencias. Se trata de generar los casos de prueba necesarios para que cada sentencia o instrucción del programa se ejecute al menos una vez.

  • Cobertura de decisiones. Consiste en escribir casos suficientes para que cada decisión tenga, por lo menos una vez, un resultado verdadero y, al menos una vez, uno falso. En general, una ejecución de pruebas que cumple la cobertura de decisiones cumple también la cobertura de sentencias.

  • Cobertura de condiciones. Se trata de diseñar tantos casos como sea necesario para que cada condición de cada decisión adopte el valor verdadero al menos una vez y el falso al menos una vez. No podemos asegurar que si se cumple la cobertura de condiciones se cumple necesariamente la de decisiones.

  • Criterio de decisión/condición. Consiste en exigir el criterio de cobertura de condiciones obligando a que se cumpla también el criterio de decisiones.

  • Criterio de condición múltiple. En el caso de que se considere que la evaluación de las condiciones de cada decisión no se realiza de forma simúltanea (por ejemplo, según se ejecuta en el procesador), se podría considerar que cada decisión multicondicional se descompone en varias decisiones unicondicionales. Es decir, una decisión como IF((a=1)AND(c=4)) THEN se convierte en una concatenación de dos decisiones: IF(a=1) y IF(c=4). En este caso, debemos conseguir que todas las combinaciones posibles de resultados (verdadero/falso) de cada condición en cada decisión se ejecuten al menos una vez.

  • La cobertura de caminos (secuencias de sentencias) es el criterio más elevado: cada uno de los posibles caminos del programa se debe ejecutar al menos una vez. Se define camino como la secuencia de sentencias encadenadas desde la sentencia inicial del programa hasta su sentencia final. Como hemos visto, el número de caminos en un programa pequeño puede ser impracticable para las pruebas. Para reducir el número de caminos a probar, se habla del concepto de camino de prueba (test path): un camino del programa que atraviesa, como máximo, una vez el interior de cada bucle que encuentra. La idea en la que se basa consiste en que ejecutar un bucle más de una vez no supone una mayor seguridad de detectar defectos en él. Sin embargo, otros especialistas recomiendan que se pruebe cada bucle tres veces: una sin entrar en su interior, otra ejecutándolo una vez y otra más ejecutándolo dos veces. Esto último es interesante para comprobar cómo se comporta9 a partir de los valores de datos procedentes, no del exterior del bucle (como en la primera iteración), sino de las operaciones de su interior.

    Si trabajamos con los caminos de prueba, existe la posibilidad de cuantificar el número total de caminos utilizando algoritmos basados en matrices que representan el grafo de flujo del programa. Así, en [SHOOMAN, 1983] y en [BEIZER, 1990] se ofrecen diversos métodos basados en ecuaciones, expresiones regulares y matrices que permiten tanto calcular el número de caminos como enumerar dichos caminos expresados como series de arcos del grafo de flujo. Saber cuál es el número de caminos del grafo de un programa ayuda a planificar las pruebas y a asignar recursos a las mismas, ya que indica el número de ejecuciones necesarias. También sirve de comprobación a la hora de enumerar los caminos.

    Los bucles constituyen el elemento de los programas que genera un mayor número de problemas para la cobertura de caminos. Su tratamiento no es sencillo ni siquiera adoptando el concepto de camino de prueba. Pensemos, por ejemplo, en el caso de varios bucles anidados o bucles que fijan valores mínimo y máximo de repeticiones. Se puede consultar una presentación detallada de su tratamiento en [BEIZER, 1990].

    3.4.1. Utilización de la complejidad ciclomática de McCabe.

    La utilización de la métrica de McCabe [MCCABE, 1976] ha sido muy popular en el diseño de pruebas desde su creación. Esta métrica es un indicador del número de caminos independientes que existen en un grafo. El propio McCabe definió como un buen criterio de prueba la consecución de la ejecución de un conjunto de caminos independientes, lo que implica probar un número de caminos igual al de la métrica. Se propone este criterio como equivalente a una cobertura de decisiones, aunque se han propuesto contraejemplos que invalidan esta suposición.

    La complejidad de McCabe V (G) se puede calcular de las tres maneras siguientes a partir de un grafo de flujo G:

  • V (G) = a - n + 2, siendo a el número de arcos o aristas del grafo y n el número de nodos.

  • V (G) = r, siendo r el número de regiones cerradas del grafo.

  • V (G) = c + 1, siendo c el número de nodos de condición

  • Veamos cómo se aplican estas fórmulas sobre el grafo de flujo de la figura 5:

  • V (G) = 14 - 11 + 2 = 5. Los arcos han sido identificados con las marcas desde a1 hasta a14. Los nodos están numerados del 1 al 11.

  • V (G) = 5. Las regiones o áreas cerradas (limitadas por aristas) del grafo son cinco, que hemos numerado en el grafo. Como puede verse, se ha marcado un área (región 5) añadiendo un arco ficticio desde el nodo 11 al nodo 1. Esto se debe a que las fórmulas de McCabe sólo son aplicables a grafos fuertemente conexos, es decir, aquellos para los cuales existe un camino entre cualesquiera dos nodos que se elijan. Los programas, con un nodo de inicio y otro de final, no cumplen esta condición. Por eso, debemos marcar dicho arco o, como alternativa, contabilizar la región externa al grafo como una más.

  • V (G) = 4 + 1. Los nodos de condición son el 2, el 4, el 5 y el 6. Todos ellos son nodos de decisión binaria, es decir, surgen dos aristas de ellos. En el caso de que de un nodo de condición (por ejemplo, una sentencia Case-of) partiera n arcos (n>2), debería contabilizarse como n-1 para la fórmula (que equivale al número de bifurcaciones binarias necesarias para simular dicha bifurcación “n-aria”).

  • Una vez calculado el valor V (G) podemos afirmar que el número máximo de caminos independientes de dicho grafo es cinco. El criterio de prueba de McCabe consiste en elegir cinco caminos que sean independientes entre sí y crear casos de prueba cuya ejecución siga dichos caminos. Para ayudar a la elección de dichos ca­minos, McCabe [MCCABE, 1982] creó un procedimiento llamado “método del camino básico”, consistente en realizar variaciones sobre la elección de un primer camino de prueba típico denominado camino básico.

    Figura 5. Cálculo de la complejidad ciclomática sobre un grafo.

    En nuestro caso, un posible conjunto de caminos (descritos como secuencias de nodos visitados) podría ser el siguiente:

    • 1-2-11

    • 1-2-3-4-10-2

    • 1-2-3-4-5-10-2

    • 1-2-3-4-5-6-7-9-4-10-2-11

    • 1-2-3-4-5-6-8-9-4-10-2-11

    Hemos subrayado los elementos de cada camino que lo hacen independiente de los demás. Conviene aclarar que algunos de los caminos quizás no se puedan ejecutar solos y requieran una concatenación con algún otro. A partir de estos caminos, el diseñador de las pruebas debe analizar el código para saber los datos de entrada necesarios para forzar la ejecución de cada uno de ellos. Una vez determinados los datos de entrada hay que consultar la especificación para averiguar cuál es la salida teóricamente correcta para cada caso.

    Puede ocurrir también que las condiciones necesarias para que la ejecución pase por un determinado camino no se puedan satisfacer de ninguna manera. Nos encontraríamos entonces ante un «camino imposible». En ese caso, debemos sustituir dicho camino por otro posible que permita satisfacer igualmente el criterio de prueba de McCabe, es decir, que ejecute la misma arista o flecha que diferencia al impo­sible de los demás caminos independientes.

    La experimentación con la métrica de McCabe ha dado como resultado las siguientes conclusiones [BEMER, 1990]:

    • V (G) marca un límite mínimo de número de casos de prueba para un programa, contando siempre cada condición de decisión como un nodo individual.

    • Parece que cuando V(G) es mayor que diez la probabilidad de defectos en el módulo o en el programa crece bastante si dicho valor alto no se debe a sentencias Case-of o similares. En estos casos, es recomendable replantearse el diseño modular obtenido, dividiendo los módulos para no superar el límite de diez de la métrica de McCabe en cada uno de ellos.

    3.5. Prueba funcional. (Prueba de la caja negra)

    La prueba funcional o de caja negra se centra en el estudio de la especificación del software, del análisis de las funciones que debe realizar, de las entradas y de las salidas. Lamentablemente, la prueba exhaustiva de caja negra también es generalmente impracticable: pensemos en el ejemplo de la suma visto en el apartado 3.3. De nuevo, ya que no podemos ejecutar todas las posibilidades de funcionamiento y todas las combinaciones de entradas y de salidas, debemos buscar criterios que permitan elegir un subconjunto de casos cuya ejecución aporte una cierta confianza en detectar los posibles defectos del software. Para fijar estas pautas de diseño de pruebas, nos apoyaremos en las siguientes dos definiciones de Myers que definen qué es un caso de prueba bien elegido:

    • El que reduce el número de otros casos necesarios para que la prueba sea razonable. Esto implica que el caso ejecute el máximo número de posibilidades de entrada diferentes para así reducir el total de casos.

    • Cubre un conjunto extenso de otros casos posibles, es decir, nos indica algo acerca de la ausencia o la presencia de defectos en el conjunto específico de entradas que prueba, así como de otros conjuntos similares.

    Veremos a continuación distintas técnicas de diseño de casos de caja negra.

    3.5.1. Particiones o clases de equivalencia

    Esta técnica utiliza las cualidades que definen un buen caso de prueba de la siguiente manera:

    • Cada caso debe cubrir el máximo número de entradas.

    • Debe tratarse el dominio de valores de entrada dividido en un número finito de clases de equivalencia que cumplan la siguiente propiedad: la prueba de un valor representativo de una clase permite suponer “razonablemente” que el resultado obtenido (existan defectos o no) será el mismo que el obtenido probando cualquier otro valor de la clase.

    El método de diseño de casos consiste entonces en:

    • Identificación de clases de equivalencia.

    • Creación de los casos de prueba correspondientes.

    Para identificar las posibles clases de equivalencia de un programa a partir de su especificación se deben seguir los siguientes pasos:

  • Identificación de las condiciones de las entradas del programa, es decir, restricciones de formato o contenido de los datos de entrada.

  • A partir de ellas, se identifican clases de equivalencia que pueden ser:

    • De datos válidos.

    • De datos no válidos o erróneos.

    La identificación de las clases se realiza basándose en el principio de igualdad de tratamiento: todos los valores de la clase deben ser tratados de la misma manera por el programa.

  • Existen algunas reglas que ayudan a identificar clases:

    • Si se especifica un rango de valores para los datos de entrada (por ejemplo, “el número estará comprendido entre 1 y 49”), se creará una clase válida (1 " número " 49) y dos clases no válidas (número < 1 y número > 49).

    • Si se especifica un número de valores (por ejemplo, “se pueden registrar de uno a tres propietarios de un piso”), se creará una clase válida (1 " propietarios " 3) y dos no válidas (propietarios < 1 y propietarios > 3).

    • Si se especifica una situación del tipo “debe ser” o booleana (por ejemplo, “el primer carácter debe ser una letra”), se identifican una clase válida (“es una letra”) y una no válida (“no es una letra”).

    • Si se especifica un conjunto de valores admitidos (por ejemplo, “pueden registrarse tres tipos de inmuebles: pisos, chalés y locales comerciales”) y se sabe que el programa trata de forma diferente cada uno de ellos, se identifica una clase válida por cada valor (en este caso son tres: piso, chalé y local) y una no válida (cualquier otro caso: por ejemplo, plaza de garaje).

    • En cualquier caso, si se sospecha que ciertos elementos de una clase no se tratan igual que el resto de la misma, deben dividirse en clases menores.

    La aplicación de estas reglas para la derivación de clases de equivalencia permi­te desarrollar los casos de prueba para cada elemento de datos del dominio de entrada. La división en clases deberían realizarla personas independientes al proceso de desarrollo del programa, ya que, si lo hace la persona que preparó la especificación o diseñó el software, la existencia de algunas clases (en concreto, las no consideradas en el tratamiento) no será, probablemente, reconocida.

    El último paso del método es el uso de las clases de equivalencia para identificar los casos de prueba correspondientes. Este proceso consta de las siguientes fases:

  • Asignación de un número único a cada clase de equivalencia.

  • Hasta que todas las clases de equivalencia hayan sido cubiertas por (incorporadas a) casos de prueba, se tratará de escribir un caso que cubra tantas clases válidas no incorporadas como sea posible.

  • Hasta que todas las clases de equivalencia no válidas hayan sido cubiertas por casos de prueba, escribir un caso para una única clase no válida sin cubrir.

  • La razón de cubrir con casos individuales las clases no válidas es que ciertos controles de entrada pueden enmascarar o invalidar otros controles similares. Por ejemplo, en un programa donde hay que “introducir cantidad (1-99) y letra inicial (A-Z)” ante el caso “105 &” (dos errores), se puede indicar sólo el mensaje “105 fuera de rango de cantidad” y dejar sin examinar el resto de la entrada (el error de introducir “&” en vez de una letra).

    Veamos un ejemplo de aplicación de la técnica. Se trata de una aplicación bancaria en la que el operador deberá proporcionar un código, un nombre para que el usuario identifique la operación (por ejemplo, “nómina”) y una orden que disparará una serie de funciones bancarias.

    Especificación

    • Código área: número de 3 dígitos que no empieza por 0 ni por 1.

    • Nombre de identificación: 6 caracteres.

    • Órdenes posibles: “cheque”, “depósito”, “pago factura”, “retirada de fondos”.

    Aplicación de las reglas

    • Código

    • número, regla 3, booleana:

    • clase válida (número)

    • clase no válida (no es número)

    • regla 5, la clase número debe subdividirse; por la regla 1, rango, obtenemos:

    • subclase válida (200 código 999)

    • dos subclases no válidas (código < 200; código > 999)

    • Nombre de id., número específico de valores, regla 2:

    • clase válida (6 caracteres)

    • dos clases no válidas (más de 6; menos de 6 caracteres)

    • Orden, conjunto de valores, regla 4:

    • una clase válida para cada orden (“cheque”, “depósito” ... ); 4 en total.

    • una clase no válida (“divisas”, por ejemplo).

    En la tabla 1 se han enumerado las clases identificadas y la generación de casos (presuponiendo que el orden de entrada es código-nombre-orden) se ofrece a continuación.

    Condición de entrada

    Clases válidas

    Clases inválidas

    Código área

    (1) 200 " código " 999

    (2) código <200

    (3) código >999

    (4) no es número

    Nombre para identificar la operación

    (5) seis caracteres

    (6) menos de 6 caracteres

    (7) más de 6 caracteres

    Orden

    (8) «cheque»

    (9) «depósito»

    (10) «pago factura»

    (11) «retirada fondos»

    (12) ninguna orden válida

    Tabla 1. Tabla de clases de equivalencia del ejemplo.

    • Casos válidos:

    • 300 Nómina Depósito (1) (5) (9)

    • 400 Viajes Cheque (1) (5) (8)

    • 500 Coches Pago-factura (1) (5) (10)

    • 600 Comida Retirada-fondos (1) (5) (11)

    • Casos no válidos:

    • 180 Viajes Pago-factura (2) (5) (10)

    • 1032 Nómina Depósito (3) (5) (9)

    • XY Compra Retirada-fondos (4) (5) (11)

    • 350 A Depósito (1) (6) (9)

    • 450 Regalos Cheque (1) (7) (8)

    • 550 Casa &%4 (1) (5) (12)

    3.5.2. Análisis de valores límite (AVL).

    Mediante la experiencia (e incluso a través de demostraciones) se ha podido constatar que los casos de prueba que exploran las condiciones límite de un programa producen un mejor resultado para la detección de defectos, es decir, es más probable que los defectos del software se acumulen en estas condiciones. Podemos definir las condiciones límite como las situaciones que se hallan directamente arriba, abajo y en los márgenes de las clases de equivalencia.

    El análisis de valores límite es un técnica de diseño de casos que complementa a la de particiones de equivalencia. Las diferencias entre ambas son las siguientes:

  • Más que elegir “cualquier” elemento como representativo de una clase de equivalencia, se requiere la selección de uno o más elementos tal que los márgenes se sometan a prueba.

  • Más que concentrarse únicamente en el dominio de entrada (condiciones de entrada), los casos de prueba se generan considerando también el espacio de salida.

  • El proceso de selección de casos es también heurístico, aunque existen ciertas reglas orientativas. Aunque parezca que el AVL es simple de usar (a la vista de las reglas), su aplicación tiene múltiples matices que requieren un gran cuidado a la hora de diseñar las pruebas. Las reglas para identificar clases son las siguientes:

  • Si una condición de entrada especifica un rango de valores (“-l.0 valor +1.0”) se deben generar casos para los extremos del rango (-1.0 y +1.0) y casos no válidos para situaciones justo más allá de los extremos (- 1.001 y + 1.001, en el caso en que se admitan 3 decimales).

  • Si la condición de entrada especifica un número de valores (“el fichero de entrada tendrá de 1 a 255 registros”), hay que escribir casos para los números máximo, mínimo, uno más del máximo y uno menos del mínimo de valores (0, 1, 255 y 256 registros).

  • Usar la regla 1 para la condición de salida (“el descuento máximo aplicable en compra al contado será el 50%, el mínimo será el 6%”). Se escribirán casos para intentar obtener descuentos de 5,99%, 6%, 50% y 50,01 %.

  • Usar la regla 2 para cada condición de salida (“el programa puede mostrar de 1 a 4 listados”). Se escriben casos para intentar generar 0, 1, 4 y 5 listados.

  • En esta regla, como en la 3, debe recordarse que:

    • Los valores límite de entrada no generan necesariamente los valores límite de salida (recuérdese la función seno, por ejemplo).

    • No siempre se pueden generar resultados fuera del rango de salida (pero es interesante considerarlo).

  • Si la entrada o la salida de un programa es un conjunto ordenado (por ejemplo, una tabla, un archivo secuencial, etc.), los casos se deben concentrar en el primero y en el último elemento.

  • Es recomendable utilizar el ingenio para considerar todos los aspectos y matices, a veces sutiles, en la aplicación del AVL.

    3.5.3. Conjetura de errores.

    La idea básica de esta técnica consiste en enumerar una lista de equivocaciones que pueden cometer los desarrolladores y de las situaciones propensas a ciertos errores. Después se generan casos de prueba en base a dicha lista (se suelen corresponder con defectos que aparecen comúnmente y no con aspectos funcionales). Esta técnica también se ha denominado generación de casos (o valores) especiales, ya que no se obtienen en base a otros métodos sino mediante la intuición o la experiencia.

    No existen directrices eficaces que puedan ayudar a generar este tipo de casos, ya que lo único que se puede hacer es presentar algunos ejemplos típicos que reflejan esta técnica. Algunos valores a tener en cuenta para los casos especiales son los siguientes:

    • El valor cero es una situación propensa a error tanto en la salida como en la entrada.

    • En situaciones en las que se introduce un número variable de valores (por ejemplo, una lista), conviene centrarse en el caso de no introducir ningún valor y en el de un solo valor. También puede ser interesante una lista que tiene todos los valores iguales.

    • Es recomendable imaginar que el programador pudiera haber interpretado algo mal en la especificación.

    • También interesa imaginar lo que el usuario puede introducir como entrada a un programa. Se dice que se debe preveer toda clase de acciones de un usuario como si fuera “completamente tonto” o, incluso, como si quisiera sabotear el programa.

    3.6. Pruebas aleatorias.

    En las pruebas aleatorias simulamos la entrada habitual del programa creando datos de entrada en la secuencia y con la frecuencia con las que podrían aparecer en la práctica, de forma continua sin parar., Esto implica usar una herramienta denominada un generador de pruebas, a las que se alimenta con una descripción de las entradas y las secuencias de entrada posibles y su probabilidad de ocurrir en la práctica. Este enfoque de prueba es muy común en la prueba de compiladores en la que se generan aleatoriamente códigos de programas que sirven de casos de prueba para la compilación.

    Si el proceso de generación se ha realizado correctamente, se crearán eventualmente todas las posibles entradas del programa en todas las posibles combinaciones y permutaciones. También se puede conseguir, indicando la distribución estadística que siguen, que la frecuencia de las entradas sea la apropiada para orientar correctamente nuestras pruebas hacia lo que es probable que suceda en la práctica. No obstante, esta forma de diseñar casos de prueba es menos utilizada que las técnicas de caja blanca y de caja negra.

    3.7. Enfoque práctico recomendado para el diseño de casos.

    Los dos enfoques estudiados, caja blanca y caja negra, representan aproximaciones diferentes para las pruebas. El enfoque práctico recomendado para el uso de las técnicas de diseño de casos pretende mostrar el uso más apropiado de cada técnica para la obtención de un conjunto de casos útiles sin perjuicio de las estrategias de niveles de prueba:

  • Si la especificación contiene combinaciones de condiciones de entrada, comenzar formando sus grafos de causa-efecto (ayudan a la comprensión de dichas combinaciones).

  • En todos los casos, usar el análisis de valores-límites para añadir casos de prueba: elegir límites para dar valores a las causas en los casos generados asumiendo que cada causa es una clase de equivalencia.

  • Identificar las clases válidas y no válidas de equivalencia para la entrada y la salida, y añadir los casos no incluidos anteriormente (¿cada causa es una única clase de equivalencia? ¿Deben dividirse en clases?).

  • Utilizar la técnica de conjetura de errores para añadir nuevos casos, referidos a valores especiales.

  • Ejecutar los casos generados hasta el momento (de caja negra) y analizar la cobertura obtenida.

  • Examinar la lógica del programa para añadir los casos precisos (de caja blanca) para cumplir el criterio de cobertura elegido si los resultados de la ejecución del punto 5 indican que no se ha satisfecho el criterio de cobertura elegido (que figura en el plan de pruebas).

  • Aunque éste es el enfoque integrado para una prueba “razonable”, en la práctica la aplicación de las distintas técnicas está bastante discriminada según la etapa de la estrategia de prueba.

    Otra cuestión importante en relación a los dos tipos de diseño de pruebas es: ¿por qué incluir técnicas de caja blanca para explorar detalles lógicos y no centrarnos mejor en probar los requisitos funcionales con técnicas de caja negra? (si el programa realiza correctamente las funciones ¿por qué hay que intentar probar un determinado número de caminos?).

    • Los errores lógicos y las suposiciones incorrectas son inversamente proporcionales a la probabilidad de que se ejecute un camino del programa (a menor probabilidad de ejecutarse un camino, mayor número de errores).

    • Se suele creer que un determinado camino lógico tiene pocas posibilidades de ejecutarse cuando, de hecho, se puede ejecutar regularmente.

    • Los errores tipográficos son aleatorios; pueden aparecer en cualquier parte del programa (sea muy usada o no).

    • La probabilidad y la importancia de un trozo de código suele ser calculada de forma muy subjetiva.

    Debemos recordar que tanto la prueba exhaustiva de caja blanca como de caja negra son impracticables. ¿Bastaría, no obstante, una prueba exhaustiva de caja blanca solamente? Véase el siguiente programa:

    if ((x+y+z)/13 = x)

    printf(“X, Y y Z son iguales”);

    else

    printf(“X, Y y Z no son iguales”);

    En este programa, una prueba exhaustiva de caja blanca (que pase por todos los caminos) no asegura necesariamente la detección de los defectos de su diseño. Véase, por ejemplo, cómo los dos casos siguientes no detectan ningún problema en el programa:

    • x=5, y=5, z=5

    • x=2, y=3, z=7

    Estos contraejemplos no pretenden influir en el tipo de técnica de diseño de casos que debemos elegir. Más bien nos indican que conviene emplear lo mejor de todas las técnicas para obtener pruebas más eficaz.

    El caso x=2, y=3, z=1 sí permitiría detectar el defecto de diseño de la decisión.

    I.E.S. Almunia. ADAIG.

    3

    Cómo dibujar un grafo de flujo.

    Para dibujar el grafo de flujo de un programa es recomendable seguir los siguientes pasos:

  • Señalar sobre el código cada condición de cada decisión (por ejemplo, «mismo producto» en la figura 3 es una condición de la decisión «haya registros y mismo producto») tanto en sentencias If-then y Case-of como en los bucles While o Until.

  • Agrupar el resto de las sentencias en secuencias situadas entre cada dos condiciones según los esquemas de representación de las estructuras básicas que mostramos a continuación.

  • Diseño de la interfaz de usuario: Pruebas del software

    Figura 4. Grafo de flujo de las estructuras básicas de programa

  • Numerar tanto las condiciones como los grupos de sentencias, de manera que se les asigne un identificador único. Es recomendable alterar el orden en el que aparecen las condiciones en una decisión multicondicional, situándolas en orden decreciente de restricción (primero, las más restrictivas). El objetivo de esta alteración es facilitar la derivación de casos de prueba una vez obtenido el grafo. Es conveniente identificar los nodos que representan condiciones asignándoles una letra y señalar cuál es el resultado que provoca la ejecución de cada una de las aristas que surgen de ellos (por ejemplo, si la condición x es verdadera o falsa).




  • Descargar
    Enviado por:Emilio
    Idioma: castellano
    País: España

    Te va a interesar