Cobertura de código - Code coverage

En informática , la cobertura de pruebas es una medida que se utiliza para describir el grado en que se ejecuta el código fuente de un programa cuando se ejecuta una serie de pruebas en particular . Un programa con una alta cobertura de prueba, medida como porcentaje, ha tenido más de su código fuente ejecutado durante la prueba, lo que sugiere que tiene una menor probabilidad de contener errores de software no detectados en comparación con un programa con baja cobertura de prueba. Se pueden usar muchas métricas diferentes para calcular la cobertura de la prueba; algunos de los más básicos son el porcentaje de subrutinas del programa y el porcentaje de sentencias del programa llamadas durante la ejecución de la suite de pruebas.

La cobertura de prueba fue uno de los primeros métodos inventados para la prueba sistemática de software . La primera referencia publicada fue de Miller y Maloney en Communications of the ACM en 1963.

Criterios de cobertura

Para medir qué porcentaje de código ha sido ejercido por un conjunto de pruebas , se utilizan uno o más criterios de cobertura . Los criterios de cobertura generalmente se definen como reglas o requisitos que un conjunto de pruebas debe satisfacer.

Criterios básicos de cobertura

Hay una serie de criterios de cobertura, siendo los principales:

  • Cobertura de funciones  : ¿se ha llamado a cada función (o subrutina ) del programa?
  • Cobertura de declaraciones  : ¿se han ejecutado todas las declaraciones del programa?
  • Cobertura de bordes  : ¿se han ejecutado todos los bordes del gráfico de flujo de control ?
  • Cobertura de  rama: ¿se ha ejecutado cada rama (también denominada ruta DD ) de cada estructura de control (como en las declaraciones if y case )? Por ejemplo, dada una declaración if , ¿se han ejecutado las ramas verdadera y falsa? Este es un subconjunto de cobertura de bordes.
  • Cobertura de condición (o cobertura de predicado): ¿se ha evaluado cada subexpresión booleana como verdadera y falsa?

Por ejemplo, considere la siguiente función C:

int foo (int x, int y)
{
    int z = 0;
    if ((x > 0) && (y > 0))
    {
        z = x;
    }
    return z;
}

Suponga que esta función es parte de un programa más grande y que este programa se ejecutó con algún conjunto de pruebas.

  • Si durante esta ejecución se llamó a la función 'foo' al menos una vez, entonces se satisface la cobertura de la función para esta función.
  • La cobertura de instrucciones para esta función se cumplirá si se llamó, por ejemplo foo(1,1), ya que, como en este caso, se ejecuta cada línea de la función, incluido z = x;.
  • Las pruebas que llaman foo(1,1)y foo(0,1)satisfarán la cobertura de la sucursal porque, en el primer caso, ifse cumplen y z = x;se ejecutan ambas condiciones , mientras que en el segundo caso (x>0)no se cumple la primera condición que impide la ejecución z = x;.
  • La cobertura de condiciones puede satisfacerse con pruebas que requieren foo(1,0)y foo(0,1). Estos son necesarios porque en los primeros casos (x>0)evalúa a true, mientras que en el segundo evalúa false. Al mismo tiempo, el primer caso lo hace (y>0) false, mientras que el segundo lo hace true.

La cobertura de condiciones no implica necesariamente cobertura de sucursales. Por ejemplo, considere el siguiente fragmento de código:

if a and b then

La cobertura de la condición se puede satisfacer mediante dos pruebas:

  • a=true, b=false
  • a=false, b=true

Sin embargo, este conjunto de pruebas no satisface la cobertura de la sucursal, ya que ninguno de los casos cumplirá la ifcondición.

La inyección de fallas puede ser necesaria para garantizar que todas las condiciones y ramas del código de manejo de excepciones tengan una cobertura adecuada durante las pruebas.

Cobertura de decisión / condición modificada

Una combinación de cobertura de funciones y cobertura de sucursales a veces también se denomina cobertura de decisión . Este criterio requiere que todos los puntos de entrada y salida del programa se hayan invocado al menos una vez y que cada decisión del programa haya adoptado todos los resultados posibles al menos una vez. En este contexto, la decisión es una expresión booleana compuesta por condiciones y cero o más operadores booleanos. Esta definición no es lo mismo que cobertura de sucursales, sin embargo, algunos usan el término cobertura de decisión como sinónimo de cobertura de sucursales .

La cobertura por condición / decisión requiere que se satisfagan tanto la cobertura de decisión como la de condición. Sin embargo, para aplicaciones críticas para la seguridad (por ejemplo, para software de aviónica), a menudo se requiere que se satisfaga la cobertura de condición / decisión modificada (MC / DC) . Este criterio amplía los criterios de condición / decisión con requisitos de que cada condición debe afectar el resultado de la decisión de forma independiente. Por ejemplo, considere el siguiente código:

if (a or b) and c then

Los criterios de condición / decisión se satisfarán mediante el siguiente conjunto de pruebas:

  • a = verdadero, b = verdadero, c = verdadero
  • a = falso, b = falso, c = falso

Sin embargo, el conjunto de pruebas anterior no satisfará la cobertura de condición / decisión modificada, ya que en la primera prueba, el valor de 'b' y en la segunda prueba el valor de 'c' no influirían en la salida. Por lo tanto, se necesita el siguiente conjunto de prueba para satisfacer MC / DC:

  • a = falso, b = verdadero, c = falso
  • a = falso, b = verdadero , c = verdadero
  • a = falso , b = falso , c = verdadero
  • a = verdadero , b = falso, c = verdadero

Cobertura de enfermedades múltiples

Este criterio requiere que se prueben todas las combinaciones de condiciones dentro de cada decisión. Por ejemplo, el fragmento de código de la sección anterior requerirá ocho pruebas:

  • a = falso, b = falso, c = falso
  • a = falso, b = falso, c = verdadero
  • a = falso, b = verdadero, c = falso
  • a = falso, b = verdadero, c = verdadero
  • a = verdadero, b = falso, c = falso
  • a = verdadero, b = falso, c = verdadero
  • a = verdadero, b = verdadero, c = falso
  • a = verdadero, b = verdadero, c = verdadero

Cobertura de valor de parámetro

La cobertura de valor de parámetro (PVC) requiere que en un método que toma parámetros, se consideren todos los valores comunes para dichos parámetros. La idea es que se prueben todos los valores posibles comunes para un parámetro. Por ejemplo, los valores comunes para una cadena son: 1) nulo, 2) vacío, 3) espacios en blanco (espacio, tabulaciones, nueva línea), 4) cadena válida, 5) cadena no válida, 6) cadena de un solo byte, 7) doble cadena de bytes. También puede ser apropiado utilizar cadenas muy largas. Si no se prueban todos los valores posibles de los parámetros, se puede producir un error. Probar solo uno de estos podría resultar en una cobertura de código del 100% ya que cada línea está cubierta, pero como solo se prueba una de las siete opciones, solo hay 14.2% de PVC.

Otros criterios de cobertura

Existen otros criterios de cobertura, que se utilizan con menos frecuencia:

  • Cobertura de secuencia de código lineal y salto (LCSAJ), también conocida como cobertura de JJ-Path  : ¿se han ejecutado todas las LCSAJ / JJ-path?
  • Cobertura de ruta  : ¿se han ejecutado todas las rutas posibles a través de una parte determinada del código?
  • Cobertura de entrada / salida  : ¿se han ejecutado todas las posibles llamadas y devoluciones de la función?
  • Cobertura de bucle  : ¿se han ejecutado todos los bucles posibles cero veces, una vez y más de una vez?
  • Cobertura estatal  : ¿se ha alcanzado y explorado cada estado en una máquina de estados finitos ?
  • Cobertura del flujo de datos  : ¿se ha alcanzado y explorado cada definición de variable y su uso?

A menudo se requieren aplicaciones de seguridad críticas o confiables para demostrar el 100% de alguna forma de cobertura de prueba. Por ejemplo, el estándar ECSS -E-ST-40C exige una cobertura de declaración y decisión del 100% para dos de los cuatro niveles de criticidad diferentes; para los demás, los valores de cobertura objetivo están sujetos a negociación entre proveedor y cliente. Sin embargo, el establecimiento de valores objetivo específicos - y, en particular, el 100% - ha sido criticado por los profesionales por varias razones (cf.) Martin Fowler escribe: "Sospecharía de algo como el 100%; olería a alguien escribiendo pruebas para hacer felices a los números de cobertura, pero sin pensar en lo que están haciendo ".

Algunos de los criterios de cobertura anteriores están relacionados. Por ejemplo, la cobertura de ruta implica decisión, declaración y cobertura de entrada / salida. La cobertura de decisiones implica la cobertura del estado de cuenta, porque cada estado de cuenta es parte de una sucursal.

La cobertura de ruta completa, del tipo descrito anteriormente, suele ser poco práctica o imposible. Cualquier módulo con una sucesión de decisiones en él puede tener hasta caminos dentro de él; Las construcciones de bucle pueden dar como resultado un número infinito de rutas. Muchas rutas también pueden ser inviables, ya que no hay una entrada al programa bajo prueba que pueda hacer que se ejecute esa ruta en particular. Sin embargo, se ha demostrado que un algoritmo de propósito general para identificar rutas no factibles es imposible (dicho algoritmo podría usarse para resolver el problema de la detención ). La prueba de ruta básica es, por ejemplo, un método para lograr una cobertura completa de sucursales sin lograr una cobertura de ruta completa.

Los métodos para las pruebas prácticas de cobertura de ruta intentan, en cambio, identificar clases de rutas de código que difieren solo en el número de ejecuciones de bucle, y para lograr una cobertura de "ruta base", el probador debe cubrir todas las clases de ruta.

En la práctica

El software de destino está construido con opciones especiales o bibliotecas y se ejecuta en un entorno controlado, para asignar cada función ejecutada a los puntos de función en el código fuente. Esto permite probar partes del software de destino a las que rara vez o nunca se accede en condiciones normales, y ayuda a asegurar que se han probado las condiciones más importantes (puntos de función). La salida resultante se analiza para ver qué áreas del código no se han ejercido y las pruebas se actualizan para incluir estas áreas según sea necesario. Combinado con otros métodos de cobertura de pruebas, el objetivo es desarrollar un conjunto de pruebas de regresión riguroso pero manejable.

Al implementar políticas de cobertura de prueba dentro de un entorno de desarrollo de software, se debe considerar lo siguiente:

  • ¿Cuáles son los requisitos de cobertura para la certificación del producto final y, de ser así, qué nivel de cobertura de prueba se requiere? El nivel típico de progresión del rigor es el siguiente: declaración, rama / decisión, condición modificada / cobertura de decisión (MC / DC), LCSAJ ( secuencia de código lineal y salto )
  • ¿Se medirá la cobertura contra las pruebas que verifican los requisitos impuestos al sistema bajo prueba ( DO-178B )?
  • ¿Es el código objeto generado directamente rastreable a las declaraciones del código fuente? Ciertas certificaciones (es decir, DO-178B Nivel A) requieren cobertura a nivel de ensamblaje si este no es el caso: "Luego, se debe realizar una verificación adicional en el código objeto para establecer la exactitud de dichas secuencias de código generadas" ( DO-178B ) párrafo-6.4.4.2.

Los autores de software pueden mirar los resultados de la cobertura de prueba para diseñar pruebas adicionales y conjuntos de entrada o configuración para aumentar la cobertura de las funciones vitales. Dos formas comunes de cobertura de prueba son cobertura de declaración (o línea) y cobertura de sucursal (o borde). La cobertura de línea informa sobre la huella de ejecución de las pruebas en términos de qué líneas de código se ejecutaron para completar la prueba. La cobertura de borde informa qué ramas o puntos de decisión de código se ejecutaron para completar la prueba. Ambos informan una métrica de cobertura, medida como porcentaje. El significado de esto depende de qué forma (s) de cobertura se hayan utilizado, ya que la cobertura de sucursales del 67% es más completa que la cobertura del estado de cuenta del 67%.

Generalmente, las herramientas de cobertura de prueba incurren en cálculo y registro además del programa real, lo que ralentiza la aplicación, por lo que normalmente este análisis no se realiza en producción. Como era de esperar, hay clases de software que no pueden someterse de manera factible a estas pruebas de cobertura, aunque se puede aproximar un grado de mapeo de cobertura mediante análisis en lugar de pruebas directas.

También hay algunos tipos de defectos que se ven afectados por estas herramientas. En particular, algunas condiciones de carrera u operaciones similares sensibles en tiempo real se pueden enmascarar cuando se ejecutan en entornos de prueba; aunque a la inversa, algunos de estos defectos pueden resultar más fáciles de encontrar como resultado de la sobrecarga adicional del código de prueba.

La mayoría de los desarrolladores de software profesionales utilizan la cobertura C1 y C2. C1 significa cobertura de estado de cuenta y C2 para cobertura de sucursal o condición. Con una combinación de C1 y C2, es posible cubrir la mayoría de las declaraciones en una base de código. La cobertura de declaraciones también cubriría la cobertura de funciones con cobertura de entrada y salida, bucle, ruta, flujo de estado, flujo de control y flujo de datos. Con estos métodos, es posible lograr una cobertura de código de casi el 100% en la mayoría de los proyectos de software.

Uso en la industria

La cobertura de las pruebas es una consideración en la certificación de seguridad de los equipos de aviónica. Las pautas por las cuales los equipos de aviónica están certificados por la Administración Federal de Aviación (FAA) están documentados en DO-178B y DO-178C .

La cobertura de prueba también es un requisito en la parte 6 de la norma de seguridad automotriz ISO 26262 Vehículos de carretera: seguridad funcional .

Ver también

Referencias