ejemplos de TDD
- TDD - Test Driven Development
- BDD
- Testing
- ejemplos avanzados
- mocking
- dependency injection
- integración CI/CD
- refactor continuo
- unit y integration tests
- coverage y pipelines
🧩 Ejemplo 1: Unit Test clásico (TypeScript)
Este ejemplo demuestra el flujo básico Red → Green → Refactor, aplicando TDD en una función pura y aislada.
// utils/math.ts
export function multiply(a: number, b: number): number {
return a * b;
}
`
// tests/math.test.ts
import { multiply } from '../utils/math';
describe('multiply', () => {
it('debería multiplicar dos números', () => {
expect(multiply(2, 3)).toBe(6);
});
it('debería manejar multiplicación por cero', () => {
expect(multiply(7, 0)).toBe(0);
});
});
➡️ Primero se define el test (falla).
➡️ Luego se implementa la función mínima.
➡️ Finalmente se refactoriza si es necesario, manteniendo los tests en verde.
🧠 Ejemplo 2: Mocking y Aislamiento (Jest)
TDD promueve pruebas unitarias puras. Si una función depende de una API o servicio externo, se debe mockear esa dependencia.
import axios from 'axios';
import { fetchUser } from '../services/user';
jest.mock('axios');
test('debería retornar datos de usuario simulados', async () => {
axios.get.mockResolvedValue({ data: { id: 1, name: 'Eduardo' } });
const user = await fetchUser(1);
expect(user.name).toBe('Eduardo');
});
✅ El mock elimina la dependencia real del servicio externo.
✅ El test se enfoca en el comportamiento esperado, no en la red.
🧩 Ejemplo 3: Dependency Injection en TDD
El uso de Inyección de Dependencias (DI) facilita aplicar TDD, ya que permite reemplazar fácilmente componentes o servicios en los tests.
// services/emailService.ts
export class EmailService {
send(email: string, message: string) {
console.log(`Email enviado a ${email}: ${message}`);
}
}
// app/userManager.ts
import { EmailService } from './emailService';
export class UserManager {
constructor(private emailService: EmailService) {}
registerUser(email: string) {
this.emailService.send(email, 'Bienvenido!');
return true;
}
}
// tests/userManager.test.ts
import { UserManager } from '../app/userManager';
const mockEmailService = { send: jest.fn() };
test('debería enviar correo al registrar usuario', () => {
const manager = new UserManager(mockEmailService);
const result = manager.registerUser('test@test.com');
expect(result).toBe(true);
expect(mockEmailService.send).toHaveBeenCalledWith('test@test.com', 'Bienvenido!');
});
✅ DI facilita el TDD porque las dependencias son inyectadas y simulables.
⚙️ Ejemplo 4: Integración con CI/CD
El TDD se integra perfectamente en pipelines de Integración Continua.
Cada commit ejecuta los tests automáticamente para validar la estabilidad del sistema.
# Comandos básicos
npm ci
npm run test
npm run coverage
Ejemplo de pipeline simplificado (GitHub Actions adaptado al formato Markdown):
# .github/workflows/test-pipeline
- name: Run tests
run: npm test
- name: Generate coverage
run: npm run coverage
✅ El pipeline ejecuta todas las pruebas en cada push.
✅ Garantiza que ningún cambio rompa la funcionalidad.
🔁 Ejemplo 5: Refactor continuo y mantenimiento
El TDD impulsa una cultura de refactorización constante, donde el código mejora mientras los tests garantizan su estabilidad.
// versión inicial
function isEven(num) {
return num % 2 === 0;
}
// refactorizado sin romper tests
const isEven = (num) => !(num & 1);
test('valida número par', () => {
expect(isEven(4)).toBe(true);
expect(isEven(3)).toBe(false);
});
✅ Los tests sirven de escudo ante errores al refactorizar.
✅ El código se simplifica progresivamente.
🧪 Ejemplo 6: Integration Tests guiados por TDD
El TDD no se limita a funciones puras. También puede aplicarse en pruebas de integración progresiva entre componentes.
import request from 'supertest';
import app from '../app';
test('POST /login retorna token', async () => {
const res = await request(app)
.post('/login')
.send({ email: 'user@test.com', password: '1234' });
expect(res.statusCode).toBe(200);
expect(res.body.token).toBeDefined();
});
➡️ Este tipo de prueba se construye tras definir un caso de uso funcional (p. ej., “un usuario puede autenticarse correctamente”).
➡️ Luego se implementa el endpoint y la lógica mínima para que el test pase.
📈 Ejemplo 7: Coverage y métricas
El TDD se complementa con la medición de cobertura para garantizar que todos los casos importantes están probados.
npx vitest run --coverage
Salida esperada:
Statements : 98%
Branches : 95%
Functions : 100%
Lines : 97%
✅ Cuanto mayor la cobertura, mayor la confianza en el código.
✅ Las métricas ayudan a detectar módulos poco probados.
💡 Ejemplo 8: Flujo TDD aplicado a casos de uso reales
Ejemplo de ciclo completo: desarrollo de un módulo de tareas con TDD.
- Escribir test que falle.
- Implementar lo mínimo.
- Refactorizar.
- Añadir nuevos casos.
// test/todo.test.js
import { TodoList } from '../todo';
test('agregar tarea', () => {
const list = new TodoList();
list.add('Aprender TDD');
expect(list.getAll()).toContain('Aprender TDD');
});
// src/todo.js
export class TodoList {
constructor() {
this.todos = [];
}
add(task) {
this.todos.push(task);
}
getAll() {
return this.todos;
}
}
✅ Test primero, código después.
✅ Refactor continuo y crecimiento incremental.
🧭 Conclusión
El TDD no solo es una técnica de testing, sino un paradigma de diseño.
Cada test escrito se convierte en una pieza viva de documentación, validación y control de calidad.
Combinado con BDD, CI/CD y herramientas de cobertura, forma la base del desarrollo sostenible y confiable en 2025.
---
¿Quieres que te prepare ahora la versión complementaria `# TDD patterns`
con patrones de diseño y estrategias típicas de TDD (test doubles, naming, anti-patterns, asserts, parametrización, etc.)
en el mismo formato limpio y con headings?
¿Te gusta este contenido? Suscríbete vía RSS