Una kata diseñada para practicar Test-Driven Development (TDD) construyendo un sistema de ascensores paso a paso.
Test-Driven Development es una técnica de desarrollo donde escribes las pruebas ANTES que el código de producción. El ciclo es:
- 🔴 Red: Escribe un test que falla
- 🟢 Green: Escribe el código mínimo para que pase
- 🔵 Refactor: Mejora el código manteniendo los tests en verde
- Requisitos claros e incrementales: Cada característica del ascensor es pequeña y bien definida
- Feedback inmediato: Sabes cuando algo funciona o no
- Diseño emergente: El código evoluciona naturalmente desde lo simple hacia lo complejo
- Errores de estado: El ascensor tiene muchos estados (piso actual, puertas, dirección) que son perfectos para practicar TDD
- Un ascensor se mueve entre varios pisos
- Tiene un panel de botones que los pasajeros presionan para solicitar pisos (requests)
- Las personas pueden llamar al ascensor desde otros pisos. Una llamada (call) tiene un piso y una dirección deseada
- Tiene puertas que pueden estar abiertas o cerradas
- Cumple un request cuando se mueve al piso solicitado y abre las puertas
- Cumple un call cuando se mueve al piso correcto, está por ir en la dirección solicitada, y abre las puertas
- Solo puede moverse entre pisos si las puertas están cerradas
Cuando tengas un ascensor funcionando, puedes abordar estas características:
- Puede haber más de un ascensor
- Solo un ascensor necesita responder a cada llamada
- En cada piso hay un monitor sobre cada puerta. Mientras el ascensor se mueve, muestra en qué piso está
- Cuando el ascensor se detiene para responder una llamada, el monitor muestra en qué dirección irá
- Al cumplir una llamada, el ascensor relevante hace un 'DING' al abrir las puertas
[Fact]
public void Lift_CanOpenDoors()
{
var lift = new Lift();
lift.OpenDoors();
lift.AreDoorsOpen.Should().BeTrue();
}🔴 Red: Este test fallará porque Lift no existe
🟢 Green: Crea la clase Lift con OpenDoors() y AreDoorsOpen
🔵 Refactor: ¿Hay algo que mejorar? Aún no
- El ascensor puede abrir puertas
- El ascensor puede cerrar puertas
- El ascensor puede subir un piso
- El ascensor puede bajar un piso
- El ascensor no puede moverse con puertas abiertas
- El ascensor puede recibir requests (botones internos)
- El ascensor se mueve al piso solicitado
- El ascensor puede recibir calls (botones externos)
- El ascensor respeta la dirección de las llamadas
- El ascensor maneja múltiples requests en cola
- El sistema tiene límites de pisos
- ... y así sucesivamente
Este proyecto usa emojis para marcar cada fase del ciclo TDD:
-
🔴 Red: Test fallando
🔴 El ascensor no puede moverse con puertas abiertas -
🟢 Green: Test pasando
🟢 El ascensor no puede moverse con puertas abiertas -
🔵 Refactor: Mejora sin cambiar comportamiento
🔵 Extraer lógica de validación de puertas a método privado
- Historial legible: Puedes ver exactamente cómo evolucionó el código
- Seguridad: Si un refactor sale mal, vuelves al último 🟢
- Aprendizaje: Puedes revisar tu proceso y mejorarlo
- Evidencia: Demuestra que seguiste TDD disciplinadamente
[Fact]
public void Lift_CantMoveUpIfTheDoorsAreOpen()
{
var lift = new Lift();
lift.OpenDoors();
var action = () => lift.MoveUp();
action.Should().Throw<InvalidOperationException>();
}Ejecuta el test → Falla porque MoveUp() no valida las puertas.
git add .
git commit -m "🔴 El ascensor no puede moverse con puertas abiertas"public void MoveUp()
{
if (AreDoorsOpen)
throw new InvalidOperationException("Cannot move with doors open");
CurrentFloor++;
}Ejecuta el test → Pasa ✓
git add .
git commit -m "🟢 El ascensor no puede moverse con puertas abiertas"public void MoveUp()
{
ValidateDoorsAreClosed();
CurrentFloor++;
}
public void MoveDown()
{
ValidateDoorsAreClosed();
CurrentFloor--;
}
private void ValidateDoorsAreClosed()
{
if (AreDoorsOpen)
throw new InvalidOperationException("Cannot move with doors open");
}Ejecuta los tests → Todos pasan ✓
git add .
git commit -m "🔵 Extraer validación de puertas a método privado"Lift-Kata/
├── Domain/
│ ├── Lift.cs # Clase principal del ascensor
│ ├── LiftSystem.cs # Sistema que maneja múltiples ascensores
│ └── Extensions/ # Métodos de extensión para mejorar semántica
├── Lift.Tests/
│ ├── LiftTests.cs # Tests unitarios del Lift
│ └── LiftSystemTests.cs # Tests de integración del sistema
└── Cosmos.Katas.sln
- .NET 9: Framework principal
- xUnit: Framework de testing
- AwesomeAssertions: Assertions fluidas (
.Should().BeTrue())
dotnet testPara ver tests específicos:
dotnet test --filter "FullyQualifiedName~LiftUnitTests"El ascensor tiene múltiples estados interdependientes:
- Piso actual
- Estado de las puertas
- Dirección
- Cola de requests/calls
Tip: Empieza con el estado más simple (puertas) antes de abordar la cola de requests.
Muchas reglas se expresan como "no puede hacer X si Y":
var action = () => lift.MoveUp();
action.Should().Throw<InvalidOperationException>();Tip: Valida el mensaje de la excepción para hacer tests más específicos.
- Tests Unitarios (
LiftTests.cs): Prueban comportamientos individuales - Tests de Sistema (
LiftSystemTests.cs): Prueban escenarios completos
Tip: Empieza con tests unitarios. Los tests de sistema vienen después.
❌ Escribir mucho código antes de testear
// NO hagas esto: implementar 5 métodos antes de escribir tests✅ Un test, un cambio mínimo
// SÍ: un test → código mínimo → siguiente test❌ Tests que prueban demasiado
// NO: un test que valida 10 cosas diferentes✅ Tests enfocados
// SÍ: cada test valida una sola regla de negocio- El ascensor tiene puertas que abren/cierran
- El ascensor se mueve un piso arriba/abajo
- No puede moverse con puertas abiertas
- Acepta requests de pisos
- Se mueve al piso solicitado y abre puertas
- Maneja múltiples requests en cola
- Acepta calls con dirección
- Respeta la dirección de las llamadas
- Tiene límites de pisos (no puede ir más allá)
- Optimiza el recorrido (no va y viene innecesariamente)
- Sistema con múltiples ascensores
- Solo un ascensor responde cada llamada
- Monitores que muestran piso actual
- Monitores que muestran dirección
- Hace 'DING' al cumplir una llamada
Después de completar la kata, reflexiona sobre:
- Diseño: ¿Cómo emergió el diseño? ¿Fue diferente de lo que imaginabas al inicio?
- Tests: ¿Cuántos tests tienes? ¿Cuántas líneas de código de producción por test?
- Confianza: ¿Qué tan seguro te sientes haciendo cambios? ¿Por qué?
- Baby Steps: ¿En qué momentos diste pasos demasiado grandes? ¿Qué pasó?
- Refactoring: ¿Cuándo refactorizaste? ¿Por qué en esos momentos?
- Fallos: ¿Qué tests fallaron inesperadamente? ¿Qué aprendiste?
Esta kata está descrita en Kata-Log, aunque ha sido modificada ligeramente para este proyecto.
¡Feliz práctica de TDD! Recuerda: 🔴 → 🟢 → 🔵 → Repetir