Tenemos un nuevo (bastante grande) inicio de proyecto, que planeamos desarrollar usando TDD.
La idea de TDD falló (muchas razones comerciales y no comerciales), pero en este momento tenemos una conversación: de todos modos, ¿deberíamos escribir pruebas unitarias o no? Mi amigo dice que no tiene sentido (o casi cero) escribir pruebas unitarias sin TDD, deberíamos centrarnos solo en las pruebas de integración. Creo lo contrario, que todavía tiene sentido escribir pruebas de unidades simples, solo para hacer que el código sea más seguro para el futuro. ¿Qué piensas?
Agregado: Creo que esto no es un duplicado de >> esta pregunta << - Entiendo la diferencia entre UT y TDD. Mi pregunta no es sobre las diferencias , sino sobre el sentido de escribir pruebas unitarias sin TDD.
fuente
Respuestas:
TDD se utiliza principalmente (1) para garantizar la cobertura, (2) y para impulsar un diseño mantenible, comprensible y comprobable. Si no usa TDD, no obtiene cobertura de código garantizada. Pero eso de ninguna manera significa que debe abandonar ese objetivo y vivir alegremente con 0% de cobertura.
Las pruebas de regresión fueron inventadas por una razón. La razón es que, a la larga, le ahorran más tiempo en errores evitados de lo que requieren un esfuerzo adicional para escribir. Esto ha sido probado una y otra vez. Por lo tanto, a menos que esté seriamente convencido de que su organización es mucho, mucho mejor en ingeniería de software que todos los gurús que recomiendan las pruebas de regresión (o si planea caer muy pronto para que no haya un largo plazo para usted), sí, usted absolutamente debe tener pruebas unitarias, exactamente por la razón que se aplica a prácticamente cualquier otra organización en el mundo: porque detectan los errores antes que las pruebas de integración, y eso le ahorrará dinero. No escribirlos es como gastar dinero gratis en la calle.
fuente
Tengo una anécdota relevante de algo que está sucediendo en este momento para mí. Estoy en un proyecto que no usa TDD. Nuestra gente de QA nos está moviendo en esa dirección, pero somos un equipo pequeño y ha sido un proceso largo y prolongado.
De todos modos , recientemente estaba usando una biblioteca de terceros para hacer una tarea específica. Hubo un problema con respecto al uso de esa biblioteca, por lo que se me asignó la tarea de escribir una versión de esa misma biblioteca por mi cuenta. En total, terminaron siendo aproximadamente 5,000 líneas de código ejecutable y aproximadamente 2 meses de mi tiempo. Sé que las líneas de código son una métrica pobre, pero por esta respuesta, creo que es un indicador de magnitud decente.
Necesitaba una estructura de datos particular que me permitiera hacer un seguimiento de un número arbitrario de bits. Dado que el proyecto está en Java, elegí Java
BitSet
y lo modifiqué un poco (también necesitaba la capacidad de rastrear los0
s principales , que BitSet de Java no hace por alguna razón .....). Después de alcanzar una cobertura del ~ 93%, comencé a escribir algunas pruebas que realmente enfatizarían el sistema que había escrito. Necesitaba comparar ciertos aspectos de la funcionalidad para asegurar que serían lo suficientemente rápidos para mis requisitos finales. Como era de esperar, una de las funciones que había anulado de laBitSet
interfaz era absurdamente lenta cuando se trataba de grandes conjuntos de bits (cientos de millones de bits en este caso). Otras funciones anuladas dependían de esta función, por lo que era un enorme cuello de botella.Lo que terminé haciendo fue ir al tablero de dibujo y encontrar una forma de manipular la estructura subyacente de
BitSet
, que es along[]
. Diseñé el algoritmo, les pedí a sus colegas su opinión y luego me puse a escribir el código. Luego, ejecuté las pruebas unitarias. Algunos de ellos se rompieron, y los que sí me indicaron exactamente dónde necesitaba buscar en mi algoritmo para solucionarlo. Después de corregir todos los errores de las pruebas unitarias, pude decir que la función funciona como debería. Por lo menos, podría estar tan seguro de que este nuevo algoritmo funcionó tan bien como el algoritmo anterior.Por supuesto, esto no es a prueba de balas. Si hay un error en mi código que las pruebas unitarias no están buscando, entonces no lo sabré. Pero, por supuesto, ese mismo error podría haber estado en mi algoritmo más lento también. Sin embargo , puedo decir con un alto grado de confianza que no tengo que preocuparme por la salida incorrecta de esa función en particular. Las pruebas unitarias preexistentes me ahorraron horas, quizás días, de intentar probar el nuevo algoritmo para asegurarme de que fuera correcto.
Ese es el punto de tener pruebas unitarias independientemente de TDD, es decir, las pruebas unitarias lo harán por usted en TDD y fuera de TDD de todos modos, cuando termine refactorizando / manteniendo el código. Por supuesto, esto debe combinarse con pruebas de regresión regulares, pruebas de humo, pruebas difusas, etc., pero las pruebas unitarias , como su nombre lo indica, prueban las cosas en el nivel atómico más pequeño posible, lo que le da instrucciones sobre dónde han aparecido los errores.
En mi caso, sin las pruebas unitarias existentes, de alguna manera tendría que encontrar un método para asegurar que el algoritmo funcione todo el tiempo. Lo cual, al final ... suena mucho a pruebas unitarias , ¿no?
fuente
Puede dividir el código aproximadamente en 4 categorías:
Las pruebas unitarias se vuelven más valiosas (es probable que detecten errores importantes) a medida que avanza en la lista. En mis proyectos personales, casi siempre hago TDD en la categoría 4. En la categoría 3 generalmente hago TDD a menos que las pruebas manuales sean más simples y rápidas. Por ejemplo, el código antialiasing sería complejo de escribir, pero mucho más fácil de verificar visualmente que escribir una prueba unitaria, por lo que la prueba unitaria solo me valdría la pena si ese código cambiara con frecuencia. El resto de mi código solo lo pongo bajo prueba unitaria después de encontrar un error en esa función.
A veces es difícil saber de antemano en qué categoría se ajusta un determinado bloque de código. El valor de TDD es que no se pierde accidentalmente ninguna de las pruebas unitarias complejas. El costo de TDD es todo el tiempo que pasa escribiendo las pruebas unitarias simples. Sin embargo, por lo general, las personas con experiencia en un proyecto saben con un grado razonable de certeza en qué categoría encajan las diferentes partes del código. Si no está haciendo TDD, al menos debería intentar escribir las pruebas más valiosas.
fuente
Ya se trate de pruebas de unidad, componente, integración o aceptación, la parte importante es que debe automatizarse. No tener pruebas automatizadas es un error fatal para cualquier tipo de software, desde los CRUD simples hasta los cálculos más complejos. El razonamiento es que escribir las pruebas automatizadas siempre costará menos que la necesidad continua de ejecutar todas las pruebas manualmente cuando no lo haga, por órdenes de magnitud. Después de haberlos escrito, solo tiene que presionar un botón para ver si pasan o fallan. Las pruebas manuales siempre demorarán mucho tiempo en ejecutarse, y dependerán de los humanos (las criaturas vivientes que se aburren, pueden carecer de atención, etc.) para poder verificar si las pruebas pasan o no. En resumen, siempre escriba pruebas automatizadas.
Ahora, sobre la razón por la cual su colega podría estar en contra de hacer cualquier tipo de prueba unitaria sin TDD: probablemente sea porque es más difícil confiar en las pruebas escritas después del código de producción. Y si no puede confiar en sus pruebas automatizadas, no valen nada . Después del ciclo TDD, primero debe hacer que una prueba falle (por la razón correcta) para poder escribir el código de producción para que pase (y no más). Este proceso es esencialmente probar sus pruebas, por lo que puede confiar en ellas. Sin mencionar el hecho de que escribir pruebas antes del código real lo empuja a diseñar sus unidades y componentes para que sean más fácilmente comprobables (altos niveles de desacoplamiento, SRP aplicado, etc.). Aunque, por supuesto, hacer TDD requiere disciplina .
En cambio, si escribe primero todo el código de producción, cuando escriba las pruebas, esperará que pasen en la primera ejecución. Esto es muy problemático, porque puede haber creado una prueba que cubre el 100% de su código de producción, sin afirmar el comportamiento correcto (¡puede que ni siquiera realice ninguna afirmación! He visto que esto sucede ), ya que no puede ver que falla primero para verificar si está fallando por la razón correcta. Por lo tanto, puede tener falsos positivos. Los falsos positivos finalmente romperán la confianza en su conjunto de pruebas, forzando esencialmente a las personas a recurrir a las pruebas manuales nuevamente, por lo que tiene el costo de ambos procesos (pruebas de escritura + pruebas manuales).
Esto significa que debe encontrar otra forma de evaluar sus pruebas , como lo hace TDD. Por lo tanto, recurre a la depuración, comentando partes del código de producción, etc., para poder confiar en las pruebas. El problema es que el proceso de "probar sus pruebas" es mucho más lento de esta manera. Agregar este tiempo al tiempo que pasará ejecutando pruebas ad-hoc manualmente (porque no tiene pruebas automáticas mientras codifica el código de producción), en mi experiencia, resulta en un proceso general que es mucho más lento que practicar TDD "por el libro" (Kent Beck - TDD por ejemplo). Además, estoy dispuesto a hacer una apuesta aquí y decir que realmente "probar sus pruebas" después de que fueron escritas requiere mucha más disciplina que TDD.
Entonces, tal vez su equipo pueda reconsiderar las "razones comerciales y no comerciales" para no hacer TDD. En mi experiencia, la gente tiende a pensar que TDD es más lento en comparación con solo escribir pruebas unitarias una vez que se hace el código. Esta suposición es errónea, como has leído anteriormente.
fuente
A menudo, la calidad de las pruebas depende de su procedencia. Soy regularmente culpable de no hacer TDD 'real': escribo un código para demostrar que la estrategia que me gustaría usar realmente funciona, luego cubro cada caso que el código debe respaldar con las pruebas posteriores. Por lo general, la unidad de código es una clase, para darle una idea general de cuánto trabajo haré felizmente sin la cobertura de la prueba, es decir, no una gran cantidad. Lo que esto significa es que el significado semántico de las pruebas coincide muy bien con el sistema bajo prueba en su estado 'terminado', porque las escribí sabiendo qué casos cumple el SUT y cómo los cumple.
Por el contrario, TDD con su política de refactorización agresiva tiende a realizar pruebas obsoletas al menos tan rápido como puede escribirlas, ya que la interfaz pública del sistema bajo los cambios de prueba. Personalmente, considero que la carga de trabajo mental de diseñar las unidades funcionales de la aplicación y mantener la semántica de las pruebas que la cubren sincronizadas es demasiado alta para mantener mi concentración y el mantenimiento de la prueba con frecuencia se cae. La base de código termina con pruebas que no prueban nada de valor o simplemente están equivocadas. Si tiene la disciplina y la capacidad mental para mantener actualizado el conjunto de pruebas, practique TDD tan rigurosamente como desee. No lo hago, así que lo he encontrado menos exitoso por esa razón.
fuente
En realidad, el tío Bob mencionó un punto muy interesante en uno de sus videos de Clean Coders. Dijo que el ciclo Rojo-Verde-Refactor se puede aplicar de 2 maneras.
Primero es la forma convencional de TDD. Escriba una prueba reprobatoria, luego pase la prueba y finalmente refactorice.
La segunda forma es escribir un código de producción muy pequeño e inmediatamente seguirlo con su prueba unitaria y luego refactorizarlo.
La idea es ir en pasos muy pequeños . Por supuesto, pierde la verificación del código de producción de que su prueba pasó de rojo a verde, pero en algunos casos donde estaba trabajando principalmente con desarrolladores junior que se negaron incluso a tratar de comprender TDD, resultó ser algo efectivo.
Nuevamente repito (y esto fue enfatizado por el tío Bob) la idea es ir en pasos muy pequeños y probar de inmediato el código de producción que se acaba de agregar.
fuente