Porqué TDD

En Laracon USA 2019, Uncle Bob preguntó a la audiencia cuantos programadores escribían pruebas unitarias. Solo un 10% levantó la mano, de este pequeño porcentaje no todos los programadores escriben las pruebas antes de escribir el código.

En este post quiero darte varias razones y motivos por los cuales al seguir la metodología TDD te convertirás en un mejor programador, escribirás código con mayor confianza y avanzarás más rápido en tu profesión.

Antes de comenzar quiero dejar claro que este post no tiene como propósito señalar ni juzgar a nadie. Es mejor agregar pruebas luego de escribir el código a no escribir ninguna prueba. Así como es mejor escribir algunas pruebas para tu aplicación a no escribir ninguna.

Primero quiero aclarar que las pruebas que escribimos -o realizamos manualmente- después de escribir el código se conocen como «Pruebas de regresión». Todos realizamos estas pruebas de una forma u otra, así sea recargando el navegador y completando un formulario.

Yo uso «Pruebas de Regresión» para:

  1. Arreglar bugs o issues presentes en código ya escrito o implementado
  2. Entender un código o funcionalidad y verificar los resultados que produce
  3. Cubrir una funcionalidad que no está asegurada por pruebas automatizadas

La ventaja de escribir estas Pruebas de Regresión como código que pueda re-ejecutarse una y otra vez en vez de realizarlas manualmente es que el código puede re-ejecutarse en fracciones de segundo mientras que llenar un formulario o incluso recargar la página toma más tiempo y esfuerzo.

Sin embargo; escribir pruebas después de implementar el código trae algunos problemas:

1. ¿Cómo verificas que la prueba sirve?

Las pruebas automatizadas son, después de todo, código escrito generalmente por el mismo programador; por lo tanto, así como podemos cometer un error escribiendo el código que queremos implementar, podemos cometer un error escribiendo la prueba.

«Entonces mejor no escribimos pruebas ¿Para qué?» se excusan algunos programadores. Disculpen por responder con otra pregunta: ¿Para qué nos ponemos el cinturón de seguridad si esto no previene el 100% de heridas en accidentes de tránsito? ¿Para qué nos lavamos las manos si esto no elimina el 100% de las bacterias? Etc.

Puesto que las pruebas son código, verificamos que funcionan ejecutando dicho código y aquí viene el punto clave: para verificar que una prueba funciona debemos verla pasar y debemos verla fallar.

Si nunca vemos una prueba automatizada fallar no podemos estar 100% seguros de que realmente funciona.

Éste es el principal problema al escribir las pruebas después de que la funcionalidad ya esté lista: la prueba puede pasar al primer intento y podemos caer en la tentación de asumir que está bien y pasar a la siguiente funcionalidad.

Pero quizás la prueba no está bien escrita, quizás aunque comentemos o eliminemos parte o toda la funcionalidad que se supone que debe estar cubierta por dicha prueba, la prueba seguirá pasando. Por ejemplo:

<?php

function test_can_see_the_contact_page()
{
    $this->get('/home')
        ->assertOk()
        ->assertSee('Contacto');
}

¿Puedes ver el problema?

La prueba pasará aunque eliminemos la página de contactos, puesto que en el home hay un enlace que dice «Contacto». Como nunca vimos la prueba fallar no nos dimos cuenta que al copiar y pegar la prueba del home se nos olvidó cambiar la url a /contacto.

Si escribes la prueba luego caes en el riesgo de escribir una Prueba de Fantasía que va a pasar siempre aunque la funcionalidad que debe cubrir exista o no.

2. Trabajarás triple

Cuando se escribe las pruebas desde el principio se trabaja el doble porque se requiere casi la misma cantidad de líneas de código en la prueba que en la implementación. Pero cuando las escribes después o no las escribes es más del doble por el tiempo que inviertes haciendo pruebas manuales. Por eso es que a la larga el desarrollo sin pruebas tarda más.

3. ¿Cómo aseguras que todas las funcionalidades estén cubiertas con pruebas?

Si escribes la prueba primero el proceso es casi siempre el mismo:

  1. Escribes una prueba
  2. La ejecutas y la ves fallar
  3. Arreglas el error señalado escribiendo una parte de la implementación
  4. Ves la prueba pasar o regresas al punto anterior
  5. Refactorizas el código si necesitas hacer mejoras
  6. Ves la prueba pasar y vuelves al punto 5 o vuelves al punto 1

Si escribes la prueba luego caes en la tentación de escribir dos o más funcionalidades antes de comenzar a escribir las pruebas, por lo tanto es más fácil terminar con una o más funcionalidades no cubiertas por pruebas.

Además al romper el sencillo proceso que propone TDD:

4. No sabes cuando la funcionalidad está lista

Una ventaja de TDD es que nos ayuda a enfocarnos en una sola cosa a la vez, ya sea:

  • Definir el resultado que queremos lograr mediante una prueba
  • Escribir el código para hacer que la prueba pase y completar la funcionalidad
  • Refactorizar para mejorar el código (sin agregar nuevas funcionalidades)

Antes de usar TDD solía tratar de escribir toda la base de datos para todo el proyecto, todas las rutas para todo el proyecto, acomodar 5 módulos a la vez, refactorizar un código de un módulo mientras creaba otro, etc. El cliente me pedía ver un demo y tenía que posponer la reunión porque todo el proyecto estaba en un estado inconsistente: no sabía qué funcionaba o qué estaba listo y que no.

No seguir TDD aumenta la carga mental que se necesita para trabajar en un sistema, reduce la confianza en lo que estamos trabajando y no establece una línea concreta que defina cuando nuestro trabajo esté listo y cuando no.

La prueba define esta línea: si la prueba pasa la funcionalidad está lista (siempre que hayas visto a la prueba fallar por las razones correctas, claro está).

Una funcionalidad puede ser:

  • Agregar a un usuario a la base de datos
  • Verificar que el correo del usuario sea válido
  • Verificar que el correo no esté repetido etc.

Cuando trabajamos con TDD escribimos pruebas puntuales para funcionalidades concretas, mientras más concreta y pequeña sea una prueba más confianza obtenemos y más podemos enfocarnos en una tarea puntual a la vez.

Cuando no tenemos pruebas es más fácil tratar de hacer 50 módulos al mismo tiempo y no terminar ninguno – o hacerlos todos mal.

Cómo comenzar a adoptar la metodología de Test Driven Development

Para adoptar TDD comienza pensando en el proceso que quieres llevar a cabo y en el resultado final. Imagina que quieres procesar el pago de una orden, el código luciría así:

<?php

function test_can_charge_an_order()
{
    // Proceso
    $paymentGateway->process($order);

    // Resultado final
    $this->assertSame($expectedAmount, $paymentGateway->totalCharged());
}

Con TDD comienzas en el núcleo o en el centro de lo que quieres lograr y luego te vas a los laterales:

  1. Para procesar una orden necesitas una $order
  2. Para crear una orden necesitas un modelo Order
  3. El modelo Order necesitará una tabla de órdenes en la DB.
  4. Para realizar un pago necesitarás un $paymentGateway
  5. Para crear un Payment Gateway necesitarás un procesador de pagos o quizás comenzar con una interfaz y un procesador de pagos falso.
  6. Para calcular la cantidad esperada necesitas que la orden tenga items y cada ítem tenga un costo fijo.

Si esta prueba te abruma es porque está abarcando demasiado muy rápidamente. Podemos comenzar con algo tan simple como verificar que podemos crear una orden:

<?php

function test_can_create_an_order()
{
    $order = Order::create([
        /**/
    ]);

    $this->assertSame($expectedAmount, $order->amount);
}

Una vez más la orden necesitará de items o renglones para calcular su cantidad, con lo cual quizás quieras crear otra prueba aún más pequeña o -al eliminar momentáneamente la necesidad de crear un gateway de pagos- tal vez ya te sientas en confianza para hacer esta segunda prueba pasar.

Comienza a describir el núcleo de la funcionalidad que quieres lograr, si resulta ser muy complicada, divide el trabajo en pruebas más pequeñas y sencillas.

Empieza a «intentar» seguir la metodología TDD

Lo más importante es empezar, aún si al final terminas comentando la prueba y escribiendo la funcionalidad primero. Intenta escribir la prueba primero aunque falles en el intento, eventualmente comenzarán a fallar menos y a establecer la práctica de TDD.

Comienza con pruebas sencillas, así luego las elimines o cambies completamente, así te parezcan muy sencillas:

<?php

function test_instantiates_a_new_order
{
    $order = new Order;

    $this->assertInstanceOf(Order::class, $order);
}

Conclusión

Probablemente menos de un 10% de desarrolladores de PHP escribe pruebas unitarias (versus un promedio de 30% en JAVA y 50% de Ruby según estimaciones en las charlas de Uncle Bob). Al seguir esta una metodología que cada vez es más requerida en el mundo laboral y quizás un día -espero- llegará a ser la norma de nuestra industria, estarás encaminándote a estar en esa cima del 10% de desarrolladores de PHP que obtiene mejores puestos, salarios y proyectos que el resto.

Comienza hoy

En Styde le he apostando a TDD desde sus inicios en el 2014, a medida que la práctica se populariza más personas se unen a mis cursos de Refactorización, Patrones de Diseño, así como a proyectos como Crea un Panel de Control con Laravel que están construidos desde 0 siguiendo esta práctica. Al aprender con estos cursos podrás ver cómo probar desde la generación de HTML dinámico o agregarle filtros a una imagen JPEG, hasta llenar formularios, paginar o filtrar datos, consultar un API y mucho más. Todo esto te llevará de la mano a familiarizarte con esta práctica hasta que puedas integrarla en tus desarrollos propios con facilidad. Únete hoy a Styde usando nuestro Plan de Black Friday y sigamos aplicando y practicando buenas prácticas juntos.

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.