¿Cómo verifica que el código haya sido cubierto automáticamente?

9

Estoy en el proceso de configurar un servidor Bamboo para algunos proyectos nuevos para impulsar TDD en un flujo de trabajo de CI / CD. Claro, las pruebas unitarias son geniales, pero solo como registro ya que están allí.

Ahora, esto podría estar mejor en un gancho de pre-recepción de GIT en ciertas ramas (es decir: ramas de desarrollo y liberación principal), pero, en todo caso, ¿cómo debería aplicarse la cobertura del código? Me complace confiar en los encargados de garantizar que el código esté cubierto, pero ¿cómo se mantienen estas cosas sin ningún deslizamiento aparte de la diligencia y la coherencia?

En resumen, me gustaría ver cómo otros imponen la cobertura de prueba como un proceso automático durante las etapas de compromiso o compilación.

Daniel Park
fuente

Respuestas:

17

No debe aplicar la cobertura del código automáticamente.

Esto es como imponer las líneas máximas de código por método: de acuerdo, la mayoría de los métodos deberían ser inferiores a, digamos, 20 LOC, pero hay casos válidos en los que los métodos serían más largos que eso.

De la misma manera, apuntar a un porcentaje dado de cobertura de código por clase puede conducir a consecuencias no deseadas. Por ejemplo:

  • Las clases de código repetitivo o las clases creadas por generadores de código pueden no probarse en absoluto. Obligar a los desarrolladores a probarlos no tendrá ningún beneficio y tendrá un costo sustancial en términos de tiempo dedicado a hacerlo.

  • El código simple que maneja partes no importantes de la aplicación no necesariamente tiene que ser probado.

  • En algunos idiomas, algunos códigos no se pueden probar. Tuve este caso en C # con métodos anónimos en una biblioteca donde realmente quería tener una cobertura de código del 100%. Esos casos pueden ser desmoralizantes para los desarrolladores.

Más importante aún, la cobertura del código debe ser proporcional a dos aspectos del código: cuán crítico y cuán complicado es :

  • Una pieza de código con una lógica complicada, que es parte de la característica principal de una aplicación, debería probarse cuidadosamente, ya que las fallas o regresiones pueden tener consecuencias importantes.

  • Un código simple que maneja una característica que nadie usa puede tener pruebas básicas que cubren solo casos básicos.

Por supuesto, aún puede usar la cobertura del código como medida , especialmente para comparar cómo los diferentes equipos logran la cobertura del código: puede haber equipos que sean menos disciplinados y más reacios a la hora de realizar las pruebas. En esos casos, es posible que desee combinar esta métrica con otras, como la cantidad de errores, el tiempo dedicado a resolver errores o la cantidad de comentarios durante las revisiones de código.

También es posible que desee aplicar al menos algo de cobertura de código, digamos 60% ¹, en proyectos individuales donde tenga sentido (tenga cuidado de excluir prototipos, código generado, CRUD, etc.) Haciendo posible que los desarrolladores marquen clases específicas como excluidas desde la cobertura del código también es agradable². En este caso, esto puede hacerse bajo una forma de verificación que falla una compilación si la cobertura del código está por debajo del mínimo requerido. Lo haría en la etapa de compilación, no en la etapa de confirmación , ya que no se espera que ejecute pruebas unitarias durante la confirmación .


¹ Consideraría el 60% como un mínimo razonable basado en mi base de código: casi todos los proyectos o clases que tienen menos del 60% de cobertura de código realmente no han sido probados . Esto puede variar mucho de un idioma a otro y de una compañía a otra (en algunas compañías, el 0% es un estándar). Asegúrese de discutir con su equipo qué es normal y qué es demasiado alto para ellos. Tal vez están constantemente alcanzando el 95% y pueden apuntar fácilmente al 99%, o tal vez luchan por aumentar su cobertura de código del 70% al 75%.

² Dado que se detectará un posible abuso durante las revisiones de código, no debe tener miedo de dar esta posibilidad a los desarrolladores. Esto es similar a la posibilidad de excluir algunas partes del código de los cheques por parte de los linters o los correctores de estilo. JSLint, StyleCop y Code Analysis son tres ejemplos en los que se admite la exclusión y en realidad es útil sin alentar el abuso.

Arseni Mourzenko
fuente
Entiendo completamente el hecho de que hacer cumplir algunas reglas de finalización es algo así como una hazaña contraproducente, si no imposible. Parece que estaba pensando demasiado técnico con una solución aquí, y tal vez todo se reduce a las prácticas de revisión algunos pasos antes de lo que discutí anteriormente, como en una etapa de solicitud de extracción. Tenía una idea de que esto era demasiado estricto, pero quería comprobar si alguien tiene algún método en práctica.
Daniel Park
1
Las solicitudes de extracción de @DanielPark son una parte vital del flujo de trabajo de GitHub IMO.
RubberDuck
Voy a marcar esta respuesta como una respuesta sobre otras debido a sus conclusiones iniciales sobre el contexto de cobertura. También eliminé el punto de que la cobertura del código se usa mejor como una medida en lugar de un criterio, y en general tener condiciones que lo rodean es muy subjetiva y no demasiado constructiva por sí sola. Creo que mi dirección en esta etapa es involucrar la métrica en las fases de solicitud de extracción y, además, ser escrupuloso para garantizar una cobertura adecuada en las áreas correctas antes de los lanzamientos publicados. Gracias por todas las respuestas.
Daniel Park
¿Quizás debería eliminarse un "código simple que maneja una característica que nadie usa"?
rjnilsson
4

Considere el siguiente código:

rsq = a*a + b*b;
if (rsq >= 0.0) {
    r = sqrt(rsq);
}
else {
    handle_this_impossible_event();
}

No hay forma de crear un caso de prueba que llegue a esa otra rama. Sin embargo, si este fuera un software de vuelo crítico para la seguridad, las personas estarían en todo el caso del autor si esa protección contra el envío de un valor negativo sqrtno estuviera presente. Típicamente, el cálculo rsq = a*a + b*by la extracción de la raíz cuadrada están separados por múltiples líneas de código. Mientras tanto, un rayo cósmico puede activar el bit de signo rsq.

De hecho, el software de vuelo ha invocado el equivalente de handle_this_impossible_event()varias veces. Por lo general, esto implica cambiar el control a una computadora redundante, apagar con gracia la computadora sospechosa, reiniciar la computadora sospechosa y, finalmente, la computadora sospechosa asume el rol de copia de seguridad. Eso es mucho mejor que la computadora de vuelo principal enloqueciendo.

Incluso en el software de vuelo, es imposible lograr una cobertura de código del 100%. Las personas que afirman haber logrado esto tienen un código trivial o no tienen suficientes pruebas contra estos eventos imposibles.

David Hammen
fuente
Si ay bson enteros de 32 bits con signo, muchos valores como 65535 y 65535 o 40000 y 40000 podrían resultar rsqnegativos.
David Conrad
2
@DavidConrad: lo usé 0.0, lo que implica eso ay bson algunos tipos de punto flotante. Con el software de vuelo, es imperativo protegerse contra la raíz cuadrada de un número negativo, incluso si sabe que el número no puede ser negativo. Los rayos cósmicos son pequeñas cosas molestas. Un experimento muy reciente ( lanzado hace menos de una semana ) estudiará si una supercomputadora en la Estación Espacial Internacional puede usar software en lugar de hardware para protegerse contra los rayos cósmicos (SEU, etc.).
David Hammen
Muy bien, pasé por alto el 0.0.
David Conrad
4

La cobertura de prueba es una métrica útil para la salud general de su proyecto. Una cobertura de prueba alta le permite tomar una decisión informada sobre si el software funcionará como se espera cuando se implemente; con una cobertura de prueba baja que implica que simplemente estás adivinando. Existen herramientas para medir la cobertura automáticamente, estas generalmente funcionan ejecutando el programa en un contexto de depuración o inyectando operaciones de contabilidad en el código ejecutado.

Existen diferentes tipos de pruebas y diferentes tipos de métricas de cobertura. Las métricas de cobertura comunes incluyen cobertura de función, cobertura de estado de cuenta, cobertura de sucursal y cobertura de condición, aunque hay más .

  • Las pruebas unitarias verifican si la implementación de una unidad conceptual (módulo, clase, método, ...) se ajusta a su especificación (en TDD, la prueba es la especificación). Las unidades sin sus propias pruebas unitarias son una señal de alerta, aunque podrían estar cubiertas por pruebas de estilo de integración.

    Las pruebas unitarias deberían implicar una cobertura casi total de la función, ya que la prueba unitaria ejercita toda la interfaz pública de esa unidad, no debería haber ninguna funcionalidad que no sea tocada por estas pruebas. Si está introduciendo pruebas unitarias a una base de código existente, la cobertura de la función es un indicador de progreso aproximado.

    Una prueba unitaria debe esforzarse por una buena cobertura del estado de cuenta (75% –100%). La cobertura del estado de cuenta es una medida de calidad para una prueba unitaria. La cobertura total no siempre es posible, y probablemente tenga un mejor uso de su tiempo que para mejorar la cobertura más allá del 95%.

    La cobertura de sucursales y condiciones es más complicada. Cuanto más complicado o importante sea un código, mayores serán estas métricas. Pero para un código poco espectacular, la alta cobertura de las declaraciones tiende a ser suficiente (y ya implica una cobertura de sucursal de al menos el 50%). Mirar el informe de cobertura de condición de una unidad puede ayudar a construir mejores casos de prueba.

  • Las pruebas de integración verifican si varias unidades pueden funcionar correctamente entre sí. Las pruebas de integración pueden ser muy útiles sin obtener una puntuación alta en ninguna métrica de cobertura. Mientras que las pruebas de integración usualmente ejercitarían una gran parte de las interfaces de su unidad (es decir, tienen una alta cobertura de función), los componentes internos de estas unidades ya han sido cubiertos por las pruebas unitarias.

Ejecutar pruebas antes de fusionar el código en una rama principal es una buena idea. Sin embargo, calcular las métricas de cobertura de prueba para todo el programa tiende a tomar mucho tiempo; este es un buen trabajo para una construcción nocturna. Si puede descubrir cómo hacer esto, un buen compromiso sería ejecutar solo pruebas modificadas o pruebas unitarias en unidades modificadas en un gancho Git. Las fallas en las pruebas no son aceptables para nada más que los compromisos de "trabajo en progreso". Si las métricas de cobertura seleccionadas caen por debajo de algún umbral (por ejemplo, la cobertura de la declaración por debajo del 80%, o la introducción de nuevos métodos sin las pruebas correspondientes), estos problemas deben tratarse como una advertencia, con una oportunidad para que el desarrollador solucione estos problemas potenciales. Sin embargo, a veces hay buenas razones para ignorar estas advertencias, y los desarrolladores deberían poder hacerlo.

Las pruebas son buenas, pero demasiado puede ser molesto. La retroalimentación rápida y relevante puede ayudar a fomentar la atención a la calidad, pero no desea que interfiera en la producción de valor. Personalmente, prefiero ejecutar pruebas manualmente, ya que me permite obtener comentarios más rápidos sobre la parte en la que estoy trabajando. Antes del lanzamiento, haré un enfoque de calidad donde usaré análisis estático, perfiladores y herramientas de cobertura de código para encontrar zonas problemáticas (algunos de estos pasos forman parte de un conjunto de pruebas de prelanzamiento).

amon
fuente
3

Nadie ha mencionado las pruebas de mutación . La idea detrás de ellos es bastante práctica e intuitiva.

Funcionan modificando el código fuente al azar (por ejemplo, cambiando ">" a "<"), por lo tanto, mutación, y verificando si estos cambios fortuitos rompen cualquier prueba.

Si no lo hacen, entonces a) el código en cuestión podría ser innecesario, o b) (más probablemente) este fragmento de código no está cubierto por una prueba, ya que romperlo no se detecta.

Konrad Morawski
fuente
1

Por supuesto, los datos de cobertura de código se pueden obtener automáticamente, pero no se deben tomar decisiones automáticas basadas en ellos, por razones que otros ya han explicado. (Demasiado borroso, demasiado margen de error).

Sin embargo, lo mejor es tener un proceso establecido mediante el cual el estado actual del proyecto en términos de cobertura de código sea revisado regularmente por seres humanos, posiblemente con informes diarios que lleguen a la bandeja de entrada del gerente del proyecto.

En entornos empresariales, esto se logra con herramientas de integración continua como Hudson, Jenkins, etc. Estas herramientas están configuradas para verificar regularmente todo el proyecto desde el repositorio de código fuente, construirlo, ejecutar las pruebas y generar informes. Por supuesto, se pueden configurar para ejecutar las pruebas en modo de cobertura de código e incluir los resultados en estos informes.

Jetbrains también hace TeamCity, que me parece un poco más ligero y adecuado para tiendas de software más pequeñas.

Por lo tanto, el gerente del proyecto recibe informes regulares de cobertura de código, usa su propio buen juicio y actúa como ejecutor si es necesario.

Mike Nakis
fuente
1
No soy el votante negativo. Sospecho que el voto negativo se debió a tu primera oración. Ciertamente, la cobertura del código se puede verificar automáticamente. Quizás deberías cambiar esa oración. El problema en cuestión es si los resultados de esa prueba de cobertura de código automatizada deben usarse a su vez de manera automatizada, por ejemplo, en un gancho git.
David Hammen
0

La cobertura del código se puede verificar automáticamente, a pesar de la opinión popular, el conjunto de herramientas Rational's Purify incluye una función de cobertura del código. Se basó en la instrumentación de todas las funciones (trabajó en los archivos binarios, actualizando cada función o llamada con un poco de código extra) para poder escribir datos que luego se mostraban al usuario. Tecnología bastante buena, especialmente en ese momento.

Sin embargo, incluso cuando intentamos realmente, realmente difícil obtener el 100% de cobertura, ¡solo logramos el 70% más o menos! Así que es un ejercicio un poco inútil.

Sin embargo, en la situación de escribir pruebas unitarias, creo que el 100% de cobertura de pruebas unitarias es aún más inútil. ¡Pruebe los métodos que requieren pruebas unitarias, no todos los captadores o colocadores! Las pruebas unitarias deben consistir en verificar las funciones difíciles (o las clases TBH) y no tratar de marcar casillas en algún proceso o herramienta que muestre buenos tics verdes.

gbjbaanb
fuente
0

He creado una herramienta para esto

https://github.com/exussum12/coverageChecker

Utilizando

bin/diffFilter --phpunit diff.txt clover.xml 70

Fallará si menos del 70% de la diferencia está cubierta por pruebas unitarias.

Obtenga la diferencia por.

git diff origin/master... > diff.txt

Asumiendo que se ramificó de maestro y se fusionará nuevamente en maestro

Ignore la bandera phpunit en el código, realmente es solo una verificación de trébol para que cualquier cosa que pueda generar trébol pueda usarla.

Como otras respuestas sugirieron, poner esto al 100% no es una buena idea

exussum
fuente