Tutoriels
Stratégies de Test pour les Horodatages : Guide Complet
Introduction
Tester du code dépendant du temps est notoirement difficile. Ce tutoriel vous enseignera des stratégies efficaces pour écrire des tests fiables pour du code lié aux horodatages.
Ce que Vous Apprendrez
- ✅ Pourquoi le test du temps est difficile
- ✅ Mocking du temps en JavaScript/Jest
- ✅ Freezegun en Python
- ✅ Test des transitions DST
- ✅ Test de multiples fuseaux horaires
- ✅ Meilleures pratiques
Pourquoi C'est Difficile
// Ce test peut échouer aléatoirement
test('l\'événement expire après 1 heure', () => {
const evenement = creerEvenement();
// ❌ Dépend du temps actuel
expect(evenement.expire()).toBe(false);
});
Problèmes :
- Le temps actuel change constamment
- Le DST peut affecter les résultats
- Les tests peuvent échouer près de minuit
Mocking en JavaScript (Jest)
Fake Timers
describe('Expiration d\'événements', () => {
beforeEach(() => {
jest.useFakeTimers();
});
afterEach(() => {
jest.useRealTimers();
});
test('l\'événement expire après 1 heure', () => {
// Fixer le temps à une date spécifique
jest.setSystemTime(new Date('2024-01-15T10:00:00Z'));
const evenement = creerEvenement({ duree: 60 * 60 * 1000 });
expect(evenement.expire()).toBe(false);
// Avancer le temps de 30 minutes
jest.advanceTimersByTime(30 * 60 * 1000);
expect(evenement.expire()).toBe(false);
// Avancer de 31 minutes de plus (total 61 min)
jest.advanceTimersByTime(31 * 60 * 1000);
expect(evenement.expire()).toBe(true);
});
});
Mock de Date.now()
test('horodatage de création', () => {
const maintenant = 1705312800000; // 2024-01-15T10:00:00Z
const espion = jest.spyOn(Date, 'now').mockReturnValue(maintenant);
const enregistrement = creerEnregistrement();
expect(enregistrement.creeA).toBe(maintenant);
espion.mockRestore();
});
Freezegun en Python
Basique
from freezegun import freeze_time
from datetime import datetime
@freeze_time("2024-01-15 10:00:00")
def test_horodatage_creation():
enregistrement = creer_enregistrement()
assert enregistrement.cree_a == datetime(2024, 1, 15, 10, 0, 0)
@freeze_time("2024-01-15 10:00:00")
def test_expiration():
evenement = creer_evenement(duree_secondes=3600)
assert not evenement.expire()
# Déplacer le temps en avant
with freeze_time("2024-01-15 11:01:00"):
assert evenement.expire()
Avec Fuseaux Horaires
from freezegun import freeze_time
from datetime import datetime
from zoneinfo import ZoneInfo
@freeze_time("2024-01-15 10:00:00", tz_offset=-5)
def test_avec_fuseau_horaire():
utc = ZoneInfo('UTC')
ny = ZoneInfo('America/New_York')
maintenant_utc = datetime.now(utc)
maintenant_ny = datetime.now(ny)
assert maintenant_utc.hour == 15 # 10 AM UTC + offset simulé
Test du DST
JavaScript
describe('Transitions DST', () => {
test('Spring Forward - heure manquante', () => {
// 31 mars 2024, 2h00 n'existe pas à Paris
jest.setSystemTime(new Date('2024-03-31T00:30:00Z'));
const evenement = planifierEvenement('Europe/Paris', '02:30');
// Doit ajuster automatiquement
expect(evenement.heureLocale).toBe('03:30');
});
test('Fall Back - heure dupliquée', () => {
// 27 octobre 2024, 2h30 se produit deux fois
jest.setSystemTime(new Date('2024-10-27T00:30:00Z'));
const evenement = planifierEvenement('Europe/Paris', '02:30');
// Doit choisir la seconde occurrence (après le changement)
expect(evenement.offset).toBe('+01:00');
});
});
Python
from freezegun import freeze_time
from datetime import datetime
from zoneinfo import ZoneInfo
def test_spring_forward():
"""Test heure manquante pendant DST"""
paris = ZoneInfo('Europe/Paris')
# Juste avant le changement
with freeze_time("2024-03-31 00:59:00"):
avant = datetime.now(paris)
# Juste après le changement
with freeze_time("2024-03-31 03:01:00"):
apres = datetime.now(paris)
# Vérifier que l'offset a changé
assert avant.strftime('%z') == '+0100'
assert apres.strftime('%z') == '+0200'
Test de Multiples Fuseaux Horaires
import pytest
from datetime import datetime
from zoneinfo import ZoneInfo
FUSEAUX_HORAIRES_TEST = [
'UTC',
'America/New_York',
'Europe/Paris',
'Asia/Tokyo',
'Australia/Sydney',
]
@pytest.mark.parametrize('zone', FUSEAUX_HORAIRES_TEST)
def test_conversion_fuseau_horaire(zone):
"""Vérifie la conversion correcte dans tous les fuseaux"""
utc = ZoneInfo('UTC')
tz = ZoneInfo(zone)
# Créer horodatage en UTC
dt_utc = datetime(2024, 6, 15, 12, 0, 0, tzinfo=utc)
# Convertir en zone locale
dt_local = dt_utc.astimezone(tz)
# Vérifier qu'ils représentent le même instant
assert dt_utc.timestamp() == dt_local.timestamp()
Meilleures Pratiques
1. Isoler l'Obtention du Temps
// ❌ Difficile à tester
function creerEvenement() {
return {
creeA: Date.now(),
// ...
};
}
// ✅ Facile à tester
function creerEvenement(obtenirTemps = Date.now) {
return {
creeA: obtenirTemps(),
// ...
};
}
2. Utiliser des Interfaces d'Horloge
# Interface d'horloge injectable
class Horloge:
def maintenant(self):
return datetime.now(timezone.utc)
class HorlogeFixe:
def __init__(self, temps_fixe):
self._temps = temps_fixe
def maintenant(self):
return self._temps
# Dans les tests
def test_avec_horloge_fixe():
horloge = HorlogeFixe(datetime(2024, 1, 15, 10, 0, tzinfo=timezone.utc))
service = MonService(horloge=horloge)
# ...
3. Tester les Cas Limites
DATES_LIMITES_TEST = [
datetime(2024, 1, 1, 0, 0, 0), # Nouvel an
datetime(2024, 2, 29, 12, 0, 0), # Année bissextile
datetime(2024, 3, 31, 2, 30, 0), # Spring Forward
datetime(2024, 10, 27, 2, 30, 0), # Fall Back
datetime(2024, 12, 31, 23, 59, 59), # Fin d'année
]
Résumé
- Utilisez fake timers en Jest pour contrôler le temps
- Utilisez freezegun en Python pour geler le temps
- Testez explicitement les transitions DST
- Testez avec multiples fuseaux horaires
- Injectez l'obtention du temps pour faciliter les tests
Essayez notre Convertisseur Unix Timestamp pour vérifier vos calculs !