test-driven-development

Par elophanto · elophanto

Applique le cycle TDD Rouge-Vert-Refactorisation. Écris d'abord un test en échec, implémente le code minimal pour le faire passer, puis refactorise. Empêche d'écrire du code avant que les tests n'existent.

npx skills add https://github.com/elophanto/elophanto --skill test-driven-development

Déclencheurs

  • tdd
  • test driven development
  • test first
  • write tests
  • red green refactor
  • test driven
  • write test first
  • failing test
  • test before code
  • strict testing

Instructions

Le cycle Red-Green-Refactor

Vous DEVEZ suivre ce cycle exact pour chaque fonctionnalité :

Étape 1 : RED — Écrire un test qui échoue

Avant d'écrire DU CODE d'implémentation, écrivez un test qui :

  • Teste le comportement spécifique que vous êtes sur le point d'implémenter
  • Est clair sur ce qu'il attend (valeurs exactes, pas des assertions vagues)
  • ÉCHOUE quand vous l'exécutez (parce que le code n'existe pas encore)
# Exécutez le test et VÉRIFIEZ qu'il échoue
shell_execute: pytest tests/test_feature.py -v
# Attendu : FAILED (red)

Si le test réussit sans implémentation, votre test est mauvais. Supprimez-le et écrivez un meilleur test qui teste vraiment le nouveau comportement.

Étape 2 : GREEN — Écrire le code minimal pour passer

Écrivez le code MINIMUM nécessaire pour que le test passe. Pas plus.

Règles :

  • Écrivez uniquement du code qui fait passer le test qui échoue
  • N'ajoutez pas de fonctionnalités que le test ne couvre pas
  • N'optimisez pas encore
  • Ne gérez pas les cas limites que le test ne teste pas
  • Codez les valeurs en dur si c'est tout ce que le test exige
# Exécutez le test et VÉRIFIEZ qu'il réussit
shell_execute: pytest tests/test_feature.py -v
# Attendu : PASSED (green)

Étape 3 : REFACTOR — Nettoyer pendant le green

Maintenant que le test passe, améliorez le code :

  • Supprimez les doublons
  • Améliorez les noms
  • Extrayez des fonctions/classes
  • Optimisez si nécessaire

Après CHAQUE changement, réexécutez le test :

shell_execute: pytest tests/test_feature.py -v
# Attendu : toujours PASSED (green)

Si le test échoue pendant le refactoring, annulez et essayez un changement plus petit.

Règles critiques

  1. N'ÉCRIVEZ JAMAIS l'implémentation avant le test. Si vous vous surprenez à écrire une fonction avant son test, ARRÊTEZ. Supprimez la fonction. Écrivez d'abord le test.

  2. NE SAUTEZ JAMAIS l'étape RED. Vous devez observer l'échec du test avant d'écrire l'implémentation. Cela vérifie que le test teste vraiment quelque chose.

  3. Un test à la fois. N'écrivez pas 10 tests puis l'implémentation. Écrivez UN test, faites-le passer, puis écrivez le test suivant.

  4. Les tests doivent être spécifiques. assert result is not None n'est pas un test. assert result == {"status": "ok", "count": 3} est un test.

  5. Exécutez les tests après CHAQUE changement. Pas après 5 changements. Après chaque changement unique. La suite de tests est votre filet de sécurité — utilisez-la constamment.

  6. Si vous cassez les tests existants, corrigez-les D'ABORD. Ne continuez pas à ajouter des fonctionnalités avec des tests cassés en arrière-plan.

Organisation du fichier de test

project/
  src/
    feature.py          # Implémentation
  tests/
    test_feature.py     # Tests pour feature.py
    conftest.py         # Fixtures partagées
  • Le fichier de test reflète le fichier source : src/auth.py -> tests/test_auth.py
  • Une fonction de test par comportement, pas par méthode
  • Utilisez des noms explicites : test_login_with_invalid_password_returns_401

Modèles de test courants

Arrange-Act-Assert :

def test_user_creation():
    # Arrange
    data = {"name": "Alice", "email": "alice@example.com"}

    # Act
    user = create_user(data)

    # Assert
    assert user.name == "Alice"
    assert user.email == "alice@example.com"
    assert user.id is not None

Tester les exceptions :

def test_invalid_email_raises():
    with pytest.raises(ValueError, match="invalid email"):
        create_user({"name": "Alice", "email": "not-an-email"})

Tester les effets secondaires :

def test_send_welcome_email(mocker):
    mock_send = mocker.patch("app.email.send")
    create_user({"name": "Alice", "email": "alice@example.com"})
    mock_send.assert_called_once_with("alice@example.com", subject="Welcome!")

Commandes de framework

Langage Exécuter les tests Mode surveillance
Python pytest -v pytest-watch
JavaScript npm test npm test -- --watch
TypeScript npx jest --verbose npx jest --watch
Rust cargo test cargo watch -x test
Go go test ./... gotestsum --watch

Outils à utiliser

  • shell_execute — Exécuter les commandes de test
  • file_write / file_patch — Écrire les fichiers de test et l'implémentation
  • file_read — Lire le code existant pour comprendre ce qu'il faut tester
  • self_read_source — Explorer la structure du projet

Quand NE PAS utiliser TDD

  • Scripts jetables ou explorations ponctuelles
  • Fichiers de configuration
  • Contenu statique (HTML, CSS, markdown)
  • Quand l'utilisateur dit explicitement « skip tests » ou « no tests needed »

Pour tout le reste : Red. Green. Refactor. À chaque fois.

Vérifier

  • La suite de tests a été réellement exécutée et le code de sortie/résultat est capturé dans la transcription, pas seulement rédigée
  • Les nombres de réussite/échec sont rapportés en chiffres (p. ex. « 42 réussis, 0 échoués »), pas « tous les tests réussissent »
  • Les nouveaux tests couvrent au moins un cas négatif/limite en plus du cas nominal ; les cas sont énumérés
  • Le delta de couverture ou les modules affectés sont rapportés quand le projet suit la couverture ; un nombre de base est cité
  • Pour les tests instables ou sensibles au timing, la exécution a été répétée au moins 3 fois et le taux de réussite est rapporté
  • Tous les tests ignorés ou xfail introduits sont énumérés avec une raison et un lien issue/TODO

Skills similaires