La Piramide de Testing Moderna¶
Proposito¶
Guia practica sobre como distribuir los tests en un proyecto. Que testear en cada nivel, cuanto testear, y como tomar decisiones sobre donde invertir esfuerzo de testing.
Cuando usarlo¶
- Al definir la estrategia de testing de una nueva feature.
- Cuando un test se siente "raro" y no sabes en que nivel deberia estar.
- Para justificar ante el equipo por que NO escribir ciertos tests.
1. La Piramide¶
/\
/ \
/ E2E \ Pocos (5-10%)
/ tests \ Flujos criticos completos
/----------\
/ \
/ Integration \ Moderados (20-30%)
/ tests \ Componente + dependencias
/--------------------\
/ \
/ Unit tests \ Muchos (60-70%)
/_________________________ \ Logica de negocio pura
2. Unit Tests (60-70% del esfuerzo)¶
Que testear¶
- Reglas de negocio puras (validaciones, calculos, state machines).
- Domain services (AvailabilityCalculator, PenalizationService).
- Value objects (TimeRange, BookingStatus transiciones).
- Funciones utilitarias con logica no trivial.
Que NO testear¶
- Getters y setters triviales.
- Constructores sin logica.
- Codigo que solo delega a otro componente.
- Frameworks (no testees que NestJS rutea bien, eso lo testea NestJS).
Ejemplo concreto (Sistema de Turnos)¶
// SI testear: regla de negocio
describe('PenalizationService', () => {
it('aplica penalizacion si cancela con menos de 24h', () => {
const bookingDate = new Date('2025-03-25T09:00:00');
const cancelDate = new Date('2025-03-24T10:00:00'); // 23h antes
const result = service.shouldApplyPenalty(bookingDate, cancelDate);
expect(result).toBe(true);
});
it('no aplica penalizacion si cancela con mas de 24h', () => {
const bookingDate = new Date('2025-03-26T09:00:00');
const cancelDate = new Date('2025-03-24T08:00:00'); // 49h antes
const result = service.shouldApplyPenalty(bookingDate, cancelDate);
expect(result).toBe(false);
});
});
// NO testear: getter trivial
// ❌ it('returns the patient id', () => { expect(booking.patientId).toBe(...) })
Caracteristicas de un buen unit test¶
- Rapido: < 10ms por test.
- Aislado: No depende de BD, red, filesystem, ni otros tests.
- Determinista: Mismo resultado siempre (no depende de la hora real, usa mocks de reloj).
- Legible: El nombre del test dice que se testea y que se espera.
3. Integration Tests (20-30% del esfuerzo)¶
Que testear¶
- Endpoints de API con BD real (o en memoria).
- Queries complejas (busqueda de disponibilidad con joins).
- Transacciones (creacion de booking con lock).
- Interaccion entre servicios internos.
Que NO testear¶
- Logica que ya esta cubierta por unit tests.
- Cada permutacion de input (eso es para unit tests).
- Servicios externos reales (mockea SendGrid, no lo llames de verdad).
Ejemplo concreto¶
describe('POST /api/v1/bookings', () => {
it('crea booking y retorna 201 con datos correctos', async () => {
// Arrange: seed doctor, availability, patient en BD de test
const doctor = await seedDoctor({ specialty: 'Cardiologia' });
await seedAvailability({ doctorId: doctor.id, day: 'MONDAY', start: '09:00', end: '13:00' });
const token = await loginAsPatient();
// Act
const response = await request(app)
.post('/api/v1/bookings')
.set('Authorization', `Bearer ${token}`)
.send({
doctorId: doctor.id,
clinicId: doctor.clinicId,
specialtyId: doctor.specialtyId,
date: '2025-03-24',
startTime: '09:00'
});
// Assert
expect(response.status).toBe(201);
expect(response.body.data.status).toBe('CONFIRMED');
// Verificar en BD
const booking = await bookingRepo.findOne(response.body.data.id);
expect(booking).toBeDefined();
expect(booking.status).toBe('CONFIRMED');
});
});
Caracteristicas¶
- BD de test: Base de datos real (o testcontainers) que se limpia entre tests.
- Moderadamente rapidos: < 1s por test.
- Cubren el contrato: Validan que el endpoint se comporta como dice el API contract.
4. E2E Tests (5-10% del esfuerzo)¶
Que testear¶
- Los 3-5 flujos mas criticos del negocio (el "money path").
- Flujos que involucran multiples servicios o pasos.
Que NO testear¶
- Variaciones del mismo flujo (eso es para integration).
- UI pixel-perfect (usa visual regression tests separados).
- Todo (los E2E son caros de mantener, se selectivo).
Ejemplo concreto¶
describe('Flujo completo de reserva', () => {
it('paciente se registra, verifica email, busca medico, y reserva turno', async () => {
// 1. Registro
const registerRes = await request(app).post('/api/v1/auth/register').send({...});
expect(registerRes.status).toBe(201);
// 2. Verificacion (simular click en email)
const verifyRes = await request(app).post('/api/v1/auth/verify-email').send({
token: capturedEmailToken
});
expect(verifyRes.status).toBe(200);
// 3. Login
const loginRes = await request(app).post('/api/v1/auth/login').send({...});
const token = loginRes.body.data.accessToken;
// 4. Buscar disponibilidad
const availRes = await request(app)
.get('/api/v1/doctors/123/availability?dateFrom=2025-03-24&dateTo=2025-03-30')
.set('Authorization', `Bearer ${token}`);
expect(availRes.body.data.slots.length).toBeGreaterThan(0);
// 5. Reservar turno
const slot = availRes.body.data.slots[0];
const bookingRes = await request(app)
.post('/api/v1/bookings')
.set('Authorization', `Bearer ${token}`)
.send({ doctorId: '123', date: slot.date, startTime: slot.startTime, ... });
expect(bookingRes.status).toBe(201);
expect(bookingRes.body.data.status).toBe('CONFIRMED');
});
});
Caracteristicas¶
- Lentos: Pueden tomar 5-30s por test.
- Fragiles: Se rompen mas facilmente con cambios de UI o API.
- Valiosos: Dan confianza de que el sistema funciona de punta a punta.
- Pocos: Selecciona solo los flujos criticos.
5. Tests que NO son parte de la piramide (pero son importantes)¶
Contract Tests¶
Verifican que el API contract se cumple. Utiles cuando frontend y backend evolucionan por separado.
Smoke Tests¶
Un subset minimo de E2E que corren en produccion despues de un deploy. "El sistema esta vivo?"
Load / Performance Tests¶
Verifican comportamiento bajo carga. No corren en CI normal, se ejecutan en etapas especificas.
Security Tests (SAST/DAST)¶
Analisis estatico y dinamico de seguridad. Se integran en el pipeline CI.
6. Reglas Practicas¶
- Si el test no falla cuando rompes el codigo, no sirve. Rompe intencionalmente la logica y verifica que el test falla.
- Si el test se rompe con cada refactor, esta testeando implementacion, no comportamiento. Reescribelo.
- Si no sabes que testear, empieza por las reglas de negocio. Son las mas valiosas.
- Un test lento es un test que nadie corre. Mantene los unit tests rapidos.
- Coverage no es calidad. 80% de coverage con tests triviales vale menos que 50% con tests que cubren reglas de negocio.
7. Coverage Guidelines¶
| Zona | Target | Por que |
|---|---|---|
| Domain services / reglas de negocio | > 90% | Es donde estan los bugs mas caros |
| Controllers / endpoints | > 70% | Cubrir happy path + errores principales |
| Config / bootstrap | No testear | Codigo de setup, no logica |
| Utilities genericas | > 80% | Si tienen logica, testear. Si son wrappers, no. |
Checklist de Completitud¶
- Piramide explicada con porcentajes
- Que testear / que no en cada nivel
- Ejemplos concretos del dominio de turnos
- Reglas practicas documentadas
- Coverage guidelines definidas
- Revisada por otro ingeniero
Archivos relacionados¶
- _template.test-plan.md - Plantilla de plan
- test-plan-auth.md - Plan de auth
- bdd-scenarios-booking.md - BDD scenarios
- ../01-specs/_template.spec.md - Donde nacen los tests