¿Cuáles son las desventajas de la programación de prueba primero?

47

Está de moda hoy en día. "Todos" lo recomiendan. Eso en sí mismo me hace sospechar.

¿Cuáles son algunas desventajas que ha encontrado al realizar el desarrollo de prueba primero (basado en pruebas)? Estoy buscando experiencias personales de practicantes expertos: puedo leer las reflexiones hipotéticas de un centenar de aspirantes en otras partes de Internet.

No pregunto porque estoy buscando odiar a TDD, sino porque es mi trabajo mejorar el proceso de desarrollo de software, y cuanto más podamos aprender sobre los problemas que enfrentan las personas, más posibilidades tenemos de mejorar el proceso.

Alex Feinman
fuente

Respuestas:

41

Hay bastantes, pero las ventajas superan con creces las desventajas.

Hay una curva de aprendizaje empinada.

Muchos desarrolladores parecen esperar que puedan ser eficientes con la programación de prueba primero desde el primer día. Desafortunadamente, toma mucho tiempo ganar experiencia y programar a la misma velocidad que antes. No puedes evitarlo.

Para ser más específicos, es muy fácil equivocarse. Muy fácilmente (con muy buenas intenciones) puede terminar escribiendo un montón de pruebas que son difíciles de mantener o probar cosas incorrectas. Es difícil dar ejemplos aquí: este tipo de problemas simplemente requieren experiencia para resolverlos. Debe tener una buena sensación de separar las preocupaciones y diseñar para la comprobabilidad. Mi mejor consejo aquí sería hacer programación de pares con alguien que conozca TDD realmente bien.

Haces más codificación por adelantado.

Test-first significa que no puedes saltarte las pruebas (lo cual es bueno) y significa que terminarás escribiendo más código por adelantado. Esto significa más tiempo. De nuevo, no puedes evitarlo. Te recompensan con un código que es más fácil de mantener, ampliar y, en general, menos errores, pero lleva tiempo.

Puede ser una venta difícil para los gerentes.

Los administradores de software generalmente solo se preocupan por los plazos. Si cambias a la programación de prueba primero y de repente te llevas 2 semanas para completar una función en lugar de una, no les va a gustar. Definitivamente esta es una batalla que vale la pena pelear y muchos gerentes están lo suficientemente informados como para obtenerla, pero puede ser una venta difícil.

Puede ser una venta difícil para otros desarrolladores.

Dado que hay una curva de aprendizaje empinada, no a todos los desarrolladores les gusta la programación de prueba primero. De hecho, supongo que a la mayoría de los desarrolladores no les gusta al principio. Puede hacer cosas como la programación de pares para ayudarlos a ponerse al día, pero puede ser una venta difícil.

Al final, las ventajas son mayores que las desventajas, pero no ayuda si ignora las desventajas. Saber de qué se trata desde el principio le ayuda a negociar algunas, si no todas, las desventajas.

Jaco Pretorius
fuente
Estas son buenas respuestas, pero ¿podrían ser más específicas sobre el n. ° 1? Estoy especialmente interesado en saber cómo / si pudo recuperar su velocidad de programación: ¿qué aprendió que no sabía cuando comenzó a hacer TDD?
Alex Feinman
Actualizado para dar algunas aclaraciones
Jaco Pretorius
77
Si está haciendo pruebas ahora, el tiempo total dedicado al desarrollo no debería cambiar significativamente. Parece que las cosas están tardando más porque estás exponiendo el tiempo que lleva escribir y mantener pruebas unitarias.
ChrisF
1
@JeffO, ¿estás familiarizado con el "Voy a escribirme una minivan!" escuela de codificacion?
Alex Feinman
1
@tvanfosson, porque están tratando de cambiar dos cosas a la vez, tanto comenzando las pruebas como también TDD, lo que puede ser problemático. También se suma a las estimaciones de tiempo, bastante correctamente, de modo que los gerentes y los clientes solo ven el aumento inicial, no es que el tiempo general se conozca realmente (por una vez) e incluso puede ser menor. Si están haciendo algunas pruebas, este aumento será menor.
ChrisF
35

Test-first asume que está escribiendo código que es:

  • comprobable de manera unitaria
  • que lo que está desarrollando tiene un enfoque obvio y no requerirá una amplia creación de prototipos o experimentación
  • que no necesitará refactorizar demasiado o que tiene tiempo para reescribir repetidamente cientos o miles de casos de prueba
  • nada está sellado
  • todo es modular
  • todo es inyectable o burlable
  • que su organización asigna un valor lo suficientemente alto a los defectos bajos para justificar el sumidero de recursos
  • que hay algo de beneficio para probar a nivel de prueba unitaria

Si su proyecto no cumple con esos requisitos, tendrá dificultades. Los promotores de TDD no tienen buenas respuestas a esta otra para sugerirle que rediseñe su producto para encajar mejor dentro de esas líneas. Hay situaciones en las que eso es imposible o indeseable.

En la práctica, también puedo tener un gran problema con las personas que piensan que las primeras pruebas prueban realmente algo sobre la función correcta del programa. En muchos casos esto no es cierto, pero incluso en los casos en que es cierto, está lejos de ser una imagen completa de la corrección. Las personas ven cientos de pruebas aprobadas y suponen que es seguro realizar menos pruebas, ya que antes de la TDD solo hacían unos cientos de casos de prueba de todos modos. En mi experiencia, TDD significa que necesita tener aún más pruebas de integración, ya que los desarrolladores también tendrán la falsa seguridad y el dolor de cambiar todas las pruebas para hacer un gran redactor puede llevar a los desarrolladores a hacer soluciones interesantes.

Ejemplos:

Mi mejor ejemplo personal es cuando escribo código de seguridad para asp.net. Si están destinados a ejecutarse en un entorno hostil desde la configuración de la máquina, están protegidos, firmados y sellados, y debido a que se ejecutan contra objetos de Dios IIS, es muy difícil burlarse de ellos correctamente. Agregue algunas restricciones para el rendimiento y el uso de la memoria y rápidamente perderá la flexibilidad para usar objetos de marcador de posición en las áreas restantes.

Cualquier tipo de microcontrolador u otro código de entorno de bajo recurso puede no ser posible para hacer un verdadero diseño de estilo OO ya que las abstracciones no se optimizan y tiene límites de recursos bajos. Lo mismo puede decirse de las rutinas de alto rendimiento en muchos casos también.

Cuenta
fuente
¿Puedes dar algunos contraejemplos? ¿Cuándo no estaría escribiendo algo que sea comprobable de manera unitaria? ¿Por qué no escribiría código que se pueda burlar o inyectar (aparte del código heredado, que es un tema en sí mismo)?
Alex Feinman
editado para agregar la sección de ejemplos
Bill
44
Convenido. El trabajo de TDD parece depender de un conjunto de suposiciones sobre las máquinas con las que está trabajando; no parece ser cierto para aproximadamente el 50% de mis proyectos.
Paul Nathan
Estoy totalmente de acuerdo ... Gran respuesta
Khelben
2
Es como cualquier cosa en este juego: apropiado para muchas situaciones, inapropiado para otros. Tenga cuidado con cualquiera que defienda un One True Path en cualquier área del desarrollo de software.
Alan B
25

El mayor inconveniente que he visto no es con el TDD en sí, sino con los profesionales. Adoptan un enfoque dogmático y fanático donde todo debe ser probado . A veces (muchas veces eso es), eso no es necesario. Además, podría no ser práctico (es decir, presentar una organización a TDD).

Un buen ingeniero encuentra compensaciones y aplica el equilibrio correcto de cuándo / dónde / cómo aplicar primero la prueba. Además, si se encuentra constantemente pasando mucho más tiempo desarrollando pruebas en lugar del código real (por un factor de 2-3 o más), está en problemas.

En otras palabras, sea pragmático y razonable con TDD (o cualquier cosa en el desarrollo de software para el caso).

luis.espinal
fuente
¿Es de ahí, quizás, de donde proviene la "nueva" definición del Código Heredado de Michael Feathers (es decir, "Código sin pruebas")?
Phill W.
Esa definición no funcionaría para mí :) Para mí, cualquier código que se ejecute en producción y que esté sujeto a cambios es código heredado, independientemente del código o la calidad de la prueba. Por lo general, asociamos "código heredado" con "código incorrecto" o "código obsoleto" cuando en realidad el código incorrecto y el código obsoleto ya están presentes en el código en desarrollo que aún no ha visto el uso de producción. Nuestro objetivo debe ser que nuestro código sea heredado desde el principio, y que sea de tal calidad y utilidad que permanezca en uso durante años, décadas por venir.
luis.espinal
6

Comencé a hacer TDD a principios de agosto de 2009 y convencí a toda mi compañía para que lo cambiara en septiembre / octubre de 2009. Actualmente, todo el equipo de desarrollo está completamente convertido, y comprometer el código no probado al repositorio se considera una mala cosa. Nos ha estado funcionando muy bien, y no puedo imaginar volver a la codificación de vaqueros.

Sin embargo, hay dos problemas que son bastante notables.

El conjunto de pruebas debe mantenerse

Cuando te tomas en serio la TDD, terminarás escribiendo muchas pruebas. Además, lleva tiempo y experiencia darse cuenta de cuál es la granularidad correcta de las pruebas (exagerar es casi tan malo como exagerar). Estas pruebas también son de código y son susceptibles a bitrot. Esto significa que debe mantenerlos como todo lo demás: actualícelo cuando actualice las bibliotecas de las que dependen, refactorice de vez en cuando ... Cuando realice grandes cambios en su código, muchas pruebas quedarán desactualizadas o incluso completamente equivocado. Si tiene suerte, simplemente puede eliminarlos, pero muchas veces terminará extrayendo los bits útiles y adaptándolos a la nueva arquitectura.

Pruebas de fuga de abstracciones de vez en cuando

Estamos utilizando Django, que tiene un marco de prueba bastante bueno. Sin embargo, a veces hace suposiciones que están ligeramente en desacuerdo con la realidad. Por ejemplo, algunos middleware pueden romper las pruebas. O, algunas pruebas hacen suposiciones sobre un backend de almacenamiento en caché. Además, si está utilizando una base de datos "real" (no SQLite3), la preparación de la base de datos para las pruebas llevará mucho tiempo. Claro, puede (y debe) usar SQLite3 y una base de datos en memoria para las pruebas que realiza localmente, pero algunos códigos se comportarán de manera diferente dependiendo de la base de datos que use. Es imprescindible configurar un servidor de integración continua que se ejecute en una configuración realista.

(Algunas personas le dirán que debe burlarse de todas las cosas, como la base de datos, o sus pruebas no son "puras", pero eso es solo ideología hablando. Si comete errores en su código de burla (y créame, lo hará), su traje de prueba será inútil.)

Dicho todo esto, los problemas que describí comienzan a notarse solo cuando está bastante avanzado con TDD ... Cuando recién está comenzando con TDD (o trabajando en proyectos más pequeños), la refactorización de pruebas no será un problema.

Ryszard Szopa
fuente
3
+1. "debe mantenerse": esto es un problema mucho menor cuando se prueba código reutilizable, ya que su interfaz y comportamiento normalmente necesitan ser estables. Por esta razón, normalmente solo hago TDD para nuestra biblioteca reutilizable.
Dimitri C.
4

Para mí, hay algún problema psicológico profundo con las pruebas cada vez que intento aplicarlas ampliamente, como en TDD: si están allí, codifico descuidadamente porque confío en que las pruebas detectarán cualquier problema. Pero si no hay pruebas para proporcionar una red de seguridad, codifico cuidadosamente, y el resultado es invariablemente mejor que con las pruebas.

Tal vez solo soy yo. Pero también he leído en alguna parte que los automóviles con todo tipo de campanas y silbatos de seguridad tienden a chocar más (porque los conductores saben que las características de seguridad están ahí), por lo que tal vez sea algo para reconocer; TDD puede ser incompatible con algunas personas.

Joonas Pulakka
fuente
Eso me parece extraño, ya que escribir código comprobable generalmente me hace disminuir la velocidad y pensar más sobre lo que estoy codificando. De hecho, me pongo un poco nervioso la codificación sin pruebas en estos días.
Matt H
1
Simplemente muestra que diferentes personas realmente reaccionan de manera diferente. No estoy criticando a TDD, obviamente algunas personas lo encuentran útil, pero el hecho es que no es para todos.
Joonas Pulakka
2
Estoy de acuerdo al 100% Escribo código mejor y más rápido sin pruebas automatizadas. Por supuesto, sería absurdo no probarlo, solo creo que la automatización es una mala elección (al menos para mí). Considero que las pruebas manuales son más rápidas que mantener un conjunto de pruebas y más seguras, pero también soy un desarrollador experimentado, así que soy muy bueno para saber qué probar y dónde y por qué, por lo que mis adiciones de código y re-factores son libre de regresión.
Ben Lee
1
Aunque debo señalar que el equipo con el que trabajo y los proyectos son lo suficientemente pequeños como para tener una buena idea de toda la arquitectura: en un equipo grande o en un proyecto muy grande, podría ver que las pruebas automatizadas son más útiles, porque entonces ningún desarrollador individual necesariamente podría oler dónde deberían estar probando para evitar regresiones.
Ben Lee
¿Estás dejando de lado el paso de refactorización?
rjnilsson
2

Una situación en la que la prueba primero realmente se interpone en mi camino es cuando quiero probar rápidamente alguna idea y ver si puede funcionar antes de escribir una implementación adecuada.

Mi enfoque es normalmente:

  1. Implemente algo que se ejecute (prueba de concepto).
  2. Si funciona, consolide agregando pruebas, mejorando el diseño, refactorizando.

A veces no llego al paso 2.

En este caso, usar TDD ha resultado tener más desventajas que ventajas para mí:

  • Escribir pruebas durante la implementación de la prueba de concepto simplemente me ralentiza e interrumpe mi flujo de pensamientos: quiero entender una idea y no quiero perder el tiempo probando detalles de mi primera implementación aproximada.
  • Puede llevar más tiempo averiguar si mi idea vale algo o no.
  • Si resulta que la idea era inútil, tengo que tirar mi código y mis pruebas unitarias bien escritas.

Entonces, cuando tengo que explorar algunas ideas nuevas, no uso TDD y solo presento pruebas unitarias cuando tengo la sensación de que el nuevo código está llegando a alguna parte.

Giorgio
fuente
1
Parece que está confundiendo el código prototipo con el código utilizable. El código prototipo es el código de prueba . No necesita ser probado y no debe crear pruebas que lo ejecuten. El paso que falta es entre 1. y 2 .: usted dice "consolidar escribiendo pruebas". El problema es que no tienes algo para consolidar, sino algo para escribir. Planee reescribir el código prototipo, no planee reutilizarlo. Reusarlo deja mucho lugar para el compromiso. La reescritura formaliza la división entre su fase de exploración y su fase de "código de calidad".
utnapistim
3
@utnapistim: no estoy confundiendo el código de prototipo con el código utilizable, más bien, los fanáticos de TDD los confunden y sugieren que también debe usar TDD para el código de prototipo. O más bien, suponen que no hay ningún código prototipo en absoluto. Además, estoy de acuerdo con usted en que a menudo debe reescribir cuando pasa del prototipo a la implementación real. A veces puede reutilizar partes del código prototipo, pero debe estar listo para volver a escribir. Realmente tienes que decidir de un caso a otro.
Giorgio
3
@utnapistim: Vea también la respuesta de luis.espinal: "El mayor inconveniente que he visto no es con el TDD en sí, sino con los profesionales. Adoptan un enfoque dogmático y fanático donde todo debe ser probado".
Giorgio
1

Desventajas o costos de TDD

Nota: Existe una gama de diferentes tipos de TDD. Independientemente de la unidad, BDD, ATDD u otras variantes, muchas de las dificultades persisten.

Efectos secundarios

Ya sea que se trate de burlas, accesorios o pruebas funcionales, las dependencias en estados o sistemas externos son a menudo la fuente de mayor complejidad en las pruebas, confusión en la forma de prueba y el mayor riesgo de equivocarse. Algunos problemas que he visto:

  • Burlándose: olvídate de afirmar el orden de las llamadas
  • Burlándose: el simulacro no coincide con una llamada o respuesta real
  • Fixture: la prueba se basa en datos poco realistas, enmascarando otros problemas
  • Fixture: prueba un estado imposible en producción
  • Funcional: fallas de compilación falsas debido a que el sistema dependiente no está disponible temporalmente
  • Funcional: la velocidad de la prueba es muy lenta

Tendrá que cambiar su enfoque de codificación, para algunos será un cambio drástico.

Diferentes personas codifican de maneras muy diferentes. En TDD, debe comenzar con una prueba que afirme un comportamiento específico y luego implementarla para que la prueba pase. He visto y era un programador cuya programación no conducía a TDD. Me tomó alrededor de 2 meses cuando comencé a acostumbrarme a cambiar mi enfoque de desarrollo.

Se necesita tiempo para comprender lo que le importa de la prueba y lo que no le importa de la prueba.

Cada equipo debe tomar una decisión explícita sobre dónde quieren trazar la línea en las pruebas. Qué cosas valoran que quieren probar y qué no. A menudo es un proceso doloroso aprender a escribir buenas pruebas, y lo que realmente te interesa de las pruebas. Mientras tanto, el código continuará en un estado de cambio hasta que haya coherencia tanto en estilo como en enfoque.

Prueba de unidad específica: grandes refactores

Un refactor grande o fundamental de una base de código significativa con decenas de miles de pruebas unitarias generará un costo enorme para actualizar todas las pruebas. Esto a menudo se manifestará en un rechazo en contra de hacer una refactorización, incluso si es lo correcto simplemente por el costo asociado con hacerlo.

dietbuddha
fuente
0

Mi analogía es barreras en una pista Scalextric. Si te los pones, te vuelves mucho menos cauteloso.

Las personas también obtienen un poco de cadete de espacio con respecto a sus pruebas, ya que funcionan bien, creen que el código se ha probado completamente, mientras que solo es el comienzo del proceso de prueba.

En mi opinión, TDD es un trampolín para BDD. Una serie de pruebas que se ejecutan realmente no ayuda a los desarrolladores de soporte sin saber lo que hacen las pruebas. Con BDD, el resultado de la prueba está en inglés, que documenta las pruebas y, por lo tanto, crea una comprensión del sistema.

Robbie Dee
fuente
-1

Los beneficios de TDD es que te obliga a proteger tu código de las personas que no lo entienden. Sí, esto a menudo te incluye a ti mismo. Pero, ¿qué sucede cuando no vale la pena guardar el código? ¡Hay un montón de código que ni siquiera debería estar allí en primer lugar! Entonces, el problema con TDD es cuando se trata de desarrolladores que escriben código incorrecto. TDD probablemente no los ayudará a escribir un buen código, es mucho más probable que escriban pruebas horribles también. Por lo tanto, en su caso, TDD solo se sumará al desorden; Las pruebas mal escritas y / o redundantes no son más divertidas que otras formas de código incorrecto.

Johan
fuente
1
Si usted mismo no comprende su propio código, ¿cómo puede un puñado de miles de millones de posibles casos de prueba evitar que el código sea incorrecto?
Michael Shaw
2
¿Porque lo entendiste cuando lo escribiste pero lo olvidaste en el camino?
Johan
+1 TDD no protege contra un desarrollador que ha entendido mal un requisito comercial. Aquí es donde entra BDD ...
Robbie Dee