Les Épreuves Automatiques
Tests automatisés, matrice, cache et artefacts
Tu avances dans les Forges Automatiques. Après avoir compris le fonctionnement des Pipelines (les chaînes d'assemblage enchantées), tu découvres une immense salle remplie de miroirs magiques et de balances de précision. Des parchemins sont suspendus dans les airs, tournant lentement tandis que des flammes colorées les inspectent sous tous les angles. Certains brillent d'un vert éclatant. D'autres rougissent et retombent dans un bac de rejet.
« Bienvenue dans la Salle des Épreuves. Ici, chaque chronique est soumise à des tests automatiques avant d'être acceptée dans la Grande Archive. Les Épreuves vérifient la forme du texte, la justesse de chaque calcul, la cohérence entre les chapitres, et le fonctionnement complet de l'ensemble. Aucun document ne passe sans avoir survécu à toutes les épreuves. »
Pourquoi automatiser les tests ?
Imagine un monde sans tests automatiques : chaque modification du code doit être vérifiée manuellement par un humain. C'est lent, sujet aux erreurs, et ça ne passe pas à l'échelle.
Les tests automatiques apportent plusieurs avantages cruciaux :
- Détection précoce : les erreurs sont attrapées avant qu'un humain ne relise le code
- Confiance : tu peux modifier du code existant sans craindre de tout casser
- Rapidité : des centaines de vérifications en quelques secondes
- Cohérence : les mêmes tests passent à chaque fois, sans oubli humain
- Documentation vivante : les tests montrent comment le code est censé fonctionner
« Un Archiviste qui vérifie à la main chaque parchemin finit par s'épuiser et par laisser passer des erreurs. Les Épreuves Automatiques ne se fatiguent jamais. Elles sont les gardiennes infaillibles de la qualité. »
Les types d'épreuves automatiques
Il existe plusieurs niveaux de tests, du plus rapide au plus complet. Chacun a un rôle précis.
Le Linting - L'épreuve de forme
Le linting vérifie que le code respecte les règles de style et de formatage. Il ne teste pas si le code fonctionne - il vérifie qu'il est bien écrit.
Exemples de vérifications :
- Le script commence-t-il par un shebang (
#!/usr/bin/env bash) ? - Les indentations sont-elles cohérentes ?
- Y a-t-il des espaces manquants ou en trop ?
- Les variables sont-elles correctement nommées ?
Le linting est ultra-rapide et attrape les erreurs les plus simples. On le lance toujours en premier.
Les tests unitaires - L'épreuve de justesse
Les tests unitaires vérifient que chaque fonction individuelle produit le bon résultat. On isole une petite partie du code et on teste ses entrées/sorties.
Exemple : si tu as une fonction qui additionne deux nombres, un test unitaire vérifie que additionner 2 3 retourne bien 5.
# Exemple de test unitaire simple
resultat=$(./calculer.sh addition 2 3)
if [ "$resultat" = "5" ]; then
echo "PASS: addition 2 + 3 = 5"
else
echo "FAIL: attendu 5, obtenu $resultat"
fi Les tests d'intégration - L'épreuve de cohérence
Les tests d'intégration vérifient que plusieurs composants fonctionnent ensemble. Un test unitaire vérifie une fonction en isolation. Un test d'intégration vérifie que les fonctions collaborent correctement.
Exemple : vérifier que la fonction de calcul et la fonction d'affichage produisent le bon résultat quand on les combine.
Les tests end-to-end - L'épreuve complète
Les tests end-to-end (bout en bout) simulent le parcours complet d'un utilisateur. Ils testent l'application entière, de l'entrée à la sortie.
Exemple : exécuter le script complet avec des arguments, vérifier que le fichier de sortie est correct, que les messages d'erreur sont appropriés, etc.
La matrice de tests - Tester partout à la fois
Ton code doit fonctionner sur différents systèmes d'exploitation et différentes versions d'outils. La matrice de tests permet de lancer les mêmes tests sur toutes les combinaisons en parallèle.
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 20, 22] Avec cette configuration, GitHub Actions crée 9 jobs (3 OS x 3 versions). Chaque combinaison est testée indépendamment.
On utilise les valeurs de la matrice dans le workflow :
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
node-version: [18, 20, 22]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm test « Les Épreuves de la Salle des Miroirs testent chaque chronique sous tous les angles possibles. Un parchemin qui fonctionne dans un miroir mais pas dans un autre est un parchemin défaillant. La matrice garantit une couverture totale. »
Le cache - Accélérer les épreuves
Télécharger les dépendances à chaque exécution du workflow est lent et gaspille des ressources. L'action actions/cache permet de sauvegarder ces fichiers entre les exécutions.
steps:
- uses: actions/checkout@v4
- name: Cache des dépendances
uses: actions/cache@v4
with:
path: ~/.npm
key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-npm-
- run: npm install
- run: npm test Le fonctionnement est simple :
path: le dossier à mettre en cachekey: un identifiant unique basé sur le système et le fichier de dépendancesrestore-keys: des clés de secours si la clé exacte n'est pas trouvée
Si le fichier package-lock.json n'a pas changé, les dépendances sont restaurées depuis le cache au lieu d'être retéléchargées. C'est beaucoup plus rapide.
Les artefacts - Conserver les résultats
Les artefacts sont des fichiers produits pendant le workflow que tu veux sauvegarder après l'exécution. Les cas d'usage les plus courants : rapports de tests, fichiers de couverture, logs.
steps:
- run: npm test -- --coverage
- name: Sauvegarder le rapport de couverture
uses: actions/upload-artifact@v4
with:
name: rapport-couverture
path: coverage/
retention-days: 30 Après l'exécution du workflow, tu retrouves les artefacts dans l'onglet "Actions" de GitHub, téléchargeables directement.
Pour récupérer un artefact dans un job ultérieur :
- uses: actions/download-artifact@v4
with:
name: rapport-couverture « Les résultats des Épreuves ne disparaissent pas dans le néant. Ils sont archivés - les rapports, les preuves, les mesures. Un bon Archiviste garde toujours une trace des vérifications effectuées. »
Les status checks obligatoires - Protéger la branche principale
Le véritable pouvoir des tests automatiques, c'est de les rendre obligatoires. Avec les required status checks, tu peux empêcher tout merge dans main si les tests ne passent pas.
Configuration sur GitHub :
- Va dans Settings > Branches
- Ajoute une Branch protection rule pour
main - Coche Require status checks to pass before merging
- Sélectionne les checks requis (le nom de tes jobs de workflow)
# Exemple : le job "tests" doit passer avant tout merge
name: CI
on:
pull_request:
branches: [main]
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test Avec cette configuration, si quelqu'un ouvre une Pull Request vers main et que les tests échouent, le bouton "Merge" sera bloqué. Personne ne peut fusionner du code cassé.
La couverture de tests
La couverture de tests (test coverage) mesure le pourcentage de ton code qui est effectivement testé. C'est un indicateur utile mais imparfait.
- 100% de couverture ne signifie pas que ton code est parfait - ça signifie que chaque ligne est exécutée au moins une fois
- Faible couverture est un signal d'alarme - des parties entières de ton code ne sont jamais testées
- Un bon objectif : viser 70-80% pour un projet standard
Les outils de couverture génèrent des rapports montrant quelles lignes sont testées (en vert) et lesquelles ne le sont pas (en rouge).
Patterns courants dans les workflows de test
Lancer le linter avant les tests
Le linting est rapide. Si le code est mal formaté, inutile de lancer des tests coûteux. On met le linter en premier :
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm run lint
tests:
needs: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: npm test Le mot-clé needs crée une dépendance : le job tests ne démarre que si lint a réussi.
Fail fast vs run all
Par défaut, dans une matrice, si un job échoue, GitHub annule les autres. C'est le mode fail-fast :
strategy:
fail-fast: true # défaut : arrête tout dès qu'un job échoue
matrix:
os: [ubuntu-latest, windows-latest] Si tu veux voir tous les résultats même quand certains échouent :
strategy:
fail-fast: false # continue même si un job échoue
matrix:
os: [ubuntu-latest, windows-latest] Tester uniquement sur les Pull Requests
Pour économiser des ressources, tu peux limiter les tests aux Pull Requests :
on:
pull_request:
branches: [main] Cela évite de relancer les tests à chaque push sur chaque branche. Les tests ne se déclenchent que quand quelqu'un propose de fusionner vers main.
« Un Maître des Épreuves sait organiser ses tests intelligemment. Les vérifications rapides passent en premier, les lentes en dernier. Les épreuves coûteuses ne sont lancées que lorsque c'est nécessaire. La sagesse, c'est aussi l'efficacité. »
Exercice pratique - Créer une suite d'épreuves automatiques
Crée tes propres Épreuves Automatiques :
- Crée un dépôt
epreuves-automatiques - Crée un script
calculer.sh(calculatrice en ligne de commande) - Crée un script de test
test-calculer.sh - Crée un workflow GitHub Actions avec un job de lint et un job de tests
- Utilise une matrice pour tester sur 2 systèmes d'exploitation
- Commite le tout et lance la vérification
Étape 1 - Créer le dépôt
mkdir epreuves-automatiques
cd epreuves-automatiques
git init -b main Étape 2 - Créer le script de calcul
Crée un fichier calculer.sh qui effectue des opérations mathématiques simples :
cat > calculer.sh << 'EOF'
#!/usr/bin/env bash
# calculer.sh - Calculatrice simple en ligne de commande
# Usage : ./calculer.sh <operation> <nombre1> <nombre2>
operation="$1"
a="$2"
b="$3"
if [ -z "$operation" ] || [ -z "$a" ] || [ -z "$b" ]; then
echo "Usage : ./calculer.sh <operation> <nombre1> <nombre2>"
echo "Opérations : addition, soustraction, multiplication"
exit 1
fi
case "$operation" in
addition)
echo $(( a + b ))
;;
soustraction)
echo $(( a - b ))
;;
multiplication)
echo $(( a * b ))
;;
*)
echo "Erreur : opération inconnue '$operation'"
exit 1
;;
esac
EOF Rends-le exécutable :
chmod +x calculer.sh Teste-le manuellement :
./calculer.sh addition 2 3
# Doit afficher : 5
./calculer.sh multiplication 4 7
# Doit afficher : 28 Étape 3 - Créer le script de test
Crée un fichier test-calculer.sh qui vérifie automatiquement chaque opération :
cat > test-calculer.sh << 'EOF'
#!/usr/bin/env bash
# test-calculer.sh - Tests automatiques pour calculer.sh
SCRIPT="./calculer.sh"
ERREURS=0
TOTAL=0
tester() {
local description="$1"
local attendu="$2"
local obtenu="$3"
TOTAL=$((TOTAL + 1))
if [ "$attendu" = "$obtenu" ]; then
echo " PASS: $description"
else
echo " FAIL: $description (attendu: '$attendu', obtenu: '$obtenu')"
ERREURS=$((ERREURS + 1))
fi
}
echo "=== Tests de calculer.sh ==="
echo ""
echo "-- Tests d'addition --"
tester "2 + 3 = 5" "5" "$($SCRIPT addition 2 3)"
tester "0 + 0 = 0" "0" "$($SCRIPT addition 0 0)"
tester "100 + 200 = 300" "300" "$($SCRIPT addition 100 200)"
echo ""
echo "-- Tests de soustraction --"
tester "10 - 3 = 7" "7" "$($SCRIPT soustraction 10 3)"
tester "5 - 5 = 0" "0" "$($SCRIPT soustraction 5 5)"
echo ""
echo "-- Tests de multiplication --"
tester "4 x 7 = 28" "28" "$($SCRIPT multiplication 4 7)"
tester "0 x 100 = 0" "0" "$($SCRIPT multiplication 0 100)"
tester "3 x 3 = 9" "9" "$($SCRIPT multiplication 3 3)"
echo ""
echo "=== Résultat : $((TOTAL - ERREURS))/$TOTAL tests passés ==="
if [ "$ERREURS" -gt 0 ]; then
exit 1
fi
exit 0
EOF Rends-le exécutable et teste-le :
chmod +x test-calculer.sh
./test-calculer.sh Tu devrais voir tous les tests passer.
Étape 4 - Créer le workflow GitHub Actions
Crée le dossier et le fichier de workflow :
mkdir -p .github/workflows # .github/workflows/epreuves.yml
name: Épreuves Automatiques
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
name: Vérification de forme (lint)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Vérifier le shebang de calculer.sh
run: |
premiere_ligne=$(head -n 1 calculer.sh)
if [ "$premiere_ligne" != "#!/usr/bin/env bash" ]; then
echo "ERREUR : calculer.sh doit commencer par #!/usr/bin/env bash"
exit 1
fi
echo "OK : shebang correct"
- name: Vérifier le shebang de test-calculer.sh
run: |
premiere_ligne=$(head -n 1 test-calculer.sh)
if [ "$premiere_ligne" != "#!/usr/bin/env bash" ]; then
echo "ERREUR : test-calculer.sh doit commencer par #!/usr/bin/env bash"
exit 1
fi
echo "OK : shebang correct"
tests:
name: Tests (${{ matrix.os }})
needs: lint
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest]
steps:
- uses: actions/checkout@v4
- name: Rendre les scripts exécutables
run: |
chmod +x calculer.sh
chmod +x test-calculer.sh
- name: Lancer les tests
run: ./test-calculer.sh Pour écrire le fichier YAML, utilise la commande cat > .github/workflows/epreuves.yml << 'EOF' suivie du contenu, puis EOF sur une ligne seule.
Étape 5 - Commiter le tout
git add calculer.sh test-calculer.sh .github/workflows/epreuves.yml
git commit -m "Ajouter la calculatrice, les tests et le workflow CI" Étape 6 - Vérifier la structure
# Le script de calcul existe et fonctionne
./calculer.sh addition 10 20
# Les tests passent
./test-calculer.sh
# Le workflow existe
cat .github/workflows/epreuves.yml Étape 7 - Lancer la vérification
bash verifier.sh .\verifier.ps1 Récapitulatif des concepts
| Concept | Description |
|---|---|
| Linting | Vérification du style et du formatage du code |
| Tests unitaires | Tests de fonctions individuelles en isolation |
| Tests d'intégration | Tests de plusieurs composants ensemble |
| Tests end-to-end | Tests du parcours complet de l'application |
| Matrice de tests | Exécution parallèle sur plusieurs OS/versions |
| Cache | Sauvegarde des dépendances entre les exécutions |
| Artefacts | Fichiers produits pendant le workflow à conserver |
| Status checks | Vérifications obligatoires avant un merge |
| Couverture de tests | Pourcentage du code effectivement testé |
| Fail fast | Arrêter tous les jobs dès qu'un échoue |
Récapitulatif YAML
| Élément | Rôle |
|---|---|
strategy: matrix: | Définir les combinaisons OS/versions à tester |
needs: job_name | Créer une dépendance entre les jobs |
fail-fast: false | Continuer même si un job échoue |
actions/cache@v4 | Mettre en cache les dépendances |
actions/upload-artifact@v4 | Sauvegarder des fichiers produits |
actions/download-artifact@v4 | Récupérer des artefacts sauvegardés |
on: pull_request: | Déclencher uniquement sur les PR |
Le Maître des Forges observe les miroirs de test tourner autour de ta chronique. Un par un, ils s'illuminent en vert. Addition - vert. Soustraction - vert. Multiplication - vert. Ubuntu - vert. macOS - vert. Le linting valide la forme. Les tests confirment la justesse.
« Tu as compris l'essence des Épreuves Automatiques. Chaque chronique que tu soumettras sera désormais passée au crible par des gardiens infatigables. Le linter vérifie la forme. Les tests unitaires vérifient la justesse. La matrice vérifie la compatibilité. Et les status checks interdisent l'entrée aux parchemins défaillants. »
Il désigne une section plus profonde des Forges, où des machines encore plus complexes assemblent des pièces entre elles automatiquement.
« Tu sais tester. Bientôt, tu sauras construire et déployer. Mais ça, c'est l'épreuve suivante. »