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 !