Durante un tiempo he estado tratando de aprender a escribir pruebas unitarias para mi código.
Inicialmente comencé a hacer TDD verdadero, donde no escribiría ningún código hasta que escribiera una prueba fallida primero.
Sin embargo, recientemente tuve que resolver un problema espinoso que involucraba mucho código. Después de pasar un par de semanas escribiendo pruebas y luego código, llegué a la desafortunada conclusión de que todo mi enfoque no iba a funcionar, y que tendría que tirar dos semanas de trabajo y comenzar de nuevo.
Esta es una decisión bastante mala cuando acabas de escribir el código, pero cuando también has escrito cientos de pruebas unitarias, se vuelve aún más emocionalmente difícil tirarlo todo a la basura.
No puedo evitar pensar que he desperdiciado 3 o 4 días de esfuerzo escribiendo esas pruebas cuando podría haber reunido el código como prueba de concepto y luego haber escrito las pruebas una vez que estuve satisfecho con mi enfoque.
¿Cómo manejan adecuadamente esas situaciones las personas que practican TDD? ¿Hay algún caso para doblar las reglas en algunos casos o siempre escribes servilmente las pruebas primero, incluso cuando ese código puede resultar inútil?
fuente
Respuestas:
Siento que hay dos problemas aquí. La primera es que no se dio cuenta de antemano de que su diseño original puede no ser el mejor enfoque. Si lo hubiera sabido de antemano, es posible que haya optado por desarrollar uno o dos prototipos de descarte rápido , para explorar las posibles opciones de diseño y evaluar cuál es la forma más prometedora de seguir. En la creación de prototipos, no necesita escribir código de calidad de producción y no necesita probar la unidad en cada rincón y grieta (o en absoluto), ya que su único objetivo es aprender, no pulir el código.
Ahora, darse cuenta de que necesita prototipos y experimentos en lugar de comenzar a desarrollar el código de producción de inmediato, no siempre es fácil y ni siquiera siempre es posible. Armado con el conocimiento recién adquirido, es posible que pueda reconocer la necesidad de crear prototipos la próxima vez. O tal vez no. Pero al menos ahora sabe que esta opción debe considerarse. Y esto en sí mismo es un conocimiento importante.
El otro problema es en mi humilde opinión con su percepción. Todos cometemos errores, y es muy fácil ver en retrospectiva lo que deberíamos haber hecho de manera diferente. Así es como aprendemos. Anote su inversión en pruebas unitarias como el precio de aprender que la creación de prototipos puede ser importante y supérela. Solo esfuérzate por no cometer el mismo error dos veces :-)
fuente
El punto de TDD es que te obliga a escribir pequeños incrementos de código en funciones pequeñas , precisamente para evitar este problema. Si ha pasado semanas escribiendo código en un dominio, y cada método de utilidad que escribió se vuelve inútil cuando reconsidera la arquitectura, entonces sus métodos son ciertamente demasiado grandes en primer lugar. (Sí, soy consciente de que esto no es exactamente reconfortante ahora ...)
fuente
Brooks dijo "planea tirar uno; lo harás, de todos modos". Me parece que estás haciendo exactamente eso. Dicho esto, debe escribir sus pruebas unitarias para probar la unidad de código y no una gran franja de código. Esas son pruebas más funcionales y, por lo tanto, deberían sobre cualquier implementación interna.
Por ejemplo, si quiero escribir un solucionador de PDE (ecuaciones diferenciales parciales), escribiría algunas pruebas tratando de resolver cosas que puedo resolver matemáticamente. Esas son mis primeras pruebas de "unidad" - lea: las pruebas funcionales se ejecutan como parte de un marco xUnit. Esos no cambiarán según el algoritmo que use para resolver el PDE. Todo lo que me importa es el resultado. Las pruebas de la segunda unidad se centrarán en las funciones utilizadas para codificar el algoritmo y, por lo tanto, serían específicas del algoritmo, por ejemplo, Runge-Kutta. Si descubriera que Runge-Kutta no era adecuado, aún tendría esas pruebas de nivel superior (incluidas las que mostraron que Runge-Kutta no era adecuado). Por lo tanto, la segunda iteración todavía tendría muchas de las mismas pruebas que la primera.
Su problema puede ser el diseño y no necesariamente el código. Pero sin más detalles, es difícil de decir.
fuente
Debe tener en cuenta que TDD es un proceso iterativo. Escriba una pequeña prueba (en la mayoría de los casos, unas pocas líneas deberían ser suficientes) y ejecútela. La prueba debería fallar, ahora trabaje directamente en su fuente principal e intente implementar la funcionalidad probada para que la prueba pase. Ahora comienza de nuevo.
No debe intentar escribir todas las pruebas de una vez, porque, como ha notado, esto no va a funcionar. Esto reduce el riesgo de perder el tiempo escribiendo pruebas que no se van a utilizar.
fuente
Creo que lo dijo usted mismo: no estaba seguro acerca de su enfoque antes de comenzar a escribir todas sus pruebas unitarias.
Lo que aprendí al comparar los proyectos TDD de la vida real con los que trabajé (no muchos, de hecho, solo 3 cubriendo 2 años de trabajo) con lo que aprendí teóricamente, es que las Pruebas automatizadas = Pruebas unitarias (sin, por supuesto, ser mutuamente exclusivo).
En otras palabras, la T en TDD no tiene que tener una U con ella ... Está automatizada, pero es menos una prueba unitaria (como en las clases y métodos de prueba) que una prueba funcional automatizada: está en el mismo nivel de granularidad funcional como la arquitectura en la que está trabajando actualmente. Comienzas a alto nivel, con pocas pruebas y solo el panorama funcional, y solo eventualmente terminas con miles de UT, y todas tus clases bien definidas en una hermosa arquitectura ...
Las pruebas unitarias le brindan una gran ayuda cuando trabaja en equipo, para evitar cambios en el código que crean ciclos interminables de errores. Pero nunca escribí algo tan preciso cuando comencé a trabajar en un proyecto, antes de tener al menos un POC de trabajo global para cada historia de usuario.
Tal vez es solo mi forma personal de hacer esto. No tengo la experiencia suficiente para decidir desde cero qué patrones o estructura tendrá mi proyecto, por lo que no perderé mi tiempo escribiendo cientos de UT desde el principio ...
En términos más generales, la idea de romper todo y tirarlo todo siempre estará ahí. Tan "continuo" como podemos tratar de ser con nuestras herramientas y métodos, a veces la única forma de combatir la entropía es comenzar de nuevo. Pero el objetivo es que cuando eso suceda, las pruebas automatizadas y unitarias que implementó harán que su proyecto ya sea menos costoso que si no existiera, y lo hará, si encuentra el equilibrio.
fuente
La combinación de las pruebas unitarias con el desarrollo basado en pruebas es fuente de mucha angustia y aflicción. Así que repasemos una vez más:
En resumen: las pruebas unitarias tienen un enfoque de implementación, TDD tiene un enfoque de requisitos. No són la misma cosa.
fuente
El desarrollo basado en pruebas está destinado a impulsar su desarrollo. Las pruebas que escribes te ayudan a afirmar la exactitud del código que estás escribiendo actualmente y a aumentar la velocidad de desarrollo desde la primera línea en adelante.
Parece creer que las pruebas son una carga y solo están destinadas a un desarrollo incremental más adelante. Esta línea de pensamiento no está en línea con TDD.
Tal vez pueda compararlo con la escritura estática: aunque se puede escribir código sin información de tipo estático, agregar el tipo estático al código ayuda a afirmar ciertas propiedades del código, liberando la mente y permitiendo enfocarse en una estructura importante, aumentando así la velocidad y eficacia.
fuente
El problema de realizar una refactorización importante es que puedes y a veces seguirás un camino que te llevará a darte cuenta de que has mordido más de lo que puedes masticar. Las refactorizaciones gigantes son un error. Si el diseño del sistema es defectuoso en primer lugar, la refactorización solo puede llevarlo lejos antes de que tenga que tomar una decisión difícil. O deje el sistema como está y trabaje alrededor de él, o planee rediseñar y hacer algunos cambios importantes.
Sin embargo, hay otra manera. El beneficio real de refactorizar el código es hacer las cosas más simples, fáciles de leer e incluso más fáciles de mantener. Cuando se acerca a un problema sobre el que tiene incertidumbre, marca un cambio, va tan lejos para ver a dónde podría conducir para obtener más información sobre el problema, luego tira el pico y aplica una nueva refactorización en función de lo que es el pico. te enseñó. La cuestión es que en realidad solo puede mejorar su código con certeza si los pasos son pequeños y sus esfuerzos de refactorización no superan su capacidad de escribir sus pruebas primero. La tentación es escribir una prueba, luego codificar, luego codificar un poco más porque una solución puede parecer obvia, pero pronto se da cuenta de que su cambio cambiará muchas más pruebas, por lo que debe tener cuidado de cambiar solo una cosa a la vez.
La respuesta, por lo tanto, es nunca hacer que su refactorización sea importante. Pequeños pasos. Comience por extraer métodos, luego busque eliminar la duplicación. Luego pase a extraer clases. Cada uno en pequeños pasos, un cambio menor a la vez. SI está extrayendo código, primero escriba una prueba. Si está eliminando el código, elimínelo y ejecute sus pruebas, y decida si alguna de las pruebas rotas será necesaria. Un pequeño paso de bebé a la vez. Parece que llevará más tiempo, pero en realidad acortará considerablemente el tiempo de refactorización.
Sin embargo, la realidad es que cada pico es aparentemente una pérdida potencial de esfuerzo. Los cambios de código a veces no van a ninguna parte, y te encuentras restaurando tu código desde tus vcs. Esto es solo una realidad de lo que hacemos día a día. Sin embargo, cada punta que falla no se desperdicia si te enseña algo. Cada esfuerzo de refactorización que falla le enseñará que está tratando de hacer demasiado y demasiado rápido, o que su enfoque puede estar equivocado. Eso tampoco es una pérdida de tiempo si aprendes algo de él. Cuanto más hagas esto, más aprenderás y más eficiente serás en ello. Mi consejo es que solo lo use por ahora, aprenda a hacer más haciendo menos y acepte que así es como probablemente deben ser las cosas hasta que mejore para identificar qué tan lejos tomar un pico antes de que no lo lleve a ninguna parte.
fuente
No estoy seguro de la razón por la cual su enfoque resultó defectuoso después de 3 días. Dependiendo de sus incertidumbres en su arquitectura, podría considerar cambiar su estrategia de prueba:
Si no está seguro sobre el rendimiento, ¿es posible que desee comenzar con algunas pruebas de integración que afirman el rendimiento?
Cuando la complejidad de la API es lo que está investigando, escriba algunas pruebas de unidades pequeñas y reales para descubrir cuál sería la mejor manera de hacerlo. No se moleste en implementar nada, solo haga que sus clases devuelvan valores codificados o haga que arrojen NotImplementedExceptions.
fuente
Para mí, las pruebas unitarias también son una ocasión para poner la interfaz en uso "real" (bueno, ¡tan real como las pruebas unitarias!).
Si me veo obligado a configurar una prueba, tengo que ejercitar mi diseño. Esto ayuda a mantener las cosas cuerdas (si algo es tan complejo que escribir una prueba es una carga, ¿cómo será usarlo?).
Esto no evita cambios en el diseño, sino que expone la necesidad de ellos. Sí, una reescritura completa es un dolor. Para (intentar) evitarlo, generalmente configuro (uno o más) prototipos, posiblemente en Python (con el desarrollo final en c ++).
De acuerdo, no siempre tienes tiempo para todas estas golosinas. Esos son precisamente los casos en que se necesita un GRANDE cantidad de tiempo para lograr sus objetivos ... y / o para mantener todo bajo control.
fuente
Bienvenido a desarrolladores creativos de circo .
En lugar de respetar todas las formas 'legales / razonables' de codificar al principio,
intente la intuición , sobre todo si es importante y nuevo para usted y si no hay una muestra que parezca que desea:
- Escriba con su instinto, a partir de cosas que ya sabe , no con tu mentalidad e imaginación.
- Y pare.
- Tome una lupa e inspeccione todas las palabras que escribe: escribe "texto" porque "texto" está cerca de String, pero se necesita "verbo", "adjetivo" o algo más preciso, lea nuevamente y ajuste el método con un nuevo sentido
. .. o, escribiste un código pensando en el futuro? quítelo
: corrija, realice otra tarea (deporte, cultura u otras cosas fuera de los negocios), vuelva y lea nuevamente.
- Todo encaja bien,
- Correcto, hacer otra tarea, volver y leer de nuevo.
- Todo encaja bien, pase a TDD
- Ahora todo está correcto, bien
- Intente el punto de referencia para señalar las cosas que deben optimizarse, hágalo.
Lo que parece:
escribiste un código que respeta todas las reglas
, obtienes una experiencia, una nueva forma de trabajar,
algo cambia en tu mente, nunca tendrás miedo con una nueva configuración.
Y ahora, si ve un UML similar al anterior, podrá decir
"Jefe, empiezo por TDD para esto ..." ¿
es otra cosa nueva?
"Jefe, probaría algo antes de decidir la forma en que codificaré ..."
Saludos desde PARIS
Claude
fuente