sintesis

Códigos disponibles en git

¿Por qué?

  • Si las pruebas son viscosas, difíciles de leer o de escribir o …​

  • Si las pruebas son rígidas, difíciles de mantener o extender o …​

  • Si las pruebas son difíciles de ejecutar, lentas o acopladas a un entorno o …​

  • Entonces las pruebas no mejoran la documentación del SUT, para corregirlo e incrementarlo

  • Entonces las pruebas no mejoran la calidad, sin retroalimentación ante cambios del SUT

  • Entonces los riesgos son altos a falta de una buen Red de Seguridad

¿Qué?

  • Son pruebas automáticas, habitualmente funcionales, cuyo SUT es una clase implantadas habitualmente por el propio desarrollador del SUT

  • Pero con esta definición pocas clases cumplen el criterio

  • Una relajación sería, aquellas pruebas cuyo SUT no tienen DOC’s fuera de los paquetes del proyecto pero excluyendo cuando acceden fuera de la memoria de ejecución. Por ejemplo, acceso a persistencia, comunicaciones, …​

¿Para qué?

  • Si las pruebas son fluidas, fáciles de leer o de escribir o …​

  • Si las pruebas son flexibles, fáciles de mantener o extender o …​

  • Si las pruebas son fáciles de ejecutar, rápidas y no acopladas a un entorno o …​

  • Entonces las pruebas mejoran la documentación del SUT, para corregirlo e incrementarlo

  • Entonces las pruebas mejoran la calidad, con retroalimentación ante cambios del SUT

  • Entonces los riesgos son bajos por una buena Red de Seguridad

¿Cómo?

Lo primero es conocer los requisitos del SUT porque si no es una pérdida de tiempo. En tal caso, es necesaria la retroalimentación con los usuarios para afinar las asunciones
— Hunt & Thomas
Pragmatic Unit Testing
Implementar pruebas sin material de referencia a menudo conduce a agujeros en las pruebas. Hacer diseño e implementación de pruebas bajo estrechas y a menudo estresantes restricciones de tiempo hace que dichos agujeros sean mayores posiblemente, incluso para probadores experimentados. Esto subraya la necesidad de un cuidadoso análisis, diseño e implementación de pruebas de antemano, que se pueden combinar durante la ejecución de la prueba con técnicas reactivas para encontrar nuevos errores
— Kaner et al
Testing Computer Software

Valores de Entrada y Salida

  • Plan detallado de valores de entradas y salidas esperadas para su implantación maximizando el número de errores detectados.

  • Deberían cubrir tanto las condiciones válidas o esperadas como las inválidas e inesperadas, incluyendo el hecho de que en muchos sistemas hay considerablemente más condiciones de error que condiciones de no-error: disco lleno, caída de la comunicación, mensajes perdidos, errores de ejecución por overflow, división por 0, …​

Entrada Salida
  • Conjunto de valores para los datos implicados en la característica que queremos probar del SUT:

    • parámetros entrada que habrá que suministrar al ejercicio del SUT

    • atributos del SUT, estado de los DOC’s, …​ que habrá que configurar en la preparación

    • ficheros, configuraciones del sistema, …​ de los que dependen el SUT, DOC, …​ que habrá que configurar en la preparación

  • Conjunto de valores resultantes correspondientes a una entrada dada:

    • valor de retorno de tipo primitivo u objeto del propio método que se ejercita

    • valor de otros de posteriores del SUT, DOC’s, …​ para las aserciones

    • efecto lateral sobre un fichero, configuración del sistema, …​ al que se podrá acceder para las aserciones

    • excepción elevada o delegada en el ejercicio del SUT, incluyendo las aserciones de las precondiciones de los métodos inmersos en el ejercicio del SUT.

La inyección de los datos de entrada La consulta de los datos de salida
  • la interfaz del SUT, la cual contempla métodos que inyectan:

    • la entrada directamente en el ejercicio del SUT.

      • factorial(int v)

    • una entrada indirectamente en la preparación del SUT

      • X x =new X(); x.set(int v);

  • la interfaz del SUT, la cual contempla métodos que obtienen:

    • la salida deseada directamente

      • factorial(int n); x.get(); …​

    • una salida que indirectamente determina la salida deseada.

      • x.get'(); …​

  • la interfaz del DOC.

    • por ejemplo, un Controlador de Tráfico (SUT) cuyo comportamiento depende de la hora (entrada) que obtiene de un Reloj (DOC) configurable

    • directamente al DOC si es accesible desde la prueba, por un diseño con inyección de dependencias del DOC en la preparación del SUT.

      • Crear el Reloj (DOC) configurado con la hora deseada (entrada) en la prueba

      • Crear el Controlador de Tráfico(SUT) inyectándole el Reloj (DOC) por parámetro del constructor

      • Ejercitar el Controlador de Tráfico (SUT) que obtendrá la hora (entrada) deseada

    • indirectamente al doble del DOC si no es accesible a la prueba por un diseño que encapsula el DOC en el SUT como servicio interno.

      • Crear el Doble del Reloj (DOC) configurado con la hora deseada (entrada) en la prueba

      • Crear el Controlador de Tráfico (SUT) inyectándole el Doble del Reloj (DOC) mediante anotaciones

      • Ejercitar el Controlador de Tráfico (SUT) que obtendrá la hora (entrada) deseada

  • la interfaz del DOC

    • por ejemplo, un Juego (SUT) que inyecta la salida que se desea comprobar a un Tablero (DOC) al que se puede, consultar tras el ejercicio del SUT.

    • directamente al DOC si es accesible desde la prueba, por un diseño con inyección de dependencias del DOC en la preparación del SUT.

      • Se crea el Tablero (DOC),

      • se crea el Juego (SUT) inyectándole el Tablero (DOC) mediante un parámetro del constructor o un set.

      • se ejercita el Juego (SUT) y

      • se consulta al Tablero (DOC) por la salida deseada

    • indirectamente al doble del DOC si no es accesible a la prueba por un diseño que encapsula el DOC en el SUT como servicio interno.

      • Se crea el Doble del Tablero (DOC),

      • Se crea el Juego (SUT) inyectándole el Doble del Tablero (DOC) mediante anotaciones,

      • Se ejercita el Juego (SUT) y

      • Se consulta al Doble del Tablero (DOC) por la salida deseada

  • la interfaz de librerías que permiten acceder para preparar ficheros, variables de entorno, …​

    • public void write(String line);

  • la interfaz de librerías que permiten acceder para consultar ficheros, variables de entorno, …​

    • public String read( );

class ClosedInterval {
  public ClosedInterval(double min,double max) {...}
  public double getLenght() {...}
  public double getMiddlePoint() {...}
  public void shift(double value) {...}
  public boolean includes(double value) {...}
  public boolean includes(ClosedInterval closedInterval) {...}
  public boolean intersected(ClosedInterval closedInterval) {...}
  public Closedinterval intersection(ClosedInterval closedInterval) {...}
  public ClosedInterval union(ClosedInterval closedInterval) {...}
  }
Entrada Salida Cruce
  • [20, -30]

AssertionError

  • [-20, 30]

50.0

longitud

5.0

punto medio

Sin datos

Alternativamente, la comprobación de los datos de salida se puede realizar evitando conocer el dato de salida esperado mediante 2 posibles técnicas:

  • Relaciones inversas, usando un código de prueba con la lógica inversa al código del SUT

    • Ejemplo: donde la corrección de la raíz cuadrada se comprueba comparando si el resultado al cuadrado del ejercicio es igual al dato de entrada

    • Ejemplo: verificar la igualdad de la pila tras desapilar y apilar el propio elemento desapilado

  • Comprobación cruzada, normalmente hay más de una manera de calcular alguna cantidad. Por tanto, para la producción, se puede escoger un algoritmo que tiene las mejores características deseables. Pero, para las pruebas, se puede usar uno alternativo y comparar ambas salidas.

    • Esta técnica es especialmente útil cuando hay una forma comprobada y conocida de realizar la tarea que resulta ser demasiado lenta o demasiado inflexible para usarla en el código de producción.

    • Ejemplo: donde la corrección de la raíz cuadrada se comprueba contrastando el resultado con el de otra herramienta validada

    • Ejemplo: bases de datos de un servidor distribuido sustituidas por una base de datos en memoria (hyperSQL, …​) usando dobles de pruebas

Estrategias de Casos de Pruebas

Pruebas de la Caja Negra o Comportamiento Pruebas de Caja Blanca o Estructural
  • Centrada en las funciones, entrada y salidas:

    • Valores x Valores/Excepción/Efecto lateral

  • Centrada en la estructura interna del programa:

    • Valores x Camino/Sentencia/Decisión/Condición/…​

  • Verifican el comportamiento correcto de las funciones externas provistas por el SUT

  • Verifican la correcta implementación de las unidades internas (sentencias, condiciones, datos, …) del SUT.

  • Basada en la selección de entradas a partir de la experiencia previa y del dominio para identificar los casos de prueba que probablemente van a mostrar fallos.

  • Basada en la selección de entradas a partir del análisis del código y deducir conjuntos de valores para que al menos se ejecute una vez cada sentencia del código, una condición, …​

  • Los casos de prueba se diseñan a partir de las especificaciones del SUT

  • Los casos de prueba se diseñan a partir de la estructura interna de la implantación del SUT

  • No requiere el conocimiento ni la implementación previa del SUT

  • Si requiere el conocimiento y la implementación previa del SUT

  • Típicamente, elaboradas por los desarrolladores

  • Típicamente, elaboradas por los probadores

  • Ambas técnicas son aplicables conjuntamente

Pruebas de Caja Negra

Prueba exhaustiva
  • Generar todas las combinaciones plausibles de entradas con sus correspondientes salidas.

    • Es la táctica más obvia pero muchas veces inviable

  • Para un método con un argumento entero con valores válidos en todo su dominio, implicaría ~4kM casos de prueba para cada valor entero y su correspondiente salida

  • Para un método que recibe la información de un formulario de 8 campos con 10 posibles valores diferentes cada uno se obtendría un conjunto de 8^10 = 1,073,741,824 casos de prueba donde quizás alguna no sería posible si un campo depende de otro …​

  • …​

  • Existen distintas técnicas de caja negra y blanca para el diseño de casos de prueba que persiguen detectar fallos reduciendo al mínimo el número de casos de prueba asegurando un criterio de cobertura

Variables independientes
  • Reducir el conjunto de los valores posibles de cada entrada a un subconjunto representativo y reducido del conjunto original

    • Entonces la combinación de todos estos valores representativos de todas las entradas reducirá drásticamente el conjunto de casos de prueba final.

  • Si tenemos una característica de un SUT que depende de 3 entradas con 100 posibles valores cada uno, se obtienen un millón de casos de prueba exhaustiva. Si se reduce el conjunto de valores con únicamente 5 valores representativos del conjunto original, se obtienen 125 casos de prueba

  • Estas técnicas son:

    • Clases de Equivalencia y

    • Análisis de Valores Límite que se aplica sobre la anterior

Variables dependientes
  • Seleccionar algunas combinaciones representativas de entre todas las combinaciones de todos los valores de entrada

  • Si tenemos un conjunto de 700 casos de prueba por la combinación de todos los valores de cada entrada, se puede escoger cierto número de combinaciones de entre las 700 que cumplan cierta característica

  • Estas técnicas son:

    • Vector Ortogonal y

    • Vectores de Pares que se aplica sobre la anterior

Las técnicas de Variables Dependientes son aplicables cuando:

  • Las entradas interactúan

    • El valor de alguna entrada influencia los valores de otras entradas, o el proceso o la salida

  • Las entradas no interactúan

    • El sistema procesaría los datos substancialmente de la misma forma sin tener en cuenta los valores

  • Un formulario de entrada en la que los valores de algún campo determinan los posibles valores de otras entradas

  • Para pruebas de compatibilidad sobre 3 tipos de arquitectura, 3 tipos de sistemas operativos, 4 tipos de impresoras, 3 tipos de redes de conexión, … de un mismo sistema con que debe mantener el mismo comportamiento

Partición en Clases de Equivalencia

  • Un Clase de Equivalencia es un subconjunto de valores de la entrada que el sistema debería manejar de forma equivalente en el ejercicio del SUT.

  • Partición en Clases de Equivalencia: para cada entrada deben repartirse todos sus posibles valores en un número finito de clases de equivalencia que cumplan la siguiente propiedad:

    • la prueba de un valor representativo de una clase de equivalencia permite suponer “razonablemente” que el resultado obtenido (existan defectos o no) será un proceso similar que el obtenido probando cualquier otro valor de esa clase de equivalencia.

  • Es un proceso heurístico, no hay receta, y se obtiene analizando distintos factores que combinan:

    • Las entradas del SUT, que cumplen los requisitos para la implementación

    • Las salidas del SUT, que pueden aportar criterios para la partición en clases de equivalencia.

    • Las precondiciones del SUT, que aportan clases de equivalencia de error en la salida.

    • Las excepciones del SUT, que aportan más clases de equivalencia de error en la salida.

Generación de Casos de Pruebas con Partición de Clases de Equivalencia Resultado
  • 1. Encontrar las Clases de Equivalencia de cada factor;

  • 2. Escoger un valor cualquiera de cada clase de equivalencia de cada factor;

  • 3. Si existen varios factores, generar todas las combinaciones de todos los valores anteriores de cada factor;

    • 3.1. Eliminar aquellas combinaciones que no son factibles por la combinación de los valores de entrada. Por ejemplo, solo existe una entrada con el número de ECTS cuando la entrada de la titulación pertenece a EEES

  • 4. Añadir la salida correspondiente a cada combinación de valores de entrada

  • El resultado será el conjunto de Casos de Prueba:

    • para los factores f1, f2, f3, …

    • con n1, n2, n3, …​ clases de equivalencia,

    • se obtienen n1·n2·n3·…​ casos de prueba.

Factor Ejemplo
  • Una entrada como parámetro:

  • A partir de la entrada de la especificación.

  • A partir de la salida de la especificación.

  • A partir de la precondición de la especificación.

  • Una entrada como atributo:

  • A partir del estado del SUT que suministra un DOC.

  • Un conjunto de distintas entradas:

  • El factor es la relación entre dos parámetros, no dos factores independientes.

  • El factor es la relación entre un parámetro y dos atributos, no tres factores independientes.

  • La relación entre dos parámetro y dos atributos, pero no como cuatro factores independientes. De esta manera se evita hacer la combinación de todos los valores de cada entrada; no hay combinación porque solo hay un factor!

  • Si existen varios factores, se procede al paso 3, para generar todas las combinaciones de todos los valores de cada factor:

    • Un examen compuesto es calificable si todos sus exámenes constituyentes son calificables porque superan su nota mínima. Para 3 exámenes se obtiene:

      • 1. No calificable, No calificable, No calificable

      • 2. No calificable, No calificable, Calificable

      • 3. No calificable, Calificable, No calificable

      • 4. No calificable, Calificable, Calificable

      • 5. Calificable, No calificable, No calificable

      • 6. Calificable, No calificable, Calificable

      • 7. Calificable, Calificable, No calificable

      • 8. Calificable, Calificable, Calificable

Análisis de Valores Límite

Los errores se esconden en los rincones y se aglomeran en los límites
— Beizer
  • La justificación se basa en la evidencia experimental y, por tanto, se aumenta el conjunto de casos de prueba para mejorar la eficacia de encontrar errores

  • Esta técnica es aplicable a la partición de clases de equivalencia cuando sus valores tiene un orden total, o sea, que para toda pareja de valores distintos se puede comprobar si uno es mayor que otro o viceversa.

    • valores enteros, reales, …​ con positivos, menores, …​

    • caracteres por su código con letras, dígitos, …​

    • fechas con pasado, primavera, …​

    • horas con pasado, primavera, …​

    • cadenas de caracteres por su orden lexicográfico con ciertos subrangos específicos

    • enumerados por su ordinal con ciertos subrangos específicos

  • Valor Límite:

    • es un valor para el que cambia el comportamiento del SUT respecto del valor anterior;

    • es un valor cuya salida es errónea y no errónea para el valor anterior, o viceversa

    • …​

  • Casos de Prueba: se generan igual que para la Partición de Clases de Equivalencia excepto que en vez de escoger cualquier valor representativo de la Clase de Equivalencia, se escogen los Valores Límites de dicha Clase de Equivalencia, produciendo un Conjunto de Casos de Prueba más exhaustivo.

Vector Ortogonal

  • Vector ortogonal asegura que se prueban todas las opciones de cada factor y todos los pares de opciones de cada pareja de factores

  • La mayoría de los errores surgirán de opciones simples para cualquier factor dado o de pares de opciones a través de pares de factores.

    • Se hace la simplificación, en muchos casos esencial, de que los errores no surgirán de triples, cuádruples opciones o combinaciones de orden superior

  • Para 3 factores con 2 valores,

    • el análisis de los valores límite desprende 2^3 = 8 combinaciones posibles;

    • el vector ortogonal se desprende la mitad asegurando que existen todas las opciones y todas las parejas de opciones

Vector

  • Para 4 factores con 7, 3, 5 y 2 valores respectivamente,

    • el análisis de los valores límite desprende 7·3·5·2 = 210 combinaciones posibles;

    • el vector ortogonal desprende 36 casos de prueba (17%) asegurando que existen todas las opciones y todas las parejas de opciones

Vector2

Vector3

Vector de Pares

  • Es otra técnica que optimiza al Vector Ortogonal compactando aún más el conjunto de casos de prueba: Herramienta

    • Descargar y descomprimir

    • Editar la entrada en Excel:

      • factores consecutivos en la primera fila a partir de la primera columna y

      • la lista de opciones de cada factor en la misma columna del factor en celdas inferiores de forma consecutiva

    • Salvar la entrada en formato texto separado por tabulaciones

    • Ejecutar el comando: c:>allpairs in.txt > out.txt

    • Visualizar el fichero de salida, aunque se ve mejor con Excel

height32

Vector5

ExamA ExamB ExamC

isQualifiable

isQualifiable

isQualifiable

notlsQualifiable

notlsQualifiable

notlsQualifiable

Pruebas de Caja Blanca

  • Todas estas técnicas requieren del análisis del código del SUT con los siguientes pasos:

  • Convertir el código a un Grafo de Control del Flujo de Ejecución

  • Calcular la Complejidad Ciclomática, V(G)

  • Obtener los Caminos Independientes, tantos como la Complejidad Ciclomática, V(G)

  • Concretar los Casos de Prueba para cada Camino Independiente

Grafo de Control de Flujo de Ejecución

  • Grafo dirigido que representa un programa de modo que se observan con claridad los diferentes puntos donde existen los cambios de dirección del control de flujo de ejecución

  • Nodos:

    • Existe un único nodo inicial y final

    • Cada nodo representa:

      • Una sentencia simple

      • La condición de una sentencia alternativa o iterativa

      • La unión de los caminos de una sentencia alternativa o iterativa

  • Arcos:

    • La conexión entre dos nodos es reflejo del paso de control de flujo de ejecución entre ambos, por tanto, un nodo puede tener:

      • Una conexión de salida, secuenciales

      • Dos conexiones de salida, el nodo condición de una sentencia alternativa o iterativa

      • Varias conexiones de salida, el nodo condición de una sentencia alternativa múltiple (switch)

      • Varios conexiones de entrada, el nodo de unión de una sentencia alternativa o iterativa.

Ejemplo Grafo Ejemplo Grafo
  • Sentencia Compuesta

{
  <sent1>;
  <sent2>;
  ...
}
GrafoDeControlDeLaSentenciaCompuesta1
  • Sentencia alternativa simple

if (<cond>)
  <sent>;
GrafoDeControlDeLaSentenciaIfThen2
  • Sentencia alternativa compuesta

if (<cond>)
  <thenSent>;
 else
  <elseSent>;
GrafoDeControlDeLaSentenciaIfElse3
  • Sentencia iterativa indeterminada 0..N

while(<cond>)
  <sent>;
GrafoDeControlDeLaSentenciaWhile4
  • Sentencia iterativa indeterminada 1..N

do {
  <sent>;
  <sent>;
  ...
} while(<cond>);
GrafoDeControlDeLaSentenciadoWhile5
  • Sentencia iterativa determinadaF

for (<init>; <cond>; <inc>)
  <sent>;
  • equivalente a:

<init>;
while (<cond) {
  <sent>;
  <inc>;
  }
GrafoDeControlDeLaSentenciadoWhile6
  • Algoritmo descendente para la Construcción de Grafo de Control del Flujo de Ejecución:

    • Inicialización. Se comienza con un nodo que contiene todas las sentencias

    • Expansión. Mientras haya nodos con sentencias anidadas

      • sustituirlos por su expansión correspondiente a los patrones anteriores: secuencial, alternativas e iterativas

    • Compactación. Todo nodo con una única conexión de entrada y un única de salida puede ser eliminable o “compactado” con su nodo sucesor.

      • En tal caso, un nodo representa una o varias sentencias

  • Ejemplo de compactación secuencial

Sin compactación 1ª compactación 2ª compactación
sentencias4
sentencias3
sentencias2
Ejemplo de Construcción de Grafo de Control de Flujo de Ejecución
  • Código

  • Inicialización

{(1)
  for (int i = 0; i < loops; i++) {(2)
   for (intj = 0; j < size; j++) {(3)
   expectedValues.add(j);(4)
  }(5)
}(6)
while (resultValues.get(0) !=expectedValues.get(0)) {(7)
  expectedValues.add(expectedValues.remove(0));(8)
  }(9)
  return expectedValues;(10)
}(11)

Coberturas8

  • Expansión. Sentencia compuesta [1-11]

  • Expansión. Sentencia iterativa [2-6]

Coberturas9

Coberturas10

  • Expansión. Sentencia iterativa [3-5]

  • Expansión. Sentencia iterativa [7-9]

Coberturas11

Coberturas12

  • Compactación. Nodos elminables

Coberturas14

Coberturas15

Complejidad Ciclomática

Formulaciones
  • Propuesto en 1996 por Watson y McCabe para medir la complejidad del código en proporción al número de caminos independientes

Sea arc el número de conexiones y nod el número de nodos

Sea nodCond el número de nodos condición

Sea reg el número de regiones encerradas entre conexiones

V(G) = arc – nod + 2

V(G) = nodCond + 1

V(G) = reg + 1

Sentencia Ejemplo Grafo Complejidad

Sentencia Compuesta

{
  <sent1>;
  <sent2>;
  ...
}
GrafoDeControlDeLaSentenciaCompuesta7
  • V(G)=arc - nod + 2 = 1 - 2 + 2 = 1;

  • V(G)=nodCon + 1 = 0 + 1 = 1;

  • V(G)=regRod + 1 = 0 + 1 =1;

Sentencia alternativa simple

if(<cond>)
  <sent>;
GrafoDeControlDeLaSentenciaIfThen8
  • V(G) =arc - nod + 2 = 3 -3 +2 =2

  • V(G) =nodCon +1 =1 + 1 = 2

  • V(G) =regRod +1 = 1 +1 = 2

Sentencia alternativa compuesta

if(<cond>)
  <thenSent>;
 else
  <elseSent>;
GrafoDeControlDeLaSentenciaIfElse9
  • V(G) =arc - nod + 2 = 4 - 4 + 2 = 2;

  • V(G) =nodCon + 1 = 1 + 1 = 2;

  • V(G) =regRod + 1 = 1 + 1 = 2;

Sentencia iterativa indeterminada 0..N

while(<cond>)
  <sent>;
GrafoDeControlDeLaSentenciaWhile10
  • V(G) =arc - nod + 2 =3 - 3 + 2 = 2;

  • V(G) =nodCon + 1 = 1 + 1 = 2;

  • V(G) =regRod + 1 = 1 + 1 = 2;

Sentencia iterativa indeterminada 1..N

do {
  <sent>;
  <sent>;
  <...>;
} while(<cond>);
GrafoDeControlDeLaSentenciadoWhile11
  • V(G) =arc - nod + 2 = 4 - 4 + 2 = 2;

  • V(G) =nodCon + 1 = 1 + 1 = 2;

  • V(G) =regRod + 1 = 1 + 1 = 2;

Sentencia iterativa determinada

for(<init>; <cond>; <inc>)
  <sent>;
  • equivalente a:

<init>;
while (<cond) {
  <sent>;
  <inc>;
}
GrafoDeControlDeLaSentenciadoWhile12
  • V(G) =arc - nod + 2 = 5 - 5 + 2 = 2;

  • V(G) =nodCon + 1 = 1 + 1 = 2;

  • V(G) =regRod + 1 = 1 + 1 = 2;

Ejemplo de Cálculo Complejidad Ciclomática
{(1)
 for (int i=0;i<loops;i++){(2)
  for (intj=0;j<size;j++){(3)
   expectedValues.add(j);(4)
  }(5)
 }(6)
 while(resultValues.get(0)
  !=expectedValues.get(0)){(7)
   expectedValues.add(
    expectedValues.
     remove(0));(8)
 }(9)
 return expectedValues;(10)
}(11)

height32

  • V(G)= arcnod + 2 = 108 + 2 = 4;

  • V(G)= nodCon + 1 = 3 + 1 = 4;

  • V(G)= regRod + 1 = 3 + 1= 4;

Caminos Independientes del Grafo de Control

  • Algoritmo

  • 1. Inicializar el Conjunto Base, cuyos elementos son Caminos Independientes, a vacío.

  • 2. Seleccionar un camino funcional, que no sea salida de error, que sea el más importante a ojos del probador y agregarlo al Conjunto Base

  • 3. while(existan nodos de decisión con salidas no utilizados && |Conjunto Base|<V(G)) do {

    • Seguir un Camino Independiente hasta uno de tales nodos

    • Seguir la conexión no utilizada y buscar regresar al Camino Independiente tan pronto como sea posible

    • Agregar el Camino Independiente al Conjunto Base

  • }

Ejemplo de Cálculo Complejidad Ciclomática
{(1)
 for (int i=0;i<loops;i++){(2)
  for (intj=0;j<size;j++){(3)
   expectedValues.add(j);(4)
  }(5)
 }(6)
 while(resultValues.get(0)
  !=expectedValues.get(0)){(7)
   expectedValues.add(
    expectedValues.
     remove(0));(8)
 }(9)
 return expectedValues;(10)
}(11)

Coberturas

  • Rojo: A, B, C, D, C, E, B, F, G, F, H.

  • Verde: A, B, F, G, F, H.

  • Azul: A, B, C, E, B, F, G, F, H.

  • Amarillo: A, B, F, H.

Casos de Prueba

  • Para cada Camino Independiente del Conjunto Base

    • Encontrar una entrada cuya ejecución recorre el Camino Independiente

    • Encontrar la salida correspondiente a la entrada anterior

  • Cobertura de Sentencia, se alcanza el 100% cuando las pruebas han ejercitado cada sentencia al menos una vez:

    • Observe la primera condición e identifique las variables que intervienen

      • Si las variables corresponden a parámetros o se leen directamente, entonces esas variables y los valores contra los que se comparan definen los valores de entrada de los casos de prueba

      • Si las variables son internas, habrá que retroceder hasta encontrar las variables externas (parámetros o leídas) de las que dependen y aplicar la misma idea

      • Si el camino hay otras condiciones deberá repetirse el proceso, agregando más variables a la parte de entrada del caso de prueba

      • Cuando no hay más condiciones, agregue la parte de salidas esperadas del caso de prueba, de acuerdo a las especificaciones del programa

  • También existe la Cobertura de Decisión que alcanza el 100% cuando las pruebas han ejercitado cada decisión con cada posible valor al menos una vez.

    • Un if con la condición evaluada a true y a false; un switch con la condición evaluada para cada caso; …​

  • Extensión en la construcción del Grafo de Control del Flujo de Ejecución que atañe a las condiciones múltiples

    • representar las sub-condiciones de las sentencias alternativas e iterativas sustituyendo al nodo condición y con conexiones según la evaluación perezosa del y-lógico y o-lógico.

height32

  • Cobertura de Condición se alcanza el 100% cuando las pruebas han ejercitado cada condición de cada decisión con cada posible valor al menos una vez.

    • un if (x>5 && y<5) con la condición x>5 a true y a false y la condición y<5 a true y a false, por tanto: true && true, false && false

  • Cobertura de Multicondición se alcanza el 100% cuando las pruebas han ejercitado tanto la Cobertura de Decisiones como de Condiciones.

  • Cobertura de Decisión/Multicondición se alcanza el 100% cuando las pruebas han ejercitado tanto como la Cobertura de Multicondición sin los casos imposibles por la evaluación de operadores con cortocircuito.

Herramientas

  • La solución manual es insufrible frente a soluciones automáticas mediante herramientas de Cobertura: EclEmma

height32

height32

Comparativa entre Pruebas de Caja Negra y Caja Blanca

Caja Negra Caja Blanca
  • Cobertura según los datos de entrada pero en la práctica es muy fiable

  • Cobertura personalizada por Sentencias, Decisiones, Condiciones, …​

  • Menos habilidad técnica

  • Más habilidad técnica

  • Menos herramientas

  • Más herramientas

  • Menos tiempo y costes

  • Más tiempo y costes

  • Muy recomendada

  • Poco recomendada, solo en puntos críticos de riesgo!!!

Diseño del SUT para la Prueba

  • Aislamiento de dependencias. En casi todos los casos, una simple clase no trabaja sola. En un sistema típico, cada clase interactúa con otras clases y depende de ellas para funcionar correctamente.

  • Al escribir pruebas, nuestra capacidad de aislar la clase de todas las demás dependencias es crucial y debemos pensar en poner mecanismos en el lugar para permitirnos hacerlo con facilidad

    • Cumplir los principios SOLID, GRASP, KISS, DRY, YAGNI, …​, Diseño por Contrato, Principio de Mínima Sorpresa, …​

    • Evitar ciclos de dependencias, alto acoplamiento, baja cohesión, elevada complejidad, …​

    • Asegurar la calidad del diseño de SUT: fluido, fuerte, robusto y reusable

  • Instanciar una clase.

    • En la mayoría de los casos, una clase no está diseñada para ser creada de forma autónoma, como lo hacemos al escribir pruebas.

    • Normalmente, las clases se crean como parte de un sistema completo.

    • Dado que dependen de otras partes del sistema, se aseguran de que esas partes están ahí y funcionan correctamente.

    • La configuración de estas partes en el entorno de pruebas es costosa y compleja.

    • Para evitar esto se necesita un mecanismo para crear la instancia de prueba sin crear el resto de las dependencias también: Inyección de Dependencias

      • ¿Cambiar el reparto de responsabilidades del SUT para la prueba?

      • ¿Desencapsular los DOC’s del SUT para la prueba?

      • ¿Obligar a todo creador del SUT a la creación de sus DOC’s?

  • Verificación de las interacciones.

    • Para poder escribir pruebas significativas, se debe comprobar el comportamiento esperado.

    • En algunos casos, este comportamiento puede observarse observando el estado resultante del SUT al final de la prueba.

    • Sin embargo, en muchos casos, la clase probada no tiene un estado significativo propio y su propósito es interactuar correctamente con otras partes.

    • Para verificar esta interacción necesitamos una forma de permitirlo durante la prueba, asegurando que todas las interacciones esperadas se llevaron a cabo como deberían.

    • Los Dobles de Pruebas: Mock, Stub, Spy, Fake y Dummy

Sintesis

sintesis

Bibliografía

Obra, Autor y Edición Portada Obra, Autor y Edición Portada
  • xUnit Test Patterns: Refactoring Test Code

    • Gerard Meszaros

    • Addison-Wesley Educational Publishers Inc; Edición: 01 (21 de mayo de 2007)

xUnitTestPatternsRefactoringTestCode

  • Effective Unit Testing

    • Lasse Koskela

    • Manning Publications Company; Edición: 1 (16 de febrero de 2013)

EffectiveUnitTesting

  • The Art of Software Testing

    • Glenford J. Myers

    • John Wiley & Sons Inc; Edición: 3. Auflage (16 de diciembre de 2011)

TheArtofSoftwareTesting

  • Pragmatic Unit Testing in Java with JUnit

    • Andy Hunt, Dave Thomas

    • The Pragmatic Programmers (1674)

PragmaticUnitTestinginJavawithJUnit

  • Testing Computer Software

    • Cem Kaner,Jack Falk,Hung Q. Nguyen

    • Wiley John + Sons; Edición: 2nd (12 de abril de 1999)

TestingComputerSoftware

  • Diseno Agil con TDD

    • Ble Jurado, Carlos

    • Lulu.com (18 de enero de 2010)

DisenoAgilconTDD

  • Test Driven Development

    • Kent Beck

    • Addison Wesley; Edición: 01 (8 de noviembre de 2002)

TestDrivenDevelopment

  • Growing Object-Oriented Software, Guided by Tests

    • Steve Freeman

    • Addison-Wesley Professional (12 de octubre de 2009)

GrowingObjectOrientedSoftware,GuidedbyTests

  • How Google Tests Software

    • James A. Whittaker,Jason Arbon,Jeff Carollo

    • Addison-Wesley Educational Publishers Inc; Edición: 01 (2 de abril de 2012)

HowGoogleTestsSoftware

  • Pragmatic Project Automation: How to Build, Deploy, and Monitor Java Apps

    • Mike Clark

    • The Pragmatic Programmers (1900)

PragmaticProjectAutomation

Ponente

  • Luis Fernández Muñoz

setillo

  • Doctor en Inteligencia Artificial por la UPM

  • Ingeniero en Informática por la UMA

  • Diplomado en Informática por la UPM

  • Profesor Titular de ETSISI de la UPM