TDD vs. Productividad

131

En mi proyecto actual (un juego, en C ++), decidí que usaría Test Driven Development 100% durante el desarrollo.

En términos de calidad del código, esto ha sido excelente. Mi código nunca ha sido tan bien diseñado o tan libre de errores. No me avergüenzo al ver el código que escribí hace un año al comienzo del proyecto, y he adquirido una idea mucho mejor de cómo estructurar las cosas, no solo para ser más fácilmente comprobable, sino para ser más simple de implementar y usar .

Sin embargo ... ha pasado un año desde que comencé el proyecto. De acuerdo, solo puedo trabajar en ello en mi tiempo libre, pero TDD todavía me está frenando considerablemente en comparación con lo que estoy acostumbrado. Leí que la velocidad de desarrollo más lenta mejora con el tiempo, y definitivamente creo que las pruebas son mucho más fáciles de lo que solía hacerlo, pero he estado en esto durante un año y todavía estoy trabajando a paso de tortuga.

Cada vez que pienso en el siguiente paso que necesita trabajo, tengo que parar cada vez y pensar en cómo escribiría una prueba para poder escribir el código real. A veces me quedo atascado durante horas, sabiendo exactamente qué código quiero escribir, pero sin saber cómo desglosarlo lo suficientemente fino como para cubrirlo completamente con pruebas. Otras veces, pensaré rápidamente en una docena de pruebas y pasaré una hora escribiendo pruebas para cubrir un pequeño fragmento de código real que de otro modo me hubiera llevado unos minutos escribir.

O, después de terminar la prueba número 50 para cubrir una entidad en particular en el juego y todos los aspectos de su creación y uso, miro mi lista de tareas y veo la próxima entidad que se codifica, y me horrorizo ​​ante la idea de escribir otras 50 pruebas similares para implementarlo.

Llegué al punto de que, mirando el progreso del año pasado, estoy considerando abandonar TDD por "terminar el maldito proyecto". Sin embargo, renunciar a la calidad del código que viene con él no es algo que esté esperando. Me temo que si dejo de escribir pruebas, dejaré el hábito de hacer que el código sea tan modular y comprobable.

¿Acaso estoy haciendo algo mal para ser tan lento en esto? ¿Existen alternativas que aceleren la productividad sin perder por completo los beneficios? TAD? ¿Menos cobertura de prueba? ¿Cómo sobreviven otras personas al TDD sin matar toda la productividad y la motivación?

Nairou
fuente
@Nairou: ¡Siempre puedes intentar "terminar el proyecto"! Haz una rama ahora. Solo escribe el código allí. Pero limite lo que hace, ya sea por tiempo o por número de entidades del juego y vea si fue más rápido. Luego puede ignorar esa rama, volver al tronco y TDD desde allí y ver cuál es la diferencia.
quamrana
99
Para mí, escribir pruebas demasiado temprano es como optimizar demasiado pronto. De todos modos, podría estar trabajando duro para probar el código que eliminará en el futuro.
LennyProgrammers
Me preocupa un poco que pases horas pensando en una forma de diseñar tu código para que sea más comprobable. La capacidad de prueba es un atributo probable de un diseño bien factorizado, pero no debe ser el objetivo primordial del diseño .
Jeremy
2
Cuando estaba aprendiendo, teníamos un truco para cuando teníamos que proporcionar documentos de diseño. Primero escribimos el código, luego escribimos los documentos para describir el código. Quizás necesite aprender una cantidad moderada de ese pragmatismo para su TDD. Si ya tiene un plan en mente, tal vez sea mejor incorporar la mayor parte de eso en el código antes de escribir las pruebas. Independientemente de lo que sugiera el idealismo, a veces es mejor hacer lo que ya está listo para hacer, en lugar de distraerse con otra cosa y luego regresar cuando ya no esté fresco.
Steve314
44
Voy a ir en contra de la opinión popular y decir que TDD no siempre es la opción correcta si estás haciendo juegos. Dado que alguien en gamedev.stackexchange ya respondió esta pregunta espectacularmente, solo vincularé esto aquí .
l46kok

Respuestas:

77

Permítame comenzar agradeciéndole que comparta su experiencia y expresando sus inquietudes ... lo que tengo que decir que no son infrecuentes.

  • Tiempo / Productividad: Escribir pruebas es más lento que no escribir pruebas. Si lo alcanzas, estoy de acuerdo. Sin embargo, si ejecuta un esfuerzo paralelo en el que aplica un enfoque que no sea TDD, es probable que el tiempo que pase el código existente de romper, detectar, depurar y corregir lo ponga en la red negativa. Para mí, TDD es lo más rápido que puedo ir sin comprometer mi confianza en el código. Si encuentra cosas en su método que no están agregando valor, elimínelas.
  • Número de pruebas: si codifica N cosas, necesita probar N cosas. parafraseando una de las líneas de Kent Beck " Pruebe solo si desea que funcione " .
  • Quedarse atascado durante horas: yo también (a veces y no> 20 minutos antes de detener la línea). Es solo su código que le dice que el diseño necesita algo de trabajo. Una prueba es solo otro cliente para su clase SUT. Si a una prueba le resulta difícil usar su tipo, es probable que también lo hagan sus clientes de producción.
  • Tedio de pruebas similares: Esto necesita un poco más de contexto para que redacte un contraargumento. Dicho esto, detente y piensa en la similitud. ¿Puedes manejar esas pruebas de alguna manera? ¿Es posible escribir pruebas contra un tipo base? Entonces solo necesita ejecutar el mismo conjunto de pruebas para cada derivación. Escucha tus pruebas. Sea el tipo correcto de perezoso y vea si puede encontrar una manera de evitar el tedio.
  • Dejar de pensar en lo que debe hacer a continuación (la prueba / especificación) no es algo malo. Por el contrario, se recomienda para que construyas "lo correcto". Por lo general, si no puedo pensar en cómo probarlo, tampoco puedo pensar en la implementación. Es una buena idea dejar en blanco las ideas de implementación hasta que llegue allí ... tal vez una solución más simple se ve ensombrecida por un diseño preventivo YAGNI-ish.

Y eso me lleva a la consulta final: ¿Cómo puedo mejorar? Mi (o una) respuesta es Leer, reflexionar y practicar.

Por ejemplo, últimamente, mantengo pestañas en

  • si mi ritmo refleja RG [Ref] RG [Ref] RG [Ref] o es RRRRGRRef.
  • % de tiempo pasado en el estado Rojo / Error de compilación.
  • ¿Estoy atrapado en un estado de compilación rojo / roto?
Gishu
fuente
1
Tengo mucha curiosidad sobre su comentario sobre la conducción de datos de las pruebas. ¿Te refieres a un solo conjunto de pruebas que procesan datos externos (como de un archivo) en lugar de volver a probar un código similar? En mi juego tengo varias entidades, y cada una es muy diferente, pero hay algunas cosas comunes que se hacen con ellas (serializarlas a través de la red, asegurarse de que no se envíen a jugadores no existentes, etc.) Hasta ahora no he encontrado una manera de consolidar esto, y también tengo conjuntos de pruebas para cada uno que son casi idénticos, diferentes solo en qué entidad usan y qué datos contienen.
@Nairoi: no estoy seguro de qué corredor de prueba está utilizando. Acabo de aprender un nombre para lo que quería transmitir. Patrón de fijación abstracto [ goo.gl/dWp3k] . Esto aún requiere que escribas tantos Fixtures como tipos SUT concretos. Si quieres ser aún más conciso, mira los documentos de tu corredor. por ejemplo, NUnit admite dispositivos de prueba paramétricos y genéricos (ahora que lo busqué) goo.gl/c1eEQ Parece lo que necesita.
Gishu
Interesante, nunca he oído hablar de accesorios abstractos. Actualmente uso UnitTest ++ que tiene accesorios, pero no abstractos. Sus accesorios son muy literales, solo una forma de consolidar el código de prueba que de lo contrario repetiría en cada prueba, para un grupo dado de pruebas.
@asgeo - no puedo editar ese comentario ... el enlace ha tomado un paréntesis final. Esto debería funcionar - goo.gl/dWp3k
Gishu
+1 para 'quedarse atascado es síntoma del diseño necesita más trabajo', aunque ... ¿qué sucede cuando te quedas atascado (como yo) en el diseño?
Lurscher
32

No necesita una cobertura de prueba del 100%. Se pragmático.

Alistair
fuente
2
Si no tiene una cobertura de prueba del 100%, no tiene una confianza del 100%.
Christopher Mahan
6060
No tiene una confianza del 100% incluso con el 100% de la cobertura de la prueba. Eso es prueba 101. Las pruebas no pueden demostrar que el código no tiene defectos; por el contrario, solo pueden demostrar que contiene defectos.
CesarGon
77
Por lo que vale, uno de los defensores más apasionados de TDD, Bob Martin, no recomienda una cobertura del 100% - blog.objectmentor.com/articles/2009/01/31/… . En la industria manufacturera (concedida, diferente del software en muchos aspectos), nadie apuesta por el 100% de confianza porque puede gastar una fracción del esfuerzo para tener una confianza del 99%.
Chance
Además (al menos la última vez que verifiqué las herramientas que tenemos), los informes de cobertura de código se relacionan con si las líneas se ejecutaron pero no incluyen la cobertura de valor; por ejemplo, hoy tuve un error que informaba que tenía todas las rutas a través del código ejecutadas en las pruebas, pero dado que había una línea como a = x + yy aunque todas las líneas en el código se ejecutaron en las pruebas, las pruebas solo se probaron para el caso donde y = 0, por lo que el error (debería haber sido a = x - y) nunca se encontró en las pruebas.
Pete Kirkham
@Chance - He leído el libro de Robert Martin "Codificador limpio ..." algún nombre largo. Decía en ese libro que debería estar cubierto asintóticamente al 100% con las pruebas, lo que está cerca del 100%. Y el enlace del blog ya no funciona.
Darius.V
22

TDD todavía me está frenando considerablemente

Eso es realmente falso.

Sin TDD, pasa algunas semanas escribiendo código que funciona principalmente y pasa el próximo año "probando" y reparando muchos (pero no todos) los errores.

Con TDD, pasas un año escribiendo código que realmente funciona. Luego, realiza las pruebas de integración final durante algunas semanas.

El tiempo transcurrido probablemente será el mismo. El software TDD tendrá una calidad sustancialmente mejor.

S.Lott
fuente
66
Entonces, ¿por qué necesito TDD? "El tiempo transcurrido es el mismo"
21
@ Peter Long: Código de calidad. El año de "pruebas" es cómo terminamos con un software basura que funciona principalmente.
S.Lott
1
@ Peter, tienes que estar bromeando. La calidad de la solución TDD será muy superior.
Mark Thomas
77
¿Por qué necesito TDD? Kent Beck enumera la tranquilidad como algo importante, y es muy convincente para mí. Vivo con el miedo constante de romper cosas cuando trabajo en código sin pruebas unitarias.
77
@ Peter Long: "El tiempo transcurrido es el mismo" ... y en cualquier momento durante ese tiempo puede entregar el código de trabajo .
Frank Shearar
20

O, después de terminar la prueba número 50 para cubrir una entidad en particular en el juego y todos los aspectos de su creación y uso, miro mi lista de tareas y veo la próxima entidad que se codifica, y me horrorizo ​​ante la idea de escribir otras 50 pruebas similares para implementarlo.

Esto me hace preguntarme cuánto estás siguiendo el paso "Refactor" de TDD.

Cuando pasan todas las pruebas, es hora de refactorizar el código y eliminar la duplicación. Si bien las personas generalmente recuerdan esto, a veces olvidan que también es el momento de refactorizar sus pruebas , para eliminar la duplicación y simplificar las cosas.

Si tiene dos entidades que se fusionan en una para permitir la reutilización del código, considere fusionar sus pruebas también. Realmente solo necesita probar las diferencias incrementales en su código. Si no realiza regularmente mantenimiento en sus pruebas, pueden volverse difíciles de manejar rápidamente.

Un par de puntos filosóficos sobre TDD que podrían ser útiles:

  • Cuando no puede encontrar la manera de escribir una prueba, a pesar de su amplia experiencia en escribir pruebas, definitivamente es un olor a código . De alguna manera, su código carece de modularidad, lo que dificulta la escritura de pruebas pequeñas y simples.
  • Agregar un poco de código es perfectamente aceptable cuando se usa TDD. Escriba el código que desea, para tener una idea de cómo se ve, luego elimine el código y comience con las pruebas.
  • Veo practicar TDD extremadamente estricto como una forma de ejercicio. Cuando comienzas, definitivamente escribe una prueba primero cada vez, y escribe el código más simple para que la prueba pase antes de continuar. Sin embargo, esto no es necesario una vez que te sientas más cómodo con la práctica. No tengo una prueba unitaria para cada ruta de código posible que escribo, pero a través de la experiencia puedo elegir qué se debe probar con una prueba unitaria y qué se puede cubrir con pruebas funcionales o de integración. Si ha estado practicando TDD de manera estricta durante un año, me imagino que también está cerca de este punto.

EDITAR: sobre el tema de la filosofía de las pruebas unitarias, creo que esto podría ser interesante para usted: The Way of Testivus

Y un punto más práctico, si no necesariamente muy útil,:

  • Menciona C ++ como su lenguaje de desarrollo. Practiqué TDD ampliamente en Java, utilizando excelentes bibliotecas como JUnit y Mockito. Sin embargo, he encontrado que TDD en C ++ es muy frustrante debido a la falta de bibliotecas (en particular, marcos de prueba) disponibles. Si bien este punto no le ayuda mucho en su situación actual, espero que lo tenga en cuenta antes de abandonar por completo el TDD.
jaustin
fuente
44
Refactorizar las pruebas es peligroso. Nadie parece hablar de esto, pero lo es. Ciertamente no tengo pruebas unitarias para probar mis pruebas unitarias. Cuando refactoriza para reducir la duplicación, generalmente aumenta la complejidad (porque su código se vuelve más general). Eso significa que es más probable que haya un error en sus pruebas .
Scott Whitlock
2
No estoy de acuerdo con que las pruebas de refactorización sean peligrosas. Solo está refactorizando cuando todo está pasando, por lo que si realiza una refactorización y todo sigue siendo verde, entonces está bien. Si está pensando que necesita escribir exámenes para sus exámenes, creo que es un indicador de que necesita escribir exámenes más simples.
jaustin
1
C ++ es difícil de probar (el lenguaje no hace cosas que faciliten las simulaciones). He notado que las funciones que son "funciones" (solo operan en argumentos, los resultados aparecen como valores / parámetros devueltos) son mucho más fáciles de probar que los "procedimientos" (retorno nulo, sin argumentos). Descubrí que en realidad puede ser más fácil probar un código C modular bien diseñado que un código C ++. No tiene que escribir en C, pero puede seguir el ejemplo modular de C. Suena completamente loco, pero he puesto pruebas unitarias en "C malo" donde todo era global y fue muy fácil: ¡todo el estado siempre está disponible!
anon
2
Creo que esto es muy cierto. Hago mucho RedGreenRedGreenRedGreen (o, más a menudo, RedRedRedGreenGreenGreen), pero rara vez refactorizo. Mis pruebas ciertamente nunca han sido refactorizadas, ya que siempre sentí que perdería aún más tiempo sin codificar. Pero puedo ver cómo podría ser la causa de los problemas que estoy enfrentando ahora. Es hora de que considere seriamente refactorizar y consolidar.
Nairou
1
Marco de simulación de Google C ++ (integrado con google C ++ test fw) - biblioteca de simulación muy, muy potente - flexible, rica en funciones - bastante comparable con cualquier otro marco de simulación existente.
ratkok
9

Pregunta muy interesante

Lo que es importante tener en cuenta es que C ++ no es fácilmente comprobable, y los juegos, en general, también son un muy mal candidato para TDD. No puede probar si OpenGL / DirectX dibuja el triángulo rojo con el controlador X y amarillo con el controlador Y fácilmente. Si el vector normal del mapa de relieve no se voltea después de la transformación del sombreador. Tampoco puede probar problemas de recorte en versiones de controladores con diferentes precisiones, etc. El comportamiento de dibujo indefinido debido a llamadas incorrectas también se puede probar solo con una revisión precisa del código y un SDK a mano. El sonido también es un mal candidato. El subprocesamiento múltiple, que de nuevo es bastante importante para los juegos, es prácticamente inútil para la prueba unitaria. Entonces es duro.

Básicamente, los juegos son una gran cantidad de GUI, sonido e hilos. La GUI, incluso con componentes estándar a los que puede enviar WM_, es difícil de probar por unidad.

Entonces, lo que puede probar es clases de carga de modelos, clases de carga de texturas, bibliotecas de matrices y algo así, que no es mucho código y, muy a menudo, no es muy reutilizable, si es solo su primer proyecto. Además, están empaquetados en formatos propietarios, por lo que no es muy probable que la entrada de terceros pueda diferir mucho, a menos que publique herramientas de modificación, etc.

Por otra parte, no soy gurú o evangelista de TDD, así que tómalo con un grano de sal.

Probablemente escribiría algunas pruebas para los componentes principales del núcleo (por ejemplo, biblioteca matricial, biblioteca de imágenes). Agregue un montón de abort()entradas inesperadas en cada función. Y lo más importante, concéntrese en un código resistente / resistente que no se rompa fácilmente.

Con respecto a los errores uno, el uso inteligente de C ++, RAII y un buen diseño ayuda mucho a evitarlos.

Básicamente, tienes mucho que hacer solo para cubrir los conceptos básicos si quieres lanzar el juego. No estoy seguro si TDD ayudará.

Descifrador
fuente
3
+1 Me gusta mucho el concepto de TDD y lo uso donde puedo, pero usted plantea un punto muy importante sobre el que los defensores de TDD guardan curiosidad. Como usted ha señalado, hay muchos tipos de programación para los cuales escribir pruebas unitarias significativas es extremadamente difícil, si no imposible. Use TDD donde tenga sentido, pero algunos tipos de código están mejor desarrollados y probados de otras maneras.
Mark Heath
@ Mark: sí, a nadie parece importarle las pruebas de integración hoy en día, pensando que debido a que tienen un conjunto de pruebas automatizadas, todo funcionará mágicamente cuando se combinen y prueben con datos reales.
gbjbaanb
De acuerdo con esto. Gracias por una respuesta pragmática que no prescribe dogmáticamente TDD como la respuesta a todo, en lugar de lo que es, que es solo otra herramienta en el kit de herramientas para desarrolladores.
jb
6

Estoy de acuerdo con las otras respuestas, pero también quiero agregar un punto muy importante: ¡Refactorización de costos!

Con pruebas unitarias bien escritas, puede reescribir de forma segura su código. Primero, las pruebas unitarias bien escritas proporcionan una excelente documentación de la intención de su código. Segundo, cualquier efecto secundario desafortunado de su refactorización quedará atrapado en el conjunto de pruebas existente. Por lo tanto, ha garantizado que las suposiciones de su antiguo código también son ciertas para su nuevo código.

Morten
fuente
4

¿Cómo sobreviven otras personas al TDD sin matar toda la productividad y la motivación?

Esto es completamente diferente de mis experiencias. Usted es increíblemente inteligente y escribe código sin errores (por ejemplo, por errores) o no se da cuenta de que su código tiene errores que impiden que su programa funcione, por lo que en realidad no están terminados.

TDD se trata de tener la humildad de saber que usted (¡y yo!) Cometemos errores.

Para mí, el tiempo de escribir pruebas unitarias está más que guardado en un tiempo de depuración reducido para proyectos que se realizan utilizando TDD desde el principio.

Si no comete errores, ¡tal vez TDD no sea tan importante para usted como para mí!

Tom
fuente
Bueno, también tiene errores en su código TDD;)
Codificador
¡Esa verdad! pero tienden a ser un tipo diferente de error, si TDD se realiza correctamente. Supongo que decir que el código tiene que estar 100% libre de errores para terminar no es correcto. Aunque si uno define un error como una desviación del comportamiento definido de la prueba de la unidad, entonces supongo que está libre de errores :)
Tom
3

Solo tengo algunas observaciones:

  1. Parece que estás intentando probar todo . Probablemente no debería, solo los casos de alto riesgo y límite de una pieza de código / método en particular. Estoy bastante seguro de que la regla 80/20 se aplica aquí: gasta el 80% escribiendo pruebas para el último 20% de su código o casos que no están cubiertos.

  2. Priorizar Ingrese al desarrollo ágil de software y haga una lista de lo que realmente realmente necesita hacer para lanzar en un mes. Luego suelte, así como así. Esto te hará pensar en la prioridad de las funciones. Sí, sería genial si tu personaje pudiera hacer un backflip, pero ¿tiene valor comercial ?

TDD es bueno, pero solo si no buscas una cobertura de prueba del 100%, y no si te impide producir valor comercial real (es decir, características, cosas que agregan algo a tu juego).

Cthulhu
fuente
1

Sí, escribir pruebas y código puede llevar más tiempo que escribir código, pero escribir código y pruebas unitarias asociadas (usando TDD) es mucho más predecible que escribir código y luego depurarlo.

La depuración casi se elimina cuando se usa TDD, lo que hace que todo el proceso de desarrollo sea mucho más predecible y al final, posiblemente más rápido.

Refactorización constante: es imposible realizar una refactorización seria sin un conjunto completo de pruebas unitarias. La forma más eficiente de construir esa red de seguridad basada en pruebas unitarias es durante la TDD. El código bien refactorizado mejora significativamente la productividad general del diseñador / equipo que mantiene el código.

Ratkok
fuente
0

Considere reducir el alcance de su juego y obtenerlo donde alguien pueda jugarlo o usted lo libere. Mantener sus estándares de prueba sin tener que esperar demasiado para lanzar su juego podría ser un término medio para mantenerlo motivado. Los comentarios de sus usuarios pueden proporcionar beneficios a largo plazo y sus pruebas le permiten sentirse cómodo con las adiciones y los cambios.

JeffO
fuente