TDD en Acción con .NET: Ejemplos reales (Parte I)

Continuando el viaje en TDD: Preparación

En el aprendizaje de TDD (Test-Driven Development), es habitual comenzar con ejercicios elementales, como el desarrollo de una calculadora o el cálculo de áreas geométricas. Quiero ser claro en este punto: estos ejercicios no son triviales, sino que son fundamentales para comprender los conceptos clave de TDD y para afianzar una base sólida en la materia. Sin embargo, la simplicidad de estos ejemplos no siempre refleja la complejidad y los matices de los proyectos de software en entornos profesionales. No obstante, antes de adentrarnos en escenarios que desafían nuestra comprensión y aplicación de TDD a un nivel más avanzado, te invito a visitar mi artículo sobre Introducción al TDD: Fundamentos y conceptos, donde se establecen las bases necesarias para aprovechar al máximo las lecciones prácticas que desglosaremos a continuación.

A pesar de la sencillez de los ejercicios iniciales, quiero recalcar su valor intrínseco. Estos no solo sirven como una introducción a TDD, sino que también son una oportunidad para practicar la Práctica deliberada, una técnica que, aplicada correctamente, puede acelerar tu desarrollo profesional en TDD. Para una comprensión más profunda de cómo integrar la práctica deliberada en tu rutina de aprendizaje y elevar tu dominio de TDD, te recomiendo el artículo de Pedro Pardal, Práctica deliberada y TDD. En él encontrarás una guía valiosa para enfocar de manera efectiva el perfeccionamiento de tus habilidades y para comprender cómo los fundamentos que inicialmente parecen básicos son cruciales en tu desarrollo como profesional del software.

El en siguiente artículo vamos a trabajar sobre un requerimiento de negocios más cercano a la realidad, vamos a avanzar aplicando TDD en conjunto con Micro-commits, tal y como sugiero aplicarlo en nuestro día a día. ¡Comencemos!

Ejemplo práctico: Utilizando TDD en un requerimiento de negocios

Cuando trabajamos en el desarrollo de software, a menudo nos enfrentamos a requerimientos que, si bien pueden parecer claros a primera vista, carecen de la profundidad y especificidad necesarias para una implementación directa. Por ejemplo:

Procesamiento de solicitudes de préstamo masivas

Se necesita recibir un lote de solicitudes de préstamo y realizar llamadas a una API de análisis de riesgo por cada solicitud. Según las respuestas, se clasificarán las solicitudes en aprobadas o rechazadas, se enviarán correos electrónicos a cada solicitante con el resultado y se despacharán eventos para los siguientes pasos en el flujo de trabajo del préstamo.

Abordando la ambigüedad con TDD

El aplicar TDD puede ser un salvavidas en estas situaciones. TDD nos insta a comenzar definiendo qué es lo que queremos que nuestro código haga en forma de Unit Tests. Esto requiere analizar y pensar críticamente sobre el requerimiento para identificar y definir esos detalles que no se han especificado.

Proceso TDD sugerido:

  • Análisis y Preguntas: Antes de escribir la primera prueba, enumeramos las preguntas que surgen del requerimiento y buscamos respuestas. Esto puede implicar discusiones con stakeholders o el equipo de negocio.

  • Casos de Prueba Iniciales: Escribimos pruebas para los flujos de trabajo obvios y positivos, identificando los casos de uso más claros del requerimiento.

  • Refinamiento: A medida que avanzamos, identificamos y escribimos pruebas para los casos de uso menos obvios, incluido el manejo de errores y comportamientos inesperados.

  • Especificación: Utilizamos ejemplos concretos para definir los criterios de aceptación en colaboración con los stakeholders.

  • Iteración y Extensión: Con cada nuevo descubrimiento o aclaración, iteramos sobre nuestras pruebas y código para garantizar que seguimos alineados con las expectativas del negocio.

Poniendo en práctica TDD: Aclaremos nuestras dudas.

El aplicar TDD y vernos obligados a definir el comportamiento esperado en nuestros tests, antes de siquiera comenzar a escribir la primera línea de código. Algunas dudas que me surgen ante el requerimiento son:

Al implementar TDD, definimos el comportamiento esperado en nuestros tests antes de ir al código. Este proceso nos lleva a aclarar ciertos aspectos críticos relacionados con la integración de APIs y el manejo de errores. En el contexto de una aplicación de préstamos que interactúa con una API de análisis de riesgos, considero las siguientes preguntas:

  • Manejo de errores:

    • ¿Qué hacer si la API falla o si hay un problema al procesar una solicitud?
  • Formatos de entrada/salida:

    • ¿Qué datos son requeridos en cada solicitud de préstamo?

    • ¿Qué argumentos se deben proporcionar a la API de análisis de riesgos?

    • ¿Qué información se espera en la respuesta de la API?

  • Criterios de aceptación:

    • ¿Cuáles son los criterios exactos para aprobar o rechazar una solicitud basándose en la respuesta de la API?

Reflexionar sobre estas preguntas nos permite escribir pruebas más efectivas y diseñar un sistema más robusto y confiable.

Poniendo en práctica TDD: Preparemos nuestro proyecto

Nota para desarrolladores con experiencia: Esta sección está diseñada para guiar paso a paso en la configuración del proyecto TDD. Si ya estás familiarizado con estos pasos y prefieres avanzar, puedes dirigirte directamente al siguiente artículo. Sin embargo, es importante destacar que el proyecto de ejemplo está configurado con .NET 8, C#, xUnit, y emplea los paquetes NuGet FluentAssertions y NSubstitute, lo cual es relevante para el contenido que sigue.

Para comenzar nuestro proyecto y asegurarnos de avanzar correctamente, te invito a realizar micro commits por cada pequeño paso que demos. Sin más que agregar, ¡comencemos!

En el proyecto de ejemplo, utilizo .NET 8, pero este proceso también es aplicable a .NET 6 o .NET 7. Las características iniciales del proyecto incluyen .NET 8, C#, xUnit, y los paquetes NuGet FluentAssertions y NSubstitute.

Puedes crear el proyecto desde tu IDE favorito o desde la interfaz de línea de comandos. Los siguientes comandos te permitirán configurar el proyecto paso a paso en la terminal. Utiliza los checkboxes para llevar un seguimiento de tu progreso:

  • Crea un proyecto de pruebas unitarias con xUnit:

    dotnet new xunit -f net8.0 -n TDD.Tests -o tests/TDD.Tests

    Este comando establece un nuevo proyecto de pruebas unitarias que usará xUnit, configurado para .NET 8 y lo ubicará en el directorio tests/TDD.Tests

  • Genera un proyecto de consola para tu aplicación:

    dotnet new console -f net8.0 -n TDD -o source/TDD

    Aquí estamos creando un proyecto de consola para .NET 8, que se ubicará en source/TDD, donde desarrollaremos la lógica de la aplicación.

  • Inicia una nueva solución de proyecto para .NET:

    dotnet new sln -n TDD

    Este comando crea un archivo de solución .sln para agrupar y manejar todos los proyectos relacionados con la aplicación.

  • Añade el proyecto de pruebas a la solución:

    dotnet sln add tests/TDD.Tests/TDD.Tests.csproj

    Esto incluye el proyecto de pruebas unitarias en la solución, lo que nos permite gestionar todas las dependencias y compilaciones desde un único punto.

  • Incorpora el proyecto de consola a la solución:

    dotnet sln add source/TDD/TDD.csproj

    Al igual que el proyecto de pruebas, añadimos el proyecto de consola a la solución para facilitar la administración y el desarrollo simultáneos.

Si prefieres ejecutar todo en una sola instrucción para ahorrar tiempo, puedes utilizar la siguiente secuencia de comandos:

dotnet new xunit -f net8.0 -n TDD.Tests -o tests/TDD.Tests && \
dotnet new console -f net8.0 -n TDD -o source/TDD
dotnet new sln -n TDD && \
dotnet sln add ./tests/TDD.Tests/TDD.Tests.csproj && \
dotnet sln add source/TDD/TDD.csproj

Esta secuencia concatenará todos los comandos anteriores en una sola línea, ejecutándolos uno tras otro, siempre y cuando cada comando se ejecute sin errores.

Como mencionamos antes, vamos a mantener la práctica de realizar micro commits. En particular, me gusta mucho utilizar gitmoji para los commits, considero que ofrecen mucha información y adicionalmente se ven muy bonitos 😊. Puedes ver los más populares visitando este enlace: gitmoji.dev

Sigamos adelante añadiendo nuestro archivo .gitignore, utilizando el template estándar de dotnet, y activemos nuestro repositorio GIT. Puedes hacerlo desde la línea de comandos utilizando los siguientes comandos:

  • Agrega el archivo.gitignore con la plantilla de .NET:

    dotnet new gitignore

    Este comando genera un archivo .gitignore óptimo para proyectos .NET, excluyendo archivos innecesarios del control de versiones.

  • Inicializa tu repositorio Git:

    git init

    Con esto creamos un nuevo repositorio Git local en nuestro proyecto.

  • Prepara los archivos para el commit:

    git add .

    Este comando incluye todos los archivos del directorio actual en el área de preparación de Git.

  • Realiza el commit inicial con gitmoji:

    git commit -m "🎉 Inicio del proyecto."

    Aquí efectuamos nuestro primer commit, utilizando el gitmoji 🎉 para simbolizar el comienzo entusiasta de nuestro proyecto.

O si prefieres puedes ejecutar todo en una sola instrucción:

dotnet new gitignore && \
git init && \
git add . && \
git commit -m "🎉 Initial project."

Es hora de agregar algunas herramientas esenciales para el testing. En TDD, las afirmaciones son cruciales y para eso, vamos a utilizar FluentAssertions, una biblioteca que nos permite escribir pruebas de una manera más natural y legible. También integraremos NSubstitute, un framework de mocking que facilita la creación de objetos falsos para nuestras pruebas.

Vamos a añadir estas herramientas a nuestro proyecto de pruebas y a la par seguiremos poniendo en práctica nuestros micro commits con los siguientes comandos:

  • Incluir FluentAssertions en el proyecto de pruebas:

    dotnet add ./tests/TDD.Tests/TDD.Tests.csproj package FluentAssertions -v 6.12.0

    Este comando agrega la versión 6.12.0 de FluentAssertions como dependencia en nuestro proyecto de pruebas, permitiéndonos utilizar sus potentes capacidades de assertions.

  • Ejecutamos un commit con la incorporación de FluentAssertions:

    git commit -m "📦️ Integrate FluentAssertions package into the project for enhanced assertion capabilities."

  • Añadir NSubstitute al proyecto de pruebas:

    dotnet add ./tests/TDD.Tests/TDD.Tests.csproj package NSubstitute -v 5.1.0

    Al ejecutar este comando, incorporamos la versión 5.1.0 de NSubstitute a nuestro proyecto de pruebas, dándonos acceso a herramientas de mocking eficientes y fáciles de usar.

  • Ejecutamos un commit con la incorporación de NSubstitute:

    git commit -m "📦️ Integrate NSubstitute package into the project for mocking dependencies in unit tests."

Para simplificar el proceso y ejecutar todos los comandos consecutivamente, puedes utilizar la siguiente instrucción combinada en tu terminal:

dotnet add ./tests/TDD.Tests/TDD.Tests.csproj package FluentAssertions -v 6.12.0 && \
git commit -am "📦️ Integrate FluentAssertions package into the project for enhanced assertion capabilities." && \
dotnet add ./tests/TDD.Tests/TDD.Tests.csproj package NSubstitute -v 5.1.0 && \
git commit -am "📦️ Integrate NSubstitute package into the project for mocking dependencies in unit tests."

Conclusión

Hemos dedicado tiempo y esfuerzo a preparar nuestro entorno para TDD, una inversión que no debe subestimarse. La configuración cuidadosa es crucial, ya que nos permite implementar TDD de manera efectiva en proyectos de software.

Te invito a continuar la lectura de este artículo en el siguiente enlace TDD en Acción con .NET: Ejemplos reales (Parte II), donde nos centraremos en cómo aplicar TDD. Exploraremos cómo las pruebas guían nuestro diseño y desarrollo, asegurando que nuestro código cumpla con los requisitos y sea fácil de mantener a largo plazo.

Fuentes

  • Kent Beck. (2002). Test Driven Development: By Example. Addison-Wesley Professional. Disponible en Amazon
  • KISS (Keep it Simple, Stupid) - A Design Principle. Interaction Design Foundation. Disponible en Interaction-Design.org
  • Hablemos de TDD, Test-Driven Development, una práctica habitual en equipos de desarrollo ágiles. Disponible en YouTube
  • Exeal (2023, Octubre). Práctica deliberada: una forma alternativa de aprender TDD. Disponible en Exeal.com.
  • Exeal (2023, Diciembre). La base de la integración continua: los micro-commits Disponible en Exeal.com
  • TDD (Test Driven Development). Desarrollo dirigido por pruebas en Software Crafters. Disponible en SoftwareCrafters.com
  • The Wrong Abstraction. Disponible en Sandimetz.com