Una introducción práctica al desarrollo basado en pruebas

¡El desarrollo basado en pruebas es difícil! Esta es la verdad no contada al respecto.

En estos días, ha leído una tonelada de artículos sobre todas las ventajas de hacer Test Driven Development (TDD). Y probablemente escuche muchas charlas en conferencias de tecnología que le dicen “¡Haga las pruebas!”, Y lo genial que es hacerlas.

¿Y sabes qué? Desafortunadamente, tienen razón (no necesariamente sobre la parte "genial", sino sobre la parte útil). ¡Las pruebas son imprescindibles ! Las ventajas típicas que enumeramos a la hora de hablar de TDD son reales:

  • Escribes un mejor software
  • Tiene protección para no romper el mundo cuando se introducen nuevas funciones
  • Su software está auto documentado
  • Evitas la ingeniería excesiva

Incluso si siempre estuve de acuerdo con estas ventajas, hubo un momento en el que pensé que no necesitaba TDD para escribir software bueno y fácil de mantener. Por supuesto, ahora sé que estaba equivocado, pero ¿por qué se me ocurrió esta idea a pesar de la brillante magia de los profesionales? La razón es solo una: déjame pedirle a Rihanna que lo diga por mí ...

¡El costo!

¡Cuesta mucho! Probablemente alguien esté pensando " pero cuesta aún más si no haces las pruebas ", y esto también es correcto. Pero estos dos costos vienen en momentos diferentes:

  • haces TDD ➡ ahora tienes un costo .
  • No hace TDD ➡ tendrá un costo en el futuro .

Entonces, ¿cómo salimos de este impasse?

La forma más eficaz de hacer algo es hacerlo de la forma más natural posible. La naturaleza de las personas es ser vaga (aquí, los desarrolladores de software son los que mejor se desempeñan) y codiciosos, por lo que debe encontrar la manera de reducir los costos ahora .Es fácil de decir, ¡pero muy difícil de hacer!

Aquí compartiré mi experiencia y lo que me ha funcionado para cambiar la relación costo / beneficio a mi favor.

Pero antes de hacer eso, analicemos algunas dificultades típicas al aplicar TDD.

¿Puedes probar la suma de dos números?

En términos generales, la teoría no es opcional; tienes que dominarlo para dominar la práctica. Sin embargo, intentar aplicar de una vez todos los conocimientos teóricos que ha adquirido anteriormente podría tener el siguiente efecto:

La lección de teoría típica sobre TDD comienza con algo como esto:

Y aqui estas como

Luego viene esto:

  • rojo, verde, ciclo de refactorización
  • pruebas de unidad, aceptación, regresión, integración
  • burlándose, talones, falsificaciones
  • si tiene suerte (¿o quizás mala suerte?), alguien le informará sobre las pruebas de contrato
  • y si tiene mucha suerte (¿o quizás muy mala suerte?), tocará la refactorización de la base de código heredada

Las cosas se ponen difíciles, pero usted es un desarrollador experimentado y todos estos conceptos no son tan difíciles de manejar para usted. Entonces la clase termina; te vas a casa y durante los días siguientes haces algunos katas de código para corregir los conceptos que acabas de aprender. Hasta aquí todo bien.

La lucha es real

Luego viene un proyecto del mundo real, con plazos reales y costos de tiempo reales, pero está motivado para aplicar su nuevo y brillante TDD. Empieza a pensar en la arquitectura de su software y empieza a escribir pruebas para la primera clase y la clase en sí; llamémosla Class1 .

Ahora piensa en el primer usuario de Class1, llamémoslo UsageOfAClass, y nuevamente lo prueba y escribe. Class1 es un colaborador de UsageOfAClass, así que ¿vas a burlarte de él? Ok, burlémoslo. Pero, ¿qué pasa con las interacciones reales de Class1 y UsageOfAClass? ¿Quizás deberías probarlos todos también? Vamos a hacerlo.

En este punto, dentro de ti, comienzas a escuchar una vocecita que dice " Me desarrollaría mucho más rápido si no tuviera que escribir estas pruebas ... ". No escuchas esta voz maligna y pasas directamente a la siguiente prueba.

Class2 va a ser utilizado por UsageOfAClass y persiste dentro de un Db. Entonces, ¿tenemos que probar Class2, su interacción con UsageOfAClass y la persistencia en la Db? Pero espera ... ¿alguien mencionó cómo hacer frente a las pruebas de E / S durante la clase de teoría TDD?

La teoría detrás de TDD no es tan difícil de entender, pero aplicarla al mundo real puede ser realmente complejo si no se aborda de la manera correcta.

Simplemente hazlo

Siempre debemos tener presente que la teoría debe inclinarse hacia nuestras necesidades y no al contrario.

El objetivo principal es hacer el trabajo. Así que mi consejo es: ¡ hazlo !

Empiece simple y simplemente haga su tarea hasta el final. Luego, cuando te quedas atascado en algún bucle mental teórico como:

  • ¿Es esto una unidad o una prueba de integración?
  • aquí debería burlarme o no?
  • oh mierda, aquí debería escribir un nuevo colaborador, así que un nuevo conjunto de pruebas unitarias infinitas solo para escribir "hey, banana" ...

olvídate de la teoría por un tiempo y da un paso adelante. ¡Hazlo como viene!

Una vez que haya terminado con su tarea, eche un vistazo a su trabajo. Mirando hacia atrás en el trabajo terminado, será mucho más fácil analizar qué habría sido lo correcto.

TDD práctico

Simplemente hazlo. Por cierto, creo que este también es el enfoque correcto para TDD.

¿Qué estaba mal en la forma en que construimos Class1, Class2 y UsageOfAClass? El enfoque.

Este es un enfoque de abajo hacia arriba:

  • analizar el problema
  • descubrir una arquitectura
  • comience a construirlo a partir de componentes de la unidad

Este enfoque es el mejor amigo de la ingeniería excesiva . Por lo general, crea el sistema para evitar cambios que cree que vendrán en el futuro, sin saber si realmente lo harán. Luego, cuando algún requisito cambia, generalmente sucede de una manera que no se ajusta a su estructura, sin importar qué tan buena sea.

Para mí, la clave para reducir drásticamente el costo inmediato de escribir con TDD ha sido adoptar un enfoque de arriba hacia abajo:

  1. traer una historia de usuario
  2. escribir una prueba muy simple de un caso de uso
  3. hazlo correr
  4. Vuelva al paso 2 hasta completar todos los casos de uso.

Mientras realiza este proceso, no se preocupe demasiado por la arquitectura, el código limpio (bueno, recuerde al menos usar nombres de variables decentes) o cualquier tipo de complicación que no sea necesaria actualmente. Simplemente haga lo que sabe que necesita ahora , hasta el final.

Las pruebas de la historia establecen claramente cuáles son los requisitos actuales y conocidos.

Una vez que haya terminado, eche un vistazo a su gran bola de código de barro de espagueti, supere la vergüenza y observe más profundamente lo que ha hecho:

  • ¡funciona! Y las pruebas lo demuestran.
  • Todo el sistema está ahí y lo que realmente se necesita para hacer el trabajo .

Ahora tiene una descripción general de todas las partes de su sistema, por lo que puede refactorizar con el conocimiento del dominio que no podría haber tenido cuando comenzó desde cero. Y las pruebas se asegurarán de que nada se rompa durante la refactorización.

Refactorización

La mejor manera para mí de comenzar a refactorizar es identificar áreas de responsabilidad y separarlas en métodos privados. Este paso ayuda a identificar responsabilidades y sus entradas y salidas.

Después de eso, las clases de colaboradores casi están ahí y solo necesita moverlas a diferentes archivos.

A medida que avanza, primero escriba pruebas para las clases que surgen del proceso y repita hasta que esté satisfecho con el resultado. Y recuerda, si te quedas atascado en algún lugar, ¡hazlo! Si hace algo malo, una vez que haya terminado, tendrá más información sobre cómo superar el error la próxima vez que lo enfrente. Hacer el trabajo es la prioridad , en la mejor de sus capacidades actuales.

De esta forma, si analiza sus errores para aprender de ellos, también perfeccionará sus habilidades.

La próxima historia de usuario

Continúe desarrollando su producto siguiendo estos pasos:

  • tomar una historia
  • hacer que funcione completamente en un ciclo de "código de prueba".
  • refactorizar

Mientras agrega funciones, continuará cambiando su software y tal vez incluso su estructura. Pero a medida que el sistema crezca, el costo del cambio mantendrá un crecimiento lineal gracias a las dos características principales de TDD:

  • descubrimiento de arquitectura (que ayuda a controlar la complejidad)
  • protección contra cambios importantes

El sistema no se diseñará en exceso, ya que la arquitectura surgirá a medida que se completen las historias. No piensa en lo que podrían ser requisitos futuros; si termina necesitándolo, el costo de implementación será bajo.

¿Qué puede hacer que salga mal?

El tamaño de la historia. Lo que construyes hasta el final debe ser del tamaño correcto. No demasiado grande (de lo contrario, tomará demasiado tiempo obtener comentarios) o demasiado pequeño (de lo contrario, no tendrá la descripción general).

¿Y si la historia es demasiado grande? Divídalo en pedazos que se puedan construir desde el principio hasta el final.

¿Que sigue?

En el próximo artículo daré un ejemplo práctico de los conceptos que expliqué aquí. Implementaremos, paso a paso, el kata del Juego de Bolos a partir de una prueba de aceptación.

No es un problema del mundo real, pero tiene la complejidad suficiente para ver cómo TDD puede ayudar a manejarlo.

Comparta su opinión y sugerencias sobre este artículo. ¿Estás de acuerdo conmigo o crees que todo esto es un montón de basura? Déjame saber lo que piensas en los comentarios; Sería muy bueno iniciar una conversación sobre TDD y compartir nuestras experiencias.

Quiero agradecer a Matteo Baglini por ayudarme a encontrar mi camino a través de un enfoque práctico para el desarrollo de software y TDD.

¡Gracias por leer!

Imagen de portada cortesía de testsigma.