🎓 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) :
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
- Commencez par les exercices 1 et 2
- Regardez les vidéos de démonstration (liens)
- Rejoignez le slack communautaire
Intermédiaire
- Implémentez le projet complet du module 5
- Créez vos propres contrats
- Partagez vos résultats
Expert
- Contribuez aux outils open source
- Créez des formations internes
- 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