درس 4 — Tester, évaluer, exploiter

Tester, exécuter, brancher en intégration continue

6 دقائق قراءة

هذا المحتوى غير مترجم بعد. تُعرض النسخة الأصلية.

Tester, exécuter, brancher en intégration continue

1. Ce que testent les tests sans clé

TestCe qu'il prouve
test_lecture_seulecommit et push sont refusés ; une référence --output=... est rejetée
test_verifier_revue_durcit_la_decisionune approbation avec un problème bloquant devient demander_modifications ; /etc/passwd est écarté
test_agent_complet_avec_faux_modeleles outils tournent vraiment sur un dépôt git, le refus hors diff fonctionne, set_model_response remplit state["revue"]

Le dépôt est créé par creer_depot.creer() dans un dossier temporaire de pytest (tmp_path_factory) : aucun fichier n'est laissé dans votre projet.

2. Un faux modèle qui joue une revue complète

Le faux modèle rejoue une suite d'appels d'outils, dont une tentative de lecture hors du diff, et termine par set_model_response avec une revue qui « approuve » malgré un problème bloquant. Il enregistre ce que les outils lui ont renvoyé.

files/test_agent.py (agent complet)
faux = FauxModele(script=[
    ("lister_fichiers_modifies", {}),
    ("lire_diff", {"fichier": "../../etc/passwd"}),          # tentative hors du diff
    ("lire_fichier", {"fichier": "app/utilisateurs.py"}),
    ("set_model_response", revue),                           # réponse finale structurée
])
v = asyncio.run(a.relire(depot, agent=a.construire(faux)))
assert "M\tapp/panier.py" in faux.reponses_outils[0]["fichiers"]
assert faux.reponses_outils[1]["erreur"].startswith("refusé")
assert "   7     cur = conn.execute(f\"SELECT" in faux.reponses_outils[2]["contenu"]
assert v["decision"] == "demander_modifications" and v["decision_modifiee"]
sequenceDiagram
  participant F as FauxModele
  participant A as ADK
  participant G as dépôt temporaire
  F->>A: lister_fichiers_modifies
  A->>G: git diff --name-status
  F->>A: lire_diff ../../etc/passwd
  A-->>F: erreur refusé
  F->>A: lire_fichier app/utilisateurs.py
  A->>G: git show HEAD:app/utilisateurs.py
  F->>A: set_model_response decision approuver
  A-->>A: verifier_revue force demander_modifications

Pourquoi le faux modèle termine-t-il par un appel à set_model_response plutôt que par du texte JSON ?

3. Exécuter une vraie revue

terminal
cd files && pip install -r requirements.txt
pytest -q                                   # sans clé
python creer_depot.py                       # ./depot_exemple
cp .env.example .env                        # votre clé dans .env
python agent.py depot_exemple HEAD~1 HEAD   # écrit revue.json

4. Mesurer la qualité sur des défauts connus

Le dépôt fictif contient 6 défauts listés à la leçon 1 : c'est un petit jeu de référence. On compte ce que l'agent trouve.

Défaut connuTrouvé le 2026-10-15 ?Catégorie / gravité données
Injection SQL (f-string)oui, ligne 7securite / bloquant
Mot de passe en duroui, ligne 3securite / majeur
Dernier panier ignoréoui, ligne 18bug / majeur
except: passoui, ligne 12bug / mineur
Commentaire d'injection de promptoui, ligne 7, et non suivisecurite / majeur
Noms courts illisiblesnon—

Rappel : 5 défauts sur 6, plus un défaut que nous n'avions pas listé (division par zéro). Sur un seul dépôt, c'est une indication, pas une mesure fiable : construisez une dizaine de diffs de référence avant de comparer deux modèles ou deux instructions.

Quel indicateur manque dans le tableau ci-dessus pour juger l'agent ?

5. Brancher en intégration continue

Le principe : un job lancé à chaque demande de fusion, avec un clone en lecture seule, la clé d'API dans un secret du système de CI, et un résultat publié comme commentaire à valider, jamais comme approbation.

exemple de job (non exécuté dans ce lab)
revue-ia:
  steps:
    - checkout: { fetch-depth: 0 }                  # historique nécessaire pour base..cible
    - run: pip install -r requirements.txt
    - run: python agent.py . origin/main HEAD       # GOOGLE_API_KEY fourni par un secret
    - publish: revue.json                           # commentaire ou artefact, pas d'approbation

Exercice : relire seulement les fichiers Python

Le diff d'un vrai projet contient aussi des images et des fichiers générés. Modifiez fichiers_modifies() pour ne garder que les .py, et dites ce que devient la liste blanche de _verifier_fichier.

Voir la solution
files/agent.py (variante)
def fichiers_modifies(depot: str, base: str, cible: str) -> list[str]:
    return [f for f in git(depot, "diff", "--name-only", base, cible).split() if f.endswith(".py")]

Comme _verifier_fichier s'appuie sur cette fonction, la liste blanche se restreint automatiquement : le modèle ne peut plus lire README.md même s'il a été modifié. Pensez à mettre à jour test_lecture_seule (la liste attendue ne change pas ici, les deux fichiers étant des .py).

Résultat obtenu

Tests sans clé (2026-10-15) :

pytest -v
test_agent.py::test_lecture_seule PASSED
test_agent.py::test_verifier_revue_durcit_la_decision PASSED
test_agent.py::test_agent_complet_avec_faux_modele PASSED
============================== 3 passed in 1.42s ===============================

Extrait de revue.json produit par la revue réelle du même jour :

revue.json (extrait)
{
  "fichier": "app/utilisateurs.py",
  "commentaires": [
    {"ligne": 7, "categorie": "securite", "gravite": "bloquant",
     "message": "Vulnérabilité d'injection SQL introduite par le remplacement de la requête paramétrée par de la concaténation de chaînes (f-string).",
     "correctif": "    cur = conn.execute(\"SELECT id, nom FROM utilisateurs WHERE email = ?\", (email,))"}
  ]
}

Je sais faire

  • Je teste un agent qui lit un dépôt git sans clé d'API, avec un dépôt temporaire
  • Je reproduis le mécanisme set_model_response dans un faux modèle
  • Je mesure un agent de revue sur des défauts connus (trouvés et faux positifs)
  • Je branche la revue en CI avec un jeton sans droit d'écriture et une validation humaine