Densidad y superposición de prueba para cobertura de código
por Parasoft
14 de septiembre de 2011
4 min leer
El concepto de densidad de cobertura está relacionado de alguna manera con la superposición de pruebas. Generalmente, desea evitar que varios casos de prueba prueben la misma funcionalidad (es decir, minimizar la superposición de pruebas).
La idea detrás de este principio es que los casos de prueba no solo se rompen debido a errores en el código, sino con mayor frecuencia debido a cambios en la especificación. El comportamiento del código que anteriormente se suponía correcto puede volverse incorrecto o insuficiente debido a los nuevos requisitos. Si el código se modifica para reflejar el nuevo requisito, los casos de prueba que afirman el comportamiento anterior pueden fallar y deben actualizarse. Si está siguiendo un enfoque TDD, la actualización de los casos de prueba debería ocurrir antes de que se cambie el código probado. En un conjunto de pruebas con un alto nivel de superposición de pruebas, un pequeño cambio en la especificación (y la implementación) puede requerir la actualización de una gran cantidad de casos de prueba. En tal situación, los casos de prueba obviamente se superponían en algún detalle del código que estaba sujeto a cambios. Tales sucesos son altamente indeseables porque aumentan significativamente el costo de mantenimiento de la suite de pruebas.
Clases de equivalencia
Minimizar la superposición de pruebas está estrechamente relacionado con el concepto matemático de la denominada clase de equivalencia. En términos matemáticos simplificados, una clase de equivalencia es un conjunto de elementos que son todos equivalentes entre sí según una determinada relación. Esta idea también es relevante para la cobertura de pruebas.
Anteriormente identificamos cuatro rutas de código diferentes para el método probado que se muestra en el Listado 2. Si observa más de cerca la lógica del código, notará que solo se evalúan los dos bits menos significativos de la entrada. Los bits restantes de la entrada no influyen en la ruta del código que se toma. Entonces, efectivamente, probar el método con un valor de entrada de 4 usará la misma ruta de código que se toma para un valor de entrada de 0. De la misma manera, los valores de entrada 1, 5, 9, 13, ... misma ruta de código que se debe tomar. A los efectos de la cobertura del trayecto, el método del Listado 2 tiene cuatro clases de equivalencia, que se resumen en la Tabla 2.
Tabla 2: Clases de equivalencia para la cobertura de ruta del Listado 2
Para lograr una cobertura de ruta completa con una superposición de prueba mínima, es suficiente seleccionar un valor de entrada de cada clase de equivalencia. No se gana nada en términos de cobertura si varios casos de prueba utilizan valores de entrada de la misma clase de equivalencia. Las clases de equivalencia varían según los criterios de cobertura. Por ejemplo, en términos de cobertura de declaración, los valores de entrada 0 y 2 están en la clase de equivalencia para cubrir la declaración nula de retorno del método de muestra, pero estarían en clases de equivalencia diferentes cuando se mira la cobertura de ruta.
La identificación de clases de equivalencia para las entradas de prueba es una herramienta útil para minimizar la superposición de pruebas, pero nuevamente, se avecinan problemas cuando avanzamos hacia una cobertura de regresión completa. Si una serie de pruebas logra una cobertura de regresión completa para un método en particular, esto implica que la serie de pruebas forma una especificación completa de ese método. Cualquier cambio en el comportamiento del método, sin importar cuán pequeño sea, resultaría en una falla en la prueba. Para el método de muestra del Listado 2, ya determinamos que serían necesarios casos de prueba con los 256 valores de entrada posibles. ¿Cuántas clases de equivalencia para las entradas de prueba habría en términos de cobertura de regresión total? Desafortunadamente, la respuesta a esta pregunta es "256".
Para una cobertura de regresión completa, ningún valor de entrada es equivalente a ningún otro valor de entrada. La cobertura de regresión completa significa que el comportamiento de un método está completamente "bloqueado". Por ejemplo, aunque los valores de entrada 0 y 4 están en la misma clase de equivalencia a los efectos de la cobertura de ruta, están en clases de equivalencia propias para una cobertura de regresión completa. Si estuvieran en la misma clase de equivalencia, esto implicaría que simplemente elegir uno de los valores (por ejemplo, 4) aún cumpliría el criterio de cobertura de regresión total. Sin embargo, en ese caso, sería posible implementar el método probado de tal manera que funcione correctamente para la entrada de 4 pero no para la entrada de 0 (por ejemplo, agregando una verificación que devuelve deliberadamente un resultado incorrecto si la entrada fue 0). Por lo tanto, 0 y 4 no pueden estar en la misma clase de equivalencia para una cobertura de regresión completa.
Densidad de cobertura de código
Nuevamente, cuando se busca una cobertura de regresión completa, el truco de elegir solo una entrada de cada clase de equivalencia ya no se puede utilizar para minimizar la superposición de pruebas. La cobertura de regresión completa siempre provocará una superposición adicional. ¿Qué otros principios se pueden utilizar para mitigar los efectos negativos de la superposición de pruebas?
Un problema frecuente es el código común que se ejecuta directa o indirectamente por una gran cantidad de casos de prueba. Es probable que los cambios de especificación que afecten a ese código común provoquen una gran cantidad de fallas. El objetivo es evitar tales concentraciones y reescribir las pruebas de una manera que limite la cantidad de código comúnmente ejecutado. La densidad de cobertura es una métrica útil que se puede utilizar para crear conjuntos de pruebas que ejecuten el código probado de una manera más uniformemente distribuida. La densidad de cobertura extiende la dicotomía de "cubierto" versus "no cubierto" a una métrica numérica que también cuenta la frecuencia con la que se ejecuta una rama o ruta. Por ejemplo, en lugar de simplemente obtener una respuesta de sí o no en cuanto a si se cubrió una línea en particular, la densidad de cobertura también le indicaría que la línea se ejecutó exactamente 500 veces. La densidad de cobertura se puede aplicar a cualquier criterio de cobertura, pero por lo general se ofrece junto con la cobertura de una sucursal o un estado de cuenta.
Una vez más, la visualización de las densidades de cobertura de la ruta es tan problemática como la visualización de una simple cobertura de ruta "sí / no". Una forma común de visualizar la densidad de cobertura es agregar marcadores de colores con diferente brillo en el editor de código fuente. Por ejemplo, un tono claro de verde podría indicar que un fragmento de código está cubierto por unos pocos casos de prueba, pero un tono de verde extremadamente oscuro sería una advertencia de que existe una gran concentración de casos de prueba que ejecutan todos el mismo fragmento en particular. de código. Idealmente, estos indicadores de advertencia deberían impulsar una refactorización que mueva el código común fuera de la ruta del código.
***
Crédito de la imagen: Jen y una cámara