Skip to content

🎓 Formation : Du Test Traditionnel au Développement Piloté par Contrat

Introduction : Pourquoi vous êtes ici

Imaginez cette situation : Vendredi 17h, vous déployez une "petite modification" sur votre infrastructure. Lundi matin, la production est down. Le coupable ? Un test qui passait en local mais pas en prod.

Ce guide va vous apprendre à éviter ça définitivement.


📚 Module 1 : Comprendre le problème (30 min)

Exercice 1.1 : Observons un test traditionnel qui ne sert à rien

Créez ce fichier et analysons-le ensemble :

# test_traditionnel.py
def test_nginx_installation():
    """Test traditionnel - Fragile et inutile"""
    # On mock tout
    mock_apt = Mock()
    mock_apt.install.return_value = True

    # On teste... quoi exactement ?
    result = install_nginx(mock_apt)

    # On vérifie qu'on a appelé le mock
    assert mock_apt.install.called_with("nginx")
    assert result == True

❓ Question de réflexion : - Que teste vraiment ce code ? - Que se passe-t-il si nginx change de nom de paquet ? - Ce test vous garantit-il que nginx fonctionnera ?

Réponse : Ce test vérifie juste que vous savez écrire des mocks. Il ne teste PAS que nginx fonctionne.

Exercice 1.2 : Le vrai coût des mauvais tests

Calculons ensemble : - 1000 tests unitaires × 30 secondes de maintenance/semaine = 8h/semaine - Faux positifs : 15/semaine × 20 min de debug = 5h/semaine - Total : 13h/semaine perdues 😱


🔄 Module 2 : La révolution des contrats (45 min)

Exercice 2.1 : Votre premier contrat

Au lieu de tester l'implémentation, définissons un contrat :

# contrat_nginx.yml
service: nginx
contrat:
  entrees:
    - port: nombre entre 1 et 65535
    - domain: string valide (regex: ^[a-z0-9.-]+$)

  comportement_attendu:
    - "doit répondre sur le port configuré"
    - "doit retourner HTTP 200"
    - "doit servir du contenu HTML"

  garanties:
    - "idempotent (relancer ne change rien)"
    - "résistant aux redémarrages"

🎯 Point clé : On ne teste plus COMMENT (implementation) mais QUOI (comportement).

Exercice 2.2 : Transformation pratique

Avant (test traditionnel) :

def test_nginx_config_file_exists():
    assert os.path.exists("/etc/nginx/nginx.conf")

Après (test par contrat) :

def test_nginx_sert_du_contenu():
    response = requests.get("http://localhost")
    assert response.status_code == 200
    assert "html" in response.headers["content-type"]

💡 Voyez la différence ? - Avant : On teste un fichier (et si nginx change d'emplacement ?) - Après : On teste le comportement (peu importe comment nginx est configuré)


🛠️ Module 3 : Mise en pratique simple (1h)

Projet guidé : Un serveur web simple

Nous allons créer ensemble une infrastructure testée, étape par étape.

Étape 1 : Créer l'environnement de test

# Créez un dossier pour apprendre
mkdir apprendre-bdd-infra
cd apprendre-bdd-infra

# Structure minimale
mkdir -p exercices/{terraform,ansible,tests}

Étape 2 : Notre premier contrat Terraform

# exercices/terraform/serveur.tf

# CONTRAT 1 : Les variables doivent être validées
variable "environnement" {
  type = string

  # Voici le contrat !
  validation {
    condition     = contains(["dev", "prod"], var.environnement)
    error_message = "❌ Environnement doit être 'dev' ou 'prod'"
  }
}

# CONTRAT 2 : Le port doit être valide
variable "port" {
  type = number
  default = 80

  validation {
    condition     = var.port > 0 && var.port < 65536
    error_message = "❌ Le port doit être entre 1 et 65535"
  }
}

# Notre ressource simple
resource "null_resource" "serveur" {
  provisioner "local-exec" {
    command = "echo 'Serveur ${var.environnement} sur port ${var.port}'"
  }
}

🧪 Testons notre contrat :

# Test 1 : Avec des valeurs valides
terraform init
terraform plan -var="environnement=dev" -var="port=8080"
# ✅ Ça passe !

# Test 2 : Avec un environnement invalide
terraform plan -var="environnement=test"
# ❌ Erreur : Environnement doit être 'dev' ou 'prod'

# Test 3 : Avec un port invalide  
terraform plan -var="environnement=dev" -var="port=99999"
# ❌ Erreur : Le port doit être entre 1 et 65535

📝 Ce que vous venez d'apprendre : - Les contrats bloquent les erreurs AVANT le déploiement - Plus besoin de tests unitaires pour ça - La validation est dans le code, pas dans les tests

Étape 3 : Test comportemental avec Ansible

# exercices/ansible/installer_nginx.yml
---
- name: Installation de nginx avec contrat
  hosts: localhost
  become: yes

  vars:
    # Nos contrats sous forme de variables
    nginx_port: 8080
    comportement_attendu:
      - "nginx répond sur le port {{ nginx_port }}"
      - "nginx sert une page HTML"

  tasks:
    - name: Installer nginx
      apt:
        name: nginx
        state: present
      when: ansible_os_family == "Debian"

    - name: Configurer nginx
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/sites-available/default
      notify: restart nginx

    # TEST COMPORTEMENTAL INTÉGRÉ
    - name: "CONTRAT : Nginx doit répondre"
      uri:
        url: "http://localhost:{{ nginx_port }}"
        status_code: 200
      retries: 3
      delay: 2
      register: resultat

    - name: "CONTRAT : Vérifier le comportement"
      assert:
        that:
          - resultat.status == 200
          - "'text/html' in resultat.content_type"
        fail_msg: "❌ Nginx ne respecte pas le contrat !"
        success_msg: "✅ Contrat respecté !"

  handlers:
    - name: restart nginx
      service:
        name: nginx
        state: restarted

🎯 Le point magique : Le test fait partie du déploiement !


🚀 Module 4 : Tests avancés mais simples (1h)

Exercice 4.1 : Tests de propriétés (générer 1000 tests en 1)

# exercices/tests/test_proprietes.py
"""
Au lieu d'écrire 50 cas de test, on définit des PROPRIÉTÉS
qui doivent TOUJOURS être vraies
"""

from hypothesis import given, strategies as st
import requests

@given(
    port=st.integers(min_value=1, max_value=65535),
    path=st.text(min_size=1, max_size=100)
)
def test_nginx_repond_toujours(port, path):
    """Propriété : Peu importe le port et le path, nginx doit répondre"""

    # Configurer nginx avec ce port (pseudo-code)
    configure_nginx(port=port)

    # Tester
    response = requests.get(f"http://localhost:{port}/{path}")

    # Propriété qui doit TOUJOURS être vraie
    assert response.status_code in [200, 404]  # Soit OK, soit Not Found
    assert response.headers.get("Server") is not None

💡 Magie : Ce test génère automatiquement des milliers de combinaisons !

Exercice 4.2 : Snapshot testing (capturer le comportement)

# exercices/tests/test_snapshot.py
"""
Au lieu de vérifier chaque détail, on capture le comportement complet
"""

def test_comportement_nginx(snapshot):
    # Déployer nginx
    deploy_nginx()

    # Capturer tout le comportement
    comportement = {
        "ports_ouverts": get_open_ports(),
        "processus": get_nginx_processes(),
        "config_valide": check_nginx_config(),
        "reponse_http": get_http_response(),
        "headers": get_response_headers()
    }

    # Comparer avec le snapshot
    snapshot.assert_match(comportement)

Premier run : Crée le snapshot Runs suivants : Vérifie que rien n'a changé


💪 Module 5 : Projet complet guidé (2h)

Construisons ensemble une vraie infrastructure

# Structure du projet
mon-projet-bdd/
├── terraform/
   ├── main.tf          # Infrastructure
   └── contracts.tf     # Validations
├── ansible/
   ├── playbook.yml     # Configuration
   └── tests.yml        # Tests intégrés
├── tests/
   ├── comportement/    # Tests comportementaux
   └── proprietes/      # Tests de propriétés
└── pipeline.yml         # CI/CD

5.1 : Le contrat Terraform

# terraform/contracts.tf
locals {
  # Définir les contrats comme des règles métier
  contrats = {
    environments_autorises = ["dev", "staging", "prod"]
    ports_autorises        = [80, 443, 8080, 8443]
    regions_autorisees     = ["eu-west-1", "eu-central-1"]

    # Règles métier
    prod_necessite_https   = true
    dev_peut_etre_public   = false
  }
}

# Validation automatique
resource "null_resource" "valider_contrats" {
  # Cette ressource échoue si les contrats ne sont pas respectés
  count = (
    var.environment == "prod" && var.enable_https == false
  ) ? file("ERREUR: Production DOIT avoir HTTPS activé") : 0
}

5.2 : Tests Molecule pour Ansible

# ansible/molecule/default/molecule.yml
---
dependency:
  name: galaxy
driver:
  name: docker
platforms:
  - name: serveur-test
    image: ubuntu:22.04
    pre_build_image: false

# La magie : les tests sont dans le processus
scenario:
  test_sequence:
    - create         # Créer le conteneur
    - converge       # Appliquer Ansible
    - verify         # Vérifier le comportement
    - destroy        # Nettoyer
# ansible/molecule/default/tests/test_comportement.py
def test_nginx_comportement_complet(host):
    """Test : Le serveur web fonctionne comme attendu"""

    # 1. Le service tourne
    assert host.service("nginx").is_running

    # 2. Le port est ouvert
    assert host.socket("tcp://0.0.0.0:80").is_listening

    # 3. Il répond correctement
    cmd = host.run("curl -s http://localhost")
    assert "Welcome" in cmd.stdout

    # 4. Il est configuré correctement
    config = host.run("nginx -t")
    assert config.rc == 0

5.3 : Pipeline CI/CD simple

# .github/workflows/pipeline.yml
name: Tests BDD Infrastructure

on: [push, pull_request]

jobs:
  valider-contrats:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2

      # 1. Valider les contrats Terraform
      - name: Valider Terraform
        run: |
          terraform init
          terraform validate
          terraform plan -var="environment=dev"

      # 2. Tester avec Molecule
      - name: Tester Ansible
        run: |
          pip install molecule molecule-docker
          cd ansible
          molecule test

      # 3. Tests de propriétés
      - name: Tests de propriétés
        run: |
          pip install pytest hypothesis
          pytest tests/proprietes/

📊 Module 6 : Métriques et ROI (30 min)

Mesurons l'impact

Avant (approche traditionnelle)

# Compteur de problèmes sur 1 mois
problemes_avant = {
    "tests_qui_cassent": 47,
    "faux_positifs": 89,
    "bugs_en_prod": 12,
    "heures_debug": 64
}

Après (approche BDD)

# Même période, avec BDD
problemes_apres = {
    "tests_qui_cassent": 3,   # -94%
    "faux_positifs": 2,        # -98%
    "bugs_en_prod": 1,         # -92%
    "heures_debug": 4          # -94%
}

# ROI
temps_gagné_par_mois = 60  # heures
cout_horaire = 75  # euros
economie_mensuelle = 4500  # euros !

🎯 Module 7 : Plan d'action pour votre équipe

Semaine 1 : Comprendre

  • [ ] Faire l'exercice 1.1 en équipe
  • [ ] Identifier vos tests les plus fragiles
  • [ ] Calculer le temps perdu en maintenance

Semaine 2 : Expérimenter

  • [ ] Créer votre premier contrat Terraform
  • [ ] Écrire un test comportemental
  • [ ] Comparer avec vos anciens tests

Semaine 3 : Adopter

  • [ ] Choisir un petit projet pilote
  • [ ] Implémenter les 3 types de tests
  • [ ] Mesurer les résultats

Semaine 4 : Déployer

  • [ ] Former l'équipe
  • [ ] Migrer progressivement
  • [ ] Célébrer les gains !

🤔 FAQ et pièges à éviter

Q : "Mais j'ai déjà 5000 tests unitaires !" R : Ne les supprimez pas tous ! Migrez progressivement, en commençant par les plus fragiles.

Q : "Mon manager veut voir de la couverture de code" R : Montrez-lui la couverture COMPORTEMENTALE. 95% de comportements testés > 100% de lignes couvertes.

Q : "C'est trop différent de nos pratiques" R : Commencez petit. Un seul module, une seule équipe. Les résultats parleront.


📚 Ressources pour aller plus loin

Débutant

  1. Commencez par les exercices 1 et 2
  2. Regardez les vidéos de démonstration (liens)
  3. Rejoignez le slack communautaire

Intermédiaire

  1. Implémentez le projet complet du module 5
  2. Créez vos propres contrats
  3. Partagez vos résultats

Expert

  1. Contribuez aux outils open source
  2. Créez des formations internes
  3. Devenez champion BDD dans votre entreprise

🎉 Conclusion

Vous avez appris : - ❌ Pourquoi les tests traditionnels échouent - ✅ Comment les contrats changent tout - 🚀 Comment implémenter progressivement - 📊 Comment mesurer le succès

Prochaine étape : Prenez UN test fragile dans votre code actuel et transformez-le en test comportemental.

Vous verrez la différence immédiatement !


"Ne testez pas l'implémentation, testez les promesses." - Un DevOps heureux