Estaba discutiendo las pruebas de unidad / integración con un colega, y él hizo un caso interesante en contra de escribir pruebas de unidad. Soy un gran defensor de la prueba de unidad (principalmente JUnit), pero estoy interesado en escuchar las opiniones de los demás, ya que hizo algunos puntos interesantes.
Para resumir sus puntos:
- Cuando se producen cambios importantes en el código (nuevo conjunto de POJO, refactorización de aplicaciones importantes, etc.), las pruebas unitarias tienden a comentarse en lugar de modificarse.
- Se dedica mejor tiempo a las pruebas de integración que cubren casos de uso, lo que hace que las pruebas de menor alcance sean menos / nada importantes.
¿Pensamientos sobre esto? Todavía estoy a prueba de unidad (como lo veo constantemente produciendo código mejorado), aunque las pruebas de integración suenan al menos tan valiosas.
unit-testing
integration-tests
Jeff Levine
fuente
fuente
Respuestas:
Tiendo a estar del lado de tu amigo porque con demasiada frecuencia, las pruebas unitarias prueban cosas incorrecta .
Las pruebas unitarias no son inherentemente malas. Pero a menudo prueban los detalles de implementación en lugar del flujo de entrada / salida. Terminas con pruebas completamente inútiles cuando esto sucede. Mi propia regla es que una buena prueba de unidad te dice que acabas de romper algo; una mala prueba unitaria simplemente te dice que acabas de cambiar algo.
Un ejemplo fuera de mi cabeza es una prueba que se metió en WordPress hace unos años. La funcionalidad que se estaba probando giraba en torno a filtros que se llamaban entre sí, y las pruebas verificaban que las devoluciones de llamada se llamarían en el orden correcto. Pero en lugar de (a) ejecutar la cadena para verificar que las devoluciones de llamada se llamen en el orden esperado, las pruebas se centraron en (b) leer algún estado interno que posiblemente no debería haber sido expuesto para comenzar. Cambie las partes internas y (b) se ponga rojo; mientras que (a) solo se vuelve rojo si los cambios en las partes internas rompen el resultado esperado al hacerlo. (b) fue claramente una prueba inútil en mi opinión.
Si tiene una clase que expone algunos métodos al mundo exterior, lo correcto para probar en mi opinión son los últimos métodos solamente . Si también prueba la lógica interna, puede terminar exponiendo la lógica interna al mundo exterior, utilizando métodos de prueba complicados o con una letanía de pruebas unitarias que invariablemente se rompen cada vez que desea cambiar algo.
Dicho todo esto, me sorprendería si su amigo es tan crítico con las pruebas unitarias per se como parece sugerir. Más bien deduciría que es pragmático. Es decir, observó que las pruebas unitarias que se escriben no tienen sentido en la práctica. Cita: "las pruebas unitarias tienden a comentarse en lugar de reelaborarse". Para mí, hay un mensaje implícito allí: si tienden a necesitar una nueva revisión, es porque tienden a apestar. Suponiendo que sí, la segunda proposición sigue: los desarrolladores perderían menos tiempo escribiendo código que es más difícil de equivocar, es decir, pruebas de integración.
Como tal, no se trata de que uno sea mejor o peor. Es solo que es mucho más fácil equivocarse, y de hecho muy a menudo se equivoca en la práctica.
fuente
Con un grupo indisciplinado de codificadores de vaqueros que piensan que todas las pruebas se vuelven "verdes" cuando se comentan todas las pruebas existentes: por supuesto. La reelaboración de las pruebas unitarias requiere la misma disciplina que escribirlas de primera mano, por lo que debe trabajar aquí en la "definición de hecho" de su equipo.
Eso no es completamente incorrecto ni completamente correcto. El esfuerzo para escribir pruebas de integración útiles depende mucho de qué tipo de software está escribiendo. Si tiene un software donde las pruebas de integración pueden escribirse casi tan fácilmente como las pruebas unitarias, se ejecutan aún rápido y le brindan una buena cobertura de su código, entonces su colega tiene razón. Pero para muchos sistemas, he visto que estos tres puntos no pueden cumplirse fácilmente mediante pruebas de integración, es por eso que debe esforzarse por tener también pruebas unitarias.
fuente
No sé si acepto los argumentos de tu colega.
Hay otros argumentos más convincentes contra las pruebas unitarias. Bueno, no exactamente en contra de ellos. Comience con el daño de diseño inducido por la prueba de DHH . Es un escritor divertido, así que léelo. Lo que tomé de él:
Si realiza grandes cambios de diseño para acomodar las pruebas de su unidad, pueden obstaculizar otros objetivos en su proyecto. Si es posible, pregunte a una persona imparcial qué enfoque es más fácil de seguir. Si su código altamente comprobable es difícil de entender, puede perder todos los beneficios que obtuvo de las pruebas unitarias. La sobrecarga cognitiva de demasiada abstracción te ralentiza y facilita los errores de fugas. No lo descartes.
Si quieres ser matizado y filosófico, hay muchos recursos excelentes:
fuente
No hagas eso. Si encuentra a alguien que hace eso, asesínelos y asegúrese de que no se reproduzcan, ya que sus hijos podrían heredar ese gen defectuoso. La clave tal como la veo al ver programas de televisión CSI es dispersar muchas muestras de ADN engañosas en todas partes. De esta manera, contaminas la escena del crimen con tanto ruido que, dada la naturaleza costosa y lenta de las pruebas de ADN, la probabilidad de que te lo identifiquen es astronómicamente baja.
fuente
En ese momento, el proceso de revisión de código dice "repara las pruebas unitarias" y esto ya no es un problema :-) Si tu proceso de revisión de código no detecta este tipo de falla importante en el código enviado, entonces ese es el problema.
fuente
Siempre trato de mantener la refactorización y el cambio de funcionalidad por separado. Cuando necesito hacer ambas cosas, generalmente primero cometo la refactorización.
Cuando se refactoriza el código sin cambiar la funcionalidad, se supone que las pruebas unitarias existentes ayudan a garantizar que la refactorización no rompa accidentalmente la funcionalidad. Entonces, para tal confirmación, consideraría deshabilitar o eliminar las pruebas unitarias como una señal de advertencia importante. Se debe decir a cualquier desarrollador que lo haga que no lo haga cuando se revise el código.
Es posible que los cambios que no cambian la funcionalidad sigan causando que las pruebas unitarias fallen debido a pruebas unitarias defectuosas. Si comprende el código que está cambiando, la causa de tales fallas en las pruebas unitarias suele ser inmediatamente obvia y fácil de solucionar.
Por ejemplo, si una función toma tres argumentos, una prueba unitaria que cubra la interacción entre los dos primeros argumentos para la función podría no haberse ocupado de proporcionar un valor válido para el tercer argumento. Este defecto en la prueba de la unidad puede quedar expuesto por una refactorización del código probado, pero es fácil de solucionar si comprende lo que se supone que debe hacer el código y lo que está probando la prueba de la unidad.
Al cambiar la funcionalidad existente, generalmente será necesario cambiar también algunas pruebas unitarias. En este caso, las pruebas unitarias ayudan a garantizar que su código cambie la funcionalidad según lo previsto y no tenga efectos secundarios no deseados.
Al corregir errores o agregar nuevas funcionalidades, generalmente es necesario agregar más pruebas unitarias. Para aquellos, puede ser útil confirmar las pruebas unitarias primero y confirmar la corrección de errores o la nueva funcionalidad más adelante. Eso hace que sea más fácil verificar que las nuevas pruebas unitarias no pasaron con el código anterior pero sí con el código más nuevo. Sin embargo, este enfoque no carece por completo de inconvenientes, por lo que también existen argumentos a favor de confirmar las nuevas pruebas unitarias y las actualizaciones de código simultáneamente.
Hay algún elemento de verdad en esto. Si puede obtener cobertura de las capas inferiores de la pila de software con pruebas dirigidas a las capas superiores de la pila de software, sus pruebas pueden ser más útiles al refactorizar el código.
Sin embargo, no creo que encuentre un acuerdo sobre la distinción exacta entre una prueba unitaria y una prueba de integración. Y no me preocuparía si tiene un caso de prueba que un desarrollador llama una prueba unitaria y otro llama una prueba de integración, siempre que puedan estar de acuerdo en que es un caso de prueba útil.
fuente
Las pruebas de integración se realizan para verificar si el sistema se comporta como debería, lo que siempre tiene valor comercial. Las pruebas unitarias no siempre hacen eso. Las pruebas unitarias podrían estar probando código muerto, por ejemplo.
Existe una filosofía de prueba que dice que cualquier prueba que no le brinde información es un desperdicio. Cuando no se está trabajando en un fragmento de código, sus pruebas unitarias siempre (o casi siempre) serán verdes, lo que le dará poca o ninguna información.
Todos los métodos de prueba estarán incompletos. Incluso si obtiene una cobertura del 100% por alguna métrica, aún no está pasando por casi todos los posibles conjuntos de datos de entrada. Entonces, no pienses que las pruebas unitarias son mágicas.
A menudo he visto la afirmación de que tener pruebas unitarias mejora su código. En mi opinión, las mejoras que las pruebas unitarias traen al código están obligando a las personas que no están acostumbradas a romper su código en piezas pequeñas y bien pensadas. Si se acostumbra a diseñar su código en piezas pequeñas y bien factorizadas normalmente, es posible que ya no necesite pruebas unitarias para obligarlo a hacerlo.
Dependiendo de cuán pesado sea el examen unitario y cuán grande sea su programa, puede terminar con una enorme cantidad de exámenes unitarios. Si es así, los grandes cambios se vuelven más difíciles. Si un gran cambio causa muchas pruebas unitarias fallidas, puede negarse a hacer el cambio, hacer un gran trabajo para arreglar muchas pruebas o descartarlas. Ninguna de las opciones es ideal.
Las pruebas unitarias son una herramienta en la caja de herramientas. Están ahí para servirle, no al revés. Asegúrate de salir de ellos al menos tanto como lo pones.
fuente
Las pruebas unitarias definitivamente no son la bala de plata que algunos defensores afirman que son. Pero en general tienen una relación costo / beneficio muy positiva. No harán que su código sea "mejor", tal vez más modular. "mejor" tiene muchos más factores que lo que implica "comprobabilidad". Pero se asegurarán de que funcione en todos los casos y usos en los que pensó, y eliminarán la mayoría de sus errores (probablemente los más triviales).
Sin embargo, el mayor beneficio de las pruebas unitarias es que hacen que su código sea más flexible y resistente al cambio. Puede refactorizar o agregar funciones, y puede estar seguro de que no rompió nada. Esto solo hace que valga la pena para la mayoría de los proyectos.
Refiriéndose a los puntos hechos por su amigo:
Si agregar POJO rompe las pruebas de su unidad, entonces probablemente estén muy mal escritas. Los POJO generalmente son el idioma que está utilizando para hablar sobre su dominio, y los problemas que está resolviendo, cambiarlos obviamente significa toda su lógica comercial, y probablemente el código de presentación debe cambiar. No puede esperar que lo que asegura que esas partes de su programa funcionen no cambie ...
Quejarse de que las pruebas de la Unidad son malas, porque la gente las comenta, es como quejarse de que los cinturones de seguridad son malos porque la gente no los usa. Si alguien comenta el código de prueba y rompe algo, debería terminar en una conversación muy seria y desagradable con su jefe ...
Las pruebas de integración son muy superiores a las pruebas unitarias. no hay duda. especialmente si estás probando un producto. Pero escribir buenas y completas pruebas de integración que le brinden el mismo valor, cobertura y seguridad de corrección que las pruebas unitarias le brindan es increíblemente difícil.
fuente
Solo puedo pensar en un argumento en contra de las pruebas unitarias, y es bastante débil.
Las pruebas unitarias muestran la mayoría de su valor cuando refactoriza o cambia el código en una etapa posterior. Usted ve una gran reducción en el tiempo requerido para agregar funciones y asegurarse de no haber roto nada.
Sin embargo: hay un costo (pequeño) en tiempo de escribir pruebas en primer lugar.
Por lo tanto: si un proyecto es lo suficientemente corto y no requiere mantenimiento / actualizaciones continuas, es posible que el costo de escribir pruebas supere sus beneficios.
fuente