¿Es la cobertura de prueba una medida adecuada de la calidad del código?

20

Si tengo un código que tiene una cobertura de prueba del 80% (todas las pruebas pasan), ¿es justo decir que es de mayor calidad que el código sin cobertura de prueba?

¿O es justo decir que es más fácil de mantener?

David_001
fuente
2
El 100% de cobertura no significa que haya sido bien probado. Pero 0% significa que no se ha probado en absoluto.
Mouviciel
1
Técnicamente no. Prácticamente sí. La experiencia ha enseñado a muchos ingenieros y probadores de software que cuando la cobertura del código alcanza alrededor del 80%, los tipos de defectos para los cuales las pruebas unitarias son adecuadas comienzan a estabilizarse. Es el principio de pareto. Básicamente, una vez que llega al punto en el que está cubriendo el 80% del código, independientemente de la calidad de sus pruebas, probablemente haya probado el 20% del código que causa la mayoría de los problemas potenciales de manera bastante exhaustiva. Esto no es absoluto, sino más bien una sabiduría convencional. Tienes que ser más minucioso si las vidas dependen de tus pruebas.
Calphool
@JoeRounceville No estoy seguro ... Puedo lograr una alta cobertura de prueba sin probar nada realmente útil. La cobertura simplemente le dice cuánto del código está siendo tocado por el conjunto de pruebas, no si las pruebas son significativas.
Andres F.
1
@AndresF. Por eso dije "técnicamente no, prácticamente sí". Las personas no son idiotas (en general). No prueban (en general) solo casos sencillos. Entonces, según la experiencia , muchas tiendas se detienen en algún lugar alrededor del 80% de cobertura, haciendo la suposición (bastante segura) de que su gente no es imbécil.
Calphool

Respuestas:

24

En sentido estricto, no es justo hacer ningún reclamo hasta que se establezca la calidad del conjunto de pruebas. Pasar el 100% de las pruebas no tiene sentido si la mayoría de las pruebas son triviales o repetitivas entre sí.

La pregunta es: en la historia del proyecto, ¿alguna de esas pruebas descubrió errores? El objetivo de una prueba es encontrar errores. Y si no lo hicieron, fallaron como pruebas. En lugar de mejorar la calidad del código, es posible que solo le den una falsa sensación de seguridad.

Para mejorar sus diseños de prueba, puede usar (1) técnicas de caja blanca, (2) técnicas de caja negra y (3) pruebas de mutación.

(1) Aquí hay algunas buenas técnicas de caja blanca para aplicar a sus diseños de prueba. Una prueba de caja blanca se construye teniendo en cuenta el código fuente específico. Un aspecto importante de las pruebas de caja blanca es la cobertura del código:

  • ¿Se llama a todas las funciones? [Cobertura funcional]
  • ¿Se ejecuta cada declaración? [Cobertura del estado de cuenta: tanto la cobertura funcional como la cobertura del estado de cuenta son muy básicas, pero mejor que nada]
  • Para cada decisión (como ifo while), ¿tiene una prueba que lo obliga a ser cierto y otro que lo obliga a ser falso? [Cobertura de decisión]
  • Para cada condición que es una conjunción (usos &&) o disyunción (usos ||), ¿cada subexpresión tiene una prueba donde es verdadero / falso? [Condición de cobertura]
  • Cobertura de bucle: ¿tiene una prueba que fuerce 0 iteraciones, 1 iteración, 2 iteraciones?
  • ¿Está cada uno breakde un bucle cubierto?

(2) Las técnicas de Blackbox se usan cuando los requisitos están disponibles, pero el código en sí no. Estos pueden conducir a pruebas de alta calidad:

  • ¿Sus pruebas de blackbox cubren múltiples objetivos de prueba? Querrá que sus pruebas sean "gordas": no solo prueban la función X, sino que también prueban Y y Z. La interacción de diferentes funciones es una excelente manera de encontrar errores.
  • El único caso en el que no desea pruebas "gordas" es cuando está probando una condición de error. Por ejemplo, prueba de entrada de usuario no válida. Si trató de lograr múltiples objetivos de prueba de entrada no válidos (por ejemplo, un código postal no válido y una dirección de calle no válida) es probable que un caso esté enmascarando al otro.
  • Considere los tipos de entrada y forme una "clase de equivalencia" para los tipos de entradas. Por ejemplo, si su código prueba para ver si un triángulo es equilátero, la prueba que usa un triángulo con lados (1, 1, 1) probablemente encontrará los mismos tipos de errores que los datos de prueba (2, 2, 2) y (3, 3, 3) encontrará. Es mejor pasar su tiempo pensando en otras clases de aportes. Por ejemplo, si su programa maneja impuestos, querrá una prueba para cada tramo de impuestos. [Esto se llama partición de equivalencia.]
  • Los casos especiales a menudo se asocian con defectos. Los datos de su prueba también deben tener valores límite, como los que se encuentran en, arriba o debajo de los bordes de una tarea de equivalencia. Por ejemplo, al probar un algoritmo de clasificación, querrá probar con una matriz vacía, una matriz de un solo elemento, una matriz con dos elementos y luego una matriz muy grande. Debe considerar los casos límite no solo para la entrada, sino también para la salida. [Este es un análisis de valor de límite de llamada.]
  • Otra técnica es "Error al adivinar". ¿Tiene la sensación de que si prueba alguna combinación especial puede hacer que su programa se rompa? ¡Entonces solo pruébalo! Recuerde: su objetivo es encontrar errores, no confirmar que el programa es válido . Algunas personas tienen la habilidad de adivinar errores.

(3) Finalmente, suponga que ya tiene muchas buenas pruebas de cobertura de caja blanca y técnicas de caja negra aplicadas. ¿Qué más puedes hacer? Es hora de probar sus pruebas . Una técnica que puede usar es la prueba de mutación.

En las pruebas de mutación, realiza una modificación en (una copia de) su programa, con la esperanza de crear un error. Una mutación podría ser:

Cambiar una referencia de una variable a otra variable; Inserte la función abs (); Cambiar menor que mayor que; Eliminar una declaración; Reemplazar una variable con una constante; Eliminar un método de anulación; Eliminar una referencia a un súper método; Cambiar orden de argumento

Cree varias docenas de mutantes, en varios lugares de su programa [el programa aún tendrá que compilarse para realizar la prueba]. Si sus pruebas no encuentran estos errores, entonces necesita escribir una prueba que pueda encontrar el error en la versión mutada de su programa. Una vez que una prueba encuentra el error, has matado al mutante y puedes probar con otro.


Anexo : Olvidé mencionar este efecto: los errores tienden a agruparse . Lo que eso significa es que cuantos más errores encuentre en un módulo, mayor será la probabilidad de que encuentre más errores. Entonces, si tiene una prueba que falla (es decir, la prueba es exitosa, ya que el objetivo es encontrar errores), no solo debe corregir el error, sino que también debe escribir más pruebas para el módulo, utilizando el Técnicas anteriores.

Mientras encuentre errores a un ritmo constante, los esfuerzos de prueba deben continuar. Solo cuando haya una disminución en la tasa de nuevos errores encontrados, debe tener la confianza de que ha realizado buenos esfuerzos de prueba para esa fase de desarrollo.

Macneil
fuente
7

Según una definición, es más fácil de mantener, ya que cualquier cambio importante es más probable que sea atrapado por las pruebas.

Sin embargo, el hecho de que el código pase las pruebas unitarias no significa que sea intrínsecamente de mayor calidad. El código aún puede estar mal formateado con comentarios irrelevantes y estructuras de datos inapropiadas, pero aún puede pasar las pruebas.

Sé qué código preferiría mantener y ampliar.

ChrisF
fuente
7

El código sin absolutamente ninguna prueba puede ser de muy alta calidad, legible, hermoso y eficiente (o basura total), así que no, no es justo decir que el código con una cobertura de prueba del 80% es de mayor calidad que el código sin cobertura de prueba.

Podría ser justo decir que el código 80% cubierto con buenas pruebas es probablemente de calidad aceptable y probablemente relativamente fácil de mantener. Pero realmente garantiza poco.

Joonas Pulakka
fuente
3

Yo lo llamaría más refactorable. La refactorización se vuelve extremadamente fácil si el código se cubre con muchas pruebas.

Sería justo llamarlo más mantenible.

Josip Medved
fuente
2

Estoy de acuerdo con la parte mantenible. Michael Feathers recientemente publicó un video de una excelente charla sobre su llamado " La sinergia profunda entre la capacidad de prueba y el buen diseño " en el que discute este tema. En la charla, dice que la relación es unidireccional, es decir, el código que está bien diseñado es comprobable, pero el código comprobable no necesariamente está bien diseñado.

Vale la pena señalar que la transmisión de video no es excelente en el video, por lo que vale la pena descargarla si desea verla en su totalidad.

Paddyslacker
fuente
-2

Me he estado haciendo esta pregunta desde hace algún tiempo en relación con la "cobertura de la condición". Entonces, ¿qué tal esta página de atollic.com "¿Por qué el análisis de cobertura de código?"

Más técnicamente, el análisis de cobertura de código encuentra áreas en su programa que no están cubiertas por sus casos de prueba, lo que le permite crear pruebas adicionales que cubren partes de su programa que de otro modo no estarían probadas. Por lo tanto, es importante comprender que la cobertura del código lo ayuda a comprender la calidad de sus procedimientos de prueba, no la calidad del código en sí .

Esto parece ser bastante relevante aquí. Si tiene un conjunto de casos de prueba que logra alcanzar un cierto nivel de cobertura (código o de otro tipo), entonces es muy probable que invoque el código bajo prueba con un conjunto bastante exhaustivo de valores de entrada. Esto no le dirá mucho sobre el código bajo prueba (a menos que el código explote o genere fallas detectables) pero le da confianza en su conjunto de casos de prueba .

En un interesante cambio de vista de Necker Cube , ¡el código de prueba ahora está siendo probado por el código bajo prueba!

David Tonhofer
fuente
-3

Hay muchas formas de garantizar que un programa haga lo que desea y de garantizar que las modificaciones no tengan efectos no deseados.

La prueba es una. Evitar la mutación de datos es otra. Entonces es un sistema de tipos. O verificación formal.

Entonces, si bien estoy de acuerdo en que las pruebas son generalmente algo bueno, un porcentaje dado de pruebas podría no significar mucho. Prefiero confiar en algo escrito en Haskell sin pruebas que en una biblioteca PHP bien probada

Andrea
fuente
¿Es esta solo tu opinión o puedes respaldarla de alguna manera?
mosquito
2
Las pruebas no son una forma de garantizar que un programa haga lo que pretendes.
Andres F.
1
Entonces me pregunto qué prueba es
Andrea
@gnat esto es, por supuesto, mi opinión. Aún así, dice lo que dice. Tomé a Haskell como un ejemplo de lenguaje cuyo compilador es muy estricto y ofrece muchas garantías sobre la buena forma de entrada, los tipos, los efectos secundarios, la mutación de datos. Tomé PHP como ejemplo de un lenguaje cuyo intérprete es muy indulgente y que ni siquiera tiene una especificación. Incluso en ausencia de pruebas, la presencia de todas las garantías del sistema de tipos y efectos generalmente produce un grado de fiabilidad decente. Para compensar eso con las pruebas, uno debería tener una suite muy completa
Andrea
Tal vez estaba un poco apurado cuando escribí, estaba hablando por teléfono, pero todavía creo que hay un punto. No quiero criticar PHP, pero creo que decir que, en comparación, Haskell ofrece un grado mucho mayor de confiabilidad es una declaración objetiva
Andrea