Testing
TDD patterns
- TDD - Test Driven Development
- Testing
- patrones y estrategias comunes
- En TDD - Test Driven Development existen patrones recurrentes que ayudan a estructurar mejor las pruebas, mantener consistencia y evitar errores de diseño o mantenimiento. Estos patrones promueven claridad, aislamiento y expresividad en el código de test.
🧩 Test Doubles
- Son objetos que reemplazan dependencias reales para aislar la unidad bajo prueba.
- Tipos comunes:
- Dummy: solo rellena parámetros sin ser usado activamente.
- Stub: devuelve datos predefinidos para controlar el flujo.
- Spy: registra interacciones para verificarlas luego.
- Mock: valida expectativas sobre llamadas y comportamientos.
- Fake: implementa lógica simplificada (p. ej. una base de datos en memoria).
- Ejemplo en el bloque [[#🧪 Ejemplo - Test Doubles]]
🧪 Ejemplo - Test Doubles
const emailService = { send: jest.fn() };
loginUser('user', '1234', emailService);
expect(emailService.send).toHaveBeenCalledWith('user');
`
🧠 Naming Patterns
- Los nombres de tests deben expresar intención, no implementación.
- Formatos recomendados:
should_<comportamiento>_when_<condición>()given_<context>_when_<action>_then_<result>()
- Ejemplo en el bloque [[#🧪 Ejemplo - Naming Patterns]]
🧪 Ejemplo - Naming Patterns
test('should return 401 when user is unauthorized', () => {
expect(login('user', 'badpass')).toBe(401);
});
⚖️ Assertions
- Las aserciones validan el resultado esperado de una prueba.
- Deben ser claras, atómicas y representativas del comportamiento.
- Reglas:
- Una sola intención por test.
- Evitar múltiples asserts no relacionados.
- Ejemplo en [[#🧪 Ejemplo - Assertions]]
🧪 Ejemplo - Assertions
expect(suma(2, 2)).toBe(4);
expect(resultado.error).toBeUndefined();
🔁 Parametrización de Tests
- Permite ejecutar el mismo test con diferentes entradas y resultados esperados.
- Reduce duplicación y mejora la cobertura de casos.
- Ejemplo en [[#🧪 Ejemplo - Parametrización de Tests]]
🧪 Ejemplo - Parametrización de Tests
test.each([
[1, 2, 3],
[5, 5, 10],
[-1, 1, 0]
])('suma(%i, %i) = %i', (a, b, esperado) => {
expect(suma(a, b)).toBe(esperado);
});
⚙️ Fixture Setup
- Los fixtures son datos o estados comunes para múltiples tests.
- Se definen en
beforeEachobeforeAllpara garantizar consistencia. - Ejemplo en [[#🧪 Ejemplo - Fixture Setup]]
🧪 Ejemplo - Fixture Setup
let usuario;
beforeEach(() => {
usuario = { nombre: 'Ana', activo: true };
});
test('usuario activo tiene acceso', () => {
expect(usuario.activo).toBe(true);
});
🧱 Anti-patterns
- Test frágiles: dependen de detalles internos que cambian con facilidad.
- Overtesting: probar detalles irrelevantes o dependencias externas.
- God tests: pruebas demasiado largas o con múltiples objetivos.
- Copy-paste tests: duplicación sin parametrización o abstracción.
- Silent tests: sin asserts claros ni verificación de resultados.
🚀 Incremental Testing
- Aplicar TDD - Test Driven Development de forma incremental garantiza que cada nueva funcionalidad sea pequeña, verificable y segura.
- Mantiene un ritmo de retroalimentación corto, ideal para CI/CD.
✍️ Test Readability
- La legibilidad es clave: los tests deben comunicar intención y propósito.
- Uso recomendado de:
- Bloques
describe()para agrupar contexto. - Comentarios solo cuando añaden valor.
- Separar preparación, acción y verificación visualmente.
- Bloques
- Ejemplo en [[#🧪 Ejemplo - Readability]]
🧪 Ejemplo - Readability
describe('Gestión de cuentas', () => {
test('crea usuario correctamente', () => {
const usuario = crearUsuario('Eva');
expect(usuario.nombre).toBe('Eva');
});
});
⏱️ Fake Time y Mocking Avanzado
- Se usa para simular pasos de tiempo, timers o eventos programados.
- Evita dependencias del reloj real, permitiendo tests reproducibles.
- Ejemplo en [[#🧪 Ejemplo - Fake Time]]
🧪 Ejemplo - Fake Time
jest.useFakeTimers();
const callback = jest.fn();
setTimeout(callback, 1000);
jest.advanceTimersByTime(1000);
expect(callback).toHaveBeenCalled();
🧩 Dependency Injection en Tests
- Permite reemplazar dependencias en tiempo de prueba.
- Favorece un diseño desacoplado y fácilmente testeable.
- Ejemplo en [[#🧪 Ejemplo - Dependency Injection]]
🧪 Ejemplo - Dependency Injection
class ServicioUsuarios {
constructor(db) { this.db = db; }
getAll() { return this.db.query('SELECT * FROM usuarios'); }
}
const fakeDb = { query: jest.fn().mockReturnValue([{ id: 1 }]) };
const servicio = new ServicioUsuarios(fakeDb);
expect(servicio.getAll()).toHaveLength(1);
🔄 Test-Driven Refactoring Patterns
- Refactorizar con soporte de tests garantiza que no se rompa la funcionalidad existente.
- Patrones comunes:
- Extract Function: mover lógica repetida a una función auxiliar.
- Replace Conditional with Polymorphism: mejorar mantenibilidad.
- Introduce Parameter Object: agrupar parámetros repetidos.
🧍 Test Isolation
- Cada test debe ejecutarse de forma independiente y sin efecto colateral.
- Se recomienda limpiar estados compartidos con
afterEacho mocks reseteados. - Ejemplo en [[#🧪 Ejemplo - Test Isolation]]
🧪 Ejemplo - Test Isolation
afterEach(() => {
jest.clearAllMocks();
});
🔁 Continuous Integration
- En entornos modernos, los patrones TDD - Test Driven Development se integran con CI/CD.
- Cada push ejecuta todos los tests, asegurando calidad continua y detección temprana de errores.
🧩 Ejemplo Completo - Patrones Combinados
describe('Gestor de pagos', () => {
let servicio, mockAPI;
beforeEach(() => {
mockAPI = { procesarPago: jest.fn().mockResolvedValue('ok') };
servicio = new GestorPagos(mockAPI);
});
test.each([
[100, 'ok'],
[0, 'error']
])('procesarPago(%i) devuelve %s', async (monto, esperado) => {
const resultado = await servicio.procesarPago(monto);
expect(resultado).toBe(esperado);
});
afterEach(() => {
jest.clearAllMocks();
});
});
¿Te gusta este contenido? Suscríbete vía RSS