0% encontró este documento útil (0 votos)
484 vistas99 páginas

Psicología y economía en pruebas de software

Este documento discute la psicología y la economía de las pruebas de software. Explica que la definición correcta de prueba es encontrar errores en un programa, no demostrar que funciona correctamente. También señala que probar todos los casos posibles de un programa es imposible debido a limitaciones de tiempo y recursos, por lo que se deben establecer estrategias económicas como las pruebas de caja negra y caja blanca.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd
0% encontró este documento útil (0 votos)
484 vistas99 páginas

Psicología y economía en pruebas de software

Este documento discute la psicología y la economía de las pruebas de software. Explica que la definición correcta de prueba es encontrar errores en un programa, no demostrar que funciona correctamente. También señala que probar todos los casos posibles de un programa es imposible debido a limitaciones de tiempo y recursos, por lo que se deben establecer estrategias económicas como las pruebas de caja negra y caja blanca.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como DOCX, PDF, TXT o lee en línea desde Scribd

El arte de las pruebas de software, segunda edición

Capítulo 2: La psicología y la economía de las pruebas de


programas
Descripción general

La prueba de software es una tarea técnica, pero también implica algunas consideraciones
importantes de la economía y la psicología humana.

En un mundo ideal, querríamos probar todas las posibles permutaciones de un programa. En la


mayoría de los casos, sin embargo, esto simplemente no es posible. Incluso un programa
aparentemente simple puede tener cientos o miles de posibles combinaciones de entrada y salida.
La creación de casos de prueba para todas estas posibilidades no es práctica. La prueba completa
de una aplicación compleja llevaría demasiado tiempo y requeriría demasiados recursos humanos
para ser económicamente viable.

Además, el probador de software necesita la actitud adecuada (quizás "visión" es una palabra
mejor) para probar con éxito una aplicación de software. En algunos casos, la actitud del evaluador
puede ser más importante que el proceso en sí. Por lo tanto, comenzaremos nuestra discusión
sobre las pruebas de software con estos problemas antes de profundizar en la naturaleza más
técnica del tema.

La psicología de las pruebas

Una de las principales causas de las pruebas deficientes de programas es el hecho de que la
mayoría de los programadores comienzan con una definición falsa del término. Podrían decir:

• "La prueba es el proceso de demostrar que no hay errores".

• "El propósito de las pruebas es mostrar que un programa realiza correctamente sus funciones
previstas".

• "La prueba es el proceso de establecer la confianza de que un programa hace lo que se supone
que debe hacer".

Estas definiciones están al revés.

Cuando prueba un programa, desea agregarle algún valor. Agregar valor a través de las pruebas
significa elevar la calidad o confiabilidad del programa. Aumentar la confiabilidad del programa
significa encontrar y eliminar errores.

Por lo tanto, no pruebe un programa para demostrar que funciona; más bien, debe comenzar con
la suposición de que el programa contiene errores (una suposición válida para casi cualquier
programa) y luego probar el programa para encontrar tantos errores como sea posible.
Por tanto, una definición más apropiada es la siguiente:

La prueba es el proceso de ejecutar un programa con la intención de encontrar errores.

Aunque esto puede parecer un juego de semántica sutil, es realmente una distinción importante.
Comprender la verdadera definición de prueba de software puede marcar una gran diferencia en
el éxito de sus esfuerzos.

Los seres humanos tienden a estar muy orientados a los objetivos, y establecer el objetivo
adecuado tiene un efecto psicológico importante. Si nuestro objetivo es demostrar que un
programa no tiene errores, entonces inconscientemente seremos dirigidos hacia este objetivo; es
decir, tendemos a seleccionar datos de prueba que tienen una baja probabilidad de hacer que el
programa falle. Por otro lado, si nuestro objetivo es demostrar que un programa tiene errores,
nuestros datos de prueba tendrán una mayor probabilidad de encontrar errores. El último
enfoque agregará más valor al programa que el primero.

Esta definición de prueba tiene innumerables implicaciones, muchas de las cuales se encuentran
dispersas a lo largo de este libro. Por ejemplo, implica que las pruebas son un proceso destructivo,
incluso un proceso sádico, lo que explica por qué a la mayoría de las personas les resulta difícil. Eso
puede ir en contra de nuestro grano; Con buena suerte, la mayoría de nosotros tenemos una
perspectiva de la vida más constructiva que destructiva. La mayoría de las personas se inclinan por
fabricar objetos en lugar de destrozarlos. La definición también tiene implicaciones sobre cómo se
deben diseñar los casos de prueba (datos de prueba) y quién debe y quién no debe probar un
programa determinado.

Otra forma de reforzar la definición adecuada de prueba es analizar el uso de las palabras
"exitoso" y "no exitoso", en particular, su uso por parte de los gerentes de proyecto al categorizar
los resultados de los casos de prueba. La mayoría de los gerentes de proyecto llaman a un caso de
prueba que no encontró un error como una "ejecución de prueba exitosa", mientras que una
prueba que descubre un nuevo error generalmente se denomina "no exitosa".

Una vez más, esto está al revés. "Fracasado" denota algo indeseable o decepcionante. Según
nuestra forma de pensar, una prueba bien construida y ejecutada de una pieza de software tiene
éxito cuando encuentra errores que pueden corregirse. Y esa misma prueba también tiene éxito
cuando finalmente establece que no hay más errores que encontrar. La única prueba fallida es
aquella que no examina adecuadamente el software y, en la mayoría de los casos, una prueba que
no encontró errores probablemente se consideraría fallida, ya que el concepto de un programa sin
errores es básicamente poco realista.

Un caso de prueba que encuentra un nuevo error difícilmente puede considerarse fallido; más
bien, ha demostrado ser una inversión valiosa. Un caso de prueba fallido es aquel que hace que un
programa produzca el resultado correcto sin encontrar ningún error.

Considere la analogía de una persona que visita a un médico debido a una sensación general de
malestar. Si el médico realiza algunas pruebas de laboratorio que no localizan el problema, no
llamamos a las pruebas de laboratorio "exitosas"; Fueron pruebas infructuosas porque el valor
neto del paciente se redujo debido a los costosos honorarios del laboratorio, el paciente aún está
enfermo y el paciente puede cuestionar la capacidad del médico como diagnosticador. Sin
embargo, si una prueba de laboratorio determina que el paciente tiene una úlcera péptica, la
prueba es exitosa porque el médico ahora puede comenzar el tratamiento apropiado. Por tanto, la
profesión médica parece utilizar estas palabras en el sentido correcto. La analogía, por supuesto,
es que debemos pensar en el programa, cuando comenzamos a probarlo, como el paciente
enfermo.

Un segundo problema con definiciones como "prueba es el proceso de demostrar que no hay
errores" es que tal objetivo es imposible de lograr para prácticamente todos los programas,
incluso los programas triviales.

Una vez más, los estudios psicológicos nos dicen que las personas se desempeñan mal cuando se
embarcan en una tarea que saben que es inviable o imposible. Por ejemplo, si se le indicara que
resolviera el crucigrama en el Sunday New York Times en 15 minutos, probablemente
observaríamos poco o ningún progreso después de 10 minutos porque la mayoría de nosotros
estaríamos resignados al hecho de que la tarea parece imposible. . Sin embargo, si le pidieran una
solución en cuatro horas, podríamos esperar razonablemente ver más progreso en los primeros 10
minutos. Definir la prueba del programa como el proceso de descubrir errores en un programa lo
convierte en una tarea factible, superando así este problema psicológico.

Un tercer problema con las definiciones comunes como “probar es el proceso de demostrar que
un programa hace lo que se supone que debe hacer” es que los programas que hacen lo que se
supone que deben hacer todavía pueden contener errores. Es decir, un error está claramente
presente si un programa no hace lo que se supone que debe hacer, pero también hay errores si un
programa hace lo que se supone que no debe hacer. Considere el programa de triángulos del
Capítulo 1. Incluso si pudiéramos demostrar que el programa distingue correctamente entre todos
los triángulos escalenos, isósceles y equiláteros, el programa aún estaría en error si hace algo que
no se supone que debe hacer (como representar 1 , 2, 3 como un triángulo escaleno o diciendo
que 0, 0, 0 representa un triángulo equilátero). Es más probable que descubramos la última clase
de errores si vemos las pruebas de programas como el proceso de encontrar errores que si las
vemos como el proceso de mostrar que un programa hace lo que se supone que debe hacer.

Para resumir, las pruebas de programas se ven más apropiadamente como el proceso destructivo
de tratar de encontrar los errores (cuya presencia se supone) en un programa. Un caso de prueba
exitoso es aquel que promueve el progreso en esta dirección al hacer que el programa falle. Por
supuesto, eventualmente querrá usar la prueba del programa para establecer cierto grado de
confianza en que un programa hace lo que se supone que debe hacer y no hace lo que no debe
hacer, pero este propósito se logra mejor mediante una exploración diligente de errores. .

Piense en alguien que se le acerca con la afirmación de que "mi programa es perfecto" (sin
errores). La mejor manera de establecer cierta confianza en esta afirmación es tratar de refutarla,
es decir, tratar de encontrar imperfecciones en lugar de simplemente confirmar que el programa
funciona correctamente para algún conjunto de datos de entrada.

La economía de las pruebas

Dada esta definición de prueba de programa, el siguiente paso apropiado es determinar si es


posible probar un programa para encontrar todos sus errores. Le mostraremos que la respuesta es
negativa, incluso para programas triviales. En general, es poco práctico, a menudo imposible,
encontrar todos los errores en un programa. Este problema fundamental, a su vez, tendrá
implicaciones para la economía de las pruebas, las suposiciones que el evaluador tendrá que hacer
sobre el programa y la manera en que se diseñan los casos de prueba.

Para combatir los desafíos asociados con las pruebas económicas, debe establecer algunas
estrategias antes de comenzar. Dos de las estrategias más prevalentes incluyen las pruebas de caja
negra y las pruebas de caja blanca, que exploraremos en las siguientes dos secciones.

Prueba de caja negra

Una estrategia de prueba importante es la prueba de caja negra, impulsada por datos o impulsada
por entrada / salida. Para utilizar este método, vea el programa como una caja negra. Su objetivo
es estar completamente despreocupado por el comportamiento interno y la estructura del
programa. En su lugar, concéntrese en encontrar circunstancias en las que el programa no se
comporte de acuerdo con sus especificaciones.

En este enfoque, los datos de prueba se derivan únicamente de las especificaciones (es decir, sin
aprovechar el conocimiento de la estructura interna del programa).

Si desea utilizar este enfoque para encontrar todos los errores en el programa, el criterio es una
prueba de entrada exhaustiva, haciendo uso de cada condición de entrada posible como un caso
de prueba. ¿Por qué? Si probó tres casos de prueba de triángulos equiláteros para el programa de
triángulos, eso de ninguna manera garantiza la detección correcta de todos los triángulos
equiláteros. El programa podría contener una verificación especial para los valores 3842, 3842,
3842 y denotar un triángulo como un triángulo escaleno. Dado que el programa es una caja negra,
la única forma de estar seguro de detectar la presencia de tal declaración es probando todas las
condiciones de entrada.

Para probar el programa de triángulos de manera exhaustiva, tendría que crear casos de prueba
para todos los triángulos válidos hasta el tamaño entero máximo del lenguaje de desarrollo. Esto
en sí mismo es un número astronómico de casos de prueba, pero de ninguna manera es
exhaustivo; encontraría errores donde el programa dijera que −3, 4, 5 es un triángulo escaleno y
que 2, A, 2 es un triángulo isósceles. Para estar seguro de encontrar todos estos errores, debe
probar utilizando no solo todas las entradas válidas, sino todas las entradas posibles. Por lo tanto,
para probar el programa de triángulo de manera exhaustiva, tendría que producir virtualmente un
número infinito de casos de prueba, lo que, por supuesto, no es posible.

Si esto suena difícil, la prueba de entrada exhaustiva de programas más grandes es un problema
aún mayor. Considere intentar una prueba exhaustiva de caja negra de un compilador de C ++. No
solo tendría que crear casos de prueba que representen todos los programas C ++ válidos
(nuevamente, virtualmente un número infinito), sino que también tendría que crear casos de
prueba para todos los programas C ++ no válidos (un número infinito) para asegurarse de que el
compilador los detecte como si fueran inválido. Es decir, el compilador debe probarse para
asegurarse de que no haga lo que se supone que no debe hacer, por ejemplo, compilar con éxito
un programa sintácticamente incorrecto.

El problema es aún peor para los programas que tienen memoria, como los sistemas operativos o
las aplicaciones de bases de datos. Por ejemplo, en una aplicación de base de datos como un
sistema de reserva de una aerolínea, la ejecución de una transacción (como una consulta de base
de datos, una reserva para un vuelo de avión) depende de lo que sucedió en transacciones
anteriores. Por lo tanto, no solo tendría que probar todas las transacciones válidas e inválidas
únicas, sino también todas las posibles secuencias de transacciones.

Esta discusión muestra que las pruebas de entrada exhaustivas son imposibles. Dos implicaciones
de esto son que (1) no se puede probar un programa para garantizar que esté libre de errores y (2)
una consideración fundamental en la prueba del programa es económica. Es decir, dado que las
pruebas exhaustivas están fuera de discusión, el objetivo debería ser maximizar el rendimiento de
la inversión en pruebas maximizando el número de errores encontrados por un número finito de
casos de prueba. Hacerlo implicará, entre otras cosas, poder mirar dentro del programa y hacer
ciertas suposiciones razonables, pero no herméticas, sobre el programa (por ejemplo, si el
programa de triángulo detecta 2, 2, 2 como un triángulo equilátero, parece razonable que hará lo
mismo para 3, 3, 3). Esto formará parte de la estrategia de diseño de casos de prueba en el
Capítulo 4.

Prueba de caja blanca

Otra estrategia de prueba, prueba de caja blanca o basada en lógica, le permite examinar la
estructura interna del programa. Esta estrategia deriva datos de prueba de un examen de la lógica
del programa (y a menudo, desafortunadamente, por descuido de la especificación).

El objetivo en este punto es establecer, para esta estrategia, la prueba de entrada analógica a
exhaustiva en el enfoque de caja negra. Hacer que cada declaración del programa se ejecute al
menos una vez puede parecer la respuesta, pero no es difícil demostrar que esto es muy
inadecuado. Sin profundizar en el punto, dado que este tema se analiza con más profundidad en el
Capítulo 4, el análogo se considera generalmente una prueba de trayectoria exhaustiva. Es decir, si
ejecuta, a través de casos de prueba, todas las posibles rutas de control que fluyen a través del
programa, entonces posiblemente el programa haya sido completamente probado.

Sin embargo, hay dos fallas en esta declaración. Una es que el número de rutas lógicas únicas a
través de un programa podría ser astronómicamente grande. Para ver esto, considere el programa
trivial representado en la Figura 2.1. El diagrama es un gráfico de flujo de control. Cada nodo o
círculo representa un segmento de declaraciones que se ejecutan secuencialmente, posiblemente
terminando con una declaración de ramificación. Cada borde o arco representa una transferencia
de control (rama) entre segmentos. El diagrama, entonces, muestra un programa de 10 a 20
sentencias que consiste en un ciclo DO que se repite hasta 20 veces. Dentro del cuerpo del bucle
DO hay un conjunto de declaraciones IF anidadas. Determinar el número de rutas lógicas únicas es
lo mismo que determinar el número total de formas únicas de moverse del punto a al punto b
(asumiendo que todas las decisiones en el programa son independientes entre sí). Este número es
aproximadamente 1014, o 100 billones. Se calcula a partir de 520 + 519 +. . . 51, donde 5 es el
número de rutas a través del cuerpo del bucle. Dado que la mayoría de las personas tienen
dificultades para visualizar tal número, considérelo de esta manera: si pudiera escribir, ejecutar y
verificar un caso de prueba cada cinco minutos, tomaría aproximadamente mil millones de años
probar todos los caminos. Si fuera 300 veces más rápido, completando una prueba una vez por
segundo, podría completar el trabajo en 3,2 millones de años, más o menos algunos años bisiestos
y siglos.
Por supuesto, en los programas reales, cada decisión no es independiente de cualquier otra
decisión, lo que significa que el número de posibles rutas de ejecución sería algo menor. Por otro
lado, los programas reales son mucho más grandes que el programa simple que se muestra en la
Figura 2.1. Por lo tanto, las pruebas de ruta exhaustivas, como las pruebas de entrada exhaustivas,
parecen poco prácticas, si no imposibles.

El segundo defecto en la afirmación “una prueba de ruta exhaustiva significa una prueba
completa” es que se pueden probar todas las rutas de un programa, pero el programa aún puede
estar cargado de errores. Hay tres explicaciones para esto.

La primera es que una prueba de ruta exhaustiva no garantiza de ninguna manera que un
programa coincida con su especificación. Por ejemplo, si se le pidió que escribiera una rutina de
clasificación en orden ascendente pero produjo por error una rutina de clasificación en orden
descendente, las pruebas de ruta exhaustivas serían de poco valor; el programa todavía tiene un
error: es el programa incorrecto, ya que no cumple con la especificación.

En segundo lugar, un programa puede ser incorrecto debido a que faltan rutas. Las pruebas de
ruta exhaustivas, por supuesto, no detectarían la ausencia de las rutas necesarias.

En tercer lugar, es posible que una prueba de ruta exhaustiva no descubra errores de sensibilidad
de los datos. Hay muchos ejemplos de tales errores, pero un simple ejemplo debería ser suficiente.
Suponga que en un programa tiene que comparar dos números para la convergencia, es decir,
para ver si la diferencia entre los dos números es menor que algún valor predeterminado. Por
ejemplo, puede escribir una declaración IF de Java como

si (a-b <c)

System.out.println ("a-b <c");

Por supuesto, la declaración contiene un error porque debería comparar c con el valor absoluto de
a-b. La detección de este error, sin embargo, depende de los valores usados para ayb y no
necesariamente se detectaría simplemente ejecutando cada ruta a través del programa.

En conclusión, aunque las pruebas de entrada exhaustivas son superiores a las pruebas de ruta
exhaustivas, ninguna de las dos resulta útil porque ambas son inviables. Quizás, entonces, hay
formas de combinar elementos de pruebas de caja negra y caja blanca para derivar una estrategia
de prueba razonable, pero no hermética. Este asunto se profundiza en el Capítulo 4.

Principios de prueba de software

Continuando con la premisa principal de este capítulo, que las consideraciones más importantes
en las pruebas de software son cuestiones de psicología, podemos identificar un conjunto de
principios o pautas de prueba vitales. La mayoría de estos principios pueden parecer obvios, pero
con demasiada frecuencia se pasan por alto. La Tabla 2.1 resume estos importantes principios, y
cada uno de ellos se analiza con más detalle en los párrafos siguientes.
Número de principio Principio
1 Una parte necesaria de un caso de prueba es
una definición de la salida o resultado
esperado.
2 Un programador debe evitar intentar probar
su propio programa.
3 Una organización de programación no debería
probar sus propios programas.
4 Inspeccione minuciosamente los resultados de
cada prueba.
5 Los casos de prueba deben escribirse para
condiciones de entrada que no son válidas e
inesperadas, así como para aquellas que son
válidas y esperadas.
6 Examinar un programa para ver si no hace lo
que se supone que debe hacer es solo la mitad
de la batalla; la otra mitad es ver si el
programa hace lo que se supone que no debe
hacer.
7 Evite los casos de prueba desechables a menos
que el programa sea realmente un programa
desechable.
8 No planifique un esfuerzo de prueba bajo la
suposición tácita de que no se encontrarán
errores.
9 La probabilidad de que existan más errores en
una sección de un programa es proporcional al
número de errores ya encontrados en esa
sección.
10 Las pruebas son una tarea extremadamente
creativa e intelectualmente desafiante.

Principio 1: Una parte necesaria de un caso de prueba es una definición de la salida o resultado
esperado.

Este principio obvio es uno de los errores más frecuentes en las pruebas de programas. Una vez
más, es algo que se basa en la psicología humana. Si el resultado esperado de un caso de prueba
no ha sido predefinido, es probable que un resultado plausible, pero erróneo, se interprete como
un resultado correcto debido al fenómeno de que “el ojo ve lo que quiere ver”. En otras palabras,
a pesar de la definición destructiva adecuada de prueba, todavía existe un deseo subconsciente de
ver el resultado correcto. Una forma de combatir esto es alentar un examen detallado de todos los
resultados, especificando con precisión, de antemano, el resultado esperado del programa. Por lo
tanto, un caso de prueba debe constar de dos componentes:

1. Una descripción de los datos de entrada al programa.


2. Una descripción precisa de la salida correcta del programa para ese conjunto de datos de
entrada.

Un problema puede caracterizarse como un hecho o grupo de hechos para los que no tenemos
una explicación aceptable, que parecen inusuales o que no se ajustan a nuestras expectativas o
preconceptos. Debería ser obvio que se requieren algunas creencias previas para que algo parezca
problemático. Si no hay expectativas, no puede haber sorpresas.

Principio 2: Un programador debe evitar intentar probar su propio programa.

Cualquier escritor sabe, o debería saber, que es una mala idea intentar editar o corregir su propio
trabajo. Sabes lo que se supone que dice la pieza y es posible que no reconozcas cuando dice lo
contrario. Y realmente no desea encontrar errores en su propio trabajo. Lo mismo se aplica a los
autores de software.

Otro problema surge con un cambio de enfoque en un proyecto de software. Después de que un
programador ha diseñado y codificado un programa de manera constructiva, es extremadamente
difícil cambiar repentinamente de perspectiva para mirar el programa con ojos destructivos.

Como muchos propietarios saben, quitar el papel tapiz (un proceso destructivo) no es fácil, pero es
casi insoportablemente deprimente si fueron sus manos las que colgaron el papel en primer lugar.
De manera similar, la mayoría de los programadores no pueden probar eficazmente sus propios
programas porque no pueden cambiar la marcha mental para intentar exponer los errores.
Además, un programador puede evitar inconscientemente encontrar errores por temor a
represalias de sus compañeros o de un supervisor, un cliente o el propietario del programa o
sistema que se está desarrollando.

Además de estos problemas psicológicos, existe un segundo problema importante: el programa


puede contener errores debido a que el programador no ha entendido bien el enunciado del
problema o la especificación. Si este es el caso, es probable que el programador lleve el mismo
malentendido a las pruebas de su propio programa.

Esto no significa que sea imposible para un programador probar su propio programa. Más bien,
implica que las pruebas son más efectivas y exitosas si lo hace otra persona.

Tenga en cuenta que este argumento no se aplica a la depuración (corrección de errores


conocidos); la depuración la realiza el programador original de forma más eficiente.

Principio 3: Una organización de programación no debe probar sus propios programas.

El argumento aquí es similar al argumento anterior. Un proyecto o una organización de


programación es, en muchos sentidos, una organización viva con problemas psicológicos similares
a los de los programadores individuales. Además, en la mayoría de los entornos, una organización
de programación o un director de proyecto se mide en gran medida en función de la capacidad de
producir un programa en una fecha determinada y por un coste determinado. Una razón de esto
es que es fácil medir los objetivos de tiempo y costo, pero es extremadamente difícil cuantificar la
confiabilidad de un programa. Por lo tanto, es difícil para una organización de programación ser
objetiva al probar sus propios programas, porque el proceso de prueba, si se aborda con la
definición adecuada, puede verse como una disminución de la probabilidad de cumplir con el
cronograma y los objetivos de costos.

Nuevamente, esto no significa que sea imposible para una organización de programación
encontrar algunos de sus errores, porque las organizaciones logran esto con cierto grado de éxito.
Más bien, implica que es más económico que las pruebas las realice una parte objetiva e
independiente.

Principio 4: Inspeccione minuciosamente los resultados de cada prueba.

Este es probablemente el principio más obvio, pero nuevamente es algo que a menudo se pasa
por alto. Hemos visto numerosos experimentos que muestran que muchos sujetos no detectaron
ciertos errores, incluso cuando los síntomas de esos errores eran claramente observables en las
listas de salida. Dicho de otra manera, los errores que se encuentran en pruebas posteriores a
menudo se pasan por alto en los resultados de pruebas anteriores.

Principio 5: Los casos de prueba deben escribirse para condiciones de entrada que no son válidas e
inesperadas, así como para aquellas que son válidas y esperadas.

Cuando se prueba un programa, existe una tendencia natural a concentrarse en las condiciones de
entrada válidas y esperadas, sin tener en cuenta las condiciones no válidas e inesperadas. Por
ejemplo, esta tendencia aparece con frecuencia en la prueba del programa de triángulos en el
Capítulo 1.

Pocas personas, por ejemplo, alimentan el programa con los números 1, 2, 5 para asegurarse de
que el programa no lo interprete erróneamente como un triángulo escaleno en lugar de un
triángulo inválido. Además, muchos errores que se descubren repentinamente en los programas
de producción aparecen cuando el programa se usa de una manera nueva o inesperada. Por lo
tanto, los casos de prueba que representan condiciones de entrada no válidas e inesperadas
parecen tener un rendimiento de detección de errores más alto que los casos de prueba para
condiciones de entrada válidas.

Principio 6: Examinar un programa para ver si no hace lo que se supone que debe hacer es solo la
mitad de la batalla; la otra mitad es ver si el programa hace lo que se supone que no debe hacer.

Este es un corolario del principio anterior. Los programas deben examinarse para detectar efectos
secundarios no deseados. Por ejemplo, un programa de nómina que produce los cheques de pago
correctos sigue siendo un programa erróneo si también produce cheques adicionales para
empleados inexistentes o si sobrescribe el primer registro del archivo de personal.

Este problema se ve con mayor frecuencia con sistemas interactivos para probar programas. Una
práctica común es sentarse en una terminal e inventar casos de prueba sobre la marcha, y luego
enviar estos casos de prueba a través del programa. El principal problema es que los casos de
prueba representan una inversión valiosa que, en este entorno, desaparece una vez finalizada la
prueba. Siempre que el programa deba probarse nuevamente (por ejemplo, después de corregir
un error o realizar una mejora), los casos de prueba deben reinventarse. La mayoría de las veces,
dado que esta reinvención requiere una cantidad considerable de trabajo, la gente tiende a
evitarla. Por lo tanto, la repetición de la prueba del programa rara vez es tan rigurosa como la
prueba original, lo que significa que si la modificación hace que falle una parte previamente
funcional del programa, este error a menudo pasa desapercibido. Guardar casos de prueba y
ejecutarlos nuevamente después de cambios en otros componentes del programa se conoce como
prueba de regresión.

Principio 8: No planifique un esfuerzo de prueba bajo la suposición tácita de que no se


encontrarán errores.

Este es un error que los gerentes de proyecto suelen cometer y es una señal del uso de una
definición incorrecta de prueba, es decir, la suposición de que la prueba es el proceso de
demostrar que el programa funciona correctamente. Una vez más, la definición de prueba es el
proceso de ejecutar un programa con la intención de encontrar errores.

Principio 9: La probabilidad de que existan más errores en una sección de un programa es


proporcional al número de errores ya encontrados en esa sección.

Este fenómeno se ilustra en la Figura 2.2. A primera vista tiene poco sentido, pero es un fenómeno
presente en muchos programas. Por ejemplo, si un programa consta de dos módulos, clases o
subrutinas A y B, y se han encontrado cinco errores en el módulo A y solo se ha encontrado un
error en el módulo B, y si el módulo A no se ha sometido intencionalmente a una prueba más
rigurosa, entonces este principio nos dice que la probabilidad de más errores en el módulo A es
mayor que la probabilidad de más errores en el módulo B.

Otra forma de enunciar este principio es decir que los errores suelen venir en grupos y que, en el
programa típico, algunas secciones parecen ser mucho más propensas a errores que otras
secciones, aunque nadie ha dado una buena explicación de por qué ocurre esto. El fenómeno es
útil porque nos da información o retroalimentación en el proceso de prueba. Si una sección
particular de un programa

parece ser mucho más propenso a errores que otras secciones, entonces este fenómeno nos dice
que, en términos de rendimiento en nuestra inversión en pruebas, los esfuerzos de prueba
adicionales se concentran mejor en esta sección propensa a errores.

Principio 10: Las pruebas son una tarea extremadamente creativa e intelectualmente desafiante.

Probablemente sea cierto que la creatividad requerida para probar un programa grande excede la
creatividad requerida para diseñar ese programa. Ya hemos visto que es imposible probar un
programa lo suficiente como para garantizar la ausencia de todos los errores. Las metodologías
que se analizan más adelante en este libro le permiten desarrollar un conjunto razonable de casos
de prueba para un programa, pero estas metodologías aún requieren una cantidad significativa de
creatividad.

Resumen

A medida que avance en este libro, tenga en cuenta estos tres principios importantes de las
pruebas:

• La prueba es el proceso de ejecutar un programa con la intención de encontrar errores.

• Un buen caso de prueba es aquel que tiene una alta probabilidad de detectar un error aún no
descubierto.

• Un caso de prueba exitoso es aquel que detecta un error aún no descubierto.

Capítulo 3: Inspecciones, tutoriales y revisiones del programa


Descripción general

Durante muchos años, la mayoría de nosotros en la comunidad de programación trabajamos bajo


la suposición de que los programas están escritos únicamente para la ejecución de la máquina y no
están pensados para que la gente los lea, y que la única forma de probar un programa es
ejecutarlo en una máquina. Esta actitud comenzó a cambiar a principios de la década de 1970
gracias a los esfuerzos de los desarrolladores de programas que vieron por primera vez el valor de
leer el código como parte de un régimen integral de pruebas y depuración.

Hoy en día, no todos los probadores de aplicaciones de software leen código, pero el concepto de
estudiar el código del programa como parte de un esfuerzo de prueba ciertamente es
ampliamente aceptado. Varios factores pueden afectar la probabilidad de que un esfuerzo dado
de prueba y depuración incluya personas que realmente lean el código del programa: el tamaño o
la complejidad de la aplicación, el tamaño del equipo de desarrollo, el cronograma para el
desarrollo de la aplicación (si el programa es relajado o intenso, por ejemplo) y, por supuesto, los
antecedentes y la cultura del equipo de programación.
Por estas razones, discutiremos el proceso de pruebas no basadas en computadora ("pruebas en
humanos"), antes de profundizar en las técnicas de prueba más tradicionales basadas en
computadora. Las técnicas de prueba humana son bastante efectivas para encontrar errores, tanto
que cada proyecto de programación debería utilizar una o más de estas técnicas. Debe aplicar
estos métodos entre el momento en que se codifica el programa y el momento en que comienza la
prueba por computadora. También puede desarrollar y aplicar métodos análogos en las primeras
etapas del proceso de programación (como al final de cada etapa de diseño), pero estos están
fuera del alcance de este libro.

Pero antes de comenzar la discusión de las técnicas de prueba en humanos, aquí hay una nota
importante: debido a que la participación de humanos da como resultado métodos menos
formales que las pruebas matemáticas realizadas por una computadora, es posible que se sienta
escéptico de que algo tan simple e informal pueda ser útil. Todo lo contrario es cierto. Estas
técnicas informales no obstaculizan el éxito de las pruebas; más bien, contribuyen sustancialmente
a la productividad y la fiabilidad de dos formas principales.

Primero, generalmente se reconoce que cuanto más temprano se encuentran los errores, menores
son los costos de corregir los errores y mayor es la probabilidad de corregir los errores
correctamente. En segundo lugar, los programadores parecen experimentar un cambio psicológico
cuando comienzan las pruebas por computadora. Las presiones inducidas internamente parecen
aumentar rápidamente y hay una tendencia a querer "arreglar este maldito error lo antes
posible". Debido a estas presiones, los programadores tienden a cometer más errores cuando
corrigen un error encontrado durante las pruebas basadas en computadora que cuando corrigen
un error encontrado anteriormente.

Inspecciones y recorridos

Los dos métodos principales de prueba en humanos son las inspecciones de código y los
recorridos. Dado que los dos métodos tienen mucho en común, discutiremos sus similitudes aquí.
Sus diferencias se analizan en secciones posteriores.

Las inspecciones y los recorridos involucran a un equipo de personas que leen o inspeccionan
visualmente un programa. Con cualquiera de los métodos, los participantes deben realizar algún
trabajo preparatorio. El clímax es un“Encuentro de las mentes”, en una conferencia de
participantes. El objetivo del encuentro es encontrar errores pero no encontrar soluciones a los
errores. Es decir, probar, no depurar.

Las inspecciones de código y los recorridos se han utilizado ampliamente durante algún tiempo. En
nuestra opinión, la razón de su éxito está relacionada con algunos de los principios del Capítulo 2.

En un recorrido, un grupo de desarrolladores, con tres o cuatro como número óptimo, realiza la
revisión. Solo uno de los participantes es el autor del programa. Por lo tanto, la mayoría de las
pruebas de programas las llevan a cabo personas distintas del autor, lo que sigue el principio de
prueba que establece que un individuo suele ser ineficaz al probar su propio programa.

Una inspección o un recorrido es una mejora con respecto al antiguo proceso de verificación de
escritorio (el proceso en el que un programador lee su propio programa antes de probarlo). Las
inspecciones y los recorridos son más efectivos, nuevamente porque otras personas además del
autor del programa están involucradas en el proceso.

Otra ventaja de los tutoriales, que resulta en menores costos de depuración (corrección de
errores), es el hecho de que cuando se encuentra un error, generalmente se ubica con precisión
en el código. Además, este proceso expone con frecuencia un lote de errores, lo que permite que
los errores se corrijan posteriormente en masa. Las pruebas basadas en computadora, por otro
lado, normalmente exponen solo un síntoma del error (el programa no termina o el programa
imprime un resultado sin sentido), y los errores generalmente se detectan y corrigen uno por uno.

Estos métodos generalmente son efectivos para encontrar del 30 al 70 por ciento de los errores de
codificación y diseño lógico en programas típicos. Sin embargo, no son eficaces para detectar
errores de diseño de alto nivel, como errores cometidos en el proceso de análisis de requisitos.
Tenga en cuenta que una tasa de éxito del 30 al 70 por ciento no significa que se puedan encontrar
hasta el 70 por ciento de todos los errores. Recuerde que el Capítulo 2 nos dice que nunca
podremos saber el número total de errores en un programa. Más bien, esto significa que estos
métodos son efectivos para encontrar hasta el 70 por ciento de todos los errores encontrados al
final del proceso de prueba.

Por supuesto, una posible crítica de estas estadísticas es que los procesos humanos solo
encuentran los errores "fáciles" (aquellos que serían triviales de encontrar con las pruebas basadas
en computadora) y que los errores difíciles, oscuros o engañosos solo pueden ser encontrados por
pruebas basadas en computadora. Sin embargo, algunos evaluadores que utilizan estas técnicas
han descubierto que los procesos humanos tienden a ser más efectivos que los procesos de
prueba basados en computadora para encontrar ciertos tipos de errores, mientras que lo contrario
es cierto para otros tipos de errores. La implicación es que las inspecciones / recorridos y las
pruebas por computadora son complementarias; La eficiencia de detección de errores se verá
afectada si uno u otro no está presente.

Finalmente, aunque estos procesos son invaluables para probar nuevos programas, tienen el
mismo valor, o incluso más, para probar modificaciones de programas. En nuestra experiencia,
modificar un programa existente es un proceso que es más propenso a errores (en términos de
errores por declaración escrita) que escribir un programa nuevo. Por lo tanto, las modificaciones
del programa también deben someterse a estos procesos de prueba, así como a las técnicas de
prueba de regresión.

Inspecciones de código

Una inspección de código es un conjunto de procedimientos y técnicas de detección de errores


para la lectura de códigos grupales. La mayoría de las discusiones sobre las inspecciones de
códigos se centran en los procedimientos, los formularios que se deben completar, etc. aquí,
después de un breve resumen del procedimiento general, nos centraremos en las técnicas reales
de detección de errores.

Un equipo de inspección suele estar formado por cuatro personas. Una de las cuatro personas
desempeña el papel de moderador. Se espera que el moderador sea un programador competente,
pero no es el autor del programa y no necesita estar familiarizado con los detalles del programa.
Los deberes del moderador incluyen

• Distribuir materiales y programar la sesión de inspección.

• Liderar la sesión

• Registro de todos los errores encontrados

• Asegurarse de que los errores se corrijan posteriormente

El moderador es como un ingeniero de control de calidad. El segundo miembro del equipo es el


programador. Los miembros restantes del equipo suelen ser el diseñador del programa (si es
diferente del programador) y un especialista en pruebas.

El moderador distribuye la lista del programa y las especificaciones del diseño a los demás
participantes varios días antes de la sesión de inspección. Se espera que los participantes se
familiaricen con el material antes de la sesión. Durante la sesión, ocurren dos actividades:

1. El programador narra, enunciado por enunciado, la lógica del programa. Durante el discurso,
otros participantes deben plantear preguntas, y se las debe investigar para determinar si existen
errores. Es probable que el programador, en lugar de los otros miembros del equipo, encuentre
muchos de los errores encontrados durante esta narración. En otras palabras, el simple acto de
leer en voz alta un programa a una audiencia parece ser una técnica de detección de errores
notablemente efectiva.

2. El programa se analiza con respecto a una lista de verificación de errores de programación


históricamente comunes (dicha lista de verificación se analiza en la siguiente sección).

El moderador es responsable de asegurar que las discusiones avancen en líneas productivas y que
los participantes centren su atención en encontrar errores, no en corregirlos. (El programador
corrige los errores después de la sesión de inspección).

Después de la sesión, el programador recibe una lista de los errores encontrados. Si se


encontraron más de unos pocos errores, o si alguno de los errores requiere una corrección
sustancial, el moderador puede hacer arreglos para volver a inspeccionar el programa después de
que se corrijan los errores. Esta lista de errores también se analiza, categoriza y usa para refinar la
lista de verificación de errores para mejorar la efectividad de las inspecciones futuras.

Como se indicó, este proceso de inspección generalmente se concentra en descubrir errores, no


en corregirlos. Sin embargo, algunos equipos pueden encontrar que cuando se descubre un
problema menor, dos o tres personas, incluido el programador responsable del código, proponen
parches obvios en el diseño para manejar este caso especial. La discusión de este problema menor
puede, a su vez, enfocar la atención del grupo en esa área particular del diseño. Durante la
discusión sobre la mejor manera de parchear el diseño para manejar este problema menor,
alguien puede notar un segundo problema. Ahora eso el grupo ha visto dos problemas
relacionados con el mismo aspecto del diseño, los comentarios probablemente serán densos y
rápidos, con interrupciones cada pocas oraciones. En unos minutos, toda esta área del diseño
podría explorarse a fondo y cualquier problema sería obvio.
La hora y el lugar de la inspección deben planificarse para evitar todas las interrupciones externas.
La cantidad de tiempo óptima para la sesión de inspección parece ser de 90 a 120 minutos. Dado
que la sesión es una experiencia mentalmente agotadora, las sesiones más largas tienden a ser
menos productivas. La mayoría de las inspecciones se realizan a una velocidad de
aproximadamente 150 declaraciones de programa por hora. Por esa razón, los programas grandes
deben examinarse en múltiples inspecciones, cada inspección se ocupa de uno o varios módulos o
subrutinas.

Tenga en cuenta que para que el proceso de inspección sea eficaz, se debe establecer la actitud
adecuada. Si el programador ve la inspección como un ataque a su carácter y adopta una postura
defensiva, el proceso será ineficaz. Más bien, el programador debe abordar el proceso con una
actitud sin ego y debe colocar el proceso en una luz positiva y constructiva: el objetivo de la
inspección es encontrar errores en el programa, mejorando así la calidad del trabajo. Por esta
razón, la mayoría de la gente recomienda que los resultados de una inspección sean un asunto
confidencial, compartido solo entre los participantes. En particular, si los gerentes de alguna
manera hacen uso de los resultados de la inspección, el propósito del proceso puede ser frustrado.

El proceso de inspección también tiene varios efectos secundarios beneficiosos además de su


efecto principal de encontrar errores. Por un lado, el programador suele recibir comentarios sobre
el estilo de programación, la elección de algoritmos y las técnicas de programación. Los otros
participantes se benefician de manera similar al estar expuestos a los errores y el estilo de
programación de otro programador. Finalmente, el proceso de inspección es una forma de
identificar temprano las secciones del programa más propensas a errores, lo que ayuda a centrar
más la atención en estas secciones durante los procesos de prueba basados en computadora (uno
de los principios de prueba del Capítulo 2).

Una lista de verificación de errores para inspecciones

Una parte importante del proceso de inspección es el uso de una lista de verificación para
examinar el programa en busca de errores comunes. Desafortunadamente, algunas listas de
verificación se concentran más en cuestiones de estilo que en errores (por ejemplo, "¿Son los
comentarios precisos y significativos?" Y "¿Están alineados los if-else, los bloques de código y los
grupos do-while?"), Y las verificaciones de errores son demasiado nebuloso para ser útil (como
"¿El código cumple con los requisitos de diseño?"). La lista de verificación de esta sección se
compiló después de muchos años de estudio de errores de software. La lista de verificación es en
gran medida independiente del lenguaje, lo que significa que la mayoría de los errores pueden
ocurrir con cualquier lenguaje de programación. Es posible que desee complementar esta lista con
errores propios de su lenguaje de programación y con errores detectados después de utilizar el
proceso de inspección.

Errores de referencia de datos

1. ¿Tiene una variable referenciada un valor que no está establecido o inicializado? Este
probablemente sea el error de programación más frecuente; ocurre en una amplia variedad de
circunstancias. Para cada referencia a un elemento de datos (variable, elemento de matriz, campo
en una estructura), intente "probar" informalmente que el elemento tiene un valor en ese punto.
2. Para todas las referencias de matriz, ¿está cada valor de subíndice dentro de los límites
definidos de la dimensión correspondiente?

3. Para todas las referencias de matriz, ¿cada subíndice tiene un valor entero? Esto no es
necesariamente un error en todos los idiomas, pero es una práctica peligrosa.

4. Para todas las referencias a través de puntero o variables de referencia, ¿está actualmente
asignada la memoria referenciada? Esto se conoce como el problema de la "referencia colgante".
Ocurre en situaciones en las que la vida útil de un puntero es mayor que la vida útil de la memoria
referenciada. Se produce una situación en la que un puntero hace referencia a una variable local
dentro de un procedimiento, el valor del puntero se asigna a un parámetro de salida o una
variable global, el procedimiento regresa (liberando la ubicación referenciada) y luego el programa
intenta usar el valor del puntero. De manera similar a verificar los errores anteriores, intente
probar informalmente que, en cada referencia que usa una variable de puntero, existe la memoria
de referencia.

5. Cuando un área de memoria tiene nombres de alias con atributos diferentes, ¿el valor de los
datos en esta área tiene los atributos correctos cuando se hace referencia a través de uno de estos
nombres? Las situaciones a buscar son el uso de la instrucción EQUIVALENCE en FORTRAN y la
cláusula REDEFINES en COBOL. Como ejemplo, un programa FORTRAN contiene una variable real A
y una variable entera B; ambos se convierten en alias para la misma área de memoria mediante el
uso de una instrucción EQUIVALENCE. Si el programa almacena un valor en A y luego hace
referencia a la variable B, es probable que haya un error, ya que la máquina usaría la
representación de bits de punto flotante en el área de memoria como un número entero.

6. ¿Tiene el valor de una variable un tipo o atributo diferente al que espera el compilador? Esta
situación puede ocurrir cuando un programa C, C ++ o COBOL lee un registro en la memoria y hace
referencia a él usando una estructura, pero la representación física del registro difiere de la
definición de la estructura.

7. ¿Existe algún problema de direccionamiento explícito o implícito si, en la máquina que se está
utilizando, las unidades de asignación de memoria son más pequeñas que las unidades de
direccionabilidad de memoria? Por ejemplo, en algunos entornos, las cadenas de bits de longitud
fija no comienzan necesariamente en los límites de bytes, sino que las direcciones solo apuntan a
los límites de bytes. Si un programa calcula la dirección de una cadena de bits y luego se refiere a
la cadena a través de esta dirección, es posible que se haga referencia a la ubicación de memoria
incorrecta. Esta situación también podría ocurrir al pasar un argumento de cadena de bits a una
subrutina.

8. Si se utilizan punteros o variables de referencia, ¿la ubicación de la memoria referenciada tiene


los atributos que espera el compilador? Un ejemplo de tal error es cuando a un puntero de C ++ en
el que se basa una estructura de datos se le asigna la dirección de una estructura de datos
diferente.

9. Si se hace referencia a una estructura de datos en múltiples procedimientos o subrutinas, ¿la


estructura se define de manera idéntica en cada procedimiento?
10. Al indexar en una cadena, ¿los límites de la cadena están desviados en uno en las operaciones
de indexación o en las referencias de subíndices a matrices?

11. Para los lenguajes orientados a objetos, ¿se cumplen todos los requisitos de herencia en la
clase de implementación?

Errores de declaración de datos

1. ¿Se han declarado explícitamente todas las variables? No hacerlo no es necesariamente un


error, pero es una fuente común de problemas. Por ejemplo, si una subrutina de programa recibe
un parámetro de matriz y no puede definir el parámetro como una matriz (como en una
instrucción DIMENSION, por ejemplo), una referencia a la matriz (como C = A (I)) se interpreta
como una llamada de función, lo que lleva a que la máquina intente ejecutar la matriz como un
programa. Además, si una variable no se declara explícitamente en un procedimiento o bloque
interno, ¿se entiende que la variable se comparte con el bloque adjunto?

2. Si todos los atributos de una variable no se indican explícitamente en la declaración, ¿se


comprenden bien los valores predeterminados? Por ejemplo, los atributos predeterminados
recibidos en Java a menudo son una fuente de sorpresa.

3. Cuando una variable se inicializa en una declaración declarativa, ¿se inicializa correctamente?
En muchos lenguajes, la inicialización de matrices y cadenas es algo complicada y, por tanto,
propensa a errores.

4. ¿Se asigna a cada variable la longitud y el tipo de datos correctos?

5. ¿La inicialización de una variable es coherente con su tipo de memoria? Por ejemplo, si una
variable en una subrutina FORTRAN necesita reinicializarse cada vez que se llama a la subrutina,
debe inicializarse con una instrucción de asignación en lugar de una instrucción DATA.

6. ¿Hay variables con nombres similares (VOLT y VOLTS, por ejemplo)? Esto no es necesariamente
un error, pero debe verse como una advertencia de que los nombres pueden haberse confundido
en algún lugar del programa.

Errores de cálculo

1. ¿Existen cálculos que utilicen variables que tengan tipos de datos inconsistentes (como no
aritméticos)?

2. ¿Hay cálculos de modo mixto? Un ejemplo es la adición de una variable de punto flotante a una
variable entera. Estos sucesos no son necesariamente errores, pero deben explorarse
detenidamente para garantizar que se comprendan las reglas de conversión del idioma. Considere
el siguiente fragmento de código de Java que muestra el error de redondeo que puede ocurrir
cuando se trabaja con números enteros:

3. int x = 1;

4. int y = 2;

5. int z = 0;
6. z = x / y;

7. System.out.println

8.

9. ("z =" + z);

SALIDA: z = 0

10. ¿Hay cálculos que utilicen variables que tengan el mismo tipo de datos pero diferentes
longitudes?

11. ¿Es el tipo de datos de la variable objetivo de una asignación más pequeño que el tipo de datos
o el resultado de la expresión de la derecha?

12. ¿Es posible una expresión de desbordamiento o subdesbordamiento durante el cálculo de una
expresión? Es decir, el resultado final puede parecer tener un valor válido, pero un resultado
intermedio puede ser demasiado grande o demasiado pequeño para los tipos de datos del
lenguaje de programación.

13. ¿Es posible que el divisor en una operación de división sea cero?

14. Si la máquina subyacente representa variables en forma de base 2, ¿hay alguna secuencia de la
inexactitud resultante? Es decir, 10 × 0,1 rara vez es igual a 1,0 en una máquina binaria.

15. Cuando corresponda, ¿puede el valor de una variable salir del rango significativo? Por ejemplo,
las declaraciones que asignan un valor a la variable PROBABILIDAD se pueden verificar para
garantizar que el valor asignado siempre sea positivo y no mayor que

16. Para las expresiones que contienen más de un operador, ¿son correctas las suposiciones sobre
el orden de evaluación y la precedencia de los operadores?

17. ¿Existen usos inválidos de la aritmética de números enteros, en particular las divisiones? Por
ejemplo, si i es una variable entera, si la expresión 2 * i / 2 == i depende de si i tiene un valor par o
impar y si la multiplicación o división se realiza primero.

Errores de comparación

1. ¿Existen comparaciones entre variables que tienen diferentes tipos de datos, como comparar
una cadena de caracteres con una dirección, fecha o número?

2. ¿Existen comparaciones de modo mixto o comparaciones entre variables de diferentes


longitudes? Si es así, asegúrese de que se comprendan bien las reglas de conversión.

3. ¿Son correctos los operadores de comparación? Los programadores frecuentemente confunden


tales relaciones como a lo sumo, al menos, mayor que, no menor que, menor que o igual.

4. ¿Cada expresión booleana indica lo que se supone que indica? Los programadores a menudo
cometen errores al escribir expresiones lógicas que involucran y, o y no.
5. ¿Los operandos de un operador booleano son booleanos? ¿Se han mezclado erróneamente los
operadores de comparación y booleanos? Esto representa otra clase frecuente de errores. Aquí se
ilustran algunos ejemplos de algunos errores típicos. Si desea determinar si i está entre 2 y 10, la
expresión 2 <i <10 es incorrecta; en su lugar, debería ser (2 <i) && (i <10). Si desea determinar si i
es mayor que xoy, i> x || y es incorrecto; en su lugar, debería ser (i> x) || (i> y). Si desea comparar
tres números por igualdad, si (a == b == c) hace algo bastante diferente. Si desea probar la relación
matemática x> y> z, la expresión correcta es (x> y) && (y> z).

6. ¿Existen comparaciones entre números fraccionarios o de punto flotante que están


representados en base 2 por la máquina subyacente? Esta es una fuente ocasional de errores
debido al truncamiento y las aproximaciones en base 2 de los números en base 10.

7. Para expresiones que contienen más de un operador booleano, ¿son correctas las suposiciones
sobre el orden de evaluación y la precedencia de los operadores? Es decir, si ve una expresión
como (if ((a == 2) && (b == 2) || (c == 3)), ¿se comprende bien si la y o la o se realiza primero?

8. ¿La forma en que el compilador evalúa las expresiones booleanas afecta al programa? Por
ejemplo, la declaración

si ((x == 0 && (x / y)> z)

puede ser aceptable para los compiladores que finalizan la prueba tan pronto como un lado de un
y sea falso, pero puede causar un error de división por cero con otros compiladores.

Errores de control de flujo

1. Si el programa contiene una rama de múltiples vías, como un GO TO calculado, ¿puede la


variable de índice exceder el número de posibilidades de rama? Por ejemplo, en la declaración

IR A (200, 300, 400), i

¿Siempre tendré el valor de 1, 2 o 3?

2. ¿Terminará eventualmente cada bucle? Diseñe una prueba o un argumento informal que
demuestre que cada ciclo terminará.

3. ¿Terminará eventualmente el programa, módulo o subrutina?

¿Es posible que, debido a las condiciones de entrada, nunca se ejecute un bucle? Si es así,
¿representa esto un descuido? Por ejemplo, si tuviera los siguientes bucles encabezados por las
siguientes declaraciones:

para (i == x; i <= z; i ++) {

...

while (NO ENCONTRADO) {

...
}

¿Qué sucede si NOTFOUND es inicialmente falso o si x es mayor que z?

4. Para un bucle controlado tanto por iteración como por una condición booleana (un bucle de
búsqueda, por ejemplo), ¿cuáles son las consecuencias de la caída del bucle? Por ejemplo, para el
bucle de código pseudo encabezado por

HAGO I = 1 a TABLESIZE MIENTRAS (NO SE ENCUENTRA)

¿Qué sucede si NOTFOUND nunca se vuelve falso?

5. ¿Hay errores uno por uno, como una iteración de más o de menos? Este es un error común en
los bucles de base cero. A menudo se olvidará de contar "0" como número. Por ejemplo, si desea
crear código Java para un bucle que cuenta hasta 10, lo siguiente sería incorrecto, ya que cuenta
hasta 11:

6. para (int i = 0; i <= 10; i ++) {

7. System.out.println (i);

Correcto, el bucle se repite 10 veces:

para (int i = 0; i <= 9; i ++) {

System.out.println (i);

8. Si el lenguaje contiene un concepto de grupos de enunciados o bloques de código (por ejemplo,


do-while o {...}), ¿hay un while explícito para cada grupo y los do's corresponden a sus grupos
apropiados? ¿O hay un corchete de cierre para cada corchete abierto? La mayoría de los
compiladores modernos se quejarán de estos desajustes.

9. ¿Existen decisiones no exhaustivas? Por ejemplo, si los valores esperados de un parámetro de


entrada son 1, 2 o 3, ¿la lógica asume que debe ser 3 si no es 1 o 2? Si es así, ¿es válida la
suposición?

Errores de interfaz

1. ¿El número de parámetros recibidos por este módulo es igual al número de argumentos
enviados por cada uno de los módulos que llaman? Además, ¿el orden es correcto?

2. ¿Los atributos (por ejemplo, tipo de datos y tamaño) de cada parámetro coinciden con los
atributos de cada argumento correspondiente?

3. ¿El sistema de unidades de cada parámetro coincide con el sistema de unidades de cada
argumento correspondiente? Por ejemplo, ¿el parámetro se expresa en grados pero el argumento
se expresa en radianes?

4. ¿El número de argumentos transmitidos por este módulo a otro módulo es igual al número de
parámetros esperados por ese módulo?
5. ¿Los atributos de cada argumento transmitido a otro módulo coinciden con los atributos del
parámetro correspondiente en ese módulo?

6. ¿El sistema de unidades de cada argumento transmitido a otro módulo coincide con el sistema
de unidades del parámetro correspondiente en ese módulo?

7. Si se invocan funciones integradas, ¿son correctos el número, los atributos y el orden de los
argumentos?

8. Si un módulo o clase tiene varios puntos de entrada, ¿alguna vez se hace referencia a un
parámetro que no está asociado con el punto de entrada actual? Tal error existe en la segunda
instrucción de asignación en el siguiente programa PL / 1:

9. A: PROCEDIMIENTO

10. (W, X);

11. W = X + 1;

12. REGRESO

13. B: ENTRADA

14. (Y, Z);

15. Y = X + Z;

FINAL;

16. ¿Altera una subrutina un parámetro que está destinado a ser solo un valor de entrada?

17. Si existen variables globales, ¿tienen la misma definición y atributos en todos los módulos que
hacen referencia a ellas?

18. ¿Se pasan las constantes como argumentos? En algunas implementaciones de FORTRAN, una
declaración como

LLAMAR SUBX (J, 3)

es peligroso, ya que si la subrutina SUBX asigna un valor a su segundo parámetro, el valor de la


constante 3 se verá alterado.

Errores de entrada / salida

1. Si los archivos se declaran explícitamente, ¿son correctos sus atributos?

2. ¿Son correctos los atributos de la declaración OPEN del archivo?

3. ¿Coincide la especificación de formato con la información de la declaración de E / S? Por


ejemplo, en FORTRAN, ¿concuerda cada declaración FORMAT (en términos del número y atributos
de los elementos) con la declaración READ o WRITE correspondiente?

4. ¿Hay suficiente memoria disponible para guardar el archivo que leerá su programa?
5. ¿Se han abierto todos los archivos antes de su uso?

6. ¿Se han cerrado todos los archivos después de su uso?

7. ¿Se detectan y manejan correctamente las condiciones de fin de archivo?

8. ¿Se manejan correctamente las condiciones de error de E / S?

9. ¿Hay errores ortográficos o gramaticales en algún texto que imprime o muestra el programa?

Otros cheques

1. Si el compilador produce una lista de identificadores de referencia cruzada, examínela en busca


de variables a las que nunca se hace referencia o que solo se hace referencia una vez.

2. Si el compilador produce una lista de atributos, verifique los atributos de cada variable para
asegurarse de que no se hayan asignado atributos predeterminados inesperados.

3. Si el programa se compiló correctamente, pero la computadora produjo uno o más mensajes de


"advertencia" o "informativos", verifique cada uno con cuidado. Los mensajes de advertencia son
indicaciones de que el compilador sospecha que está haciendo algo dudosa validez; todas estas
sospechas deben revisarse. Los mensajes informativos pueden enumerar variables no declaradas o
usos del lenguaje que impiden la optimización del código.

4. ¿Es el programa o módulo lo suficientemente sólido? Es decir, ¿comprueba la validez de su


entrada?

5. ¿Falta alguna función en el programa?

Tabla 3.1: Resumen de la lista de verificación de errores de inspección, Parte I

Referencia de datos Cálculo


1. ¿Se ha utilizado una variable no definida? 1. ¿Cálculos sobre variables no aritméticas?
2. ¿Subíndices dentro de los límites? 2. ¿Cálculos de modo mixto?
3. ¿Subíndices no enteros? 3. ¿Cálculos sobre variables de diferentes
longitudes?
4. ¿Referencias colgantes? 4. ¿El tamaño del objetivo es menor que el
tamaño del valor asignado?
5. ¿Corregir atributos al crear un alias? 5. ¿Desbordamiento o subdesbordamiento del
resultado intermedio?
6. ¿Coinciden los atributos de registro y 6. ¿División por cero?
estructura?
7. ¿Calcula direcciones de cadenas de bits? 7. ¿Inexactitudes de la base 2?
¿Pasando argumentos de cadena de bits?
8. ¿Son correctos los atributos de 8. ¿El valor de la variable está fuera del rango
almacenamiento basados? significativo?
9. ¿Las definiciones de estructura coinciden 9. ¿Se entiende la precedencia del operador?
con todos los procedimientos?
10. ¿Errores uno a uno en las operaciones de 10. ¿Las divisiones enteras son correctas?
indexación o subíndice?
11. ¿Se cumplen los requisitos de herencia?

Declaración de datos Comparación


1. ¿Todas las variables declaradas? 1. ¿Comparaciones entre variables
inconsistentes?
2. ¿Se entienden los atributos 2. ¿Comparaciones de modo mixto?
predeterminados?
3. ¿Las matrices y cadenas se inicializaron 3. ¿Las relaciones de comparación son
correctamente? correctas?
4. ¿Se asignaron las longitudes, los tipos y las 4. ¿Las expresiones booleanas son correctas?
clases de almacenamiento correctas?
5. ¿Inicialización consistente con la clase de 5. ¿Comparación y expresiones booleanas
almacenamiento? mezcladas?
6. ¿Alguna variable con nombres similares? 6. ¿Comparaciones de valores fraccionarios en
base 2?
7. ¿Se entiende la precedencia del operador?
8. ¿Se entiende la evaluación del compilador
de expresiones booleanas?

Tabla 3.2: Resumen de la lista de verificación de errores de inspección, Parte II

Flujo de control De entrada y salida


1. ¿Se excedieron las ramas de múltiples vías? 1. ¿Son correctos los atributos del archivo?
2. ¿Terminará cada bucle? 2. ¿Las declaraciones ABIERTAS son correctas?
3. ¿Terminará el programa? 3. ¿La especificación de formato coincide con
4. ¿Alguna derivación de bucle debido a las la declaración de E / S?
condiciones de entrada? 4. ¿El tamaño del búfer coincide con el tamaño
5. ¿Son correctos los posibles fallos de bucle? del registro?
6. ¿Errores de iteración uno por uno? 5. ¿Archivos abiertos antes de su uso?
7. ¿Coinciden las declaraciones DO / END? 6. ¿Archivos cerrados después de su uso?
8. ¿Alguna decisión no exhaustiva? 7. ¿Se manejan las condiciones de fin de
9. ¿Algún error textual o gramatical en la expediente?
información de salida? 8. ¿Se han manejado los errores de E / S?

Interfaces Otros cheques


1. ¿Número de parámetros de entrada igual al 1. ¿Alguna variable no referenciada en la lista
número de argumentos? de referencias cruzadas?
2. ¿Los atributos de parámetro y argumento 2. Lista de atributos ¿qué se esperaba?
coinciden? 3. ¿Algún mensaje de advertencia o
3. ¿El sistema de unidades de parámetros y informativo?
argumentos coincide? 4. ¿Se verificó la validez de la entrada?
4. ¿Número de argumentos transmitidos a los 5. ¿Falta función?
módulos llamados igual al número de
parámetros?
5. ¿Los atributos de los argumentos
transmitidos a los módulos llamados son
iguales a los atributos de los parámetros?
6. ¿Sistema de unidades de argumentos
transmitidos a módulos llamados igual al
sistema de unidades de parámetros?
7. ¿Es correcto el número, los atributos y el
orden de los argumentos de las funciones
integradas?
8. ¿Alguna referencia a parámetros no
asociados con el punto de entrada actual?
9. ¿Se han alterado los argumentos de sólo
entrada?
10. ¿Definiciones de variables globales
consistentes en todos los módulos?
11. ¿Constantes pasadas como argumentos?

Tutoriales

El recorrido del código, al igual que la inspección, es un conjunto de procedimientos y técnicas de


detección de errores para la lectura de códigos en grupo. Tiene mucho en común con el proceso
de inspección, pero los procedimientos son ligeramente diferentes y se emplea una técnica de
detección de errores diferente.

Al igual que la inspección, el recorrido es una reunión ininterrumpida de una a dos horas de
duración. El equipo de recorrido consta de tres a cinco personas. Una de estas personas juega un
papel similar al del moderador en el proceso de inspección, otra persona desempeña el papel de
secretaria (una persona que registra todos los errores encontrados), y una tercera persona
desempeña el papel de evaluador. Las sugerencias sobre quiénes deben ser de tres a cinco
personas varían. Por supuesto, el programador es una de esas personas. Las sugerencias para los
demás participantes incluyen (1) un programador con mucha experiencia, (2) un experto en
lenguajes de programación, (3) un nuevo programador (para dar una perspectiva fresca e
imparcial), (4) la persona que eventualmente mantendrá el programa. , (5) alguien de un proyecto
diferente y (6) alguien del mismo equipo de programación que el programador.

El procedimiento inicial es idéntico al del proceso de inspección: los participantes reciben los
materiales con varios días de anticipación para que puedan ponerse al día con el programa. Sin
embargo, el procedimiento en la reunión es diferente. En lugar de simplemente leer el programa o
usar listas de verificación de errores, los participantes "juegan con la computadora". La persona
designada como probador llega a la reunión armada con un pequeño conjunto de casos de prueba
en papel: conjuntos representativos de entradas (y salidas esperadas) para el programa o módulo.
Durante la reunión, cada caso de prueba se ejecuta mentalmente. Es decir, los datos de prueba se
recorren a través de la lógica del programa. El estado del programa (es decir, los valores de las
variables) se monitorea en papel o pizarra.

Por supuesto, los casos de prueba deben ser de naturaleza simple y pocos en número, porque la
gente ejecuta programas a una velocidad que es muchos órdenes de magnitud más lenta que una
máquina. Por lo tanto, los casos de prueba en sí mismos no juegan un papel crítico; más bien,
sirven como un vehículo para comenzar y para cuestionar al programador sobre su lógica y
suposiciones. En la mayoría de los tutoriales, se encuentran más errores durante el proceso de
interrogar al programador que los que se encuentran directamente en los propios casos de
prueba.

Al igual que en la inspección, la actitud de los participantes es fundamental. Los comentarios


deben dirigirse al programa en lugar de al programador. En otras palabras, los errores no se ven
como debilidades en la persona que los cometió. Más bien, se consideran inherentes a la dificultad
del desarrollo del programa.

El recorrido debe tener un proceso de seguimiento similar al descrito para el proceso de


inspección. Además, los efectos secundarios observados en las inspecciones (identificación de
secciones propensas a errores y educación sobre errores, estilo y técnicas) también se aplican al
proceso de recorrido.

Comprobación de escritorio

Un tercer proceso de detección de errores humanos es la práctica más antigua de verificación de


escritorio. Una verificación de escritorio puede verse como una inspección o un recorrido por una
sola persona: una persona lee un programa, lo verifica con respecto a una lista de errores y / o
recorre los datos de prueba a través de él.

Para la mayoría de las personas, la verificación documental es relativamente improductiva. Una


razón es que es un proceso completamente indisciplinado. Una segunda razón, y más importante,
es que va en contra de un principio de prueba del Capítulo 2: el principio de que las personas
generalmente son ineficaces al probar sus propios programas. Por esta razón, podría deducir que
la verificación de escritorio la realiza mejor una persona que no sea el autor del programa (por
ejemplo, dos programadores pueden intercambiar programas en lugar de verificar sus propios
programas), pero incluso esto es menos efectivo que la guía o proceso de inspección. La razón es
el efecto sinérgico del recorrido o del equipo de inspección. La sesión de equipo fomenta un
ambiente de competición saludable; a la gente le gusta lucirse encontrando errores. En un proceso
de verificación de escritorio, dado que no hay nadie ante quien pueda presumir, este efecto
aparentemente valioso falta. En resumen, la verificación de escritorio puede ser más valiosa que
no hacer nada en absoluto, pero es mucho menos efectiva que la inspección o el recorrido.

Calificaciones de pares

El último proceso de revisión humana no está asociado con la prueba del programa (es decir, su
objetivo no es encontrar errores). Sin embargo, este proceso se incluye aquí porque está
relacionado con la idea de lectura de código.

La calificación por pares es una técnica para evaluar programas anónimos en términos de su
calidad general, mantenibilidad, extensibilidad, usabilidad y claridad. El propósito de la técnica es
proporcionar al programador una autoevaluación.

Se selecciona un programador para que actúe como administrador del proceso. El administrador, a
su vez, selecciona aproximadamente de 6 a 20 participantes (6 es el mínimo para preservar el
anonimato). Se espera que los participantes tengan antecedentes similares (no debe agrupar a
programadores de aplicaciones Java con programadores de sistemas de lenguaje ensamblador,
por ejemplo). A cada participante se le pide que seleccione dos de sus propios programas para ser
revisados. Un programa debe ser representativo de lo que el participante considera su mejor
trabajo; el otro debe ser un programa que el programador considere de peor calidad.

Una vez recopilados los programas, se distribuyen aleatoriamente a los participantes. A cada
participante se le asignan cuatro programas para que los revise. Dos de los programas son los
programas "mejores" y dos son programas "más pobres", pero no se le dice al revisor cuál es cuál.
Cada participante dedica 30 minutos a cada programa y luego completa un formulario de
evaluación después de revisar el programa. Después de revisar los cuatro programas, cada
participante califica la calidad relativa de los cuatro programas. El formulario de evaluación le pide
al revisor que responda, en una escala del 1 al 7 (1 significa definitivamente "sí", 7 significa
definitivamente "no"), preguntas como estas:

• ¿El programa fue fácil de entender?

• ¿Fue visible y razonable el diseño de alto nivel?

• ¿El diseño de bajo nivel fue visible y razonable?

• ¿Le resultaría fácil modificar este programa?

• ¿Estaría orgulloso de haber escrito este programa?

También se solicita al revisor comentarios generales y sugerencias de mejoras.

Después de la revisión, los participantes reciben los formularios de evaluación anónimos para sus
dos programas contribuidos. Los participantes también reciben un resumen estadístico que
muestra la clasificación general y detallada de sus programas originales en todo el conjunto de
programas, así como un análisis de cómo sus calificaciones de otros programas se comparan con
las calificaciones de otros revisores del mismo programa. El propósito del proceso es permitir a los
programadores autoevaluar sus habilidades de programación. Como tal, el proceso parece ser útil
tanto en entornos industriales como en aulas.

Resumen

En este capítulo se analizó una forma de prueba que los desarrolladores no suelen considerar: las
pruebas en humanos. La mayoría de la gente asume que debido a que los programas están
escritos para ejecución de máquinas, las máquinas también deberían probar programas. Esta
suposición no es válida. Las técnicas de prueba en humanos son muy efectivas para revelar
errores. De hecho, la mayoría de los proyectos de programación deben incluir las siguientes
técnicas de prueba humana:

• Inspecciones de código utilizando listas de verificación

• Tutoriales grupales

• Comprobación de escritorio

• Revisiones hechas por colegas


INTRODUCTION TO
SOFTWARE
TESTING
Los primeros cinco capítulos de este libro ayudan a los probadores prácticos a llenar su "caja de
herramientas" con varios criterios de prueba. Si bien es necesario estudiar los criterios de prueba
de forma aislada, uno de los Los obstáculos más difíciles para una organización de software que
pasa a las pruebas de nivel 3 o 4 son Integrar estrategias de prueba efectivas en el proceso general
de desarrollo de software.

Este capítulo analiza varios problemas relacionados con la aplicación de criterios de prueba
durante desarrollo de software. La filosofía primordial es pensar: si el evaluador mira esto como
un problema técnico, y busca soluciones técnicas, generalmente encontrará que los obstáculos no
son tan grandes como parecen a primera vista.

6.1 PRUEBAS DE REGRESIÓN

La prueba de regresión es el proceso de volver a probar el software que se ha modificado.


Regresión

Las pruebas constituyen la gran mayoría del esfuerzo de prueba en software comercial desarrollo y
es una parte esencial de cualquier proceso de desarrollo de software viable.

Los componentes o sistemas grandes tienden a tener grandes conjuntos de pruebas de regresión.
Aunque muchos desarrolladores no quieren creerlo (¡incluso cuando se enfrentan a pruebas
indiscutibles!), pequeños cambios en una parte de un sistema a menudo causan problemas en
partes distante del sistema. Las pruebas de regresión se utilizan para encontrar este tipo de
problema.

Vale la pena enfatizar que las pruebas de regresión deben automatizarse. De hecho, podría

Hay que decir que las pruebas de regresión no automatizadas equivalen a ninguna prueba de
regresión.

Se encuentra disponible una amplia variedad de herramientas disponibles comercialmente.

Capturar / reproducir

Las herramientas automatizan las pruebas de programas que utilizan interfaces gráficas de
usuario. Control de versiones software, ya en uso para administrar diferentes versiones de un
sistema dado, de manera efectiva gestiona los equipos de prueba asociados a cada versión.
Software de secuencias de comandos gestiona el proceso de obtención de entradas de prueba,
ejecución del software, clasificación las salidas, comparando las salidas reales y esperadas, y
generando pruebas informes.
El objetivo de esta sección es explicar qué tipos de pruebas deberían incluirse en una regresión.

conjunto de pruebas, qué pruebas de regresión ejecutar y cómo responder a las pruebas de
regresión que fallar. Tratamos cada uno de estos temas por turno. Dirigimos al lector interesado
en el detalle sobre cualquiera de estos temas a las notas bibliográficas.

El ingeniero de pruebas se enfrenta a un problema de Ricitos de Oro al determinar qué pruebas


incluir en un conjunto de prueba de regresión. Incluir todos los conjuntos de pruebas posibles da
como resultado un gran conjunto de pruebas de regresión. El resultado es que el equipo de
prueba no se puede ejecutar con tanta frecuencia a medida que se realizan cambios en el
software. Para muchas organizaciones de desarrollo, este período

equivale a un día; Las pruebas de regresión se ejecutan por la noche para evaluar el cambio de
software. ese día, y los desarrolladores revisaron los resultados a la mañana siguiente. Si la
regresión las pruebas no terminan de manera oportuna, el proceso de desarrollo se interrumpe.
Eso Vale la pena invertir dinero en este problema en términos de cálculo adicional recursos para
ejecutar las pruebas, pero, en algún momento, la ventaja marginal de agregar una prueba dada no
vale el gasto marginal de los recursos necesarios para ejecutar eso. Por otro lado, un conjunto
demasiado pequeño no cubrirá la funcionalidad de el software lo suficientemente bien, y
demasiadas fallas pasarán la regresión prueba puesta al cliente.

El párrafo anterior en realidad no dice qué pruebas están en la prueba de regresión.

conjunto, solo que el conjunto debe ser del tamaño correcto. Algunas organizaciones tienen una
política que para cada informe de problema que ha llegado del campo, existe una prueba de
regresión que, en principio, detecta el problema. La idea es que los clientes estén más dispuestos a
estar cargados con nuevos problemas que con el mismo problema una y otra vez. Lo anterior
enfoque tiene un cierto encanto desde la perspectiva de la trazabilidad, en el sentido de que cada
prueba elegido de esta manera tiene un fundamento concreto.

Los criterios de cobertura que forman el corazón de este libro proporcionan una base excelente
para evaluar conjuntos de pruebas de regresión. Por ejemplo, si la cobertura de nodo en el
formulario de la cobertura de llamadas a métodos muestra que algunos métodos nunca se
invocan, entonces es un buena idea decidir que el método es un código muerto con respecto a ese
particular aplicación o incluir una prueba que dé como resultado una llamada al método. Si una o
más pruebas de regresión fallan, el primer paso es determinar si el cambio al software es
defectuoso, o si el conjunto de prueba de regresión está roto. En cualquier caso, se requiere
trabajo adicional. Si no falla ninguna prueba de regresión, todavía queda trabajo por hacer. La
razón es que un conjunto de pruebas de regresión que es satisfactorio para una versión dada del
software no es necesariamente satisfactorio para una versión posterior. Cambios en el software a
menudo se clasifican en correctivos, perfectivos, adaptativos y preventivos. Todos estos los
cambios requieren pruebas de regresión. Incluso cuando la funcionalidad externa (deseada) del
software no cambia, el conjunto de prueba de regresión aún debe volver a analizarse para ver si es
adecuado. Por ejemplo, el mantenimiento preventivo puede resultar en ventas al por mayor
reestructuración interna de algunos componentes. Si los criterios utilizados para seleccionar el
original
Las pruebas de regresión se derivaron de la estructura de la implementación, entonces es poco
probable que el conjunto de prueba cubra adecuadamente la nueva implementación.

La evolución de un conjunto de pruebas de regresión a medida que cambia el software asociado es


un desafío.

Los cambios en la interfaz externa son particularmente dolorosos, ya que tal cambio puede
provocar que todas las pruebas fallen. Por ejemplo, suponga que una entrada en particular se
mueve de un menú desplegable a otro. El resultado es que el aspecto de captura / reproducción
de la ejecución cada caso de prueba necesita una actualización. O supongamos que la nueva
versión del software genera una salida adicional. Todos los resultados esperados ahora están fuera
de fecha y necesita ser aumentada. Claramente, soporte automatizado para mantener la prueba
sets es tan crucial como el soporte automatizado para ejecutar las pruebas.

Agregar un número (pequeño) de pruebas a un conjunto de pruebas de regresión suele ser


sencillo. El costo marginal de cada prueba adicional suele ser bastante pequeño. Sin embargo,
acumulativamente el equipo de prueba puede volverse difícil de manejar. Eliminar pruebas de un
conjunto de pruebas de regresión es una tarea peligrosa. proposición. Invariablemente, se
mostrará una falla en el campo que una de las pruebas eliminadas hubiera encontrado.
Afortunadamente, los mismos criterios que orientan la construcción de un conjunto de pruebas de
regresión se aplica al decidir cómo actualizar el conjunto de pruebas de regresión.

Un enfoque diferente para limitar la cantidad de tiempo necesario para ejecutar la regresión
pruebas, y un foco de gran parte de la atención en la literatura de investigación, es seleccionar
sólo un subconjunto de las pruebas de regresión. Por ejemplo, si la ejecución de un determinado
caso de prueba no visita nada modificado, entonces el caso de prueba tiene que realizar el lo
mismo antes y después de la modificación y, por lo tanto, se puede omitir con seguridad. Selección

Las técnicas incluyen ecuaciones lineales, ejecución simbólica, análisis de ruta, datos análisis de
flujo, gráficos de dependencia del programa, gráficos de dependencia del sistema, modificación
análisis, definición de firewall, identificación de clústeres, segmentación, recorridos de gráficos y
modificaciones análisis de entidades. Por ejemplo, como podría adivinar un lector del Capítulo 2, la
selección del flujo de datos las técnicas eligen pruebas solo si tocan pares de UD nuevos,
modificados o eliminados; se omiten otras pruebas.

Una técnica de selección es inclusiva en la medida en que incluye pruebas que son "Reveladora de
modificaciones". Las técnicas inseguras tienen una inclusividad de menos del 100%.

Una técnica es precisa en la medida en que omite pruebas de regresión que no son reveladora de
modificaciones. Una técnica es eficiente en la medida en que determinan el subconjunto
apropiado del conjunto de pruebas de regresión es menos intensivo desde el punto de vista
computacional que simplemente ejecutando las pruebas omitidas. Finalmente, una técnica es
general en la medida en que se aplica a una amplia variedad de situaciones prácticas. Para
continuar con el ejemplo, los datos el enfoque de flujo para seleccionar pruebas de regresión no es
necesariamente seguro o preciso, de complejidad polinomial en ciertos atributos del programa, y
requiere, obviamente, datos información de flujo e instrumentación del programa a nivel de
gráfico de flujo de datos. El bibliográfico
La sección de notas contiene sugerencias para obtener más detalles sobre este trabajo, incluyendo
evaluaciones empíricas.

6.2 INTEGRACIÓN Y PRUEBAS

El software se compone de muchas piezas de distintos tamaños. Los programadores individuales


son a menudo responsable de probar los componentes de nivel más bajo (clases, módulos y
métodos). Después de eso, las pruebas deben llevarse a cabo en colaboración con la integración
de software.

El software se puede integrar de muchas formas. En esta sección se analizan los estrategias que
deben emplearse durante las pruebas. No intentamos catalogarlos todos desde el punto de vista
del proceso.

La prueba de integración es la prueba de incompatibilidades e interfaces entre otros componentes


que funcionan correctamente. Es decir, son las pruebas necesarias para integrar subcomponentes
en un componente de trabajo más grande. Esto enfáticamente no es lo mismo como probar un
componente ya integrado.

Este capítulo utiliza el término "componente" de software en un sentido muy amplio: Un


componente es una parte de un programa que se puede probar independientemente del

programa o sistema. Por lo tanto, clases, módulos, métodos, paquetes e incluso fragmentos de
código pueden considerarse componentes.

Las pruebas de integración a menudo se realizan con un sistema incompleto. El probador puede
ser evaluar cómo sólo dos de los muchos componentes del sistema trabajan juntos, puede ser

probar los aspectos de integración antes de que el sistema completo esté completo, o sistema
juntos pieza por pieza y evaluando cómo encaja cada nuevo componente con los componentes
previamente integrados.

6.2.1 Stubs y controladores

Al probar partes incompletas de software, los desarrolladores y evaluadores a menudo necesitan


componentes de software adicionales, a veces llamados andamios. Los dos más comunes los tipos
de andamios se conocen como talones de prueba y controladores de prueba. Un talón de prueba
es un esqueleto o implementación de propósito especial de un módulo de software, utilizado para
desarrollar o probar un componente que llama al stub o depende de él. Reemplaza un
componente llamado.

Para los programas OO, la comunidad XP ha desarrollado una versión del stub llamado el
simulacro. Un simulacro es una clase de reemplazo de propósito especial que incluye
comportamiento verificación para comprobar que una clase bajo prueba hizo las llamadas
correctas al simulacro. El controlador de prueba es un componente de software o herramienta de
prueba que reemplaza un componente que toma cuidado del control y / o la llamada de un
componente de software.

Una de las responsabilidades de un stub de prueba es devolver valores al componente de llamada.


Estos valores rara vez son los mismos que los del componente real que se está transfiriendo.
volvería, o de lo contrario no necesitaríamos el talón, pero a veces deben satisfacer ciertas
limitaciones.

La acción más simple para un stub es asignar valores constantes a las salidas. Más sofisticado ños
enfoques pueden ser devolver valores aleatorios, valores de una consulta de tabla, o para permitir
que el usuario ingrese valores de retorno durante la ejecución. Se han incluido herramientas de
prueba capacidades de stubbing automatizadas desde la década de 1980. Descubrir herramientas
más sofisticadas métodos que necesitan ser stub, y pregunte al evaluador qué tipo de
comportamiento el stub debería tener. Algunas herramientas recopilan instancias de objetos que
tienen el retorno correcto escriba y póngalos a disposición como posibles devoluciones de talón.
Por defecto, este es un poderoso enfoque que el ingeniero de pruebas solo necesita anular según
sea necesario. Es posible para generar stubs automáticamente a partir de especificaciones
formales de los componentes del software, pero no tenemos conocimiento de esta funcionalidad
en las herramientas disponibles. Los programadores también generar sus propios stubs al realizar
sus propias pruebas de unidad o módulo.

La forma más simple de controlador es un método main () para una clase. Programadores
efectivos a menudo incluyen un main () para cada clase, que contiene declaraciones que llevan a
cabo simples prueba de la clase. Si la clase es un ADT, el controlador de prueba main () creará
algunos objetos de la clase, agregue valores, recupere valores y use las otras operaciones en la
clase. Técnicas de capítulos anteriores, como restricciones de secuenciación y pruebas, se puede
implementar en el controlador.

Los controladores de prueba pueden incluir valores codificados de forma rígida o recuperar los
valores de una fuente como el probador o un archivo. Existen herramientas para generar
controladores de prueba automáticamente.

Tanto el controlador de prueba como los generadores de talón de prueba se incluyen en otras
herramientas de prueba.

6.2.2 Orden de prueba de integración de clases

Al integrar varios componentes, es importante decidir en qué orden las clases o subsistemas
deben integrarse y probarse. Las clases dependen unas de otras de varias maneras. Una clase
puede usar métodos o variables definidas en otra, una clase puede heredar de otra, o una clase
puede agregar objetos de otra clase dentro sus objetos de datos. Si la clase A usa métodos
definidos en la clase B y B no está disponible, entonces necesitamos stubs para que esos métodos
prueben A. Por lo tanto, tiene sentido probar B primero, cuando se prueba A, podemos usar
objetos reales de B en lugar de stubs. Esto se denomina problema de orden de prueba de
integración de clases (CITO), y el problema general

El objetivo es integrar y probar las clases en el orden que requiera la menor cantidad de
apéndices, ya que la creación de stubs de prueba se considera un costo importante de las pruebas
de integración. Si las dependencias entre las clases no tienen ciclos, el orden de su integración es
bastante simple. Las clases que no dependen de ninguna otra clase se prueban primero.
Luego se integran con clases que dependen solo de ellos, y las nuevas clases son probados. Si las
clases se representan como nodos en un "gráfico de dependencia", con bordes que representan
dependencias, este enfoque sigue una clasificación topológica de la grafico.

El problema se complica cuando el gráfico de dependencia tiene ciclos, porque eventualmente


llegaremos a una clase que depende de otra clase que no ha aún ha sido integrado y probado.
Aquí es cuando se requiere algún tipo de apéndice. Para Por ejemplo, suponga que la clase A usa
métodos en la clase B, B usa métodos en la clase C, y C agrega un objeto de clase A. Cuando esto
sucede, el probador de integración debe "romper el ciclo" eligiendo una clase en el ciclo para
probar primero. La esperanza es para elegir la clase que resulte en el menor trabajo extra
(principalmente el de crear talones).

Los diseñadores de software pueden observar que los diagramas de clases a menudo tienen pocos
o ningún ciclo y, de hecho, la mayoría de los libros de texto de diseño recomiendan
encarecidamente no incluir ciclos en diseños. Sin embargo, es común agregar clases y relaciones a
medida que avanza el diseño, por ejemplo, para mejorar el rendimiento o la capacidad de
mantenimiento

Los diagramas generalmente contienen ciclos al final del diseño o implementación de bajo nivel, y
los probadores prácticos tienen que resolver el problema de CITO. La literatura de investigación
propone numerosas soluciones al problema CITO. Esto sigue siendo un área de investigación activa
y estas soluciones aún no se han convertido en herramientas.

6.3 PROCESO DE PRUEBA

Muchas organizaciones posponen todas las actividades de prueba de software hasta el final del
desarrollo, después de que la implementación haya comenzado, o incluso después de que haya
terminado. Esperando hasta esta última etapa del proceso, las pruebas terminan comprimiéndose,
no hay suficientes recursos (tiempo y presupuesto) permanecen, los problemas con etapas
anteriores se han resuelto tomando tiempo y dinero de las pruebas, y los evaluadores no tienen
tiempo suficiente para planificar para las pruebas. En lugar de planificar y diseñar pruebas, los
desarrolladores solo tienen tiempo para ejecutar pruebas, generalmente de manera ad hoc. El
punto clave es que el objetivo es crear software de alta calidad, y el viejo adagio de que "la calidad
no se puede probar en" sigue vigente muy relevante. Un probador no puede aparecer en el último
minuto y hacer un mal producto. bien; la alta calidad debe formar parte del proceso desde el
principio.

Esta sección analiza cómo integrar las pruebas con el desarrollo, donde las pruebas

Las actividades comienzan tan pronto como comienzan las actividades de desarrollo y se llevan a
cabo en paralelo con las etapas de desarrollo. Actividades específicas, incluida la planificación,
activas pruebas y actividades que influyen en el desarrollo, se pueden asociar con cada uno de las
fases tradicionales del ciclo de vida. Estas actividades pueden ser realizadas por los
desarrolladores o por ingenieros de prueba independientes, y se puede asociar con etapas de
desarrollo Aplicación de criterios en la práctica dentro de los límites de cualquier proceso de
desarrollo específico. Estas actividades de prueba permiten el probador para detectar y prevenir
fallas durante el desarrollo del software proceso.
Los proyectos que comienzan las actividades de prueba después de que se completa la
implementación a menudo producen software muy poco fiable. Los probadores sabios (y las
organizaciones de prueba de nivel 4) incorporan una cadena de planes y procedimientos de
prueba que comienzan en los primeros pasos del desarrollo de software, y continúe con todos los
pasos siguientes. Integrando pruebas de software actividades en todas las etapas del ciclo de vida
del desarrollo de software, podemos hacer dramáticos mejoras en la efectividad y eficiencia de las
pruebas e impacto en el software. proceso de desarrollo de tal manera que es más probable que el
software de alta calidad sea construido.

Otros libros de texto y la literatura de investigación contienen docenas de procesos de software.


(cascada, espiral, prototipos evolutivos, programación eXtreme, etc.). Esta sección utiliza las
siguientes etapas distintas sin asumir ningún orden o mapearlas en un proceso específico. Por
tanto, las sugerencias de esta sección se pueden adaptar a cualquier se está utilizando el proceso.

1. Análisis y especificación de requisitos

2. Diseño de sistemas y software

3. Diseño intermedio

4. Diseño detallado

5. Implementación

6. Integración

7. Implementación del sistema

8. Operación y mantenimiento

Cualquier proceso de desarrollo implica comunicación, comprensión y transición. de información


entre etapas. Se pueden cometer errores durante cualquier etapa, en el manejo de la información,
o en la transferencia de la información de una etapa a otra.

La integración de las pruebas con el proceso consiste en tratar de encontrar errores en cada etapa.
además de evitar que estos errores se propaguen a otras etapas. Además, la integración de
pruebas a lo largo del ciclo de vida proporciona una forma de verificar y rastrear consistencias

entre las etapas. Las pruebas no deben aislarse en etapas separadas, sino más bien estar en una
pista paralela que afecta a todas las etapas.

Las pruebas tienen diferentes objetivos durante cada etapa, y estos objetivos son logrado de
diferentes maneras. Estos subobjetivos de prueba en cada etapa lograr el objetivo general de
garantizar un software de alta calidad. Para la mayoría de las etapas las actividades de prueba se
pueden dividir en tres categorías amplias: acciones de prueba: probar el producto o artefactos
creados en esa etapa; diseño de prueba: uso de los artefactos de desarrollo de esa etapa o probar
artefactos de la etapa anterior para prepararse para probar el software final; y probar la
influencia: utilizando el desarrollo o los artefactos de prueba para influir futuras etapas de
desarrollo.
6.3.1 Análisis y especificación de requisitos

Un documento de requisitos y especificaciones de software contiene una descripción completa

del comportamiento externo del sistema de software. Proporciona una forma de Cuadro 6.1.
Prueba de objetivos y actividades durante el análisis de requisitosy especificación

Actividades de objetivos

Asegúrese de que los requisitos sean probables Configure los requisitos de prueba

Asegúrese de que los requisitos sean los criterios de prueba correctos

Asegúrese de que los requisitos estén completos 􀀀 se necesita software de soporte

Influir en la arquitectura del software 􀀀 planes de prueba en cada nivel

􀀀 construir prototipos de prueba

Aclarar los elementos de los requisitos y los criterios de prueba

Desarrollar el plan de prueba del proyecto

comunicarse con las otras etapas del desarrollo y definir los contenidos

y límite del sistema de software. La tabla 6.1 resume los principales objetivos

y actividades durante el análisis y la especificación de requisitos.

El principal objetivo de la acción de prueba es evaluar los propios requisitos. Cada requisito

debe ser evaluado para asegurar que sea correcto, comprobable y que los requisitos

juntos están completos. Se han presentado muchos métodos para hacer esto, la mayoría

comúnmente inspecciones y prototipos. Estos temas están bien descritos en otros lugares.

y no se tratan explícitamente en este libro. Un punto clave es que los requisitos

debe evaluarse antes de que comience el diseño.

El principal objetivo del diseño de pruebas es prepararse para las actividades de prueba y
verificación del sistema.

Los requisitos de prueba deben escribirse según los criterios de prueba estatales para el software.

Se deben desarrollar planes de prueba de alto nivel y de sistema para delinear la estrategia de
prueba.

El plan de prueba también debe incluir el alcance y los objetivos de la prueba en cada etapa.
Este plan de prueba de alto nivel se hará referencia en los planes de prueba detallados
posteriores. La prueba

Los requisitos deben describir el software de soporte necesario para las pruebas en cada etapa.

Los requisitos de prueba deben satisfacerse mediante pruebas posteriores.

El principal objetivo de la influencia de las pruebas es influir en el diseño de la arquitectura del


software.

Los planes de prueba del proyecto y los escenarios de prueba del sistema representativos deben
construirse para mostrar

que el sistema cumpla con los requisitos. El proceso de desarrollo de los escenarios de prueba.

A menudo ayudará a detectar especificaciones de requisitos ambiguas e inconsistentes. La

Los escenarios de prueba también proporcionarán comentarios a los diseñadores de arquitectura


de software y

ayúdelos a desarrollar un diseño que sea fácilmente comprobable.

6.3.2 Diseño de software y sistemas

El diseño del sistema y del software divide los requisitos en hardware o software

sistemas y construye la arquitectura general del sistema. El diseño del software debe representar

el sistema de software funciona para que se puedan transformar en ejecutables

programas. La Tabla 6.2 resume los principales objetivos y actividades durante el sistema.

y diseño de software.

El principal objetivo de la acción de prueba es verificar el mapeo entre los requisitos

especificación y el diseño. Cualquier cambio en la especificación de requisitos debe

reflejarse en los correspondientes cambios de diseño. Las pruebas en esta etapa deberían ayudar

validar el diseño y la interfaz.

Cuadro 6.2. Prueba de objetivos y actividades durante el sistema y el software

diseño

Actividades de objetivos

Verificar el mapeo entre los requisitos Validar el diseño y la interfaz

Especificación y diseño del sistema Diseño de pruebas del sistema

Garantizar la trazabilidad y la capacidad de prueba Desarrollar criterios de cobertura

Diseño de la interfaz de influencia Plan de prueba de aceptación del diseño


Prueba de usabilidad del diseño (si es necesario)

El principal objetivo del diseño de la prueba es prepararse para las pruebas de aceptación y
usabilidad.

Se crea un plan de prueba de aceptación que incluye requisitos de prueba de aceptación, prueba

criterios y un método de prueba. Además, especificaciones de requisitos y diseño del sistema.

Las especificaciones deben mantenerse rastreables y comprobables para referencias y cambios


para

las últimas etapas. Las pruebas en la etapa de diseño del sistema y del software también preparan
para

pruebas unitarias y pruebas de integración eligiendo criterios de cobertura de los anteriores

capítulos.

El principal objetivo de influencia de la prueba es influir en el diseño de la interfaz de usuario.


Usabilidad

pruebas o un prototipo de interfaz debe diseñarse para aclarar el cliente

deseos de interfaz. Las pruebas de usabilidad se llevan a cabo cuando la interfaz de usuario es una
parte integral

parte del sistema.

6.3.3 Diseño intermedio

En el diseño intermedio, el sistema de software se divide en componentes y clases.

asociado con cada componente. Las especificaciones de diseño están escritas para cada
componente.

y clase. Muchos problemas en los grandes sistemas de software surgen de la interfaz de los
componentes.

desajustes. El principal objetivo de la acción de prueba es evitar desajustes de interfaces.

La Tabla 6.3 resume los principales objetivos y actividades durante el diseño intermedio.

El principal objetivo del diseño de pruebas es prepararse para las pruebas unitarias, las pruebas de
integración y

prueba del sistema escribiendo los planes de prueba. Los planes de prueba de unidad e
integración se refinan

en este nivel con información sobre interfaces y decisiones de diseño. Preparar

para las pruebas en las etapas posteriores, pruebe las herramientas de soporte, como los
controladores de prueba, los stubs y las pruebas
Se deben adquirir o construir herramientas de medición.

El principal objetivo de influencia de la prueba es influir en el diseño detallado. La integración de


clases

y el orden de prueba (CITO) de la Sección 6.2.2 debe determinarse para tener la

efecto adecuado en el diseño detallado.

Cuadro 6.3. Prueba de objetivos y actividades durante el diseño intermedio

Actividades de objetivos

Evite discrepancias de interfaces Especifique casos de prueba del sistema

Prepararse para las pruebas unitarias Desarrollar planes de integración y pruebas unitarias

Cree o recopile herramientas de soporte de prueba

Sugerir orden de integración de clases

Cuadro 6.4. Prueba de objetivos y actividades durante el diseño detallado

Actividades de objetivos

Esté preparado para probar cuando los módulos estén listos Cree casos de prueba

(si es unidad)

Construir especificaciones de prueba

(si es integración)

6.3.4 Diseño detallado

En la etapa de diseño detallado, los probadores escriben las especificaciones del subsistema y el
pseudocódigo

para módulos. La Tabla 6.4 resume los principales objetivos y actividades durante

diseño detallado. El principal objetivo de la acción de prueba en la etapa de diseño detallado es


hacer

asegúrese de que todos los materiales de prueba estén listos para probar cuando se escriban los
módulos.

Los evaluadores deben prepararse para las pruebas unitarias y de integración. Los probadores
deben refinar

planes de prueba detallados, generar casos de prueba para pruebas unitarias y escribir
especificaciones de prueba detalladas

para pruebas de integración. El principal objetivo de influencia de la prueba es influir en el

pruebas de implementación y unidad e integración.


6.3.5 Implementación

En algún momento durante el desarrollo del software, el "caucho se pone en marcha" y el

los programadores comienzan a escribir y compilar clases y métodos. La tabla 6.5 resume

los principales objetivos y actividades durante la implementación.

El principal objetivo de la acción de prueba es realizar pruebas unitarias efectivas y eficientes. La

La eficacia de las pruebas unitarias se basa en gran medida en el criterio de prueba utilizado y la
prueba

datos generados. Las pruebas unitarias realizadas en esta etapa son las especificadas por la prueba
unitaria.

plan, criterios de prueba, casos de prueba y herramientas de soporte de prueba que se prepararon
en el

etapas anteriores. Los resultados de las pruebas unitarias y los problemas deben guardarse e
informarse correctamente

para su posterior procesamiento. Diseñadores y desarrolladores cuyas funciones son cada vez más
ligeras.

en este punto debe estar disponible para ayudar a los evaluadores.

El principal objetivo del diseño de pruebas es prepararse para la integración y las pruebas del
sistema. La

El principal objetivo de influencia de las pruebas es que las pruebas unitarias eficientes puedan
ayudar a garantizar una integración temprana.

y prueba del sistema. Es mucho más barato y más fácil encontrar y corregir errores durante la
unidad.

¡pruebas!

Cuadro 6.5. Probar objetivos y actividades durante

implementación

Actividades de objetivos

Pruebas unitarias eficientes Cree valores de casos de prueba

Generación automática de datos de prueba Realización de pruebas unitarias

Informar los problemas correctamente

Cuadro 6.6. Probar objetivos y actividades durante

integración

Actividades de objetivos
Pruebas de integración eficientes Realización de pruebas de integración

6.3.6 Integración

El problema de CITO, discutido en la sección anterior, es el principal problema durante el software.

integración. La Tabla 6.6 resume los principales objetivos y actividades durante

integración.

El principal objetivo de la acción de prueba es realizar pruebas de integración. Integracion e


integracion

las pruebas comienzan tan pronto como los componentes necesarios de un subsistema integrado

pasar las pruebas unitarias. En la práctica, el problema de CITO se puede resolver de manera
pragmática mediante

integrando clases tan pronto como se entregan desde las pruebas unitarias. Pruebas de
integración

se preocupa por encontrar errores que resulten de interacciones inesperadas entre

componentes.

6.3.7 Implementación del sistema

La Tabla 6.7 resume los principales objetivos y actividades durante la implementación del sistema.

El principal objetivo de la acción de prueba es realizar pruebas del sistema, pruebas de aceptación
y usabilidad.

pruebas. La prueba del sistema tiene el propósito particular de comparar el software

sistema a sus objetivos originales, en particular, validando si el software cumple

los requisitos funcionales y no funcionales. Se desarrollan casos de prueba de prueba del sistema

del plan de prueba del sistema y del proyecto de la especificación de requisitos

y fase de diseño de software según los criterios tratados en los capítulos anteriores. Aceptación

Las pruebas se pueden iniciar tan pronto como se completen las pruebas del sistema. Aceptación

Las pruebas aseguran que el sistema completo satisface las necesidades de los clientes, y debe

hacerse con su participación. Los casos de prueba se derivan de planes de prueba de aceptación

y datos de prueba configurados previamente. Las pruebas de usabilidad evalúan la interfaz de


usuario del

software. También debe hacerse con la participación del usuario.

6.3.8 Operación y mantenimiento

Después de que el software sea lanzado (o entregado, implementado o lo que sea), los usuarios
ocasionalmente encuentra nuevos problemas o solicita nuevas funciones. Cuando el software está

cambiado, debe someterse a una prueba de regresión. Las pruebas de regresión ayudan a
garantizar que

Cuadro 6.7. Probar objetivos y actividades durante

despliegue del sistema

Actividades de objetivos

Pruebas de sistemas eficientes Realización de pruebas de sistemas

Pruebas de aceptación eficientes Realización de pruebas de aceptación

Pruebas de usabilidad eficientes Realice pruebas de usabilidad

Cuadro 6.8. Probar objetivos y actividades durante

operación y mantenimiento

Actividades de objetivos

Pruebas de regresión eficientes Capture los problemas del usuario

Realizar pruebas de regresión

El software actualizado aún posee la funcionalidad que tenía antes de las actualizaciones, también

como la funcionalidad nueva o modificada. La Tabla 6.8 resume los principales objetivos y

actividades durante la operación y mantenimiento.

6.3.9 Resumen

Un factor clave para inculcar la calidad en un proceso de desarrollo se basa en la

ética profesional. Tanto los desarrolladores como los probadores pueden elegir anteponer la
calidad. Si

el proceso es tal que el evaluador no sabe cómo probarlo, luego no construye

eso. Esto a veces dará lugar a conflictos con la gestión basada en el tiempo, pero incluso si

pierde la discusión, se gana el respeto. Es importante que los desarrolladores comiencen

probar las actividades temprano. También ayuda a oponerse a tomar atajos. Casi

todos los proyectos eventualmente se enfrentarán a tomar atajos que, en última instancia,
reducirán

la calidad del software. ¡Combatirlo! Si pierde la discusión, ganará respeto:

Documente sus objeciones, vote con los pies y no tenga miedo de tener razón.
También es fundamental que se gestionen los artefactos de prueba. La falta de organización es un
seguro

receta para el fracaso. Ponga los artefactos de prueba bajo control de versiones, hágalos
fácilmente disponibles,

y actualícelos periódicamente. Estos artefactos incluyen documentos de diseño de prueba,


pruebas,

resultados de las pruebas y soporte automatizado. Es importante realizar un seguimiento de los


criterios basados

fuente de las pruebas, por lo que cuando la fuente cambia, es posible rastrear qué

las pruebas deben cambiar.

6.4 PLANES DE PRUEBA

Un énfasis importante para muchas organizaciones es la documentación, incluidos los planes de


prueba e informes del plan de prueba. Desafortunadamente, poner demasiado énfasis en la
documentación puede conducir a un entorno en el que se producen muchos informes sin sentido,
pero no se hace nada útil. Por eso este libro se centra en el contenido, no en la forma. La

Los contenidos de un plan de prueba son esencialmente cómo se crearon las pruebas, por qué se

creado y cómo se ejecutarán.

Sin embargo, producir planes de prueba es un requisito esencial para muchas organizaciones.

Las empresas y los clientes suelen imponer plantillas o esquemas. En vez de

Al examinar muchos tipos diferentes de planes de prueba, observamos la definición estándar de


IEEE.

Desafortunadamente, esto es bastante antiguo (¡1983!), Pero sigue siendo el más conocido. A

La búsqueda rápida en la Web le proporcionará más planes de prueba y esquemas de planes de


prueba.

de lo que jamás podrías usar. El estándar ANSI / IEEE 829-1983 describe un plan de prueba como:

"Un documento que describe el alcance, el enfoque, los recursos y el cronograma de

actividades de prueba. Identifica los elementos de prueba, las características que se van a probar,
las pruebas

tareas, quién hará cada tarea y cualquier riesgo que requiera planificación de contingencia ".

Varios tipos generales diferentes de planes de prueba son:

1. Un plan de misión dice "por qué". Por lo general, solo aparece un plan de misión por
organización.

o grupo. Los planes de misión describen la razón por la que la organización


existen, suelen ser muy breves (de 5 a 10 páginas) y son las menos detalladas de todas

planes.

2. Un plan estratégico dice "qué" y "cuándo". Una vez más, solo un plan estratégico generalmente

se utiliza por organización, aunque algunas organizaciones desarrollan una estrategia

planificar para cada categoría de proyecto. Los planes estratégicos pueden decir cosas como
"nosotros

siempre hará Cobertura de borde durante las pruebas unitarias "y" Pruebas de integración

será impulsado por acoplamientos ". Los planes estratégicos son más detallados y más largos

que los planes de misión, a veces de 20 a 50 páginas o más. Rara vez se detallan

lo suficiente como para ser directamente útil para los probadores o desarrolladores en práctica.

3. Un plan táctico dice "cómo" y "quién". La mayoría de las organizaciones usan uno en general

plan táctico por producto. Los planes tácticos son los más detallados de todos los planes,

y suelen ser documentos vivos. Es decir, un plan táctico puede comenzar su vida como un

tabla de contenido, y se agregará continuamente durante la vida útil del producto

o desarrollo de productos. Por ejemplo, un plan de prueba táctica especificaría cómo

cada unidad individual será probada.

A continuación, se describen dos planes de prueba de muestra, que se proporcionan solo como
ejemplo. Los planes

se derivaron de numerosas muestras que se han publicado en la Web, por lo que no

representan exactamente una sola organización. El primero es para probar el sistema y el segundo

es un plan táctico para pruebas unitarias. Ambos se basan en el estándar IEEE 829-1983.

1. Propósito

El propósito de un plan de prueba es definir las estrategias, el alcance de la prueba, la filosofía,

probar los criterios de entrada y salida y las herramientas de prueba que se utilizarán. La

El plan también debe incluir información de gestión, como la asignación de recursos,

asignaciones de personal y horarios.

2. Público objetivo y aplicación

(a) El personal de prueba y el personal de aseguramiento de la calidad deben poder comprender

e implementar el plan de prueba.

(b) El personal de aseguramiento de la calidad debe poder analizar los resultados y


hacer recomendaciones sobre la calidad del software sometido a prueba a la dirección.

(c) Los desarrolladores deben poder comprender qué funcionalidades serán

probado y las condiciones en las que se realizarán las pruebas.

(d) El personal de marketing debe poder comprender con qué configuraciones

(hardware y software) se probó el producto.

(e) Los gerentes deben comprender el cronograma en la medida en que se realicen las pruebas.

a realizar y cuando estará terminado.

3. Entregables

Los resultados de las pruebas son los siguientes entregables:

(a) Casos de prueba, incluidos los valores de entrada y los resultados esperados

(b) Criterios de prueba satisfechos

(c) Informes de problemas (generados como resultado de las pruebas)

(d) Análisis de cobertura de prueba

4. Información incluida

Cada plan de prueba debe contener la siguiente información. Tenga en cuenta que esto puede

(y a menudo lo hace) sirve como el esquema del plan de prueba real, y se puede adaptar

a la mayoría de los entornos con éxito.

(A. Introducción

(b) Elementos de prueba

(c) Características probadas

(d) Funciones no probadas (por ciclo)

(e) Criterios de prueba, estrategia y enfoque

􀀀 Sintaxis

􀀀 Descripción de la funcionalidad

􀀀 Valores de argumentos para pruebas

􀀀 Salida esperada

􀀀 Exclusiones específicas

􀀀 Dependencias

􀀀 Criterios de éxito del caso de prueba


(f) Estándares de aprobación / reprobación

(g) Criterios para comenzar la prueba

(h) Criterios para suspender la prueba y requisitos para reiniciar

(i) Documentos de comunicaciones de estado / entregables de prueba

(j) Requisitos de hardware y software

(k) Responsabilidades para determinar la gravedad del problema y corregirlo.

(l) Necesidades de personal y capacitación

(m) Horarios de prueba

(n) Riesgos y contingencias

(o) Aprobaciones

El plan anterior es de un estilo muy general y de alto nivel. El siguiente ejemplo está en un

estilo mucho más detallado, y es más adecuado para planes de pruebas tácticas para ingenieros.

1. Propósito

El propósito del plan de prueba es describir el alcance, enfoque, recursos,

y calendario de todas las actividades de prueba. El plan debe identificar los elementos

a probar, las características a probar, las tareas de prueba a realizar,

el personal responsable de cada tarea, y los riesgos asociados a esta

plan.

El plan de prueba debe ser un documento dinámico que puedan utilizar los evaluadores,

gerentes y desarrolladores. El plan de prueba debe evolucionar a medida que evoluciona el


proyecto.

Al final del proyecto, el plan de prueba debe documentar las actividades y

ser el vehículo por el cual todas las partes firman indicando la aprobación de la final

producto.

2. Esquema

Un plan de prueba tiene la siguiente estructura:

(a) Identificador del plan de prueba

(b) Introducción

(c) Elementos de referencia de prueba


(d) Funciones que se probarán

(e) Funciones que no se probarán

(f) Enfoque de la prueba

(g) Criterios para aprobar / reprobar

(h) Criterios para suspender la prueba y requisitos para reiniciar

(i) Prueba de entregables

(j) Tareas de prueba

(k) Necesidades ambientales

(l) Responsabilidades

(m) Necesidades de personal y capacitación

(n) Horario

(o) Riesgos y contingencias

(p) Aprobaciones

Las secciones están ordenadas en la secuencia anterior. Las secciones adicionales pueden ser

incluido si es necesario. Si parte o todo el contenido de una sección está en otra

documento, se puede incluir una referencia a ese documento. El material de referencia

debe estar fácilmente disponible. Las siguientes secciones brindan detalles sobre el contenido

de cada sección.

3. Identificador del plan de prueba

Dé un identificador único (nombre) a este plan de prueba.

4. Introducción

Proporcione una descripción o el propósito del software, de modo que tanto el probador como el

El cliente tiene claro el propósito del software y el enfoque que debe tomar.

en pruebas.

5. Elementos de referencia de prueba

Identificar los elementos a los que hacen referencia las pruebas, incluida su versión / revisión.

y fechas. Proporcione referencias a los siguientes documentos, si están disponibles:

(a) Especificación de requisitos

(b) Especificación de diseño


(c) Guía del usuario

(d) Guía de operaciones

(e) Guía de instalación

(f) Diagramas de análisis, incluido el flujo de datos, etc.

(g) UML u otros documentos de modelado

6. Funciones que se probarán

Identifique todas las funciones y combinaciones de funciones que deben probarse. Identificar

el diseño de prueba que está asociado con cada característica y cada combinación de

características.

7. Funciones que no se probarán

Identifique todas las características y combinaciones significativas de características que no serán

probado. Lo más importante es indicar por qué.

8. Enfoque de las pruebas

Para cada grupo principal de características o combinaciones de características, especifique el


enfoque

que asegurará que estos grupos de características se prueben adecuadamente. Especificar

las principales actividades, criterios y herramientas que se utilizarán.

El enfoque debe describirse con suficiente detalle para identificar los principales

tareas de prueba y estime cuánto tiempo tomará cada una.

9. Criterios para aprobar / reprobar

Especifique la medida que se utilizará para determinar si cada elemento de prueba tiene

prueba aprobada o fallida. ¿Se basará en un criterio? El número de conocidos

fallas?

10. Criterios para suspender las pruebas y requisitos para reiniciar

En determinadas situaciones, las pruebas deben detenerse y el software debe devolverse a los
desarrolladores.

Especifique los criterios utilizados para suspender todo o parte de la prueba.

Especifique las actividades que deben repetirse para reanudar o reiniciar las actividades de
prueba.

11. Entregables de prueba


Identifique los documentos que deben incluirse en el informe. La siguiente

son documentos candidatos.

(a) Plan de prueba

(b) Especificaciones de diseño de prueba

(c) Especificaciones del caso de prueba

(d) Proceso de prueba

(e) Registros de prueba

(f) Probar informes de problemas

(g) Informes resumidos de las pruebas

(h) Pruebe los datos de entrada y los datos de salida de prueba (o dónde se encuentran)

12. Tareas de prueba

Identificar las tareas necesarias para prepararse y realizar las pruebas. Identificar todos

dependencias entre las tareas.

13. Necesidades ambientales

Especifique las propiedades necesarias y deseadas del entorno de prueba.

Esta especificación debe contener:

(a) Las características físicas de las instalaciones, incluido el hardware.

(b) Cualquier software de comunicaciones y sistema

(c) El modo de uso (autónomo, transitorio, basado en la web, etc.)

(d) Cualquier otro software o suministros necesarios para ejecutar la prueba.

(e) Herramientas de prueba necesarias

(f) Cualquier otra necesidad de pruebas (por ejemplo, publicaciones) y dónde conseguirlas

14. Responsabilidades

Identifique los grupos responsables de todos los aspectos de las pruebas y la corrección de
problemas.

Además, identifique los grupos responsables de proporcionar la referencia de la prueba.

elementos identificados y las necesidades ambientales anteriores.

15. Necesidades de personal y capacitación

Especifique las necesidades de personal de prueba en términos de conocimientos y habilidades.


Identificar la formación
opciones cuando sea apropiado y necesario.

16. Horario

Incluya todos los hitos de prueba identificados en el cronograma del proyecto de software. Definir

cualquier hito de prueba adicional necesario. Estime el tiempo necesario para

realizar cada tarea de prueba y especificar el cronograma para cada tarea de prueba y prueba

hito.

17. Riesgos y contingencias

Identifique las suposiciones de riesgo del plan de prueba. Por ejemplo, especializado

el conocimiento puede ser necesario pero no disponible. Especificar planes de contingencia para

cada.

18. Aprobaciones

Especifique los nombres y cargos de todas las personas que deben aprobar este plan, y

incluya un espacio para que firmen y fechen el documento.

6.5 IDENTIFICACIÓN DE SALIDAS CORRECTAS

La principal contribución de este libro es el conjunto de criterios de cobertura para las pruebas.
Pero no

Independientemente del criterio de cobertura que se utilice, tarde o temprano uno quiere saber si

un programa dado se ejecuta correctamente en una entrada determinada. Este es el problema del
oráculo en

pruebas de software.

El problema del oráculo puede ser sorprendentemente difícil de resolver, por lo que es útil tener

una variedad de enfoques disponibles. Esta sección describe varios enfoques comunes

al problema del oráculo.

6.5.1 Verificación directa de salidas

Si tiene suerte, su programa vendrá con una especificación, y la especificación

será claro en cuanto a qué salida acompaña a una entrada determinada. Por ejemplo, un
programa de clasificación

debe producir una permutación de su entrada en un orden específico.

Hacer que un humano evalúe la exactitud de un resultado dado suele ser eficaz,

pero también es caro. Naturalmente, es más económico automatizar este proceso. Automatizando
La verificación directa de la salida, cuando sea posible, es uno de los mejores métodos de
verificación.

comportamiento del programa. A continuación se muestra un esquema de un verificador para


clasificar. Note que el

El algoritmo de comprobación no es otro algoritmo de clasificación. No solo es diferente, no es

particularmente simple. Es decir, escribir comprobadores de salida puede resultar complicado.

Entrada: Estructura S

Hacer copia T de S

Ordena

// Verifica que S sea una permutación de T

Compruebe que S y T sean del mismo tamaño

Para cada objeto en S

Compruebe si el objeto aparece en S y T la misma cantidad de veces

// Verifique que S esté ordenado

Para cada índice i pero último en S

Compruebe si S [i] <= S [i + 1]

Desafortunadamente, la verificación directa no siempre es posible. Considere un programa que

analiza las redes de Petri, que son útiles para modelar procesos con estado. Una salida

de tal análisis es la probabilidad de estar en cualquier estado dado. Es dificil de mirar

una probabilidad dada y afirmar que es correcta; después de todo, es solo un número. Cómo

¿Sabes si todos los dígitos son, de hecho, los correctos? Para las redes de Petri, la final

las probabilidades no pueden relacionarse fácilmente con la red de Petri de entrada.

6.5.2 Cálculos redundantes

Cuando la verificación directa no es aplicable, se pueden utilizar cálculos redundantes en su lugar.

Por ejemplo, para evaluar automáticamente la corrección de un programa mínimo,

se podría usar otra implementación de min, preferiblemente una confiable o "gold"

versión. Esto inicialmente parece ser una circularidad; ¿Por qué debería uno confiar en una
implementación?

mas que otro?

Formalicemos el proceso. Suponga que el programa bajo prueba tiene la etiqueta P,


y P (t) es la salida de P en la prueba t. Una especificación S de P también especifica una salida

S (t), y normalmente exigimos que S (t) = P (t) .1 Suponga que S es, en sí mismo, ejecutable,

lo que nos permite automatizar el proceso de verificación de salida. Si S mismo contiene

una o más fallas, una ocurrencia común, S (t) puede muy bien ser incorrecta. Si P (t) es

incorrecto exactamente de la misma manera, el fallo de P pasa desapercibido. Si P falla en algunos

que sea diferente de S en alguna prueba t, entonces se investigará la discrepancia,

con al menos la posibilidad de que se descubran las fallas tanto en S como en P.

Un problema potencial es cuando P y S tienen fallas que resultan en errores y

salidas idénticas (y por lo tanto normales). Algunos autores han sugerido que el

Oracle S debe desarrollarse independientemente de P para reducir esta posibilidad. A partir de


una

Desde el punto de vista práctico, este desarrollo independiente es difícil de lograr.

Además, es muy poco probable que el desarrollo independiente conduzca a fracasos


independientes.

Tanto la evidencia experimental como los argumentos teóricos sugieren que

las fallas ocurren a una tasa sustancialmente superior a la que se esperaría dada una suposición

de independencia. La razón básica de esto es que algunas entradas son "más difíciles" que

otros para hacerlo bien, y son precisamente estas entradas las que tienen más probabilidades de
desencadenar

fallas comunes en múltiples implementaciones.

Aún así, probar una implementación contra otra es una técnica práctica y efectiva.

para las pruebas. En la industria, la técnica se implementa con mayor frecuencia en regresión.

pruebas, donde la versión ejecutable de una especificación S es simplemente la versión anterior

del software. Las pruebas de regresión son extremadamente efectivas para identificar problemas.

en software, y debe ser una parte estándar de cualquier software comercial serio

actividad de desarrollo.

A veces, un problema puede tener diferentes algoritmos para resolverlo e implementaciones

de los diferentes algoritmos son excelentes candidatos para compararlos

otros, aunque el problema común de fallas aún persiste. Por ejemplo, considere

algoritmos de búsqueda. Una rutina de búsqueda binaria podría probarse fácilmente comparando
el resultado con una búsqueda lineal.

6.5.3 Comprobaciones de coherencia

Una alternativa a la verificación directa o los cálculos redundantes es el análisis de coherencia.

El análisis de coherencia suele estar incompleto. Considere el ejemplo de la red de Petri

de nuevo. Dada una probabilidad putativa, ciertamente se puede decir que si es negativa o

más grande que la unidad, entonces está mal. El análisis de coherencia también puede ser interno.
Recordar

el modelo RIP (accesibilidad, infección, propagación) para fallas del Capítulo 1.

Las comprobaciones externas solo pueden examinar las salidas, por lo que la infección debe
propagarse durante

el error a detectar.

Las verificaciones internas aumentan la posibilidad de identificar un comportamiento defectuoso


con solo

primeras dos propiedades (RI). Es bastante común que los programadores requieran ciertas
relaciones

para sujetar estructuras internas. Por ejemplo, una representación de objeto podría

requieren que un contenedor dado nunca contenga objetos duplicados. Marcando estos
"invariantes"

relaciones es una forma extremadamente eficaz de encontrar fallas. Programadores capacitados


en

El desarrollo de software bajo el modelo de contrato puede producir el código para dicha
verificación.

en el curso del desarrollo normal. Para el software orientado a objetos, tales comprobaciones

se organizan típicamente en torno a invariantes de objeto, tanto en la abstracción del objeto

y en su representación, así como en las precondiciones y poscondiciones del método de objeto.

Herramientas como las facilidades de afirmación pueden activar eficientemente tales


verificaciones durante

probarlos y apagarlos, si es necesario para el rendimiento, durante el funcionamiento.

6.5.4 Redundancia de datos

Un método extremadamente poderoso para evaluar la corrección de una entrada dada es


considerar

cómo se comporta el programa en otras entradas. Considere un cálculo del seno


función. Dado un cálculo sin (x) para alguna entrada x, es bastante difícil decidir

si la salida es exactamente correcta. La comparación con otra implementación de sine es

una posibilidad, pero otras técnicas son posibles. Si el seno está disponible, es probable que

el coseno también lo es, y es una identidad que sin (x) 2 + cos (x) 2 = 1, para todos los valores de x.

Esta última comprobación ayuda en muchos casos, pero no ayuda en el caso en que ambos

sin (x) y cos (x) resultan ser incorrectos en formas de compensación. Por ejemplo, si cos

Sucede que se implementa con un llamado al pecado, entonces no hemos avanzado mucho.

Aún así, las identidades son un enfoque extremadamente útil. Además, a menudo trabajan con

clases. Por ejemplo, agregar un elemento a un contenedor y luego eliminar el elemento

del recipiente a menudo tiene un efecto bien definido sobre el recipiente. Para algunos

contenedores, como bolsas, el resultado no es ningún cambio. Para otros contenedores, como

conjuntos, el resultado puede no ser ningún cambio, o puede ser un cambio de un elemento,
dependiendo

sobre si el artículo estaba originalmente en el contenedor.

Aún más poderosas son las identidades que usan el mismo programa, pero en diferentes

entradas. Considere sin (x) nuevamente. Otra identidad es sin (a + b) = sin (a) cos (b) +

cos (a) sin (b) .2 Tenemos una relación en las entradas (es decir, a + b = x) y una relación

en las salidas (sin (x) es una expresión simple en términos de seno aplicada a ayb).

Estas comprobaciones se pueden repetir tantas veces como se desee con diferentes opciones
aleatorias para

el valor de a. Resulta que incluso el implementador más malicioso de una función sinusoidal

tiene una posibilidad muy pequeña de engañar a tal control. Esto es realmente poderoso

comprobación de salida!

Este mtodo se aplica, en su forma ms poderosa, slo a matemticas de buen comportamiento.

funciones como seno. Sin embargo, el enfoque se aplica a muchos otros tipos de

software. Considere una implementación de TCAS, Traffic Collision and Avoidance

Sistema desplegado en aviones comerciales. La función de este sistema es dar

orientación a los pilotos sobre la mejor manera de evitar una posible colisión. En la "resolución
vertical"

modo, las salidas de TCAS, o "advertencias de resolución", son para mantenerse niveladas,
subir o bajar.

TCAS es un sistema complejo que considera muchos factores, incluidos múltiples

posiciones de las distintas aeronaves, la existencia de procesamiento TCAS complementario

en otras aeronaves, proximidad al suelo, etc.

Para aplicar la técnica de redundancia de datos, supongamos que volvemos a ejecutar el software
TCAS

con posiciones ligeramente diferentes para algunos o todos los aviones. Esperaríamos,

en muchos casos, el aviso de resolución no cambiaría. Si la resolución

aviso parecía ser inestable para algunos insumos estrechamente relacionados, habríamos

un fuerte indicio de que el piloto puede que no desee depositar mucha confianza en el

asesoramiento de resolución. De vuelta en el laboratorio, los ingenieros de TCAS podrían querer


pagar

atención especial a tales entradas, tal vez incluso considerándolas fallas.

La técnica ilustrada con el software TCAS se puede aplicar a cualquier software.

donde el espacio de entrada tiene alguna noción de continuidad. En cualquier sistema de este
tipo, hace

sentido para hablar de entradas "cercanas", y en muchos de estos sistemas, la salida puede ser

se espera que varíe de alguna manera continua por partes.

6.6 NOTAS BIBLIOGRÁFICAS

Binder [33] tiene una descripción práctica excelente y detallada de las pruebas de regresión,

en el que afirmó que las pruebas de regresión no automatizadas equivalen a ninguna regresión

pruebas. Rothermel y Harrold publicaron el marco de pruebas de regresión de

inclusividad, precisión, eficiencia y generalidad [303], y evaluó una técnica segura

empíricamente [153]. Artículos posteriores de Li, Harman y Hierons [208] y Xie y

Notkin [361] son buenos lugares para comenzar en la literatura sobre pruebas de regresión.

Las nociones de talones y controladores han existido durante décadas. Fueron discutidos

en libros que se remontan a la década de 1970 [88, 104, 165, 249]. Beizer señaló que

la creación de stubs puede ser costosa y propensa a errores [29]. Herramientas actuales como
JUnit

utilice el término "simulacro" como una forma especializada de un talón [27].


El primer artículo que definió el problema CITO fue el de Kung et al. [197]. Ellos

demostró que cuando las clases no tienen ciclos de dependencia, derivar una integración

El orden es equivalente a realizar una clasificación topológica de clases basada en

su gráfico de dependencia, un conocido problema de teoría de grafos. En la presencia

de los ciclos de dependencia, propusieron una estrategia para identificar

componentes (SCC) y eliminar asociaciones hasta que no queden ciclos. Cuando ahí

es más de un candidato para romper el ciclo, el enfoque de Kung et al. elige

al azar.

La mayoría de los investigadores [197, 48, 321, 329] estimaron el costo CITO contando el

número de apéndices de prueba que deben crearse durante las pruebas de integración. Este
método

asume que todos los stubs son igualmente difíciles de escribir. Briand y col. señaló eso

el costo de los talones no es constante y se desarrolló un algoritmo genético para resolver el

Problema de CITO [44]. Malloy y col. primero intenté considerar la complejidad del stub de prueba
cuando

estimación del esfuerzo de prueba [223].

Briand y col. mostró que la complejidad de la construcción de stub para las clases principales

es inducida por la construcción probable de stubs para la mayoría de las funciones miembro
heredadas

[47]. Abdurazik y Offutt desarrollaron un nuevo algoritmo, basado en el acoplamiento

análisis, que utiliza más información sobre cómo la clase cortada se empareja con otros

clases para encontrar un pedido más barato [3].

Algunas buenas fuentes para obtener detalles sobre el proceso de prueba y las definiciones
aceptadas de

términos son estándares IEEE [175], estándares BCS [317], libros de Hetzel [160], De-

Millo y col. [100], Kaner, Falk y Nguyen [182], Dustin, Rashka y Paul [109],

y Copeland [90].

Weyuker [341] escribió un ensayo temprano identificando el problema del oráculo y varios

enfoques para abordarlo. Tanto Meyer [241] como Liskov [355] hablan sobre cómo

articular afirmaciones comprobables en el contexto del modelo de contrato. Varios comerciales


Las herramientas admiten la comprobación de afirmaciones.

La noción de construir múltiples versiones fue defendida en la tolerancia a fallas

contexto por varios autores, principalmente por Avizienis [22]. Límites de confiabilidad

para software multiversión fueron explorados por primera vez de forma experimental por Knight y
Leveson

[188], luego teóricamente por Eckhardt y Lee [110] y Littlewood y Miller [214],

y en un contexto diferente por Geist et al. [133]. El software de múltiples versiones realmente
funciona

mejor para las pruebas que para la tolerancia a fallos. Si dos versiones del programa se comportan

diferente en las mismas entradas, entonces sabemos que hemos encontrado una buena prueba, y
al menos

una de las versiones es incorrecta. En particular, es útil ver las pruebas de regresión como

un arreglo de prueba de múltiples versiones.

Blum y Kannan [38] y Lipton [210] dan tratamientos teóricos de la redundancia de datos

para ciertos problemas matemáticamente bien definidos; Ammann y Knight

[18] proporcionan un enfoque menos potente, pero más aplicable.

NOTAS

1 Si S está subdeterminado, entonces el requisito S (t) = P (t) no es correcto. En cambio, S debería

ser visto como que permite un conjunto de posibles resultados, y la restricción de corrección es
que P

produce uno de ellos, a saber, P (t) ∈ S (t).

2 Si lo deseamos, podemos reescribir las llamadas cos (x) a las llamadas sin (π / 2 - x), pero esto no
es estrictamente necesario

7 criterios de ingeniería para tecnologías


Este capítulo analiza cómo diseñar los criterios de los capítulos 2 al 5 para

utilizado con varios tipos diferentes de tecnologías. Estas tecnologías han llegado a

prominencia después de gran parte de la literatura de investigación en pruebas de software, pero


ahora son

muy común y representa un gran porcentaje de nuevas aplicaciones que se están creando.

A veces modificamos los criterios y, a veces, simplemente discutimos cómo construir el

modelos a los que se pueden aplicar los criterios existentes. Algunas de estas tecnologías, como
como aplicaciones web y software integrado, tienden a tener una confiabilidad extremadamente
alta

requisitos. Por lo tanto, las pruebas son cruciales para el éxito de las aplicaciones. El capítulo

explica en qué se diferencian estas tecnologías desde el punto de vista de las pruebas, y

resume algunos de los enfoques existentes para probar el software que utiliza las tecnologías.

Las tecnologías orientadas a objetos se hicieron prominentes a mediados de la década de 1990 y


los investigadores

han pasado bastante tiempo estudiando sus problemas particulares. Un número

de los problemas con el software orientado a objetos se han discutido en capítulos anteriores,
incluyendo

varios aspectos de la aplicación de criterios de gráficos en el Capítulo 2, mutación de integración

en el Capítulo 5 y el problema de CITO en el Capítulo 6. Este capítulo analiza cómo

El uso de clases afecta las pruebas y se enfoca en algunos desafíos que los investigadores tienen

solo comencé a abordar. La mayoría de estas soluciones que aún no se han abierto camino

en herramientas automatizadas. El más importante de estos desafíos es la detección de


problemas.

en el uso de herencia, polimorfismo y enlace dinámico.

Una de las áreas más activas en términos de tecnología e investigación de pruebas.

es el de las aplicaciones web y los servicios web. La mayoría del software web está orientado a
objetos

en la naturaleza, pero la Web permite algunas estructuras muy interesantes1 que requieren
probadores

adaptar sus técnicas y criterios. Un aspecto interesante de las aplicaciones web

y servicios es que tienen que funcionar muy bien - el entorno es bastante competitivo

y los usuarios no tolerarán fallas. Las aplicaciones web también tienen estrictas

requisitos de seguridad, que se tratan en el Capítulo 9. Las aplicaciones web se crean

con un tipo particular de interfaz gráfica de usuario, HTML que se ejecuta en un navegador, pero

probar GUI generales trae complejidades adicionales. Algunas de las ideas en este

El capítulo aún se encuentra en la etapa de investigación y desarrollo, por lo que es posible que no
esté listo para

uso práctico.
Por último, este capítulo analiza algunos de los problemas relacionados con las pruebas en tiempo
real y

software. Se combinan porque muchos sistemas incorporan ambos. La

cantidad de software integrado está creciendo muy rápidamente, ya que aparece en todo tipo de

dispositivos mecánicos y electrónicos. Muchos de estos sistemas también tienen

requisitos, un tema que postergamos hasta el Capítulo 9.

7.1 PROBAR SOFTWARE ORIENTADO A OBJETOS

Los lenguajes orientados a objetos enfatizan la definición de abstracciones en el software.


Abstracciones

tales como conceptos de modelos de tipos de datos abstractos en un dominio de aplicación.

Estas abstracciones se implementan en clases que representan tipos definidos por el usuario que

tienen tanto estado como comportamiento. Este enfoque de la abstracción tiene muchos
beneficios, pero

también cambia la forma en que se deben realizar las pruebas. El factor más importante es

que el software orientado a objetos cambia gran parte de la complejidad de nuestro software de

algoritmos en unidades y métodos de cómo conectamos componentes de software. Por lo tanto,


nosotros

necesitan menos énfasis en las pruebas unitarias y más en las pruebas de integración.

Otro factor es que las relaciones en los componentes orientados a objetos tienden a

ser muy complejo. Las relaciones compositivas de herencia y agregación,

especialmente cuando se combina con polimorfismo, introduce nuevos tipos de fallas y

requieren nuevos métodos de prueba. Esto se debe a que la forma en que las clases y los
componentes

están integrados es diferente en los lenguajes orientados a objetos.

Los lenguajes orientados a objetos utilizan clases (abstracción de datos), herencia, polimorfismo,

y enlace dinámico para soportar la abstracción. Nuevos tipos creados por herencia

son descendientes del tipo existente. Una clase extiende su clase padre si introduce

un nuevo nombre de método y no anula ningún método en un antepasado

clase. (Este nuevo método se llama método de extensión). Una clase refina el padre

clase si proporciona un nuevo comportamiento que no está presente en el método anulado, no


llama
método anulado, y su comportamiento es semánticamente coherente con el del

método anulado.

Los programadores utilizan dos tipos de herencia, subtipo y subclase. Si la clase B usa

herencia de subtipos de la clase A, entonces es posible sustituir libremente cualquier instancia

de clase B para una instancia de A y aún así satisfacer a un cliente arbitrario de clase A.

se llama principio de sustitución. En otras palabras, B tiene una relación "es-a" con

R. Por ejemplo, una silla “es un” caso especial de un mueble. La herencia de subclase permite

clases descendientes para reutilizar métodos y variables de clases ancestrales sin

asegurando necesariamente que las instancias de los descendientes cumplan con las
especificaciones

del tipo de ancestro. Aunque ha habido una intensa discusión sobre qué uso de

la herencia es apropiada, desde la perspectiva del evaluador, los programadores profesionales

utilice ambos tipos. En el caso de la herencia de subtipos, los evaluadores deben centrarse en
verificar

que se cumple el principio de sustitución. La falta de principios rectores firmes en la subclase

La herencia brinda a los probadores amplias oportunidades para encontrar fallas.

Estas abstracciones tienen efectos importantes en la integración de componentes. Si la clase B


hereda

de la clase A, y tanto A como B definen un método m (), entonces m () se llama

método polimórfico. Si se declara que un objeto x es de tipo A (en Java, "A x;"),

luego, durante la ejecución, x puede tener el tipo A real (de "x = new A ();")

o B (de "x = nuevo B ();"). Cuando se realiza una llamada a un método polimórfico (por

ejemplo, “x.m ();”), la versión que se ejecuta depende del tipo real actual del objeto. La colección
de métodos que se pueden ejecutar se llama polimórfica

conjunto de llamadas (PCS). En este ejemplo, el PCS para x.m () es {A :: m (), B :: m ()}.

7.1.1 Problemas únicos con la prueba del software OO

Varios problemas de prueba son exclusivos del software orientado a objetos. Algunos
investigadores han

afirmó que las técnicas de prueba tradicionales no son tan efectivas para las aplicaciones
orientadas a objetos
software y, a veces, probar las cosas incorrectas. Los métodos tienden a ser más pequeños y
menos

complejas, por lo que las técnicas de prueba basadas en rutas pueden ser menos útiles. Como se
discutió anteriormente,

la herencia, el polimorfismo y el enlace dinámico introducen problemas especiales.

La ruta de ejecución ya no se basa en el tipo declarado estático de la clase, pero el

tipo dinámico; y eso no se sabe hasta la ejecución.

Cuando se prueba software orientado a objetos, una clase se considera generalmente como la
base

unidad de prueba. Esto conduce a cuatro niveles de clases de prueba.

1. Prueba intra-método: las pruebas se construyen para métodos individuales (esto es tradicional

examen de la unidad)

2. Prueba entre métodos: múltiples métodos dentro de una clase se prueban en conjunto

(esta es la prueba de módulo tradicional)

3. Pruebas intraclase: las pruebas se construyen para una sola clase, generalmente como
secuencias.

de llamadas a métodos dentro de la clase

4. Prueba entre clases: se prueba más de una clase al mismo tiempo, generalmente para ver cómo
interactúan (este es un tipo de prueba de integración)

Las primeras investigaciones en pruebas orientadas a objetos se centraron en el método inter e


intraclase

niveles. La investigación posterior se centró en la prueba de interacciones entre

clases y sus usuarios y pruebas a nivel de sistema del software OO. Problemas asociados

con herencia, la unión dinámica y el polimorfismo no se pueden abordar en el

niveles entre métodos o intraclase. Estos requieren múltiples clases que están acopladas

a través de la herencia y el polimorfismo, es decir, pruebas entre clases.

La mayor parte de la investigación en pruebas orientadas a objetos se ha centrado en uno de dos


problemas.

Uno es el orden en el que las clases deben integrarse y probarse. El CITO

El problema se discutió en el Capítulo 6. El otro es el desarrollo de técnicas y cobertura

Criterios de selección de pruebas. Estos criterios de cobertura son refinamientos de uno o


más de los criterios presentados en los capítulos anteriores.

7.1.2 Tipos de fallas orientadas a objetos

Una de las tareas más difíciles para los ingenieros de software orientados a objetos es visualizar las
interacciones

que puede ocurrir en presencia de herencia, polimorfismo y dinámica

Unión. ¡A menudo son muy complejos! Esta visualización asume que una clase encapsula

información de estado en una colección de variables de estado y tiene un conjunto de


comportamientos

que se implementan mediante métodos que utilizan esas variables de estado.

Como ejemplo, considere el diagrama de clases de UML y el fragmento de código que se muestran

en la Figura 7.1. En la figura, V y X extienden W, V anula el método m () y X

anula los métodos m () y n (). Las menos ("-") indican que los atributos son privados

y los más (“+”) indican que los atributos no son privados. El declarado

el tipo de o es W, pero en la línea 10, el tipo real puede ser V o W. Dado que V

anula m (), la versión de m () que se ejecuta depende de la bandera de entrada a la

método f ().

Para ilustrar problemas de anulación de métodos y polimorfismo, considere el

jerarquía de herencia simple que se muestra a la izquierda de la Figura 7.2. La clase raíz A contiene

cuatro variables de estado y seis métodos. Las variables de estado están protegidas, que

significa que están disponibles para los descendientes de A (B y C). B declara una variable de
estado
y tres métodos y C declara tres métodos. Las flechas en la figura

muestre la anulación: B :: h () anula A :: h (), B :: i () anula A :: i (), C :: i () anula

B :: i (), C :: j () anula A :: j () y C :: l () anula A :: l (). La mesa a la derecha de

La figura 7.2 muestra las definiciones y usos de las variables de estado para algunos de los
métodos.

en la jerarquía. El problema comienza con una llamada a A :: d (). Este pequeño ejemplo tiene

anula m (), la versión de m () que se ejecuta depende de la bandera de entrada a la


método f ().

Para ilustrar problemas de anulación de métodos y polimorfismo, considere el

jerarquía de herencia simple que se muestra a la izquierda de la Figura 7.2. La clase raíz A contiene

cuatro variables de estado y seis métodos. Las variables de estado están protegidas, que

significa que están disponibles para los descendientes de A (B y C). B declara una variable de
estado

y tres métodos y C declara tres métodos. Las flechas en la figura

muestre la anulación: B :: h () anula A :: h (), B :: i () anula A :: i (), C :: i () anula

B :: i (), C :: j () anula A :: j () y C :: l () anula A :: l (). La mesa a la derecha de

La figura 7.2 muestra las definiciones y usos de las variables de estado para algunos de los
métodos.

en la jerarquía. El problema comienza con una llamada a A :: d (). Este pequeño ejemplo tiene g ()
llama a h (), se ejecuta la versión de h () definida en B (la línea de trazos claros de

A :: g () a A :: h () enfatiza que A :: h () no se ejecuta). Entonces el control continúa

B :: i (), A :: i (), y luego a A :: j ().

Cuando el objeto es de tipo C real, podemos ver de dónde viene el término "yo-yo"

de. El control procede de A :: g () a B :: h () a C :: i (), luego retrocede a través de B :: i ()

a A :: i (), de vuelta a C :: j (), de vuelta a B :: k (), y finalmente a C :: l ().

Este ejemplo ilustra algunas de las complejidades que pueden resultar en

programas debido a la anulación de métodos y al polimorfismo. Junto con esto

La complejidad inducida conlleva más dificultad y esfuerzo requerido en las pruebas.

Categorías de fallas y anomalías hereditarias

La herencia ayuda a los desarrolladores a ser más creativos, más eficientes y reutilizar previamente

componentes de software existentes. Desafortunadamente, también permite una serie de

anomalías y fallas potenciales que la evidencia anecdótica ha demostrado que son algunas de las

los problemas más difíciles de detectar, diagnosticar y corregir. La tabla 7.1 resume la

tipos de fallas que resultan de la herencia y el polimorfismo. La mayoría se aplica a toda la


programación.

idiomas, aunque el idioma que se utiliza afectará los detalles de cómo

las fallas miran.


Como se señaló anteriormente, las fallas orientadas a objetos son diferentes de las fallas en

Software OO. La siguiente discusión asume que se manifiesta una anomalía o falla

a través del polimorfismo en un contexto que utiliza una instancia del antepasado. Por lo tanto,
nosotros

Supongamos que las instancias de clases descendientes pueden sustituirse por instancias de la

antepasado.

En una falla de uso de tipo inconsistente (ITU), una clase descendiente no anula ninguna

método heredado. Por tanto, no puede haber comportamiento polimórfico. Cada instancia de

una clase descendiente C que se usa cuando se espera una instancia del ancestro T

solo puede comportarse exactamente como una instancia de T.Es decir, solo los métodos de T
pueden ser

usó. Cualquier método adicional especificado en C está oculto ya que la instancia de C es

siendo utilizado como si fuera una instancia de T. Sin embargo, todavía es posible un
comportamiento anómalo.

Si una instancia de C se usa en múltiples contextos (es decir, mediante coerción, digamos

primero como una T, luego como una C, luego una T nuevamente), puede ocurrir un
comportamiento anómalo si C tiene

métodos de extensión. En este caso, uno o más de los métodos de extensión pueden llamar

un método de T o definir directamente una variable de estado heredada de T.Anómalo


El comportamiento ocurrirá si cualquiera de estas acciones da como resultado una incoherencia
heredada

Expresar.

La figura 7.4 muestra un ejemplo de jerarquía de clases. Class Vector es un dato secuencial

estructura que soporta el acceso directo a sus elementos. Class Stack usa métodos heredados

de Vector para implementar la pila. La tabla superior resume las llamadas realizadas

por cada método, y la tabla inferior resume las definiciones y usos (representados

como "d" y "u", respectivamente) del espacio de estado de Vector.

El método Stack :: pop () llama a Vector :: removeElementAt () y Stack :: push ()

llama a Vector :: insertElementAt (). Estas dos clases tienen claramente una semántica diferente.

Siempre que una instancia de Stack se use solo como Stack, no habrá ningún comportamiento

problemas. Además, si la instancia de Stack se usa solo como un vector, no habrá

cualquier problema de comportamiento. Sin embargo, si el mismo objeto se usa a veces como una
pila

ya veces como Vector, pueden ocurrir problemas de comportamiento.

El fragmento de código de la Tabla 7.1.2 ilustra este problema. Tres elementos son
empujado a una pila s, entonces se llama al método vectorial g (). Desafortunadamente, g ()
elimina

un elemento del medio de la pila, que viola su semántica. Peor,

los tres estallidos después de la llamada a g () ya no funcionan. La culpa se manifiesta cuando

control llega a la primera llamada a Stack :: pop () en la línea 14. Aquí, el elemento eliminado

de la pila no es el último elemento que se agregó, por lo tanto, la restricción de integridad de la


pila

será violado. En la tercera llamada a Stack :: pop (), el programa probablemente

falla porque la pila está vacía.

En una falla de anomalía de definición de estado (SDA), las interacciones de estado de un


descendiente

no son consistentes con los de su antepasado. Los métodos de refinación implementados en

el descendiente debe dejar al antepasado en un estado que sea equivalente al estado

que los métodos anulados del antepasado habrían dejado al antepasado adentro. Para que esto

ser cierto, los métodos de refinamiento proporcionados por el descendiente deben tener el mismo
estado

interacciones como cada método público que se anula. Desde una perspectiva de flujo de datos,

esto significa que los métodos de refinamiento deben proporcionar definiciones para los
heredados

variables de estado que son coherentes con las definiciones del método anulado. Si

no, entonces existe una anomalía potencial en el flujo de datos. Sea o no una anomalía en realidad

ocurre depende de las secuencias de métodos que son válidos con respecto a la

antepasado.

La Figura 7.5 muestra un ejemplo de jerarquía de clases y tablas de definiciones y usos.

El padre es de clase W y tiene descendientes X e Y. W define los métodos m () y

n (), cada uno de los cuales tiene las definiciones y usos que se muestran en la tabla. Suponga que
un

La secuencia de llamada de método válida es W :: m () seguida de W :: n (). Como tabla de


definiciones

y usa shows, W :: m () define la variable de estado W :: v y W :: n () la usa. Ahora considera

la clase X y su método de refinamiento X :: n (). También usa la variable de estado W :: v, que es


coherente con el método anulado y con la secuencia del método indicada anteriormente.

Hasta ahora, no hay inconsistencia en cómo X interactúa con el estado de W.

Ahora considere la clase Y y el método Y :: m (), que anula W :: n () hasta

refinamiento. Observe que Y :: m () no define W :: v, como lo hace W :: m (); pero define

Y :: w en su lugar. Ahora, existe una anomalía en el flujo de datos con respecto a la secuencia del
método.

metro(); n () para la variable de estado W :: v. Cuando se invoca esta secuencia de métodos

una instancia de Y, Y :: w se define primero (porque Y :: m () se ejecuta), pero luego W :: v es

utilizado por el método X :: n (). Por lo tanto, la suposición hecha en la implementación de X :: n ()

que W :: v está definido por una llamada a m () antes de una llamada a n () ya no se cumple, y un

Se ha producido una anomalía en el flujo de datos. En este ejemplo en particular, se produce una
falla ya que

no existe una definición previa de W :: v cuando Y es el tipo de instancia que se está utilizando.

En general, es posible que el programa no falle en este punto, sino que solo cree una

Expresar.

En una inconsistencia de definición de estado debido a la falla de ocultación de variable de estado


(SDIH), introduciendo

una variable de estado local puede provocar una anomalía en el flujo de datos. Si una variable
local v

se introduce en una definición de clase y el nombre de la variable es el mismo que un heredado

variable v, la variable heredada está oculta del alcance del descendiente

(a menos que se califique explícitamente, como en super.v). Una referencia a v se referirá al


descendiente

v. Esto no es un problema si se anulan todos los métodos heredados, ya que no hay otros

El método podría hacer referencia implícita a la v heredada. Sin embargo, este patrón

de la herencia es la excepción más que la regla. Algunos métodos generalmente no son

anulado. Puede existir una anomalía en el flujo de datos si un método que normalmente define

La v heredada se anula en un descendiente cuando se oculta una variable de estado heredada.

por una definición local.

Como ejemplo, considere nuevamente la jerarquía de clases que se muestra en la Figura 7.5.
Suponer
la definición de la clase Y tiene la variable de estado local v que oculta la variable heredada

Virginia Occidental. Además, suponga que el método Y :: m () define v, tal como W :: m () define
W :: v. Dado

la secuencia del método m (); n (), existe una anomalía en el flujo de datos entre W e Y con

respecto a W :: v.

En un estado definido incorrectamente como falla (SDI), un método de anulación define el mismo

variable de estado v que define el método anulado. Si el cálculo realizado

por el método primordial no es semánticamente equivalente al cálculo de la

método anulado con respecto a v, luego el comportamiento dependiente del estado subsiguiente
en el antepasado puede verse afectado, y el comportamiento observado externamente del
descendiente

será diferente del antepasado. Esta no es una anomalía del flujo de datos, pero es un potencial

anomalía de comportamiento.

En una falla indirecta de definición de estado inconsistente (IISD), un descendiente agrega un

método de extensión que define una variable de estado heredada. Por ejemplo, considere

la jerarquía de clases que se muestra en la Figura 7.6 (a), donde Y especifica una variable de estado
xy

método m (), y el descendiente D especifica el método e (). Dado que e () es una extensión

método, no se puede llamar directamente desde un método heredado, (T :: m ()), porque e ()

no es visible para el método heredado. Sin embargo, si se anula un método heredado,

el método primordial (como D :: m () como se muestra en la Figura 7.6 (b) puede llamar a e ()

e introducir una anomalía en el flujo de datos al tener un efecto en el estado del antepasado

que no es semánticamente equivalente al método anulado (por ejemplo, con respecto a

la variable T :: y en el ejemplo). Que ocurra un error depende del estado

La variable está definida por e (), donde e () se ejecuta en la secuencia de llamadas realizadas por
un

cliente, y qué comportamiento dependiente del estado tiene el antepasado en la variable definida

adiós().

En una falla de comportamiento de construcción anómalo, versión 1 (ACB1), el constructor

de una clase ancestral C llama a un método polimórfico definido localmente f (). Porque f ()

es polimórfico, una clase descendiente D puede anularlo. Si D lo hace, entonces la versión de D de


f () se ejecutará cuando el constructor de C llame a f (), no a la versión definida por C.

Para ver esto, considere la jerarquía de clases que se muestra en la mitad izquierda de la Figura
7.7. Clase C

constructor llama a C :: f (). La clase D contiene el método predominante D :: f () que define

la variable de estado local D :: x. No hay interacción aparente entre D y C

ya que D :: f () no interactúa con el estado de C. Sin embargo, C interactúa con D's

Estado a través de la llamada que el constructor de C hace a C :: f (). En la mayoría de las personas
orientadas a objetos

lenguajes (incluidos Java y C-Sharp), llamadas de constructor a polimórficos

Los métodos ejecutan el método más cercano a la instancia que se está creando.

Para la clase C en la jerarquía de la figura 7.7, la versión más cercana de f () a C es

especificado por C mismo y, por lo tanto, se ejecuta cuando se está construyendo una instancia de
C.

Para D, la versión más cercana es D :: f (), lo que significa que cuando una instancia de Dis está es
posible que se haya pasado por alto la inicialización de una variable de estado particular. En esto

caso, existe una anomalía en el flujo de datos entre el constructor y cada uno de los métodos

que usará primero la variable después de la construcción (y cualquier otro uso hasta una definición

ocurre).

Un ejemplo de construcción incompleta se muestra en el fragmento de código en

Cuadro 7.3. La clase AbstractFile contiene la variable de estado fd que no es inicializada por

un constructor. La intención del diseñador de AbstractFile es que una clase descendiente

proporcionar la definición de fd antes de que se utilice, que se realiza mediante el método open ()
en el

clase descendiente SocketFile. Si cualquier descendiente del que se pueda crear una instancia
define fd,

y ningún método usa fd antes de la definición, no hay problema. Sin embargo, una falla

Ocurrirá si alguna de estas condiciones no se cumple.

Observe que si bien la intención del diseñador es que un descendiente proporcione la

definición necesaria, existe una anomalía en el flujo de datos dentro de AbstractFile con respecto

af d para los métodos read () y write (). Ambos métodos usan fd, y si alguno
se llama inmediatamente después de la construcción, se producirá una falla. Tenga en cuenta que
esto

El diseño introduce el no determinismo en AbstractFile ya que no se conoce en el diseño.

tiempo a qué tipo de instancia se vinculará fd, o si se vinculará en absoluto.

Supongamos que el diseñador de AbstractFile también diseñó e implementó Socket-

Archivo, como también se muestra en la Tabla 7.3. Al hacerlo, el diseñador se ha asegurado de que
el

La anomalía del flujo de datos que existe en AbstractFile se evita mediante el diseño de Socket-

Archivo. Sin embargo, esto todavía no elimina el problema del no determinismo y

la introducción de fallas ya que un nuevo descendiente puede fallar en proporcionar la necesaria

definición.

En una falla de anomalía de visibilidad de estado (SVA), las variables de estado en una clase de
ancestro

A se declaran privados y un método polimórfico A :: m () define A :: v. Suponer

que B es descendiente de A y C de B, como se muestra en la Figura 7.8 (a). Además, C

proporciona una definición primordial de A :: m () pero B no. Dado que A :: v tiene privado

visibilidad, no es posible que C :: m () interactúe correctamente con el estado de A

definiendo directamente A :: v. En su lugar, C :: m () debe llamar a A :: m () para modificar v. Ahora


suponga

que B también anula m (Figura 7.8 (b)). Luego, para que C :: m () defina correctamente A :: v,

C :: m () debe llamar a B :: m (), que a su vez debe llamar a A :: m (). Por tanto, C :: m () no tiene

control sobre la anomalía del flujo de datos! En general, cuando hay presentes variables de estado
privadas, la única forma de asegurarse de evitar una anomalía en el flujo de datos es por cada

en un descendiente para llamar al método reemplazado en su clase antecesora. Falla

Hacerlo probablemente resultará en una falla en el estado y comportamiento de A.

Prueba de herencia, polimorfismo y enlace dinámico

Las pruebas de flujo de datos se pueden aplicar al software OO ampliando el concepto de


acoplamiento

del Capítulo 2, sección 2.4.2. Recuerde que un acoplamiento-def es una última definición

(last-def) en un método que puede alcanzar un primer uso, llamado acoplamiento-uso, en otro
método. Una ruta de acoplamiento entre dos unidades de programa es una ruta de una definición
de acoplamiento

a un uso de acoplamiento. El camino debe estar claro.

En programas que utilizan herencia, polimorfismo y enlace dinámico, identificando

las definiciones, usos y acoplamientos es más compleja, por lo que la semántica de la

Las características del lenguaje OO deben considerarse con mucho cuidado.

En las siguientes definiciones, o es un identificador cuyo tipo es una referencia a un

instancia de un objeto, apuntando a una ubicación de memoria que contiene una instancia (valor)

de algún tipo. La referencia o solo puede referirse a instancias cuyas instancias reales

los tipos son el tipo base de o o un descendiente del tipo de o. Así, en Java

declaración A o = new B () ;, el tipo base de o, o tipo declarado, es A y está instanciado

tipo, o tipo real, es B. B debe ser descendiente de A.

La figura 7.9 muestra una familia tipo con raíces en W. Todos los miembros de una familia tipo
comparten

algún comportamiento común, como se define en los antepasados de cada clase. Cada definición
de tipo

por una clase define una familia de tipos. Los miembros de la familia incluyen el tipo base de

una jerarquía y todos los tipos que son descendientes de ese tipo base. La figura 7.9 (b) muestra

las cuatro familias de tipos definidas por la jerarquía en la Figura 7.9 (a).

Figura 7.10. Fragmento (a) del diagrama de flujo de control y definiciones asociadas

y usos (b).

El conjunto satisfactorio para la llamada a m () en el nodo 2 es {W.m (), X.m (), Y.m (), Z.m ()}

y el conjunto i-def contiene los siguientes pares ordenados:

yo def (2, o, m ()) = {(W.m (), {W.v}), (X.m (), {W.v, X.x}),

(Y.m (), {W.v, Y.w}), (Z.m (), {W.v})}

Cada par de definiciones y usos en la Figura 7.10 indica un método satisfactorio para

un método y el conjunto de variables de estado que define el método. En este ejemplo,

X.m () define las variables de estado v de la clase W yx de X.

De la figura 7.10 (b), el conjunto de i-use para el nodo 2 está vacío, ya que ninguno de los
satisfactorios
Los métodos para m () hacen referencia a cualquier variable de estado. Sin embargo, hay dos
métodos que

satisfacer la llamada a o.n () en el nodo 3 que tienen conjuntos de i-use no vacíos (pero sus
conjuntos de i-def

están vacíos), lo que produce el siguiente conjunto de i-use:

yo uso (3, o, n ()) = {(W.n (), {W.v}), (Z.n (), {W.v})}

Analizar caminos polimórficos

Los pares def-use y las rutas de acoplamiento son más complicados en los programas orientados a
objetos.

En las siguientes definiciones, m () es un método, Vm es el conjunto de variables a las que se hace


referencia

por m (), y Nm el conjunto de nodos en el gráfico de flujo de control de m (). Además, defs (i) es

el conjunto de variables definidas en el nodo i y usos (i) es el conjunto de variables utilizadas.


entrada (m)

es el nodo de entrada del método m (), exit (m) es el nodo de salida, primero (p) es el primer nodo
en

ruta py last (p) es el último nodo.

Las siguientes definiciones tratan los efectos de la herencia y el polimorfismo.

El conjunto de clases que pertenecen a la misma familia de tipos especificada por c es la familia
(c),

donde c es la clase de ancestro base. type (m) es la clase que define el método m () y

type (o) es la clase c que es el tipo declarado de variable o. o debe hacer referencia a una instancia

de una clase que pertenece a la familia de tipos de c. estado (c) es el conjunto de variables de
estado para la clase

c, ya sea declarado en c o heredado de un antepasado. i-defs (m) es el conjunto de variables

Figura 7.10. Fragmento (a) del diagrama de flujo de control y definiciones asociadas

y usos (b).

El conjunto satisfactorio para la llamada a m () en el nodo 2 es {W.m (), X.m (), Y.m (), Z.m ()}

y el conjunto i-def contiene los siguientes pares ordenados:

yo def (2, o, m ()) = {(W.m (), {W.v}), (X.m (), {W.v, X.x}),

(Y.m (), {W.v, Y.w}), (Z.m (), {W.v})}

Cada par de definiciones y usos en la Figura 7.10 indica un método satisfactorio para
un método y el conjunto de variables de estado que define el método. En este ejemplo,

X.m () define las variables de estado v de la clase W yx de X.

De la figura 7.10 (b), el conjunto de i-use para el nodo 2 está vacío, ya que ninguno de los
satisfactorios

Los métodos para m () hacen referencia a cualquier variable de estado. Sin embargo, hay dos
métodos que

satisfacer la llamada a o.n () en el nodo 3 que tienen conjuntos de i-use no vacíos (pero sus
conjuntos de i-def

están vacíos), lo que produce el siguiente conjunto de i-use:

yo uso (3, o, n ()) = {(W.n (), {W.v}), (Z.n (), {W.v})}

Analizar caminos polimórficos

Los pares def-use y las rutas de acoplamiento son más complicados en los programas orientados a
objetos.

En las siguientes definiciones, m () es un método, Vm es el conjunto de variables a las que se hace


referencia

por m (), y Nm el conjunto de nodos en el gráfico de flujo de control de m (). Además, defs (i) es

el conjunto de variables definidas en el nodo i y usos (i) es el conjunto de variables utilizadas.


entrada (m)

es el nodo de entrada del método m (), exit (m) es el nodo de salida, primero (p) es el primer nodo
en

ruta py last (p) es el último nodo.

Las siguientes definiciones tratan los efectos de la herencia y el polimorfismo.

El conjunto de clases que pertenecen a la misma familia de tipos especificada por c es la familia
(c),

donde c es la clase de ancestro base. type (m) es la clase que define el método m () y

type (o) es la clase c que es el tipo declarado de variable o. o debe hacer referencia a una instancia

de una clase que pertenece a la familia de tipos de c. estado (c) es el conjunto de variables de
estado para la clase

c, ya sea declarado en c o heredado de un antepasado. i-defs (m) es el conjunto de variables que


se definen indirectamente dentro de m () y i-uses (m) es el conjunto de variables utilizadas por

metro().

Una secuencia de acoplamiento es un par de nodos que llaman a dos métodos en secuencia; el
primero
El método define una variable y el segundo la usa. Ambas llamadas deben realizarse a través de

el mismo objeto de instancia. El método de llamada se llama método de acoplamiento f (), y

llama a m (), el método antecedente, para definir x, y n (), el método consecuente, para

utilizar x. Esto se ilustra en la Figura 7.11 (repetida del Capítulo 2). Si el antecedente

o método consecuente es el mismo que el método de acoplamiento, que es un caso especial que

se maneja implícitamente. Si el método antecedente o consecuente se llama desde otro

método que es llamado por f (), este es un flujo de datos indirecto (lado derecho de la Figura 7.11),

que no discutimos.

El esquema de flujo de control que se muestra en la Figura 7.12 ilustra el método de acoplamiento

llamando tanto al método antecedente como al consecuente. El esquema se abstrae

los detalles del diagrama de flujo de control y muestra solo los nodos que son relevantes para el
acoplamiento

análisis. Los segmentos de línea delgada representan el flujo de control y las líneas más gruesas

indican el flujo de control que forma parte de una ruta de acoplamiento. Los segmentos de línea
pueden representar

múltiples subrutas. Una ruta se puede anotar con un conjunto de transmisión como [o, o.v],

que contiene variables para las que la ruta es de definición clara.

Suponiendo que las subtrayectorias intermedias están definidas con respecto al estado

variable o.v, el camino en la figura 7.12 de h a i a j a k y finalmente l forma una transmisión

camino con respecto a o.v. El objeto o se llama variable de contexto.

Cada secuencia de acoplamiento s j, k tiene algunas variables de estado que están definidas por el

método antecedente y luego utilizado por el método consecuente. Este conjunto de variables

es el conjunto de acoplamiento t

s j, k de s j, k y se define como la intersección de esas variables

definido por m () (una definición indirecta o i-def) y utilizado por n () (un uso indirecto o i-uso)

a través del contexto de instancia proporcionado por una variable de contexto o que está
vinculada a un

instancia de t. Qué versiones de m () y n () se ejecutan está determinada por el tipo real

t de la instancia vinculada a o. Los miembros del conjunto de acoplamiento se denominan


acoplamiento.
variables.

Las secuencias de acoplamiento requieren que haya al menos una ruta definida entre

cada nodo de la secuencia. Identificar estos caminos como parte de secuencias completas de

nodos da como resultado el conjunto de rutas de acoplamiento. Se considera que una ruta de
acoplamiento transmite

una definición de una variable para un uso.

Cada ruta consta de hasta tres subrutas. La subruta de definición indirecta es la porción

de la ruta de acoplamiento que se produce en el método antecedente m (), que se extiende desde

la última definición (indirecta) de una variable de acoplamiento al nodo de salida de m (). La

La subruta de uso indirecto es la parte del método consecuente n () que se extiende desde

el nodo de entrada de n () al primer uso (indirecto) de una variable de acoplamiento. La


transmisión

subruta es la parte de la ruta de acoplamiento en el método de acoplamiento que se extiende

del nodo antecedente al nodo consecuente, con el requisito de que ninguno

se modifica el valor de la variable de acoplamiento ni la variable de contexto.

Cada secuencia de acoplamiento tiene un único conjunto de rutas de acoplamiento para cada tipo
de acoplamiento.

subruta. Estos conjuntos se utilizan para formar rutas de acoplamiento haciendo coincidir

elementos de cada conjunto. El conjunto de caminos de acoplamiento se forma mediante la


combinación de elementos.

del conjunto de subtrayectos de definición indirecta con un elemento del conjunto de


subtrayectos de transmisión,

y luego agregar un elemento del conjunto de subrutas de uso indirecto. El conjunto completo de

Las trayectorias de acoplamiento se forman tomando el producto cruzado de estos conjuntos.

Para ver los efectos de la herencia y el polimorfismo en las rutas, considere la

diagrama de clases que se muestra en la Figura 7.13 (a). La familia de tipos contiene las clases A, B,

y C.La clase A define los métodos m () yn () y las variables de estado u y v. Clase

B define el método l () y anula la versión de n () de A. Del mismo modo, C anula el de A

versión de m (). Las definiciones y usos de cada uno de estos métodos se muestran en

Figura 7.13 (b).

La figura 7.14 muestra las rutas de acoplamiento de un método que utiliza la jerarquía en
Figura 7.13 (a). La figura 7.14 (a) muestra el tipo declarado de la variable de acoplamiento o es

A, y la Figura 7.14 (b) muestra los métodos antecedente y consecuente cuando el

tipo también es A. La secuencia de acoplamiento s j, k se extiende desde el nodo j donde el

se llama al método antecedente m () al sitio de llamada del método consecuente en el nodo

k. Como se muestra, el conjunto de acoplamiento correspondiente para s j, k cuando o está


vinculado a una instancia de A es A

s j, k

= {A.v}. Por lo tanto, el conjunto consta de las rutas de acoplamiento para s j, k que se extienden

desde el nodo e en A.m () al nodo de salida de en A.m (), de regreso al nodo consiguiente k

en el método de acoplamiento, ya través del nodo de entrada de A.n () al nodo g. No hay

ruta de acoplamiento con respecto a A.u porque A.u no aparece en el conjunto de acoplamiento

para A.m () y A.n ().

Ahora, considere el efecto sobre los elementos que componen el conjunto de rutas de
acoplamiento

cuando o está vinculado a una instancia de B, como se muestra en la figura 7.14 (c). El juego de
acoplamiento para

este caso es diferente de cuando o estaba vinculado a una instancia de A. Esto se debe a que B

proporciona un método de anulación B.n () que tiene un conjunto de uso diferente al anulado

método A.n (). Así, el conjunto de acoplamiento es diferente con respecto al antecedente

método A.m () y el método consiguiente B.n (), dando como resultado B

s j, k

= {A.u}. En turno,

esto da como resultado un conjunto diferente de rutas de acoplamiento. El conjunto de caminos


de acoplamiento ahora se extiende

desde el nodo f en A.m () de regreso a través del sitio de llamada en el nodo k en el método de
acoplamiento,

ya través del nodo de entrada de B.n () al nodo g de B.n ().

Finalmente, la Figura 7.14 (c) muestra la secuencia de acoplamiento que resulta cuando o está
ligado

a una instancia de C. Primero, observe que la ejecución del nodo j en el método de acoplamiento

da como resultado la invocación del método antecedente, que ahora es C.m (). Igualmente,
la ejecución del nodo k da como resultado la invocación del método consecuente n (). Desde

C no anula m () y como C es descendiente de B, la versión de n ()

que se invoca es en realidad B.n (). Por tanto, el conjunto de acoplamiento para s j, k se toma con
respecto

al método antecedente C.m () y al método consecuente B.n (), que

produce Cs

j, k

= {A.u}. El conjunto de rutas de acoplamiento correspondiente incluye las rutas que

comenzar en el nodo e en C.m () y extender hasta el nodo de salida de C.m (), luego volver al nodo

j del método de acoplamiento, y a través del nodo de entrada de B.n () al nodo g, también en

B.n ().

La Tabla 7.4 resume las rutas de acoplamiento para los ejemplos que se muestran en la Figura

7.14. Los caminos se representan como secuencias de nodos. Cada nodo tiene la forma

método (nodo), donde método es el nombre del método que contiene el nodo,

y nodo es el identificador de nodo dentro del método. Tenga en cuenta que los prefijos "llamar"

o "return" se agregan a los nombres de los nodos que corresponden a call o return

sitios.

Para tener en cuenta la posibilidad de un comportamiento polimórfico en un sitio de llamada, la


definición

de una secuencia de acoplamiento debe modificarse para manejar todos los métodos que pueden

ejecutar. Un triple de unión para una secuencia de acoplamiento contiene el método antecedente

m (), el método consecuente n () y el conjunto de variables de acoplamiento que resultan de

el enlace de la variable de contexto a una instancia de un tipo particular. El triple

coincide con un par de métodos p () y q () que potencialmente se pueden ejecutar como resultado
de

ejecutando los nodos antecedente y consecuente j y k. Cada uno puede ser de diferente

clases que son miembros de la familia de tipos definida por c, siempre que p () sea un

método para m () o q () es un método predominante para n (). Habrá exactamente

un triple vinculante para cada clase d ∈ familia (c) que define un método primordial para

ya sea m () o n ().
Una secuencia de acoplamiento induce un conjunto de triples de unión. Este conjunto siempre
incluye

el triple vinculante que corresponde a los métodos antecedente y consecuente, incluso

cuando no hay un método anulado. En este caso, el único miembro de la vinculación

triple set será el tipo declarado de la variable de contexto, asumiendo que el tipo no es

resumen. Si el tipo es abstracto, una instancia del descendiente concreto más cercano a

se utiliza el tipo declarado.

Como ejemplo, el conjunto de triples de unión para la secuencia de acoplamiento s j, k que se


muestra

en la Figura 7.14 se muestra en la Tabla 7.5. La primera columna da el tipo t del contexto

variable de s j, k, las dos columnas siguientes son los métodos antecedente y consecuente

que se ejecutan para una t en particular, y la columna final da el conjunto de variables de


acoplamiento

inducida cuando la variable de contexto está vinculada a una instancia de t. La jerarquía de tipos

correspondiente al tipo de acoplamiento t se muestra en la Figura 7.13.

Las rutas de acoplamiento de instancias anteriores no permiten el comportamiento polimórfico


cuando

el tipo real difiere del tipo declarado. Esto requiere que un acoplamiento de instancias

da como resultado un conjunto de rutas para cada miembro de la familia de tipos. El número de

Las rutas están limitadas por el número de métodos primordiales, ya sea definidos directamente o

heredado de otro tipo. Las rutas de acoplamiento polimórficas se forman considerando

cada triple de unión.

Criterios de prueba orientados a objetos

El análisis anterior permite identificar definiciones y usos de acoplamiento en presencia

de herencia y polimorfismo. Esta información se utiliza para respaldar las pruebas por

Adaptar los criterios de flujo de datos del Capítulo 2 para definir subrutas en programas OO

que debe ser probado.

Los criterios de flujo de datos del Capítulo 2 están adaptados para herencia y polimorfismo.

como sigue. En las definiciones, f () representa un método que se está probando, s j, k es un

secuencia de acoplamiento en f (), donde j y k son nodos en el diagrama de flujo de control de f (),
y Ts j, k representa un conjunto de casos de prueba creados para satisfacer s j, k.

El primer criterio se basa en la suposición de que cada secuencia de acoplamiento debe

cubrirse durante las pruebas de integración. En consecuencia, todas las secuencias de


acoplamiento requieren

que cada secuencia de acoplamiento en f () esté cubierta por al menos un caso de prueba.

Definición 7.53 Todas las secuencias de acoplamiento (ACS): Para cada secuencia de acoplamiento

s j en f (), hay al menos un caso de prueba t ∈ Ts j, k tal que hay un acoplamiento

ruta inducida por s j, k que es una subruta de la traza de ejecución de f (t).

ACS no considera herencia o polimorfismo, por lo que el siguiente criterio incluye

contextos de instancia de llamadas. Esto se logra asegurándose de que haya al menos una

prueba para cada clase que pueda proporcionar un contexto de instancia para cada secuencia de
acoplamiento.

La idea es que la secuencia de acoplamiento debe probarse con todos los tipos posibles

sustitución que puede ocurrir en un contexto de acoplamiento dado.

Definición 7.54 All-Poly-Classes (APC): Para cada secuencia de acoplamiento s j, k en

método f (), y para cada clase en la familia de tipos definida por el contexto

de s j, k, hay al menos un caso de prueba t tal que cuando f () se ejecuta usando t,

hay una ruta p en el conjunto de rutas de acoplamiento de s j, k que es una subruta del

rastro de ejecución de f (t).

La combinación (s j, k, c) es factible si y solo si c es el mismo que el declarado

tipo de la variable de contexto para s j, k, o c es un hijo del tipo declarado y define un

método primordial para el método antecedente o consecuente. Es decir, solo clases

que anulan los métodos antecedente o consecuente.

ACS requiere que se cubran las secuencias de acoplamiento, pero no considera la

interacciones de estado que pueden ocurrir cuando pueden estar involucradas múltiples variables
de acoplamiento.

Por lo tanto, algunas definiciones o usos de variables de acoplamiento pueden no cubrirse durante

pruebas.

El siguiente criterio aborda estas limitaciones al requerir que hasta la última definición

de una variable de acoplamiento v en un método antecedente de s j, k alcanza cada primer uso


de v en un método consecuente de s j, k. Por lo tanto, debe haber al menos un caso de prueba que

ejecuta cada ruta de acoplamiento factible p con respecto a cada variable de acoplamiento v.

Definición 7.55 All-Coupling-Defs-Uses (ACDU): para cada variable de acoplamiento

v en cada acoplamiento s j, k de t, hay una ruta de acoplamiento p inducida por s j, k,

tal que p es una subruta de la traza de ejecución de f (t) para al menos una prueba

caso t ∈ Ts j, k.

APC requiere el uso de múltiples contextos de instancia y ACDU requiere definiciones

para llegar a usos. El criterio final fusiona estos requisitos. Además de

herencia y polimorfismo, el criterio All-Poly-Coupling-Defs-Uses requiere

que todas las rutas de acoplamiento se ejecuten para cada miembro de la familia de tipos definida
por

el contexto de una secuencia de acoplamiento.

Definición 7.55 All-Coupling-Defs-Uses (ACDU): para cada variable de acoplamiento

v en cada acoplamiento s j, k de t, hay una ruta de acoplamiento p inducida por s j, k,

tal que p es una subruta de la traza de ejecución de f (t) para al menos una prueba

caso t ∈ Ts j, k.

APC requiere el uso de múltiples contextos de instancia y ACDU requiere definiciones

para llegar a usos. El criterio final fusiona estos requisitos. Además de

herencia y polimorfismo, el criterio All-Poly-Coupling-Defs-Uses requiere

que todas las rutas de acoplamiento se ejecuten para cada miembro de la familia de tipos definida
por

el contexto de una secuencia de acoplamiento.

Definición 7.56 All-Poly-Coupling-Defs-Uses (APCDU): Para cada acoplamiento

secuencia s j, k en el método f (), para cada clase en la familia de tipos definida por

el contexto de s j, k, para cada variable de acoplamiento v de s j, k, para cada nodo m que

tiene una última definición de v y cada nodo n que tiene un primer uso de v, hay en la ubicación
del usuario, el navegador del usuario, el sistema operativo e incluso la hora de

día. Una aplicación web es un programa de software completo que se implementa en la web.

Los usuarios acceden a las aplicaciones web mediante solicitudes HTTP y la interfaz de usuario
normalmente
se ejecuta dentro de un navegador en la computadora del usuario (HTML). Un caso de prueba para
una web

La aplicación se describe como una secuencia de interacciones entre componentes en clientes.

y servidores. Es decir, son caminos de transiciones a través de la aplicación web.

7.2.1 Prueba de sitios web de hipertexto estático

El trabajo inicial en las pruebas de sitios web se centró en la validación del lado del cliente y el lado
del servidor estático

validación de enlaces. Una lista extensa de herramientas de soporte de prueba web existentes
está en

un sitio web mantenido por Hower.2 Las herramientas comerciales y gratuitas disponibles incluyen
un enlace

herramientas de verificación, validadores HTML, herramientas de captura / reproducción,


herramientas de prueba de seguridad y

herramientas de estrés de carga y rendimiento.

Todas estas son herramientas de medición y validación estáticas. Tales pruebas buscan

enlaces muertos, es decir, enlaces a URL que ya no son válidas, y evalúa la navegación

estructura para buscar rutas no válidas entre páginas y accesos directos que los usuarios pueden

querer.

Una forma común de modelar sitios web estáticos es mediante un gráfico, con páginas web como
nodos.

y enlaces como bordes. El gráfico se puede construir comenzando con una página introductoria,

luego, de forma recursiva, realizar una búsqueda en amplitud de todos los enlaces de esa página.
La

El gráfico resultante del sitio web se prueba atravesando todos los bordes del gráfico (borde

cobertura).

7.2.2 Prueba de aplicaciones web dinámicas

Uno de los muchos desafíos con las aplicaciones web de prueba es que la interfaz de usuario (en

el cliente) y la mayor parte del software (en el servidor) están separados. El probador
generalmente

no tiene acceso a los datos, el estado o la fuente en el servidor. Esta sección primero

analiza las estrategias de prueba del lado del cliente y sus limitaciones, luego las pruebas del lado
del servidor
estrategias que se pueden utilizar cuando el evaluador tiene acceso a la implementación.

Pruebas de aplicaciones web en el lado del cliente

La prueba de enlaces de hipertexto estático funciona bien cuando todas las páginas y enlaces
están codificados estáticamente

en HTML, pero no cuando partes del sitio web se crean dinámicamente o incluyen

entradas del usuario. Necesitamos alguna forma de generar entradas para campos de formulario.
Generando

un gráfico de sitio web también se vuelve indecidible si algunas páginas o enlaces solo están
disponibles

después de que se proporcionen ciertas entradas.

Un método es explorar de forma no determinista las "secuencias de acción",

una URL determinada. Los datos para los campos de formulario se pueden elegir a partir de las
entradas suministradas previamente por el

pruebas.

Otro método para generar datos de entrada se basa en la recopilación de datos de

usuarios de una aplicación web. A esto se le llama datos de sesión de usuario. La mayoría de los
servidores web

capturar los datos que los usuarios envían a las aplicaciones web en el servidor, o su

la configuración se puede modificar para recopilar los datos. Estos datos se recopilan en nombre-
valor

pares y se utiliza para generar entradas de prueba.

Otro enfoque para encontrar entradas se llama prueba de derivación. Muchas aplicaciones web

imponer restricciones a las entradas a través de formularios HTML. Estas limitaciones

vienen en dos formas. Los usuarios de la validación de scripts del lado del cliente son programas
pequeños, generalmente escritos

en JavaScript, que se ejecutan en la computadora del cliente y verifican sintácticamente los datos
de entrada

antes de enviarlo al servidor. Esto se usa comúnmente para asegurar que los

los campos se completan, los campos de datos numéricos solo contienen números y similares.

Otro formulario utiliza atributos explícitos asociados con campos de formulario HTML. Por
ejemplo,
un cuadro de texto se puede configurar para permitir solo cadenas hasta una longitud máxima
preestablecida, y

los valores de las listas desplegables están preestablecidos para ser los valores contenidos en el
HTML.

Las pruebas de omisión crean entradas que violan intencionalmente estas reglas de validación y

restricciones, luego envía las entradas directamente a la aplicación web sin permitir

la página web los valida.

Una limitación de ambos enfoques proviene del hecho de que encontrar todos los

pantallas en una aplicación web es indecidible. Dependen de búsquedas heurísticas para intentar

para identificar todas las pantallas, pero si algunas pantallas solo aparecen cuando ciertas
entradas raras son

siempre que sea posible, puede resultar muy difícil encontrarlos. Los enfoques del lado del
servidor pueden tener la

capacidad de mirar la fuente del programa y, por lo tanto, encontrar más pantallas potenciales.

Prueba del lado del servidor de aplicaciones web

Las aplicaciones de software web permiten cambios en el control de ejecución de la aplicación.

que no aparecen en el software tradicional. En programas tradicionales, el control

el flujo está completamente administrado por el programa, por lo que el probador solo puede
afectarlo a través de la prueba

entradas. Las aplicaciones web no tienen esta misma propiedad. Al ejecutar aplicaciones web,

los usuarios pueden interrumpir el flujo de control normal sin alertar al "programa

controlador." El modelo de controlador de programa que todavía se enseña en programación


básica

y las clases de sistema operativo no se aplican exactamente a las aplicaciones web porque

el flujo de control se distribuye entre el cliente y uno o más servidores.

Los usuarios pueden modificar el flujo de control esperado en el cliente presionando la parte
posterior o

Actualizar botones en el navegador o modificando directamente la URL en el navegador.

Estas interacciones introducen cambios imprevistos en el flujo de ejecución, creando

Las rutas de control no se pueden representar con técnicas tradicionales como el flujo de control.

gráficos. Los usuarios también pueden afectar directamente a los datos de formas impredecibles,
por ejemplo,
modificar valores de campos de formulario ocultos. Además, los cambios en la configuración del
lado del cliente

puede afectar el comportamiento de las aplicaciones web. Por ejemplo, los usuarios pueden
convertir

desactiva las cookies, lo que puede provocar un mal funcionamiento de las operaciones
posteriores.

Este análisis conduce a una serie de nuevas conexiones, que clasificamos de la siguiente manera.

􀀀 Los enlaces estáticos tradicionales se representan en HTML con la etiqueta <A>.

􀀀 Los enlaces <A> dinámicos realizan una solicitud desde una página web estática a los
componentes de software

para ejecutar algún proceso. No se envían datos de formulario en la solicitud y el tipo

de la solicitud HTTP siempre es get.

􀀀 Los enlaces de formularios dinámicos realizan una solicitud desde un formulario en una página
web estática enviando

datos a los componentes de software que procesan los datos, utilizando una etiqueta <FORM>. El
tipo

de la solicitud HTTP puede ser get o post, como se especifica en el atributo <method>

de la etiqueta <FORM>. Los datos que se envían a través de formularios impactan en el back-end

procesamiento, que es importante para las pruebas.

El HTML creado dinámicamente se crea mediante componentes de software web, que


normalmente

responde al usuario con documentos HTML. El contenido del HTML

los documentos a menudo dependen de las entradas, lo que complica el análisis.

􀀀 Las GUI creadas dinámicamente y dependientes del estado son páginas HTML cuyo contenido

y la forma están determinados no sólo por las entradas, sino por parte del estado en el

servidor, como la fecha u hora, el usuario, el contenido de la base de datos o la información de la


sesión.

Los documentos HTML pueden contener Javascript, que son de la web

programa de aplicación que se ejecuta en el cliente. También pueden contener enlaces,

que determinan la ejecución del programa. El Javascript y los enlaces pueden

ser diferente en diferentes momentos, que es la forma en que los diferentes usuarios ven
diferentes programas.
􀀀 El usuario introduce las transiciones operativas fuera del control del

HTML o software. Las transiciones operativas incluyen el uso del botón Atrás, el

botón de reenvío y reescritura de URL. Este tipo de transición es nuevo para el software web,

muy difícil de anticipar, y a menudo conduce a problemas porque son

difícil de anticipar para los programadores.

􀀀 Las conexiones de software local se encuentran entre los componentes de software de back-end
en el

servidor web, como llamadas a métodos.

􀀀 Las conexiones de software fuera del sitio ocurren cuando las aplicaciones web acceden a
componentes de software

que están disponibles en un sitio remoto. Se puede acceder a ellos enviando llamadas

o mensajes al software en otro servidor, usando HTTP o alguna otra red

protocolo. Este tipo de conexión, aunque potente, es difícil de analizar porque

el probador no sabe mucho sobre el software externo.

􀀀 Las conexiones dinámicas ocurren cuando los nuevos componentes de software se instalan
dinámicamente

durante la ejecución. Tanto la plataforma J2SE como .NET permiten aplicaciones web

para detectar y utilizar los nuevos componentes. Este tipo de conexión es especialmente

difícil de evaluar porque los componentes no están disponibles hasta después

el software está implementado.

El resultado neto de este tipo de transiciones es que las estructuras de análisis tradicionales

como gráficos de flujo de control, gráficos de llamadas, gráficos de flujo de datos y dependencia de
datos

los gráficos no pueden modelar con precisión las aplicaciones web. Es decir, el programa es posible

el flujo de control no se puede conocer estáticamente. Estas estructuras de análisis son necesarias
para

pruebas, por lo tanto, se necesitan nuevas técnicas para modelar aplicaciones web para admitir
estas

ocupaciones.

Un primer intento de desarrollar pruebas para aplicaciones web intentó aplicar el flujo de datos
análisis de componentes de software web. Los pares de definición-uso se pueden dividir entre
clientes

páginas web y múltiples componentes de software de servidor.

Una sección atómica es una sección de HTML (posiblemente incluye lenguaje de programación

rutinas como JavaScript) que tiene la propiedad de que si se envía parte de la sección

para un cliente, toda la sección es. La sección atómica permite que las aplicaciones web sean

modelado de la misma manera que los bloques básicos y los diagramas de flujo de control
permiten

aplicaciones a modelar. Estos gráficos se pueden utilizar para implementar el gráfico.

criterios en el Capítulo 2.

Hasta ahora, estas ideas solo han aparecido en la literatura de investigación y no

llegó a la aplicación práctica.

7.2.3 Prueba de servicios web

Los servicios web introducen algunas arrugas más para los probadores. Desafortunadamente, el
término

El "servicio web" no ha sido estandarizado y la literatura contiene varios

definiciones, algunas de las cuales entran en conflicto. Los más comunes dependen de tecnologías
particulares.

como XML y el protocolo simple de acceso a objetos (SOAP). Este libro

adoptar un enfoque más genérico. Un servicio web es una aplicación modular distribuida,

cuyos componentes se comunican intercambiando datos en formatos estructurados.

Probar los servicios web es difícil porque son aplicaciones distribuidas con inusuales

comportamientos en tiempo de ejecución. Los detalles de diseño e implementación no están


disponibles,

por lo que los evaluadores deben utilizar las pruebas del lado del cliente. Un solo proceso
empresarial a menudo implica

múltiples servicios web. Además, estos múltiples servicios web pueden estar ubicados en

diferentes servidores, y pertenecen a diferentes empresas. Los servicios web interactúan pasando

mensajes y datos de forma estructurada, específicamente con XML. Aunque las tecnologías

existen para verificar los aspectos sintácticos de las interacciones, el problema de si

los dos servicios web se comportan correctamente con todos los mensajes posibles es más difícil.
El objetivo de la comunicación del servicio web es permitir que se describan los servicios web,

anunciados, descubiertos e invocados a través de Internet. Esta infraestructura utiliza

varias tecnologías para permitir que los servicios web funcionen juntos. El marcado extensible

El lenguaje (XML) se utiliza para transmitir mensajes y datos. La descripción universal,

La especificación de descubrimiento e integración (UDDI) se utiliza para mantener directorios de

información sobre servicios web. Estos directorios registran información sobre la web.

servicios, incluida la ubicación y los requisitos, en un formato que otros servicios web

y las aplicaciones pueden leer. El lenguaje de descripción de servicios web (WSDL) se utiliza para

describir cómo acceder a los servicios web y qué operaciones pueden realizar. JABÓN

ayuda al software a transmitir y recibir mensajes XML a través de Internet.

La investigación sobre las pruebas de servicios web acaba de comenzar y hasta ahora se ha
centrado en la

mensajes. Las entradas a los componentes del servicio web son mensajes XML que corresponden

a requisitos sintácticos específicos. Estos requisitos se pueden describir en XML

esquemas, que proporcionan una descripción basada en la gramática de los mensajes.


Investigadores

están comenzando a aplicar los criterios de prueba de sintaxis del Capítulo 5 para probar los
servicios web.

7.3 PRUEBA DE INTERFACES GRÁFICAS DE USUARIO

Las interfaces gráficas de usuario (GUI) representan la mitad o más de la cantidad de fuente

código en sistemas de software modernos. Sin embargo, hay muy poca ayuda para probar este
gran

cantidad de software. Las pruebas de GUI se dividen en dos categorías. Las pruebas de usabilidad
se refieren a

evaluar qué tan utilizable es la interfaz, utilizando principios del diseño de la interfaz de usuario.

Si bien las pruebas de usabilidad son extremadamente importantes, están fuera del alcance de
este libro.

Las pruebas funcionales se refieren a evaluar si la interfaz de usuario funciona según lo previsto.

Las pruebas funcionales se pueden dividir en cuatro tipos. Prueba del sistema GUI

se refiere a realizar pruebas del sistema a través de la GUI. La única diferencia en

La prueba del sistema GUI para otros tipos de pruebas es cómo automatizar las pruebas. Regresión
prueba se refiere a probar la interfaz de usuario después de realizar cambios. El tipo más común
de

La herramienta de prueba de regresión es una herramienta de captura y reproducción. En general,


capturan algunos

las entradas del usuario, reproducirlas a través de la interfaz de usuario después de que se hayan
realizado los cambios e informar

las diferencias. Hay docenas de herramientas de captura y reproducción disponibles en el


mercado, el concepto es bastante simple, por lo que no se tratan más en este capítulo. Aporte

Las pruebas de validación prueban qué tan bien el software reconoce y responde a las entradas no
válidas.

Las técnicas utilizadas para las pruebas de validación de entrada no se ven particularmente
afectadas

mediante el uso de una interfaz gráfica de usuario en lugar de otros tipos de interfaz, por lo que

El tema no se trata en esta sección. Finalmente, las pruebas de GUI se refieren a evaluar qué tan
bien

la GUI funciona. El evaluador hace preguntas como "¿Todos los controles de la IU funcionan como

previsto? "," ¿El software permite al usuario navegar a todas las pantallas que el

que esperaría el usuario? ”y“ ¿No se permiten las navegaciones inapropiadas? ”.

7.3.1 Prueba de GUI

Una técnica bastante obvia para probar GUI es usar algún tipo de máquina de estados finitos

modelo, luego aplique los criterios basados en gráficos del Capítulo 2. Modelado de una GUI como

una máquina de estado es bastante sencilla, ya que naturalmente son sistemas basados en
eventos.

Cada evento de usuario (presionar un botón, ingresar texto, navegar a otra pantalla)

provoca una transición a un nuevo estado. Un camino es una secuencia de aristas a través de las
transiciones,

y representa un caso de prueba. Una ventaja de este enfoque es que el esperado

La salida es simplemente el estado final al que debe llegar la entrada del caso de prueba.

El problema con este enfoque es la posibilidad de explosión estatal; incluso pequeñas GUI

tendrá miles de estados y transiciones.

El número de estados se puede reducir de varias formas. Estado finito variable

las máquinas reducen el número de estados abstractos agregando variables al modelo.


Estos modelos deben crearse a mano. Además, si estos modelos se utilizan para verificación

a través de un oráculo de prueba automatizado, asignaciones efectivas entre el resumen de la


máquina

estados y el estado concreto de la GUI deben desarrollarse a mano.

Una variación del modelo de máquina de estado para particiones de generación de casos de
prueba de GUI

el espacio de estado en diferentes máquinas en función de las tareas del usuario. El probador
identifica un

tarea del usuario (llamada responsabilidad, que se puede realizar con la GUI. Cada responsabilidad

se convierte en una secuencia de interacción completa (CIS). Estos son similares, pero

no exactamente igual que los casos de uso en el Capítulo 2. Cada CIS es un gráfico, y el gráfico

Se pueden utilizar criterios de cobertura para cubrirlos. Aunque es relativamente sencillo de


definir

las responsabilidades, convertirlas en modelos FSM debe hacerse a mano,

que es un gasto significativo.

Otro enfoque para probar las GUI se basa en modelar el comportamiento del usuario,
específicamente

imitando a los usuarios novatos. La intuición es que los usuarios expertos toman breves y directos

rutas a través de la GUI que no son particularmente útiles para realizar pruebas. Usuarios novatos,
en

por otro lado, tome caminos indirectos que ejerciten la GUI de diferentes maneras. En esto

enfoque, un experto comienza generando algunas secuencias de entradas. Estas secuencias


iniciales

se utilizan para generar pruebas aplicando algoritmos genéticos para modificarlos a

parecen más secuencias creadas por novatos.

Un enfoque de compromiso más reciente se basa en un modelo de flujo de eventos a través de

la GUI. El modelo de flujo de eventos se desarrolla en dos pasos. Primero, cada evento está
codificado

en condiciones previas. Las condiciones previas incluyen el estado en el que se puede ejecutar el
evento

y los efectos del evento, es decir, el cambio de estado que ocurre como resultado de la
evento. En segundo lugar, el probador representa todas las posibles secuencias de eventos que se
pueden

ejecutado en la GUI como un conjunto de gráficos dirigidos. En tercer lugar, condiciones previas y
efectos

se utilizan para generar pruebas mediante un enfoque dirigido a objetivos. Porque lo esperado

Las salidas son los estados objetivo de los eventos, esto permite automatizar los oráculos de
prueba.

creado y verificado. El modelo de grafo dirigido se utiliza para generar pruebas que satisfacen

criterios en el Capítulo 2.

Se pueden encontrar más detalles sobre cómo se pueden aplicar estos métodos en los artículos en

la sección bibliográfica.

7.4 SOFTWARE EN TIEMPO REAL Y SOFTWARE INTEGRADO

Los sistemas de software en tiempo real deben responder a estímulos de entrada generados
externamente dentro de

un período finito y especificado. Los sistemas en tiempo real a menudo están integrados y
funcionan

en el contexto de un sistema de ingeniería más grande, a veces diseñado para un

plataforma y aplicación.

Los sistemas en tiempo real normalmente interactúan con otros subsistemas y procesos en el

mundo físico. Esto se denomina entorno del sistema en tiempo real. Por ejemplo,

el entorno de un sistema en tiempo real que controla un brazo robótico incluye elementos que
vienen

por una cinta transportadora y mensajes de otros sistemas de control de robot a lo largo del

misma línea de producción. Los sistemas en tiempo real suelen tener limitaciones de tiempo
explícitas que

especificar el tiempo de respuesta y el comportamiento temporal de los sistemas en tiempo real.


Por ejemplo,

Una limitación de tiempo para un sistema de monitoreo de vuelo puede ser que una vez que el
permiso de aterrizaje

se solicita, se debe proporcionar una respuesta dentro de los 30 segundos. Una limitación de
tiempo en

el tiempo de respuesta de una solicitud se denomina fecha límite. Las limitaciones de tiempo
provienen del
características dinámicas del entorno (movimiento, aceleración, etc.) o de

decisiones de diseño y seguridad impuestas por un desarrollador de sistemas.

La puntualidad se refiere a la capacidad del software para hacer frente a las limitaciones de
tiempo. Por ejemplo,

Una limitación de tiempo para un sistema de monitoreo de vuelo puede ser que una vez que el
permiso de aterrizaje

se solicita, se debe proporcionar una respuesta dentro de los 30 segundos. Fallos en el software

puede dar lugar a violaciones de la puntualidad del software y accidentes costosos. Por lo tanto,
los probadores deben

detectar la violación de las limitaciones de tiempo.

Los sistemas en tiempo real a veces se denominan reactivos porque reaccionan a los cambios en

su entorno. Estos cambios son reconocidos por sensores y el sistema influye

el medio ambiente a través de actuadores. Dado que los sistemas en tiempo real controlan el
hardware

que interactúa estrechamente con entidades y personas en el mundo real, a menudo necesitan

ser confiable.

Una aplicación en tiempo real se define por un conjunto de tareas que implementa un

funcionalidad para el sistema en tiempo real. El entorno de ejecución de una aplicación en tiempo
real

es todo el resto de software y hardware necesario para que el sistema se comporte

según lo previsto, por ejemplo, sistemas operativos en tiempo real y dispositivos de E / S.

Normalmente se utilizan dos tipos de tareas en tiempo real. Las tareas periódicas se activan a

frecuencia fija, por lo que se conocen todos los puntos en el tiempo en que se activan tales tareas

antemano. Por ejemplo, una tarea con un período de 4 unidades de tiempo se activará en

tiempos 0, 4, 8, etc. Las tareas periódicas se pueden activar en cualquier momento. Conseguir

puntualidad en un sistema en tiempo real, las tareas aperiódicas deben especificarse con
restricciones en

su patrón de activación. Cuando existe una restricción, las tareas se denominan esporádicas.

Una restricción común es un tiempo mínimo entre llegadas entre dos tareas consecutivas

activaciones. Las tareas también pueden tener un desplazamiento que denota el tiempo antes de
cualquier instancia
puede estar activado.

Los evaluadores a menudo quieren saber el tiempo de ejecución más largo (la literatura suele
llamar

este "peor de los casos". Desafortunadamente, esto es muy difícil de estimar, por lo que un
objetivo común Las salidas son los estados objetivo de los eventos, esto permite automatizar los
oráculos de prueba.

creado y verificado. El modelo de grafo dirigido se utiliza para generar pruebas que satisfacen

Criterios en el Capítulo 2.

Se pueden encontrar más detalles sobre cómo se pueden aplicar estos métodos en los artículos en

la sección bibliográfica.

7.4 SOFTWARE EN TIEMPO REAL Y SOFTWARE INTEGRADO

Los sistemas de software en tiempo real deben responder a estímulos de entrada generados
externamente dentro de

un período finito y especificado. Los sistemas en tiempo real a menudo están integrados y
funcionan

en el contexto de un sistema de ingeniería más grande, a veces diseñado para un

plataforma y aplicación.

Los sistemas en tiempo real normalmente interactúan con otros subsistemas y procesos en el

mundo físico. Esto se denomina entorno del sistema en tiempo real. Por ejemplo,

el entorno de un sistema en tiempo real que controla un brazo robótico incluye elementos que
vienen

por una cinta transportadora y mensajes de otros sistemas de control de robot a lo largo del

misma línea de producción. Los sistemas en tiempo real suelen tener limitaciones de tiempo
explícitas que

especificar el tiempo de respuesta y el comportamiento temporal de los sistemas en tiempo real.


Por ejemplo,

Una limitación de tiempo para un sistema de monitoreo de vuelo puede ser que una vez que el
permiso de aterrizaje

se solicita, se debe proporcionar una respuesta dentro de los 30 segundos. Una limitación de
tiempo en

el tiempo de respuesta de una solicitud se denomina fecha límite. Las limitaciones de tiempo
provienen del

características dinámicas del entorno (movimiento, aceleración, etc.) o de


decisiones de diseño y seguridad impuestas por un desarrollador de sistemas.

La puntualidad se refiere a la capacidad del software para hacer frente a las limitaciones de
tiempo. Por ejemplo,

Una limitación de tiempo para un sistema de monitoreo de vuelo puede ser que una vez que el
permiso de aterrizaje

se solicita, se debe proporcionar una respuesta dentro de los 30 segundos. Fallos en el software

puede dar lugar a violaciones de la puntualidad del software y accidentes costosos. Por lo tanto,
los probadores deben

detectar la violación de las limitaciones de tiempo.

Los sistemas en tiempo real a veces se denominan reactivos porque reaccionan a los cambios en

su entorno. Estos cambios son reconocidos por sensores y el sistema influye

el medio ambiente a través de actuadores. Dado que los sistemas en tiempo real controlan el
hardware

que interactúa estrechamente con entidades y personas en el mundo real, a menudo necesitan

ser confiable.

Una aplicación en tiempo real se define por un conjunto de tareas que implementa un

funcionalidad para el sistema en tiempo real. El entorno de ejecución de una aplicación en tiempo
real

es todo el resto de software y hardware necesario para que el sistema se comporte

según lo previsto, por ejemplo, sistemas operativos en tiempo real y dispositivos de E / S.

Normalmente se utilizan dos tipos de tareas en tiempo real. Las tareas periódicas se activan a

frecuencia fija, por lo que se conocen todos los puntos en el tiempo en que se activan tales tareas

antemano. Por ejemplo, una tarea con un período de 4 unidades de tiempo se activará en

tiempos 0, 4, 8, etc. Las tareas periódicas se pueden activar en cualquier momento. Conseguir

puntualidad en un sistema en tiempo real, las tareas aperiódicas deben especificarse con
restricciones en

su patrón de activación. Cuando existe una restricción, las tareas se denominan esporádicas.

Una restricción común es un tiempo mínimo entre dos tareas consecutivas

activaciones. Las tareas también pueden tener un desplazamiento que denota el tiempo antes de
cualquier instancia

puede estar activado.


Los evaluadores a menudo quieren saber el tiempo de ejecución más largo (la literatura suele
llamar

este "peor de los casos". Desafortunadamente, esto es muy difícil de estimar, por lo que un
objetivo común

La falla de puntualidad ocurre cuando el entorno (o sensores y actuadores) se comporta

diferente de lo esperado. Por ejemplo, si un mecanismo de manejo de interrupciones está sujeto

a un retraso imprevisto, entonces el tiempo interno entre llegadas puede acortarse

que lo esperado.

Se produce un error de puntualidad cuando el sistema se desvía internamente de los supuestos.

sobre su comportamiento temporal. Esto es similar cuando un programa en tiempo no real tiene
un

error de estado. Los errores de puntualidad son difíciles de detectar sin un registro extenso

y conocimiento preciso sobre el comportamiento interno del sistema. Además, puntualidad

Los errores solo pueden ser detectables y dar lugar a fallas de puntualidad a nivel del sistema para

Órdenes de ejecución específicas.

Una falta de puntualidad es una violación de una restricción de tiempo que se puede observar
externamente.

En un sistema duro en tiempo real, esto tiene alguna penalización o consecuencia para el

funcionamiento continuo del sistema en general. Dado que las limitaciones de tiempo
normalmente se expresan

con respecto al comportamiento observable externamente de un sistema (o componente),

las fallas de puntualidad son a menudo fáciles de detectar.

Prueba de puntualidad

Los criterios de prueba deben adaptarse para abordar la puntualidad porque es difícil de
caracterizar

una secuencia crítica de entradas sin considerar el efecto sobre el conjunto de activos

tareas y protocolos en tiempo real. Sin embargo, los criterios de prueba presentados en capítulos
anteriores

rara vez utilizan información sobre el diseño en tiempo real en la generación de casos de prueba,
ni tampoco

predicen qué órdenes de ejecución pueden revelar fallas en supuestos fuera de línea.

La puntualidad se analiza y mantiene tradicionalmente mediante el análisis de programación.


las técnicas o la puntualidad se regula en línea mediante el control de admisión y la contingencia

esquemas. Sin embargo, estas técnicas hacen suposiciones sobre las tareas y

patrones de activación que deben ser correctos para mantener la puntualidad. Además, un

El análisis completo de la programabilidad de los modelos de sistemas no triviales es complicado y


requiere

reglas específicas que debe seguir el sistema de tiempo de ejecución. Probar la puntualidad es más

general; se aplica a todas las arquitecturas del sistema y se puede utilizar para ganar confianza en

supuestos mediante el muestreo sistemático entre las órdenes de ejecución que pueden conducir

a los plazos incumplidos. Sin embargo, solo algunas de las posibles órdenes de ejecución revelan

Violaciones de puntualidad en presencia de fallas de tiempo. Por tanto, el desafío es

encontrar órdenes de ejecución que causarán fallas de puntualidad que resulten en fallas.

Las pruebas en tiempo real a menudo dependen de modelos formales del software. Una
aproximación

es describir el software con redes de Petri cronometradas. Luego grafica criterios como

ya que la cobertura de borde o ruta se puede usar para intentar encontrar entradas de prueba que
violen la puntualidad

limitaciones.

Otro enfoque para modelar la puntualidad es especificar restricciones de tiempo en una


restricción

grafica y especifica el sistema bajo prueba usando álgebra de procesos. Solo restricciones

en las entradas del sistema.

Otro enfoque especifica las limitaciones de tiempo utilizando un gráfico de región de reloj. Un
cronometrado

La especificación de automatización del sistema se "aplana" a una entrada convencional

Automatización de salida que se utiliza para derivar pruebas de conformidad para la


implementación.

en cada región del reloj.

Otra técnica de modelado que se utiliza para sistemas en tiempo real es la lógica temporal.

Los elementos de los casos de prueba son pares de entradas y salidas temporizadas. Estos pares
pueden

combinarse y desplazarse en el tiempo para crear una gran cantidad de casos de prueba parciales;
el número de estos pares crece rápidamente con el tamaño y las limitaciones en el

software.

Los autómatas cronometrados también se han utilizado para verificar secuencias de transiciones
de acción cronometradas.

Este enfoque utiliza un análisis de accesibilidad para determinar qué transiciones a

prueba; es decir, un enfoque basado en gráficos. Esto puede sufrir una explosión espacial estatal
para

grandes modelos dinámicos. Una forma de mejorar ese problema es utilizar una muestra

algoritmo basado en grid-autómatas y máquinas de estado finito no deterministas para reducir

el esfuerzo de prueba.

Un enfoque de modelo no formal utiliza algoritmos genéticos. Los datos se recopilan durante

ejecución del sistema real y visualizado para análisis posterior. La aptitud de un caso de prueba es

calculado en base a su singularidad y qué excepciones son generadas por los sistemas

y probar el arnés durante la ejecución de la prueba.

Un enfoque para generar pruebas es derivar estáticamente órdenes de ejecución de un

sistema en tiempo real antes de su puesta en funcionamiento. Cada orden de ejecución se trata
como

Se puede aplicar un programa secuencial separado y métodos de prueba convencionales. Esto

solo funciona si todos los tiempos de activación de tareas son fijos.

La mayoría de los enfoques anteriores se basan en criterios de gráficos (Capítulo 2) de una manera

u otro. Un enfoque diferente se basa en la mutación (Capítulo 5). En base a mutación

pruebas de puntualidad, las fallas potenciales se modelan como operadores de mutación.


Mutantes

que tienen el potencial de violar la puntualidad, y los casos de prueba son

construido que intenta matar a los mutantes.

Se han definido ocho tipos de mutantes. El operador de mutación del conjunto de tareas

cambia los puntos en el tiempo en que se toma un recurso. La mutación del tiempo de ejecución

El operador aumenta o disminuye el tiempo de ejecución de una tarea en un delta de tiempo


constante.

El operador de mutación de cambio de tiempo de espera cambia el intervalo de tiempo que un


recurso es
bloqueado. El operador de mutación de tiempo de bloqueo aumenta o disminuye el tiempo en
que un recurso

está bloqueado. El operador de mutación de tiempo de desbloqueo cambia cuando se desbloquea


un recurso.

El operador de mutación de restricción de precedencia agrega o quita precedencia

relaciones de restricción entre pares de tareas. El operador mutante de tiempo entre llegadas

Disminuye el tiempo entre llegadas entre las solicitudes de ejecución de una tarea mediante una
constante.

tiempo. El operador de mutación de desplazamiento de patrón cambia el desplazamiento entre


dos

patrones por unidades de tiempo constante. Se crean casos de prueba para matar mutantes de
puntualidad

mediante la verificación de modelos y mediante algoritmos genéticos.

7.5 NOTAS BIBLIOGRÁFICAS

Las notas bibliográficas de este capítulo siguen el orden de las secciones anteriores,

software orientado a objetos, aplicaciones web, GUIS y tiempo real e integrado

software.

Buenas fuentes para construir abstracciones en lenguaje orientado a objetos son byMeyer

[241], Liskov, Wing y Guttag [212, 213] y Firesmith [119]. Liskov, ala

y Guttag contribuyó con el principio de sustitución. Discusiones sobre si el

El principio de sustitución siempre debe seguirse puede ser encontrado por Lalonde y Pugh

[199] y Taivalsaari [323].

Binder señaló cómo las relaciones OO tienden a ser complejas [35]. y Berard

articuló primero las diferencias en la integración [31]. La conexión entre herencia,

El polimorfismo, la unión dinámica y la indecidibilidad se deben a Barbey [24].

Doong y Frankl [105] escribieron un artículo fundamental sobre la orientación a objetos basada en
el estado

pruebas. Los tres niveles de prueba de clase de prueba intramétodo, prueba entre métodos,

y las pruebas intraclase se deben a Harrold y Rothermel [152]; Gallagher y Offutt

[132] agregó pruebas entre clases.

Varios artículos se centraron en las pruebas entre métodos e intraclase [118, 152, 281,
315], pruebas de interacciones entre clases y sus usuarios [284] y pruebas a nivel de sistema

[181].

El gráfico yo-yo fue proporcionado por Alexander y Offutt [266], y basado en

la discusión de Binder de que la ejecución a veces puede "rebotar" hacia arriba y hacia abajo

entre los niveles de herencia [35]. Las categorías de fallas y anomalías OO son

debido a Alexander y Offutt [9, 266]. La mayor parte de la prueba de flujo de datos y acoplamiento
OO

Alexander desarrolló los criterios como parte de su trabajo de tesis [7, 10, 11].

La idea de utilizar un gráfico para representar sitios web estáticos fue propuesta inicialmente por

Ricca y Tonella [300]. Kung y col. [198, 215] también desarrolló un modelo para representar

sitios web como un gráfico, y proporcionaron definiciones preliminares para desarrollar pruebas
basadas

en el gráfico en términos de recorridos de páginas web. Su modelo incluye transiciones de enlaces


estáticos

y se centra en el lado del cliente con un uso limitado del software del servidor. Ellos

definir pruebas intraobjeto, donde se seleccionan rutas de prueba para las variables que tienen

def-use cadenas dentro del objeto, prueba entre objetos, donde se seleccionan las rutas de
prueba

para variables que tienen cadenas de def-use a través de objetos y pruebas entre clientes, donde

Las pruebas se derivan de un gráfico de accesibilidad que está relacionado con las interacciones de
datos.

entre los clientes.

Benedikt, Freire y Godefroid [30] iniciaron la idea de "secuencias de acción" en

una herramienta llamada VeriWeb. Las pruebas de VeriWeb se basan en gráficos donde los nodos
son web

las páginas y los bordes son enlaces HTML explícitos, y el tamaño de los gráficos está controlado
por

un proceso de poda.

Elbaum, Karre y Rothermel [112, 113] propusieron la idea de "sesión de usuario

data ”para generar casos de prueba para aplicaciones web.

Liu, Kung, Hsia y Hsu [216] primero intentaron aplicar el análisis de flujo de datos a la web
componentes de software. La atención se centró en las interacciones de datos y su modelo no

incorporar páginas web generadas dinámicamente o transiciones operativas.

Las pruebas de derivación se deben a Offutt, Wu, Du y Huang [278]. El análisis sobre el

Las conexiones en aplicaciones web en la Sección 7.2.2 se deben a Offutt y Wu [359, 360].

También introdujeron el concepto de sección atómica.

Di Lucca y Di Penta [217] propusieron secuencias de prueba a través de una aplicación web.

que incorpora algunas transiciones operativas, centrándose específicamente en la espalda

y transiciones de botón de avance. En el tiempo, este artículo es el primer trabajo publicado que
aborda

transiciones operativas, aunque es posterior al informe técnico anterior por

Offutt y Wu [359]. El modelo de Di Lucca y Di Penta se centró en las capacidades del navegador

sin considerar el software del lado del servidor o la web generada dinámicamente

páginas.

El uso de técnicas de prueba basadas en sintaxis para crear mensajes XML como pruebas para

Los componentes de servicios web se deben a Offutt y Wu [279, 280].

Nielson [253] proporciona una excelente descripción general de la importancia de la usabilidad


web.

en general e incluye una discusión sobre las pruebas de usabilidad.

Los primeros trabajos en el uso de modelos de máquina de estado para generar pruebas para GUI
son por

Clarke [79], Chow [77], Esmiloglu [115] y Bernhard [32]. El estado finito variable

modelo de máquina se debe a Shehady et al. [312].

También podría gustarte