Desarrollo impulsado por prueba

El desarrollo basado en pruebas (TDD) es un caso especial de programación basada en pruebas que agrega el elemento de diseño continuo.

La programación de prueba primero implica la producción de pruebas unitarias automatizadas para el código de producción, antes escribes ese código de producción. En lugar de escribir pruebas después (o, más típicamente, nunca escribir esas pruebas), siempre comienza con una prueba unitaria. Para cada pequeña parte de la funcionalidad en el código de producción, primero crea y ejecuta una prueba enfocada pequeña (idealmente muy pequeña) que especifica y valida lo que hará el código. Es posible que esta prueba ni siquiera compile, al principio, porque es posible que no existan todas las clases y métodos que requiere. Sin embargo, funciona como una especie de especificación ejecutable. Luego logra que se compile con un código de producción mínimo, para que pueda ejecutarlo y ver cómo falla. (A veces espera que falle y pasa, lo cual es información útil). Luego, produce exactamente tanto código como permitirá que pase la prueba.

Esta técnica se siente extraña, al principio, para algunos programadores que la prueban. Es un poco como los escaladores de roca trepando poco a poco por una pared de roca, colocando anclas en la pared a medida que avanzan. ¿Porque pasar por todo este problema? ¿Seguramente te ralentiza considerablemente? La respuesta es que solo tiene sentido si termina confiando en gran medida y repetidamente en esas pruebas unitarias más adelante. Aquellos que practican la prueba primero afirman regularmente que esas pruebas unitarias compensan con creces el esfuerzo requerido para escribirlas.

Para el trabajo de prueba primero, normalmente usará uno de los marcos de prueba de unidad automatizados de la familia xUnit (JUnit para Java, NUnit para C#, etc.). Estos marcos hacen que sea bastante sencillo crear, ejecutar, organizar y administrar grandes conjuntos de pruebas unitarias. (En el mundo de Java, al menos, están cada vez más integrados en los mejores IDE). Esto es bueno, porque a medida que trabaja con las pruebas primero, acumula muchas, muchas pruebas unitarias.

Beneficios del trabajo de prueba primero

Conjuntos completos de pruebas de unidades automatizadas sirven como una especie de red para detectar errores. Determinan, de manera precisa y determinista, el comportamiento actual del sistema. Los buenos equipos de prueba primero descubren que obtienen muchos menos defectos a lo largo del ciclo de vida del sistema y dedican mucho menos tiempo a la depuración. Las pruebas unitarias bien escritas también sirven como excelente documentación de diseño que, por definición, siempre está sincronizada con el código de producción. Un beneficio un tanto inesperado: muchos programadores informan que "la pequeña barra verde" que muestra que todas las pruebas se ejecutan sin problemas se vuelve adictiva. Una vez que se acostumbre a estos pequeños y frecuentes comentarios positivos sobre el estado de su código, es muy difícil dejarlo. Finalmente, si el comportamiento de su código está definido con muchas buenas pruebas unitarias, es mucho más safepara ti refactorizar el código. Si una refactorización (o un ajuste de rendimiento o cualquier otro cambio) presenta un error, sus pruebas lo alertan rápidamente.

Desarrollo basado en pruebas: llevándolo más lejos

El desarrollo basado en pruebas (TDD) es un caso especial de programación basada en pruebas que agrega el elemento de diseño continuo. Con TDD, el diseño del sistema no está limitado por un documento de diseño en papel. En su lugar, permite que el proceso de escritura de pruebas y código de producción dirija el diseño a medida que avanza. Cada pocos minutos, refactoriza para simplificar y aclarar. Si puede imaginar fácilmente un método, una clase o un modelo de objeto completo más claro y más limpio, refactorice en esa dirección, protegido todo el tiempo por un conjunto sólido de pruebas unitarias. La presunción detrás de TDD es que realmente no puede saber qué diseño le servirá mejor hasta que tenga los brazos metidos hasta los codos en el código. A medida que aprende sobre lo que realmente funciona y lo que no, se encuentra en la mejor posición posible para aplicar esos conocimientos, mientras aún están frescos en su mente. Y toda esta actividad está protegida por sus suites de pruebas unitarias automatizadas.

Puede comenzar con una buena cantidad de diseño inicial, aunque es más común comenzar con bastante diseño de código sencillo; algunos bocetos UML de pizarra a menudo son suficientes en el mundo de la programación extrema. Pero la cantidad de diseño con la que comienza importa menos, con TDD, que la cantidad que permite que ese diseño se desvíe de su punto de partida a medida que avanza. Es posible que no realice cambios arquitectónicos radicales, pero puede refactorizar el modelo de objetos en gran medida, si eso parece ser lo más inteligente. Algunas tiendas tienen más libertad política para implementar TDD verdadero que otras.

Prueba primero frente a depuración

Es útil comparar el esfuerzo dedicado a escribir pruebas por adelantado con el tiempo dedicado a la depuración. La depuración a menudo implica examinar grandes cantidades de código. El trabajo de prueba primero le permite concentrarse en una parte del tamaño de un bocado, en la que menos cosas pueden salir mal. Es difícil para los gerentes predecir cuánto tiempo tomará realmente la depuración. Y en cierto sentido, se desperdicia mucho esfuerzo de depuración. La depuración implica inversión de tiempo, andamiaje e infraestructura (puntos de interrupción, observación de variables temporales, declaraciones de impresión) que son esencialmente desechables. Una vez que encuentra y corrige el error, todo ese análisis se pierde esencialmente. Y si no se pierde por completo para usted, ciertamente se pierde para otros programadores que mantienen o amplían ese código. Con el trabajo de prueba primero, las pruebas están ahí para que todos las usen, para siempre. Si un error reaparece de alguna manera, la misma prueba que lo detectó una vez puede detectarlo nuevamente. Si aparece un error porque no hay una prueba coincidente, puede escribir una prueba que lo capture a partir de ese momento. De esta manera, muchos practicantes de la prueba primero afirman que es el epítome de trabajar de manera más inteligente en lugar de más duro.

Técnica y herramientas de prueba primero

No siempre es trivial escribir una prueba unitaria para cada aspecto del comportamiento de un sistema. ¿Qué pasa con las GUI? ¿Qué pasa con los EJB y otras criaturas cuyas vidas se gestionan mediante marcos basados ​​en contenedores? ¿Qué pasa con las bases de datos y la persistencia en general? ¿Cómo se prueba que una excepción se lanza correctamente? ¿Cómo se prueban los niveles de rendimiento? ¿Cómo se mide la cobertura, la granularidad y la calidad de las pruebas? Estas preguntas están siendo respondidas por la comunidad que prioriza las pruebas con un conjunto de herramientas y técnicas en constante evolución. Se sigue invirtiendo un tremendo ingenio para hacer posible cubrir todos los aspectos del comportamiento de un sistema con pruebas unitarias. Por ejemplo, a menudo tiene sentido probar un componente de un sistema aislado de sus colaboradores y recursos externos, utilizando objetos falsos y simulados. Sin esas simulaciones o falsificaciones, es posible que las pruebas unitarias no puedan crear una instancia del objeto bajo prueba. O en el caso de recursos externos como conexiones de red, bases de datos o GUI, el uso de algo real en una prueba podría ralentizarla enormemente, mientras que el uso de una versión falsa o simulada mantiene todo funcionando rápidamente en la memoria. Y aunque algunos aspectos de la funcionalidad siempre pueden requerir prueba manual, el porcentaje para el cual esto es indiscutiblemente cierto sigue reduciéndose.