En ingeniería de software , la cobertura de código , también llamada cobertura de prueba , es una medida porcentual del grado en el que se ejecuta el código fuente de un programa cuando se ejecuta un conjunto de pruebas en particular . Un programa con una alta cobertura de código tiene una mayor parte 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 una baja cobertura de código. [1] [2] Se pueden utilizar muchas métricas diferentes para calcular la cobertura de prueba. Algunas de las más básicas son el porcentaje de subrutinas del programa y el porcentaje de declaraciones del programa llamadas durante la ejecución del conjunto de pruebas.
La cobertura de código 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. [3]
Para medir qué porcentaje de código ha sido ejecutado por un conjunto de pruebas , se utilizan uno o más criterios de cobertura . Estos suelen definirse como reglas o requisitos que un conjunto de pruebas debe satisfacer. [4]
Existen varios criterios de cobertura, pero los principales son: [5]
Por ejemplo, considere la siguiente función C :
int foo ( int x , int y ) { int z = 0 ; si (( x > 0 ) && ( y > 0 )) { z = x ; } devuelve z ; }
Supongamos que esta función es parte de un programa más grande y que este programa se ejecutó con algún conjunto de pruebas.
foo
se llamó al menos una vez.foo(1,1)
, porque en este caso, se ejecutaría cada línea de la función, incluida z = x;
.foo(1,1)
y foo(0,1)
porque, en el primer caso, if
se cumplen ambas condiciones y z = x;
se ejecuta, mientras que en el segundo caso, la primera condición, (x>0)
, no se cumple, lo que impide la ejecución de z = x;
.foo(1,0)
, foo(0,1)
y foo(1,1)
. Estas son necesarias porque en el primer caso, (x>0)
se evalúa como true
, mientras que en el segundo, se evalúa como false
. Al mismo tiempo, el primer caso hace (y>0)
false
, el segundo caso no se evalúa (y>0)
(debido a la evaluación diferida del operador booleano), el tercer caso lo hace true
.En los lenguajes de programación que no realizan evaluación de cortocircuito , la cobertura de condiciones no implica necesariamente la cobertura de bifurcaciones. Por ejemplo, considere el siguiente fragmento de código Pascal :
Si a y b entonces
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 rama ya que ninguno de los casos cumplirá la if
condición.
La inyección de errores 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.
Una combinación de cobertura de función y cobertura de rama a veces también se denomina cobertura de decisión . Este criterio requiere que cada punto de entrada y salida en el programa se haya invocado al menos una vez, y que cada decisión en el programa haya tomado todos los resultados posibles al menos una vez. En este contexto, la decisión es una expresión booleana que comprende condiciones y cero o más operadores booleanos. Esta definición no es la misma que la cobertura de rama, [6] sin embargo, el término cobertura de decisión a veces se usa como sinónimo de ella. [7]
La cobertura de condición/decisión requiere que se cumplan tanto la cobertura de decisión como la de condición. Sin embargo, para aplicaciones críticas para la seguridad (como el software de aviónica ), a menudo se requiere que se cumpla la cobertura de condición/decisión modificada (MC/DC) . Este criterio extiende 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:
Si ( a o b ) y c entonces
Los criterios de condición/decisión se cumplirán mediante el siguiente conjunto de pruebas:
Sin embargo, el conjunto de pruebas anterior no satisfará la cobertura de decisión/condició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 el resultado. Por lo tanto, se necesita el siguiente conjunto de pruebas para satisfacer MC/DC:
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:
La cobertura de valores de parámetros (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. [8] Por ejemplo, los valores comunes para una cadena son: 1) null , 2) empty, 3) whitespace (space, tabs, newline), 4) valid string, 5) invalid string, 6) single-byte string, 7) double-byte string. También puede ser apropiado usar cadenas muy largas. Si no se prueba cada valor de parámetro posible, puede resultar en 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 un 14,2% de PVC.
Existen otros criterios de cobertura que se utilizan con menos frecuencia:
A menudo, se exige que las aplicaciones críticas o confiables para la seguridad demuestren el 100% de alguna forma de cobertura de prueba. Por ejemplo, el estándar ECSS -E-ST-40C exige una cobertura del 100% de declaraciones y decisiones para dos de los cuatro niveles de criticidad diferentes; para los demás, los valores de cobertura objetivo dependen de la negociación entre el proveedor y el cliente. [11] Sin embargo, el establecimiento de valores objetivo específicos, y, en particular, el 100%, ha sido criticado por los profesionales por varias razones (cf. [12] ). Martin Fowler escribe: "Yo desconfiaría de cualquier valor de 100%; parecería que alguien escribe pruebas para hacer felices a los números de cobertura, pero no piensa en lo que está haciendo". [13]
Algunos de los criterios de cobertura anteriores están relacionados. Por ejemplo, la cobertura de la ruta implica cobertura de decisiones, declaraciones y entrada/salida. La cobertura de decisiones implica cobertura de declaraciones, porque cada declaración es parte de una rama.
La cobertura completa de rutas, del tipo descrito anteriormente, suele ser poco práctica o imposible. Cualquier módulo con una sucesión de decisiones puede tener hasta rutas dentro de él; las construcciones de bucle pueden dar como resultado una cantidad infinita de rutas. Muchas rutas también pueden ser inviables, en el sentido de que no hay ninguna 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 inviables es imposible (tal algoritmo podría usarse para resolver el problema de detención ). [14] La prueba de ruta base es, por ejemplo, un método para lograr una cobertura completa de la rama sin lograr una cobertura completa de la ruta. [15]
Los métodos para probar la cobertura de rutas prácticas, en cambio, intentan identificar clases de rutas de código que difieren solo en el número de ejecuciones de bucle, y para lograr la cobertura de la "ruta base", el evaluador debe cubrir todas las clases de rutas. [ cita necesaria ] [ aclaración necesaria ]
El software de destino se crea con opciones o bibliotecas especiales 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 garantizar que se hayan probado las condiciones más importantes (puntos de función). Luego, se analiza el resultado para ver qué áreas del código no se han utilizado y las pruebas se actualizan para incluir estas áreas según sea necesario. En combinación con otros métodos de cobertura de pruebas, el objetivo es desarrollar un conjunto riguroso, pero manejable, de pruebas de regresión.
Al implementar políticas de cobertura de pruebas dentro de un entorno de desarrollo de software, se debe considerar lo siguiente:
Los autores de software pueden observar los resultados de la cobertura de pruebas para diseñar pruebas adicionales y conjuntos de entrada o configuración para aumentar la cobertura sobre funciones vitales. Dos formas comunes de cobertura de pruebas son la cobertura de declaraciones (o líneas) y la cobertura de ramas (o bordes). La cobertura de líneas 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 bordes 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 un porcentaje. El significado de esto depende de qué forma(s) de cobertura se hayan utilizado, ya que una cobertura de ramas del 67 % es más completa que una cobertura de declaraciones del 67 %.
En general, las herramientas de cobertura de pruebas implican cálculos y registros además del programa en sí, lo que ralentiza la aplicación, por lo que normalmente este análisis no se realiza en producción. Como es de esperar, hay clases de software que no se pueden someter a estas pruebas de cobertura, aunque se puede aproximar un grado de mapeo de cobertura a través del análisis en lugar de pruebas directas.
También existen algunos tipos de defectos que se ven afectados por estas herramientas. En particular, algunas condiciones de carrera u operaciones similares sensibles al tiempo real pueden enmascararse cuando se ejecutan en entornos de prueba; aunque, a la inversa, algunos de estos defectos pueden volverse 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 sentencias y C2, cobertura de ramificaciones o condiciones. Con una combinación de C1 y C2, es posible cubrir la mayoría de las sentencias en una base de código. La cobertura de sentencias 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. [17]
La cobertura de las pruebas es un factor a tener en cuenta en la certificación de seguridad de los equipos de aviónica. Las directrices por las que la Administración Federal de Aviación (FAA) certifica los equipos de aviónica están documentadas en DO-178B [16] y DO-178C [18] .
La cobertura de pruebas también es un requisito en la parte 6 de la norma de seguridad automotriz ISO 26262 Vehículos de carretera - Seguridad funcional . [19]