Psicología y economía en pruebas de software
Psicología y economía en pruebas de software
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.
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.
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:
• "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".
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:
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.
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.
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.
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)
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.
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:
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.
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.
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.
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.
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.
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.
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:
• Un buen caso de prueba es aquel que tiene una alta probabilidad de detectar un error aún no
descubierto.
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
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
• Liderar la sesión
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.
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).
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.
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.
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.
11. Para los lenguajes orientados a objetos, ¿se cumplen todos los requisitos de herencia en la
clase de implementación?
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.
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.
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?
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).
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
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.
2. ¿Terminará eventualmente cada bucle? Diseñe una prueba o un argumento informal que
demuestre que cada ciclo terminará.
¿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:
...
...
}
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
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:
7. System.out.println (i);
System.out.println (i);
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
11. W = X + 1;
12. REGRESO
13. B: ENTRADA
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
4. ¿Hay suficiente memoria disponible para guardar el archivo que leerá su programa?
5. ¿Se han abierto todos los archivos antes de su uso?
9. ¿Hay errores ortográficos o gramaticales en algún texto que imprime o muestra el programa?
Otros cheques
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.
Tutoriales
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.
Comprobación de escritorio
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:
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:
• Tutoriales grupales
• Comprobación de escritorio
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
3. Diseño intermedio
4. Diseño detallado
5. Implementación
6. Integración
8. Operación y mantenimiento
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
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
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
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.
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 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.
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
programas. La Tabla 6.2 resume los principales objetivos y actividades durante el sistema.
y diseño de software.
reflejarse en los correspondientes cambios de diseño. Las pruebas en esta etapa deberían ayudar
diseño
Actividades de objetivos
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
las últimas etapas. Las pruebas en la etapa de diseño del sistema y del software también preparan
para
capítulos.
deseos de interfaz. Las pruebas de usabilidad se llevan a cabo cuando la interfaz de usuario es una
parte integral
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.
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
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.
Actividades de objetivos
Prepararse para las pruebas unitarias Desarrollar planes de integración y pruebas unitarias
Actividades de objetivos
Esté preparado para probar cuando los módulos estén listos Cree casos de prueba
(si es unidad)
(si es integración)
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
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
los programadores comienzan a escribir y compilar clases y métodos. La tabla 6.5 resume
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.
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!
implementación
Actividades de objetivos
integración
Actividades de objetivos
Pruebas de integración eficientes Realización de pruebas de integración
6.3.6 Integración
integración.
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
componentes.
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.
los requisitos funcionales y no funcionales. Se desarrollan casos de prueba de prueba del sistema
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
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
Actividades de objetivos
operación y mantenimiento
Actividades de objetivos
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
6.3.9 Resumen
ética profesional. Tanto los desarrolladores como los probadores pueden elegir anteponer la
calidad. Si
eso. Esto a veces dará lugar a conflictos con la gestión basada en el tiempo, pero incluso si
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
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,
fuente de las pruebas, por lo que cuando la fuente cambia, es posible rastrear qué
Los contenidos de un plan de prueba son esencialmente cómo se crearon las pruebas, por qué se
Sin embargo, producir planes de prueba es un requisito esencial para muchas organizaciones.
Desafortunadamente, esto es bastante antiguo (¡1983!), Pero sigue siendo el más conocido. A
de lo que jamás podrías usar. El estándar ANSI / IEEE 829-1983 describe un plan de prueba como:
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 ".
1. Un plan de misión dice "por qué". Por lo general, solo aparece un plan de misión por
organización.
planes.
2. Un plan estratégico dice "qué" y "cuándo". Una vez más, solo un plan estratégico generalmente
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
A continuación, se describen dos planes de prueba de muestra, que se proporcionan solo como
ejemplo. Los planes
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
probar los criterios de entrada y salida y las herramientas de prueba que se utilizarán. La
(e) Los gerentes deben comprender el cronograma en la medida en que se realicen las pruebas.
3. Entregables
(a) Casos de prueba, incluidos los valores de entrada y los resultados esperados
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. Introducción
Sintaxis
Descripción de la funcionalidad
Salida esperada
Exclusiones específicas
Dependencias
(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
y calendario de todas las actividades de prueba. El plan debe identificar los elementos
plan.
El plan de prueba debe ser un documento dinámico que puedan utilizar los evaluadores,
ser el vehículo por el cual todas las partes firman indicando la aprobación de la final
producto.
2. Esquema
(b) Introducción
(l) Responsabilidades
(n) Horario
(p) Aprobaciones
Las secciones están ordenadas en la secuencia anterior. Las secciones adicionales pueden ser
debe estar fácilmente disponible. Las siguientes secciones brindan detalles sobre el contenido
de cada sección.
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.
Identificar los elementos a los que hacen referencia las pruebas, incluida su versión / revisió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.
El enfoque debe describirse con suficiente detalle para identificar los principales
Especifique la medida que se utilizará para determinar si cada elemento de prueba tiene
fallas?
En determinadas situaciones, las pruebas deben detenerse y el software debe devolverse a los
desarrolladores.
Especifique las actividades que deben repetirse para reanudar o reiniciar las actividades de
prueba.
(h) Pruebe los datos de entrada y los datos de salida de prueba (o dónde se encuentran)
Identificar las tareas necesarias para prepararse y realizar las pruebas. Identificar todos
(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.
16. Horario
Incluya todos los hitos de prueba identificados en el cronograma del proyecto de software. Definir
realizar cada tarea de prueba y especificar el cronograma para cada tarea de prueba y prueba
hito.
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
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
será claro en cuanto a qué salida acompaña a una entrada determinada. Por ejemplo, un
programa de clasificación
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.
Entrada: Estructura S
Hacer copia T de S
Ordena
analiza las redes de Petri, que son útiles para modelar procesos con estado. Una salida
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
versión. Esto inicialmente parece ser una circularidad; ¿Por qué debería uno confiar en una
implementación?
S (t), y normalmente exigimos que S (t) = P (t) .1 Suponga que S es, en sí mismo, ejecutable,
una o más fallas, una ocurrencia común, S (t) puede muy bien ser incorrecta. Si P (t) es
salidas idénticas (y por lo tanto normales). Algunos autores han sugerido que el
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
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.
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.
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.
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
Las comprobaciones externas solo pueden examinar las salidas, por lo que la infección debe
propagarse durante
el error a detectar.
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"
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
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
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
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!
funciones como seno. Sin embargo, el enfoque se aplica a muchos otros tipos de
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.
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,
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
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
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
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
demostró que cuando las clases no tienen ciclos de dependencia, derivar una integración
componentes (SCC) y eliminar asociaciones hasta que no queden ciclos. Cuando ahí
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
Problema de CITO [44]. Malloy y col. primero intenté considerar la complejidad del stub de prueba
cuando
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
análisis, que utiliza más información sobre cómo la clase cortada se empareja con otros
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
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
Blum y Kannan [38] y Lipton [210] dan tratamientos teóricos de la redundancia de datos
NOTAS
ser visto como que permite un conjunto de posibles resultados, y la restricción de corrección es
que P
2 Si lo deseamos, podemos reescribir las llamadas cos (x) a las llamadas sin (π / 2 - x), pero esto no
es estrictamente necesario
utilizado con varios tipos diferentes de tecnologías. Estas tecnologías han llegado a
muy común y representa un gran porcentaje de nuevas aplicaciones que se están creando.
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.
de los problemas con el software orientado a objetos se han discutido en capítulos anteriores,
incluyendo
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
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
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
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
cantidad de software integrado está creciendo muy rápidamente, ya que aparece en todo tipo de
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
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
requieren nuevos métodos de prueba. Esto se debe a que la forma en que las clases y los
componentes
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
clase. (Este nuevo método se llama método de extensión). Una clase refina el padre
método anulado.
Los programadores utilizan dos tipos de herencia, subtipo y subclase. Si la clase B usa
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
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
utilice ambos tipos. En el caso de la herencia de subtipos, los evaluadores deben centrarse en
verificar
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 ()}.
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,
Cuando se prueba software orientado a objetos, una clase se considera generalmente como la
base
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
3. Pruebas intraclase: las pruebas se construyen para una sola clase, generalmente como
secuencias.
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)
clases y sus usuarios y pruebas a nivel de sistema del software OO. Problemas asociados
niveles entre métodos o intraclase. Estos requieren múltiples clases que están acopladas
Una de las tareas más difíciles para los ingenieros de software orientados a objetos es visualizar las
interacciones
Unión. ¡A menudo son muy complejos! Esta visualización asume que una clase encapsula
Como ejemplo, considere el diagrama de clases de UML y el fragmento de código que se muestran
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
método f ().
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
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
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
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
Cuando el objeto es de tipo C real, podemos ver de dónde viene el término "yo-yo"
La herencia ayuda a los desarrolladores a ser más creativos, más eficientes y reutilizar previamente
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
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
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
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
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
cualquier problema de comportamiento. Sin embargo, si el mismo objeto se usa a veces como una
pila
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
control llega a la primera llamada a Stack :: pop () en la línea 14. Aquí, el elemento eliminado
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.
n (), cada uno de los cuales tiene las definiciones y usos que se muestran en la tabla. Suponga que
un
Y :: w en su lugar. Ahora, existe una anomalía en el flujo de datos con respecto a la secuencia del
método.
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.
una variable de estado local puede provocar una anomalía en el flujo de datos. Si una variable
local v
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
anulado. Puede existir una anomalía en el flujo de datos si un método que normalmente define
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
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.
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
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
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().
de una clase ancestral C llama a un método polimórfico definido localmente f (). Porque f ()
Para ver esto, considere la jerarquía de clases que se muestra en la mitad izquierda de la Figura
7.7. Clase C
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
Los métodos ejecutan el método más cercano a la instancia que se está creando.
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).
Cuadro 7.3. La clase AbstractFile contiene la variable de estado fd que no es inicializada por
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
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
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-
definición.
En una falla de anomalía de visibilidad de estado (SVA), las variables de estado en una clase de
ancestro
proporciona una definición primordial de A :: m () pero B no. Dado que A :: v tiene privado
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
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
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
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 ()}
yo def (2, o, m ()) = {(W.m (), {W.v}), (X.m (), {W.v, X.x}),
Cada par de definiciones y usos en la Figura 7.10 indica un método satisfactorio para
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
Los pares def-use y las rutas de acoplamiento son más complicados en los programas orientados a
objetos.
por m (), y Nm el conjunto de nodos en el gráfico de flujo de control de m (). Además, defs (i) es
es el nodo de entrada del método m (), exit (m) es el nodo de salida, primero (p) es el primer nodo
en
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
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 ()}
yo def (2, o, m ()) = {(W.m (), {W.v}), (X.m (), {W.v, X.x}),
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,
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
Los pares def-use y las rutas de acoplamiento son más complicados en los programas orientados a
objetos.
por m (), y Nm el conjunto de nodos en el gráfico de flujo de control de m (). Además, defs (i) es
es el nodo de entrada del método m (), exit (m) es el nodo de salida, primero (p) es el primer nodo
en
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
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
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
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
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],
Suponiendo que las subtrayectorias intermedias están definidas con respecto al estado
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
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
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
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 subruta de uso indirecto es la parte del método consecuente n () que se extiende desde
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
y luego agregar un elemento del conjunto de subrutas de uso indirecto. El conjunto completo de
diagrama de clases que se muestra en la Figura 7.13 (a). La familia de tipos contiene las clases A, B,
versión de m (). Las definiciones y usos de cada uno de estos métodos se muestran en
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
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
ruta de acoplamiento con respecto a A.u porque A.u no aparece en el conjunto de acoplamiento
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
s j, k
= {A.u}. En turno,
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,
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
que se invoca es en realidad B.n (). Por tanto, el conjunto de acoplamiento para s j, k se toma con
respecto
produce Cs
j, k
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.
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
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
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
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
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
inducida cuando la variable de contexto está vinculada a una instancia de t. La jerarquía de tipos
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
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
Los criterios de flujo de datos del Capítulo 2 están adaptados para herencia y polimorfismo.
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.
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
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
método f (), y para cada clase en la familia de tipos definida por el contexto
hay una ruta p en el conjunto de rutas de acoplamiento de s j, k que es una subruta del
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
ejecuta cada ruta de acoplamiento factible p con respecto a cada variable de acoplamiento v.
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.
que todas las rutas de acoplamiento se ejecuten para cada miembro de la familia de tipos definida
por
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.
que todas las rutas de acoplamiento se ejecuten para cada miembro de la familia de tipos definida
por
secuencia s j, k en el método f (), para cada clase en la familia de tipos definida por
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
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
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).
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.
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
una URL determinada. Los datos para los campos de formulario se pueden elegir a partir de las
entradas suministradas previamente por el
pruebas.
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
Otro enfoque para encontrar entradas se llama prueba de derivación. Muchas aplicaciones web
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
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.
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
y las clases de sistema operativo no se aplican exactamente a las aplicaciones web porque
Los usuarios pueden modificar el flujo de control esperado en el cliente presionando la parte
posterior o
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 <A> dinámicos realizan una solicitud desde una página web estática a los
componentes de software
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
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
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,
Las conexiones de software local se encuentran entre los componentes de software de back-end
en el
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
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
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
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
criterios en el Capítulo 2.
Los servicios web introducen algunas arrugas más para los probadores. Desafortunadamente, el
término
definiciones, algunas de las cuales entran en conflicto. Los más comunes dependen de tecnologías
particulares.
adoptar un enfoque más genérico. Un servicio web es una aplicación modular distribuida,
Probar los servicios web es difícil porque son aplicaciones distribuidas con inusuales
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
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,
varias tecnologías para permitir que los servicios web funcionen juntos. El marcado extensible
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
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
están comenzando a aplicar los criterios de prueba de sintaxis del Capítulo 5 para probar los
servicios web.
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
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
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 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
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,
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
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
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
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.
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
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
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
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
Los sistemas en tiempo real a veces se denominan reactivos porque reaccionan a los cambios en
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
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.
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
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
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
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
Los sistemas en tiempo real a veces se denominan reactivos porque reaccionan a los cambios en
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
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.
activaciones. Las tareas también pueden tener un desplazamiento que denota el tiempo antes de
cualquier instancia
este "peor de los casos". Desafortunadamente, esto es muy difícil de estimar, por lo que un
objetivo común
que lo esperado.
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
Los errores solo pueden ser detectables y dar lugar a fallas de puntualidad a nivel del sistema para
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
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.
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
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
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.
grafica y especifica el sistema bajo prueba usando álgebra de procesos. Solo restricciones
Otro enfoque especifica las limitaciones de tiempo utilizando un gráfico de región de reloj. Un
cronometrado
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.
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
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
sistema en tiempo real antes de su puesta en funcionamiento. Cada orden de ejecución se trata
como
La mayoría de los enfoques anteriores se basan en criterios de gráficos (Capítulo 2) de una manera
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
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.
patrones por unidades de tiempo constante. Se crean casos de prueba para matar mutantes de
puntualidad
Las notas bibliográficas de este capítulo siguen el orden de las secciones anteriores,
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
El principio de sustitución siempre debe seguirse puede ser encontrado por Lalonde y Pugh
Binder señaló cómo las relaciones OO tienden a ser complejas [35]. y Berard
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,
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].
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
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.
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.
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
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].
Di Lucca y Di Penta [217] propusieron secuencias de prueba a través de una aplicación web.
y transiciones de botón de avance. En el tiempo, este artículo es el primer trabajo publicado que
aborda
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 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