Introducción
Probar código que depende del tiempo es notoriamente difícil. Este tutorial te enseñará estrategias efectivas para escribir tests confiables para código relacionado con timestamps.
Lo que Aprenderás
- ✅ Por qué el testing de tiempo es difícil
- ✅ Mocking de tiempo en JavaScript/Jest
- ✅ Freezegun en Python
- ✅ Testing de transiciones DST
- ✅ Testing de múltiples zonas horarias
- ✅ Mejores prácticas
Por qué es Difícil
JAVASCRIPT1// Este test puede fallar aleatoriamente 2test('el evento expira después de 1 hora', () => { 3 const evento = crearEvento(); 4 // ❌ Depende del tiempo actual 5 expect(evento.expirado()).toBe(false); 6});
Problemas:
- El tiempo actual cambia constantemente
- DST puede afectar resultados
- Tests pueden fallar cerca de medianoche
Mocking en JavaScript (Jest)
Fake Timers
JAVASCRIPT1describe('Expiración de eventos', () => { 2 beforeEach(() => { 3 jest.useFakeTimers(); 4 }); 5 6 afterEach(() => { 7 jest.useRealTimers(); 8 }); 9 10 test('el evento expira después de 1 hora', () => { 11 // Fijar tiempo a una fecha específica 12 jest.setSystemTime(new Date('2024-01-15T10:00:00Z')); 13 14 const evento = crearEvento({ duracion: 60 * 60 * 1000 }); 15 expect(evento.expirado()).toBe(false); 16 17 // Avanzar tiempo 30 minutos 18 jest.advanceTimersByTime(30 * 60 * 1000); 19 expect(evento.expirado()).toBe(false); 20 21 // Avanzar 31 minutos más (total 61 min) 22 jest.advanceTimersByTime(31 * 60 * 1000); 23 expect(evento.expirado()).toBe(true); 24 }); 25});
Mock de Date.now()
JAVASCRIPT1test('timestamp de creación', () => { 2 const ahora = 1705312800000; // 2024-01-15T10:00:00Z 3 const espia = jest.spyOn(Date, 'now').mockReturnValue(ahora); 4 5 const registro = crearRegistro(); 6 expect(registro.creadoEn).toBe(ahora); 7 8 espia.mockRestore(); 9});
Freezegun en Python
Básico
PYTHON1from freezegun import freeze_time 2from datetime import datetime 3 4@freeze_time("2024-01-15 10:00:00") 5def test_timestamp_creacion(): 6 registro = crear_registro() 7 8 assert registro.creado_en == datetime(2024, 1, 15, 10, 0, 0) 9 10@freeze_time("2024-01-15 10:00:00") 11def test_expiracion(): 12 evento = crear_evento(duracion_segundos=3600) 13 14 assert not evento.expirado() 15 16 # Mover tiempo hacia adelante 17 with freeze_time("2024-01-15 11:01:00"): 18 assert evento.expirado()
Con Zonas Horarias
PYTHON1from freezegun import freeze_time 2from datetime import datetime 3from zoneinfo import ZoneInfo 4 5@freeze_time("2024-01-15 10:00:00", tz_offset=-5) 6def test_con_zona_horaria(): 7 utc = ZoneInfo('UTC') 8 ny = ZoneInfo('America/New_York') 9 10 ahora_utc = datetime.now(utc) 11 ahora_ny = datetime.now(ny) 12 13 assert ahora_utc.hour == 15 # 10 AM UTC + offset simulado
Testing de DST
JavaScript
JAVASCRIPT1describe('Transiciones DST', () => { 2 test('Spring Forward - hora faltante', () => { 3 // 31 marzo 2024, 2:00 AM no existe en Madrid 4 jest.setSystemTime(new Date('2024-03-31T00:30:00Z')); 5 6 const evento = programarEvento('Europe/Madrid', '02:30'); 7 8 // Debe ajustar automáticamente 9 expect(evento.horaLocal).toBe('03:30'); 10 }); 11 12 test('Fall Back - hora duplicada', () => { 13 // 27 octubre 2024, 2:30 AM ocurre dos veces 14 jest.setSystemTime(new Date('2024-10-27T00:30:00Z')); 15 16 const evento = programarEvento('Europe/Madrid', '02:30'); 17 18 // Debe elegir la segunda ocurrencia (después del cambio) 19 expect(evento.offset).toBe('+01:00'); 20 }); 21});
Python
PYTHON1from freezegun import freeze_time 2from datetime import datetime 3from zoneinfo import ZoneInfo 4 5def test_spring_forward(): 6 """Test hora faltante durante DST""" 7 madrid = ZoneInfo('Europe/Madrid') 8 9 # Justo antes del cambio 10 with freeze_time("2024-03-31 00:59:00"): 11 antes = datetime.now(madrid) 12 13 # Justo después del cambio 14 with freeze_time("2024-03-31 03:01:00"): 15 despues = datetime.now(madrid) 16 17 # Verificar que el offset cambió 18 assert antes.strftime('%z') == '+0100' 19 assert despues.strftime('%z') == '+0200' 20 21def test_fall_back(): 22 """Test hora duplicada durante DST""" 23 madrid = ZoneInfo('Europe/Madrid') 24 25 # Primera ocurrencia de 2:30 (CEST) 26 primera = datetime(2024, 10, 27, 2, 30, fold=0, tzinfo=madrid) 27 28 # Segunda ocurrencia de 2:30 (CET) 29 segunda = datetime(2024, 10, 27, 2, 30, fold=1, tzinfo=madrid) 30 31 # Mismo tiempo local, diferente offset 32 assert primera.strftime('%z') == '+0200' 33 assert segunda.strftime('%z') == '+0100'
Testing de Múltiples Zonas Horarias
PYTHON1import pytest 2from datetime import datetime 3from zoneinfo import ZoneInfo 4 5ZONAS_HORARIAS_TEST = [ 6 'UTC', 7 'America/New_York', 8 'Europe/Madrid', 9 'Asia/Tokyo', 10 'Australia/Sydney', 11] 12 13@pytest.mark.parametrize('zona', ZONAS_HORARIAS_TEST) 14def test_conversion_zona_horaria(zona): 15 """Verifica conversión correcta en todas las zonas""" 16 utc = ZoneInfo('UTC') 17 tz = ZoneInfo(zona) 18 19 # Crear timestamp en UTC 20 dt_utc = datetime(2024, 6, 15, 12, 0, 0, tzinfo=utc) 21 22 # Convertir a zona local 23 dt_local = dt_utc.astimezone(tz) 24 25 # Verificar que representan el mismo instante 26 assert dt_utc.timestamp() == dt_local.timestamp()
Mejores Prácticas
1. Aísla la Obtención de Tiempo
JAVASCRIPT1// ❌ Difícil de probar 2function crearEvento() { 3 return { 4 creadoEn: Date.now(), 5 // ... 6 }; 7} 8 9// ✅ Fácil de probar 10function crearEvento(obtenerTiempo = Date.now) { 11 return { 12 creadoEn: obtenerTiempo(), 13 // ... 14 }; 15}
2. Usa Interfaces de Reloj
PYTHON1# Interfaz de reloj inyectable 2class Reloj: 3 def ahora(self): 4 return datetime.now(timezone.utc) 5 6class RelojFijo: 7 def __init__(self, tiempo_fijo): 8 self._tiempo = tiempo_fijo 9 10 def ahora(self): 11 return self._tiempo 12 13# En tests 14def test_con_reloj_fijo(): 15 reloj = RelojFijo(datetime(2024, 1, 15, 10, 0, tzinfo=timezone.utc)) 16 servicio = MiServicio(reloj=reloj) 17 # ...
3. Prueba Casos Límite
PYTHON1FECHAS_LIMITE_TEST = [ 2 datetime(2024, 1, 1, 0, 0, 0), # Año nuevo 3 datetime(2024, 2, 29, 12, 0, 0), # Año bisiesto 4 datetime(2024, 3, 31, 2, 30, 0), # Spring Forward 5 datetime(2024, 10, 27, 2, 30, 0), # Fall Back 6 datetime(2024, 12, 31, 23, 59, 59), # Fin de año 7]
Resumen
- Usa fake timers en Jest para controlar el tiempo
- Usa freezegun en Python para congelar el tiempo
- Prueba explícitamente transiciones DST
- Prueba con múltiples zonas horarias
- Inyecta la obtención de tiempo para facilitar testing
¡Prueba nuestro Convertidor Unix Timestamp para verificar tus cálculos!