unit testing - las - ¿Es realmente buena la cobertura de código al 100% cuando se realizan pruebas unitarias?
pruebas unitarias java (8)
Siempre aprendí que hacer una cobertura máxima de código con pruebas unitarias es bueno . También escucho a desarrolladores de grandes empresas como Microsoft decir que escriben más líneas de código de prueba que el código ejecutable en sí.
Ahora, ¿ es realmente genial? ¿No parece a veces una pérdida completa de tiempo que tiene un efecto único para dificultar el mantenimiento ?
Por ejemplo, digamos que tengo un método DisplayBooks()
que rellena una lista de libros de una base de datos. Los requisitos del producto indican que si hay más de cien libros en la tienda, solo se deben mostrar cien .
Entonces, con TDD,
- Comenzaré haciendo una prueba unitaria de
BooksLimit()
que guardará doscientos libros en la base de datos, llamaré aDisplayBooks()
y hacer unAssert.AreEqual(100, DisplayedBooks.Count)
. - Entonces probaré si falla,
- Luego cambiaré
DisplayBooks()
estableciendo el límite de resultados en 100, y - Finalmente volveré a ejecutar la prueba para ver si tiene éxito.
Bueno, ¿no es mucho más fácil ir directamente al tercer paso y nunca hacer la prueba de unidad BooksLimit()
en absoluto? ¿Y no es más ágil, cuando los requisitos cambiarán del límite de 100 a 200 libros, cambiar solo un carácter, en lugar de cambiar las pruebas, ejecutar pruebas para verificar si falla, cambiar el código y ejecutar las pruebas nuevamente para verificar si tiene éxito?
Nota: supongamos que el código está completamente documentado. De lo contrario, algunos pueden decir, y estarían en lo cierto, que realizar pruebas unitarias completas ayudará a comprender el código que carece de documentación. De hecho, tener una prueba unitaria de BooksLimit()
mostrará muy claramente que hay un número máximo de libros para mostrar, y que este número máximo es 100. Entrar en el código de no pruebas unitarias sería mucho más difícil, ya que aunque se puede implementar un límite for (int bookIndex = 0; bookIndex < 100; ...
o foreach ... if (count >= 100) break;
Bueno, ¿no es mucho más fácil ir directamente al tercer paso y nunca hacer la prueba de unidad BooksLimit () en absoluto?
Sí ... Si no pasa tiempo escribiendo pruebas, pasará menos tiempo escribiendo pruebas. Su proyecto puede llevar más tiempo en general, porque dedicará mucho tiempo a la depuración, pero ¿tal vez sea más fácil explicárselo a su gerente? Si ese es el caso ... consigue un nuevo trabajo! Las pruebas son cruciales para mejorar su confianza en su software.
Unittesting le da el mayor valor cuando tiene una gran cantidad de código. Es fácil depurar una tarea simple usando unas pocas clases sin probar la unidad. Una vez que salgas al mundo y trabajes en bases de código de millones de líneas, lo necesitarás. Simplemente no puedes hacer que tu depurador pase a través de todo. Simplemente no puedes entender todo. Necesitas saber que las clases que estás dependiendo del trabajo. Debe saber si alguien dice "Solo haré este cambio en el comportamiento ... porque lo necesito", pero han olvidado que hay otros doscientos usos que dependen de ese comportamiento. Unittesting ayuda a prevenir eso.
Con respecto a hacer el mantenimiento más difícil: ¡DE NINGUNA MANERA! No puedo capitalizar eso lo suficiente.
Si eres la única persona que ha trabajado en tu proyecto, entonces sí, podrías pensar eso. ¡Pero eso es una locura! Intente ponerse al día en un proyecto de 30k líneas sin pruebas de unidad. Intente agregar funciones que requieran cambios significativos en el código sin pruebas de unidad. No hay confianza de que no esté rompiendo las suposiciones implícitas hechas por los otros ingenieros. Para un mantenedor (o nuevo desarrollador en un proyecto existente) las pruebas de unidad son clave. Me he apoyado en pruebas de unidad para la documentación, para el comportamiento, para las suposiciones, para decirme cuándo he roto algo (que pensé que no tenía relación). A veces, una API mal escrita tiene pruebas mal escritas y puede ser una pesadilla para cambiar, porque las pruebas absorben todo su tiempo. Eventualmente, querrá refactorizar este código y corregirlo, pero sus usuarios también se lo agradecerán, ya que su API será mucho más fácil de usar.
Una nota sobre la cobertura:
Para mí, no se trata de una cobertura de prueba del 100%. La cobertura al 100% no encuentra todos los errores, considere una función con dos afirmaciones if
:
// Will return a number less than or equal to 3
int Bar(bool cond1, bool cond2) {
int b;
if (cond1) {
b++;
} else {
b+=2;
}
if (cond2) {
b+=2;
} else {
b++;
}
}
Ahora considera que escribo una prueba que prueba:
EXPECT_EQ(3, Bar(true, true));
EXPECT_EQ(3, Bar(false, false));
Eso es 100% de cobertura. Esa es también una función que no cumple con el contrato - Bar(false, true);
falla, porque devuelve 4. Así que "cobertura completa" no es el objetivo final.
Honestamente, me saltaría las pruebas para BooksLimit()
. Devuelve una constante, por lo que probablemente no vale la pena el tiempo para escribirlas (y debe probarse al escribir DisplayBooks()
). Podría estar triste cuando alguien decida (incorrectamente) calcular ese límite a partir del tamaño del estante, y ya no cumple con nuestros requisitos. He sido quemado por "no vale la pena probar" antes. El año pasado escribí un código que le dije a mi compañero de trabajo: "Esta clase es principalmente de datos, no necesita ser probada". Tenía un método. Tenía un error. Fue a producción. Nos avisó en mitad de la noche. Me sentí estúpido. Así que escribí las pruebas. Y luego reflexioné mucho sobre qué código constituye "no vale la pena probar". No hay mucho
Entonces, sí, puedes saltarte algunas pruebas. La cobertura de prueba del 100% es excelente, pero no significa mágicamente que su software sea perfecto. Todo se reduce a la confianza ante el cambio.
Si pongo la class A
, la class B
y la class C
juntas, y encuentro algo que no funciona, ¿quiero pasar el tiempo depurando las tres? No. Quiero saber que A
y B
ya cumplieron con sus contratos (a través de pruebas de unidad) y mi nuevo código en la class C
probablemente está roto. Así que lo pruebo. ¿Cómo puedo saber si está roto, si no hago una prueba de unidad? ¿Al presionar algunos botones y probar el nuevo código? Eso es bueno, pero no suficiente. Una vez que su programa se amplíe, será imposible volver a ejecutar todas las pruebas manuales para comprobar que todo funciona correctamente. Es por eso que las personas que hacen Unittest generalmente automatizan la ejecución de sus pruebas también Dígame "Pass" o "Fail", no me diga "la salida es ...".
OK, voy a ir a escribir algunas pruebas más ...
¡El primer 100% es difícil de conseguir, especialmente en grandes proyectos! e incluso si lo hace cuando se cubre un bloque de código, no significa que esté haciendo lo que se supone que debe hacer a menos que su prueba esté afirmando todas las entradas y salidas posibles (que es casi imposible).
Por lo tanto, no consideraría que una pieza de software sea buena simplemente porque tiene una cobertura de código del 100%, pero la cobertura de código sigue siendo algo bueno.
Bueno, ¿no es mucho más fácil ir directamente al tercer paso y nunca hacer la prueba de unidad BooksLimit () en absoluto?
Bueno, tener esa prueba allí te hace sentir bastante seguro de que si alguien cambia el código y la prueba falla, notarás que algo está mal con el nuevo código, por lo tanto, evitarás cualquier error potencial en tu aplicación.
100% de cobertura de prueba unitaria es generalmente un olor a código, una señal de que alguien ha superado todo el TOC sobre la barra verde en la herramienta de cobertura, en lugar de hacer algo más útil.
En algún lugar alrededor del 85% está el punto dulce, donde una prueba falla más a menudo que no indica un problema real o potencial, en lugar de ser simplemente una consecuencia inevitable de cualquier cambio textual que no esté dentro de los marcadores de comentarios. No está documentando ninguna suposición útil sobre el código si sus suposiciones son "el código es lo que es, y si fuera de alguna manera diferente, sería otra cosa". Ese es un problema que se resuelve con una herramienta de suma de comprobación que tiene en cuenta los comentarios, no una prueba de unidad.
Me gustaría que hubiera alguna herramienta que te permitiera especificar la cobertura objetivo. Y luego, si accidentalmente lo revisas, muestra las cosas en amarillo / naranja / rojo para empujarte hacia la eliminación de algunas de las pruebas extra espurias.
Cuando el cliente decide cambiar el límite a 200, buena suerte para encontrar errores relacionados con esa prueba aparentemente trivial. Especialmente, cuando tienes otras 100 variables en tu código, y hay otros 5 desarrolladores trabajando en el código que se basa en esa pequeña información.
Mi punto: si es valioso para el valor comercial (o, si no te gusta el nombre, para el núcleo muy importante del proyecto), pruébalo. Solo descarte cuando no haya una forma posible (o económica) de probarlo, como la interfaz de usuario o la interacción del usuario, o cuando esté seguro de que el impacto de no escribir esa prueba es mínimo. Esto es válido para proyectos con requisitos vagos o que cambian rápidamente [como descubrí dolorosamente].
Para el otro ejemplo que presenta, se recomienda probar los valores de los límites. Por lo tanto, puede limitar sus pruebas a solo cuatro valores: 0, un número mágico entre 0 y BooksLimit, BooksLimit, y un número mayor.
Y, como dijeron otras personas, haga pruebas, pero sea 100% positivo, otra cosa puede fallar.
Cuando miras un problema aislado, tienes toda la razón. Pero las pruebas unitarias tratan de cubrir todas las intenciones que tiene para un determinado código.
Básicamente, las pruebas unitarias formulan tus intenciones. Con un número cada vez mayor de intenciones, el comportamiento del código que se va a probar siempre puede compararse con todas las intenciones realizadas hasta el momento. Cada vez que se realiza un cambio, puede probar que no hay ningún efecto secundario que rompa las intenciones existentes. Los errores recién encontrados no son más que una intención (implícita) que no está contenida en el código, por lo que usted formula su intención como una nueva prueba (que falla al principio) y la repara.
Para el código de una sola vez, las pruebas unitarias no valen la pena porque no se esperan cambios importantes. Sin embargo, para cualquier bloque de código que se va a mantener o que sirve como componente de otro código, la garantía de que todas las intenciones se mantienen para cualquier versión nueva vale mucho (en términos de menor esfuerzo para tratar manualmente de detectar efectos secundarios) .
El punto de inflexión en el que las pruebas unitarias realmente le ahorran tiempo y, por lo tanto, el dinero depende de la complejidad del código, pero siempre hay un punto de inflexión que generalmente se alcanza después de pocas iteraciones de cambios. Además, por último, pero no menos importante, le permite enviar arreglos y cambios mucho más rápido sin comprometer la calidad de su producto.
Estoy de acuerdo con @soru, la cobertura de prueba del 100% no es un objetivo racional.
No creo que exista ninguna herramienta o métrica que pueda decirle la cantidad "correcta" de cobertura. Cuando estaba en la escuela de posgrado, el trabajo de mi asesor de tesis consistió en diseñar una cobertura de prueba para el código "mutado". Toma un conjunto de pruebas y luego ejecuta un programa automatizado para cometer errores en el código fuente que se está probando. La idea era que el código mutado contenía errores que se encontrarían en el mundo real y, por lo tanto, un conjunto de prueba que encontró el mayor porcentaje de código roto fue el ganador.
Si bien su tesis fue aceptada, y ahora es profesor en una importante escuela de ingeniería, tampoco encontró:
1) un número mágico de cobertura de prueba que sea óptimo 2) cualquier suite que pueda encontrar el 100% de los errores.
Tenga en cuenta que el objetivo es encontrar el 100% de los errores, no encontrar el 100% de cobertura.
Si el 85% de @ soru tiene razón o no es un tema de discusión. No tengo medios para evaluar si un número mejor sería 80% o 90% o cualquier otra cosa. Pero como una evaluación de trabajo, el 85% siente lo correcto para mí.
Lo siento por mi ingles.
La cobertura del código al 100% es un trastorno fisiológico gerencial para impresionar a las partes interesadas artificialmente. Hacemos pruebas porque hay un código complejo que puede llevar a defectos. Por lo tanto, debemos asegurarnos de que el código complejo tenga un caso de prueba, que esté probado y que los defectos se solucionen antes de su uso.
Deberíamos apuntar a probar algo que es complejo y no solo todo. Ahora bien, este complejo debe expresarse en términos de algún número métrico que puede ser la complejidad ciclomática, las líneas de código, las agregaciones, el acoplamiento, etc. o probablemente su culminación de todas las cosas anteriores. Si encontramos que la métrica es más alta, necesitamos asegurarnos de que esa parte del código esté cubierta. A continuación se encuentra mi artículo que cubre cuál es el mejor% de cobertura de código.
No existe una relación excepcional entre la cobertura del código y un buen software. Puede imaginar fácilmente un fragmento de código que tenga una cobertura de código del 100% (o cerca) y que todavía contenga muchos errores. (¡Lo que no significa que las pruebas sean malas!)
Su pregunta acerca de la agilidad del enfoque de "no probar en absoluto" es buena solo para una perspectiva corta (lo que significa que es muy probable que no sea buena si planea construir su programa por más tiempo). Sé por mi experiencia que tales pruebas simples son muy útiles cuando su proyecto se hace más y más grande y, en algún momento, necesita hacer cambios significativos. Esto puede ser un momento cuando se dirá a sí mismo ''fue una buena decisión gastar algunos minutos adicionales para escribir esa pequeña prueba que detectó el error que acabo de presentar " .
Recientemente fui un gran fanático de la cobertura de códigos, pero ahora se convirtió (por suerte) en un enfoque de "cobertura de problemas" . Esto significa que sus pruebas deben cubrir todos los problemas y errores detectados, no solo las ''líneas de código'' . No hay necesidad de hacer una ''carrera de cobertura de código'' .
Entiendo la palabra ''Ágil'' en términos de pruebas numéricas como ''cantidad de pruebas que me ayudan a desarrollar un buen software y no perder el tiempo para escribir un fragmento de código innecesario'' en lugar de ''cobertura al 100%'' o ''ninguna prueba''. Es muy subjetivo y se basa en su experiencia, equipo, tecnología y muchos otros factores.
El efecto secundario psicológico de la "cobertura del 100% del código" es que puedes pensar que tu código no tiene errores, lo que nunca es cierto :)