Resumen de Testing (Ingeniería I)

De Cuba-Wiki
Revisión del 16:57 23 dic 2007 de 200.114.207.173 (discusión) (back con template)
(difs.) ← Revisión anterior | Revisión actual (difs.) | Revisión siguiente → (difs.)

Plantilla:Back

Verificación

¿Estamos haciendo el producto correctamente? Necesariamente es en relación a un componente anterior, que describe nuestro producto.

Distinto de validación: ¿Estamos haciendo el producto correcto? Basada en el uso de modelos.

Estática

Sobre una representación estática del sistema

  • Inspecciones, Revisiones, Walkthrough
  • Análisis de Reglas Sintácticas sobre código
  • Análisis Data Flow sobre código
  • Model Checking
  • Theorem Proving (prueba de teoremas)

Dinámica

Consiste en ejecutar y observar comportamiento

  • Testing
  • Run time monitoring
  • Run time verification

Definiciones de errores

  • Falla (failure): Diferencia entre los resultados esperados y reales
  • Defecto (defect o fault): Está en el texto del programa, una especificación, un diseño, y desde allí se hace visible una falla
  • Error: Equivocación humana
  • Un error lleva a uno o más defectos, que están presentes en un producto de software
  • Un defecto lleva a cero, una o más fallas: la manifestación del defecto


Testing

Verificación Dinámica de la adecuación del sistema a los requerimientos (de distinto tipo).

Es el proceso de ejecutar un producto para verificar que satisface los requerimientos e identificar diferencias entre el comportamiento real y el comportamiento esperado. No prueba la correctitud del software.

Se realiza ejecutando el programa y comparando resultados contra un oráculo (el mismo tester). Se asume que se puede ejecutar el programa, que se conoce el resultado esperado y que el resultado esperado puede compararse con el resultado obtenido (problema de oráculos: visibilidad y comparación).

Paradoja del pesticida
Luego de correr un pesticida (test), los bugs que sobreviven son los más fuertes, con lo que se deben aplicar tests más potentes y el tipo de error debe cambiar.


Proceso de Testing

El testing debe ser un proceso paralelo al desarrollo, no una actividad al final.

El test de sistema se prepara inicialmente en función de los requerimientos (luego este test dará lugar al de aceptación). Así se realiza la derivación de los casos de test funcionales.

Sobre la base anterior se realiza un diseño preliminar que incluye al test de integración, y el diseño detallado el test de unidades. Una vez todo listo se comienza con la programación.

Asociado a la implementación de los tests, está la creación del ambiente, la ejecución de los casos, la documentación, el seguimiento, etc.


Tests no funcionales

  • Test de seguridad, validando disponiblidad, integridad y confidencialidad de datos y servicios
  • Test de performance, validando los tiempos de acceso y respuesta del sistema
  • Test de stress, validando el uso del sistema en sus límites de capacidad y verificando sus reacciones más allá de los mismos
  • Test de Usabilidad


Testing Funcional o de Sistema

El testing funcional debe testear que un programa p implementa una funcionalidad f correctamente, es decir, que para todo elemento del dominio el resultado de p coincide con el de f. También debe avisar que la entrada no pertenece al dominio, y en caso de errores no destruir el sistema (o colgarse) sino simplemente notificar del error. Se busca testear que haga lo que debe y no haga lo que no debe.

Definiciones

  • Casos de test: descripciones de condiciones o situaciones a testear. Suele surgir de una especificación de test.
  • Dato de test: asignación de valores concretos de parámetros para ejecutar un caso de test
  • Test Suite T: conjunto de datos de test con los que se testea el programa
  • Si P es correcto para todo elemento de T, se dice que T es exitoso para P
  • Requerimientos de Test: Qué quiero testear. En el marco del testing de sistemas reactivos se llama el Test Purpuse.
  • Especificaciones de Test: Supuestos y definiciones que sirven para generar los casos de test para el requerimiento de test

Criteros de selección

Un criterio es un subconjunto de conjuntos finitos del dominio de Inputs del programa P (puede estar expresado en términos de un conjunto de predicados también llamados casos). Se dice que un conjunto de datos T satisface un criterio C sii .

  • Un Criterio C es Consistente para P sii para todo par T1 y T2 de test sets que satisfacen C, T1 es exitoso para P sii T2 lo es.
  • Un Criterio C es Completo para P sii si P es incorrecto entonces hay un test set T que no es exitoso para P.

No hay manera de evaluar si un determinado criterio es completo y/o consistente (mucho menos generar uno), con lo cual se usan heurísticas para generarlos.

Los criterios pueden ser de caja negra (basados en la especificación y en la IO de datos de la funcionalidad) o de caja blanca (teniendo en cuenta la estructura interna del programa).

Técnica de partición de dominios

Se basa en los requerimientos, en la descripción de alguna funcionalidad sea formal o informal.

  1. Elegir una funcionalidad que pueda testearse en forma independiente
  2. Determinar sus parámetros u otros objetos del ambiente que pueden afectar su funcionamiento
  3. Determinar las características relevantes de cada objeto determinado en el punto 2 y de la relación entre estos objetos en el output
  4. Determinar elecciones para cada característica de cada objeto
  5. Clasificación: errores, únicos, relaciones,

Las categorías pueden extraerse del texto del requerimiento, del modelo de datos (ciclo de vida de entidades o transiciones del diagrama de actividades, cardinalidades estáticas) y de los parámetros de IO.

Es conveniente generar siempre clases inválidas además de válidas. En un rango siempre conviene testear separadamente las condiciones de borde, que suelen ser las más conflictivas, además de quedar fuera del rango por encima o por debajo.

Puede combinarse con la notación arbórea para identificar las distintas choices en los casos de test, en lugar de generar la tabla gigante.

Técnica de grafo de causa-efecto

El grafo de causa efecto permite definir combinaciones relevantes de categorías binarias sobre inputs para definir casos. Allí difiere de la parte combinatoria de Category-Partition. Provee un display visual de las relaciones entre una causa y la otra; ayuda a detectar ambigüedades o incompletitud en la especificación.

El grafo está formado por nodos que representan la entrada (causas) booleanos, nodos intermedios que actúan como compuertas lógicas y nodos de salida, también booleanos, que representan distintos estados.

La técnica se basa en generar todos los outputs admisibles con la siguiente heurística:

  • Si hay un or, todas las opciones false con sólo una señal en true más una con todas false
  • Si hay un and, una con todas true y las demás con una sola en false

El orden resultante es O(n*k*o), en lugar de O(2^n), donde n es la cantidad de categorías binarias sobre el input, k la profundidad del DAG y o la cantidad de combinaciones del output válidas

Técnica de arreglos ortogonales

Se basa en 2-wise testing, que sostiene que la mayoría de los errores se dan por combinaciones de uno o dos parámetros. Se busca poder realizar tests más económicos sin caer en la explosión combinatoria de partición de dominios.

Para esto se generan arreglos ortogonales de los factores tomados de a dos: se generan todas las posibles combinaciones de todos los pares de factores. En las tablas, se toman las filas como los casos de test y las columnas como los factores. Luego se unen todas las combinaciones obtenidas en una tabla de casos de test.

Se define level como la cantidad de valores (choices) posibles para un determinado factor. Los tests se tabulan como ; por ejemplo, implica que se usaron 18 corridas para testear 7 factores: uno de seis niveles y seis de tres niveles. La strength se define como la cantidad de columnas tales que las posibilidades aparecen la misma cantidad de veces.

Conclusiones

  • Ninguna técnica es completa
  • Las técnicas atacan distintos problemas
  • Lo mejor es combinar varias de estas técnicas para complementar las ventajas de cada una
  • Depende del programa a testear
  • Sin especificaciones de requerimientos todo es mucho más difícil
  • Tener en cuenta la conjetura de defectos
  • Ser sistemático y documentar las suposiciones sobre el comportamiento o el modelo de fallas

Cobertura

La cobertura permite evaluar la calidad de los casos de test determinando si se consideraron todos los aspectos testeables del sistema.

  • La especificación de tests cubre la especificación o los requerimientos del sistema?
  • El conjunto de casos de test cubre la estructura de mi workflow o mi caso de uso?
    • Para los escenarios normales y las acciones alternativas, hay datos que ejerciten cada uno de estos flujos?
    • Todas las actividades y todas las elecciones que hacen cambiar el flujo de trabajo han sido ejercitadas?
    • Determinemos flujos punta a punta interesantes del workflow y verifiquemos que los estamos cubriendo (testing de sistema)
  • Los casos de tests cubren la combinatoria esperada sobre mi especificación?
    • Tengo identificadas como categorías o choices las condiciones que aparecen en la especificación?
    • Se dan los cruces que espera un experto en el dominio?
  • Los datos cubren a los casos de test?
  • Las ejecuciones cubren el grafo de control o el de flujo de datos siguiendo algún criterio estructural?
  • Los datos cubren estructuralmente la GUI?


Test de Integración

Test orientado a verificar que las partes de un sistema que funcionan bien aisladamente, también lo hacen en conjunto.

Testeamos la interacción, la comunicación entre partes. No debe confundirse con testear un sistema-integrado (test de sistema).

Se utilizan programas auxiliares: drivers (simula llamadas) y stubs (simula subprogramas). Requieren un esfuerzo considerable de programación. Se busca minimizar su uso. Las partes más complejas del sistema conviene conectarlas de a poco.

Error de interpretación

La funcionalidad provista por un módulo difiere del comportamiento esperado por quien usa un módulo. Supongamos que A llama a B:

  • La funcionalidad provista por B no es la requerida por la especificación de A
  • B provee más funcionalidades de las que usa A
  • A puede llamar a B con parámetros fuera del dominio de B

Error de ubicación de llamada

La (instrucción de) llamada a un módulo no está en un lugar correcto del código. Relacionado al camino en el que está ubicada la llamada al método.

  • La instrucción está en un camino que no debiera contener la llamada
  • La instrucción está mal ubicada dentro de un camino que debe contener a la llamada
  • La instrucción no está presente en un camino que debiera contener a la llamada

Error de Interfaz

El estándar establecido entre llamador y llamado se viola. Relacionado a la aridad de los métodos.

  • Los parámetros no están en el orden correcto
  • Los parámetros no son del tipo correcto
  • Las reglas de llamadas no se respetan (por valor, por referencia)
  • El dominio del parámetro formal difiere del dominio del parámetro real
  • Problemas de sincronismo que hace que se acceda a información desactualizada

Tipos de interfaces

  • Parámetros y return
  • Memoria compartida
  • Intercambio de mensajes (asincrónicas)


Test de Unidad

Se realiza sobre una unidad de código pequeña, claramente definida. En general es llevado a cabo por los desarrolladores.

Una unidad es:

  • El trabajo de un único programador
  • La cosa más pequeña que puede ser probada
  • Pequeña... 50 a 300 líneas de código

Una unidad no es:

  • La entidad más pequeña que provee una determinada funcionalidad
  • Lo primero que se puede probar sin programas auxiliares
  • 50,000 - 100,000 líneas de código

Testing estructural de unidades

Se representa el flujo de un programa mediante un flowgraph. Un camino en un flowgraph desde el nodo asociado al inicio del programa hasta el nodo asociado a la terminación del programa se llama camino completo; una ejecución del programa que termina satisfactoriamente, está asociada a un camino completo en el flowgraph del programa.

Un criterio de testing estructural permite identificar entidades que deben cubrirse con los datos de test para satisfacer el criterio, como ser nodos, arcos o caminos del flowgraph. Para cada camino puede haber un set de datos de entrada para que se ejecute, de lo contrario, se dice que es no factible.

El proceso de testing estructural es el siguiente:

  1. Con el código como base, dibujamos el grafo de flujo de control
  2. Determinamos un conjunto de caminos que cumple el criterio
  3. Preparamos los datos de test que forzarán la ejecución de cada camino (por ej, usando ejecución simbólica)
  4. Evaluamos si satisfacemos el criterio
  5. Eventualmente, iteramos

Sin embargo, aunque encuentra muchos errores, no conviene hacer un testing guiado por el código en lugar de las funcionalidades, pues no es lo que llega al usuario final. Es conveniente realizar el testing estructural de unidades una vez satisfechos los tests funcionales.

Cubrimiento de Sentencias o Instrucciones

Todas las sentencias del programa deben ejercitarse, equivale a cubrir todos los nodos del flowgraph.

Cubrimiento de decisiones o Branch

Todas las decisiones en el control del programa deben ejercitarse al menos una vez por true, y al menos una vez por false. Equivale a cubrir todos los arcos del flowgraph e implica criterio de sentencias.

Cubrimiento de condiciones

Todas las condiciones de todas las decisiones en el control del programa deben ejercitarse al menos una vez por true, y al menos una vez por false. Se difiere del anterior en el sentido de que cada decisión de control puede estar compuesta por varias condiciones. Aquí deben testearse individualmente todas ellas. Notar que condiciones no implica branch ni viceversa, ya que no se testean todas las combinaciones de los valores de verdad de las condiciones, sino que se evalúan independientemente.

Cubrimiento de caminos

Todo camino del flujo de control del programa debe ejercitarse al menos una vez, equivale a cubrir todos los caminos del flowgraph. Es poco factible por la explosión combinatoria que implica.

Data flow testing

  • Una sentencia que guarda un valor en la posición de memoria de una variable, crea una definición.
  • Una sentencia que trae el valor de la posición de memoria de una variable es un uso de la definición activa de esa variable
    • Un uso de x es un un uso predicado o p-uso si aparece en el predicado de una sentencia que representa una bifurcación de control
    • En otro caso, se llama uso computacional o c-uso (aparece del lado derecho de una asignación)

El def-use flowgraph de un programa P y una variable X es un flowgraph de P donde cada definición o c-uso de X se asocia a un nodo, y cada p-uso de X a un arco.

Una DUA (definition-use association) es una terna [d, u, x] tal que

  • la variable x está definida en el nodo d
  • la variable x se usa en el nodo u o en el arco u
  • hay al menos un camino desde d hasta u que no contiene otra definición de x además de la de d (libre de definiciones para x)

Es decir, es una terna que vincula una definición de X con su uso inmediato.

A partir de estos conceptos se generan distintos criterios:

  • all defs
  • all c-use
  • all p-use
  • all uses
  • all c-use some p-uses
  • all p-uses some c-uses
  • all du-paths
Cubrimiento All-uses
Para cada variable en el programa, deben ejercitarse toda las asociaciones entre cada definición y toda uso de la misma (tal que esa definición esté activa), equivale a cubrir todas las DUAS del programa.

Subsumption

Un criterio A subsume a otro criterio B si un conjunto de datos de test T satisface un criterio A, entonces T satisface B. Por ejemplo, instrucciones subsume a branches, que subsume a caminos.


<graphviz> digraph G { // Config rankdir=LR; size="8,6"; node [style=rounded, fontname=Arial, fontsize=10]; edge [fontname=Arial, fontsize=10];

// Criterios AllPaths; AllDUPaths; AllUses; AllDefs; AllCUses_SomePUses; AllCUses; AllPUses_SomeCUses; AllPUses; Branch; Statement; // Transiciones AllPaths->AllDUPaths; AllDUPaths->AllUses; AllUses->AllDefs; AllUses->AllCUses_SomePUses; AllUses->AllPUses_SomeCUses; AllCUses_SomePUses->AllCUses; AllPUses_SomeCUses->AllPUses; AllPUses->Branch; Branch->Statement; } </graphviz>


Ejecución del testing

Selección de datos

Se seleccionan y generan los datos que cumplen con los casos de tests diseñados para ejecutarlos.

Ambiente de test

El test de unidades se realiza por los programadores en el ambiente de desarrollo, mientras que los tests de aceptación, integración y sistema se realizan en un ambiente de testing separado del de desarrollo. Una vez aceptados, pueden pasar a producción, donde son reabsorbidos por desarollo.

Terminación del testing

  • Se terminó el tiempo o recursos
  • Se corrieron todos los tests derivados sin detectarse ningún error
  • Porcentaje de cubrimiento de ciertas técnicas elegidas
  • Fault-rate más bajo que un cierto valor especificado (# de errores por unidad de tiempo de testing)
  • Se encontró un número predeterminado de errores (% del número total de errores estimado)

Documentación

Se deben documentar los casos de test, los criterios utilizados, los datos de prueba y criterios de terminación; también es necesario un reporte de ejecución luego de haberlos ejecutado que informe del ambiente y los resultados obtenidos.

Seguimiento

Debe realizarse un seguimiento de los errores desde que son detectados hasta que son finalmente aceptados, previo haber pasado por desarrollo para su corrección mediante debugging.

Regression test

El test de regresión consiste en retestear un sistema luego de haber sido modificado para corregir un determinado elemento, adaptarse a un nuevo ambiente, mejorar prestaciones, etc. Los casos de regresión pueden ser:

  • Reusables, testeando aspectos no modificados
  • Restesteables, testeando aspectos de igual especificación pero distinta implementación
  • Obsoletos, testeando una funcionalidad cuyo requerimiento fue modificado