Activité pratique, programmer avec IA

Durée1h15

L’objectif de cette session est de découvrir, par la pratique, ce qu’un agent IA de codage sait faire, et ce dont l’ingénieur reste seul responsable.

Elle ne cherche pas à vérifier que vous obtenez le « bon » code — un agent IA vous le donnerait en quelques secondes — mais à vous faire observer ce qui se passe quand on programme avec une IA : où elle aide, où elle se trompe, et ce dont vous restez responsable. Les consignes ci-dessous peuvent sembler contraignantes, mais chacune protège une de ces observations : les contourner, c’est vous priver de ce que la séance cherche à vous faire découvrir.

Consignes globales

Quand vous transmettez quelque chose à l’agent (spécification, code, cas de test), transmettez-le tel que vous l’avez écrit, sans le compléter oralement ni « arranger » au passage, même si vous repérez un manque. L’écart entre ce que vous vouliez, ce que vous avez écrit et ce que l’agent a produit est précisément l’objet d’étude : le masquer, c’est vous priver de la leçon.

Que le code soit tapé par vous ou produit par l’agent, c’est vous qui répondez de sa correction. L’objectif de la séance n’est pas d’apprendre à faire écrire du code par une IA, mais à spécifier ce qui est attendu et à vérifier que le résultat s’y conforme — deux compétences qui ne se délèguent pas.

Contenu de l’activité

De la spécification au code

Dans cet exercice, vous allez spécifier une fonction Python, la faire implémenter par l’agent IA, puis confronter votre spécification à une spécification de référence pour mesurer l’écart entre ce que vous vouliez, ce que vous avez demandé et ce que l’agent a produit.

Le problème support : compter le nombre de jours ouvrés entre deux dates.

Votre spécification, puis génération par IA

En binôme et sans utiliser l’agent IA, rédigez dans un fichier la spécification d’une fonction Python comptant le nombre de jours ouvrés entre deux dates. Appuyez-vous sur les bonnes pratiques de spécification vues dans le support : décrivez le contexte et l’objectif, les entrées et sorties (types, contraintes), les cas limites et les comportements en cas d’erreur, et donnez au moins un exemple concret entrée → sortie.

Votre rôle à cette étape

Ne consultez pas l’agent IA : c’est votre rôle d’ingénieur de définir ce qui doit être fait avant de déléguer comment le faire.

Confiez ensuite à l’agent IA la production du code Python à partir de votre spécification. N’ajoutez au prompt aucune précision qui ne figure pas déjà dans votre spécification écrite — même si en relisant vous repérez un manque.

Conservez le code obtenu sans le retoucher dans un fichier solution.py, et insérez à son début (en commentaires) le prompt exact transmis à l’agent.


La spécification de référence, puis génération par l’IA

Vous trouverez ci-dessous une spécification de référence, complète et sans ambiguïté, de la même fonction. Générez une seconde fois le code, cette fois à partir de cette spécification, et conservez ce second code avec son prompt dans un fichier solution_ref.py.

Spécification de référence

Objectif. Compter le nombre de jours ouvrés strictement compris dans un intervalle de dates, en excluant les week-ends et les jours fériés.

Signature.

from datetime import date

def compter_jours_ouvres(date_debut: date, date_fin: date,
                         feries: set[date] | None = None) -> int:
    ...

Entrées.

  • date_debut, date_fin : objets datetime.date (pas datetime, donc pas d’heure ni de fuseau horaire à gérer).
  • feries : ensemble de dates fériées à exclure. Par défaut None, interprété comme « aucun jour férié ». Le choix des fériés est une donnée d’entrée, pas une responsabilité de la fonction — cela la rend testable et indépendante d’un pays.

Sortie. Un entier >= 0 : le nombre de jours ouvrés.

Convention de bornes. Intervalle inclusif aux deux extrémités : date_debut et date_fin sont comptés s’ils sont ouvrés. Conséquence : si date_debut == date_fin et que ce jour est ouvré, le résultat est 1 ; s’il tombe un week-end ou un férié, 0.

Définition de « jour ouvré ». Un jour est ouvré s’il n’est ni un samedi, ni un dimanche, ni un élément de feries. Samedi et dimanche identifiés via date.weekday() (5 = samedi, 6 = dimanche).

Cas particuliers.

  • Si date_fin < date_debut : lever ValueError (l’intervalle est invalide ; on ne devine pas l’intention de l’utilisateur).
  • Si l’un des arguments n’est pas un date : lever TypeError.
  • feries peut contenir des dates hors de l’intervalle : elles sont simplement sans effet.

Contrat / invariants. Fonction pure : pas d’effet de bord, pas de dépendance à la date du jour, résultat déterministe.


Analyse des différences

Comparez solution.py (code généré à partir de votre spécification) et solution_ref.py (code généré à partir de la spécification de référence), et répondez par écrit :

  • Sur quels points votre spécification initiale était-elle ambiguë ou silencieuse ? (bornes, week-end, fériés, ordre des dates, types, cas invalides…)
  • Pour chaque ambiguïté, indiquez quelle hypothèse l’agent IA a-t-il choisie à votre place ? Était-elle explicite dans le code (docstring, exception) ou silencieuse ?
  • Sur une même paire de dates, les deux versions renvoient-elles le même résultat ? Si non, pourquoi ? Cas proposés : du vendredi 1er mai 2026 (ferié) au vendredi 8 mai 2026 (férié) ; du lundi 4 mai 2026 au lundi 4 mai 2026 (même date (ouvrée) début et fin)
À retenir

Vous venez de vérifier par l’expérience ce que le support sur l’utilisation d’une IA annonçait : l’agent IA ne signale pas une spécification incomplète — il la comble par une hypothèse, souvent non documentée. La qualité du code produit est plafonnée par la précision de la spécification fournie.

Génération de tests

Le support sur l’utilisation d’une IA vous a donné le vocabulaire et les règles ; cette partie les met en pratique. L’objectif n’est pas de savoir « qui teste le mieux », mais de vérifier par l’expérience deux choses lues dans le support : un test n’a de valeur que si son oracle vient de la spécification, et un test écrit à partir du code hérite des bugs du code.

On reprend la fonction compter_jours_ouvres de l’exercice précédent et on va produire ses tests.


Compléter et exécuter une amorce de tests (sans IA)

Ci-dessous vous avez une amorce des tests pour la fonction : trois tests, avec des oracles calculés à la main (un cas nominal, un cas limite, un cas invalide). Complétez le tableau en ajoutant deux ou trois nouveaux cas, dont au moins un cas calendaire subtil (le 1er mai 2026 est un bon candidat). N’oubliez pas que pour chaque cas, vous devez calculer le résultat attendu (l’oracle) à la main avant de l’écrire : c’est la règle de l’oracle vue dans le support — la valeur attendue vient de la spécification, jamais de ce que renvoie le code.

Test Catégorie Entrées Oracle (attendu) Justification
test_nominal_semaine Cas nominal date(2026, 5, 18) → date(2026, 5, 22) 5 Lundi 18 au vendredi 22 mai 2026 : 5 jours ouvrés consécutifs, aucun week-end dans l’intervalle
test_dates_egales_jour_ouvre Cas limite date(2026, 5, 4) → date(2026, 5, 4) 1 Même jour (un lundi), borne de fin incluse : le jour ouvré est compté une fois
test_ordre_inverse_leve_valueerror Cas invalide date(2026, 5, 8) → date(2026, 5, 4) ValueError date_fin < date_debut : l’intervalle est invalide, la fonction lève une exception

Créez un fichier tests_compter_jours_ouvres.py qui implémente tous les cas de test identifiés. Partez du code ci-dessous (qui implémente les cas de test du tableau) et complétez le avec les cas que vous avez identifié.

import unittest
from datetime import date
from solution import compter_jours_ouvres   # code généré à partir de votre spécification 

class TestJoursOuvres(unittest.TestCase):
    def test_nominal_semaine(self):
        # lundi 18 -> vendredi 22 mai 2026 : 5 jours ouvrés
        self.assertEqual(compter_jours_ouvres(date(2026, 5, 18), date(2026, 5, 22)), 5)

    def test_dates_egales_jour_ouvre(self):
        # même jour, un lundi : 1 (borne de fin incluse)
        self.assertEqual(compter_jours_ouvres(date(2026, 5, 4), date(2026, 5, 4)), 1)

    def test_ordre_inverse_leve_valueerror(self):
        with self.assertRaises(ValueError):
            compter_jours_ouvres(date(2026, 5, 8), date(2026, 5, 4))

if __name__ == "__main__":
    unittest.main(verbosity=2)

Exécutez vos tests sur le code généré à partir de votre spécification (fichier solution.py) et notez les échecs : pour chacun, indiquez s’il s’agit d’un défaut du code, ou d’un oracle erroné de votre côté ?


Confronter vos tests à ceux de l’IA

Demandez maintenant à l’agent IA de générer ses tests pour la fonction, puis comparez ses tests aux vôtres en remplissant le tableau suivant :

Catégorie de cas Présente chez vous ? Présente chez l’IA ? Oracle vérifié à la main ?
Nominal semaine
Dates égales
Ordre inversé
Entrées invalides / exceptions
Bissextile, changement d’année
Férié tombant un week-end
Bornes

Analysez deux ou trois oracles générés par l’IA et recalculez-les indépendamment à la main. Notez sur la dernière colonne si l’oracle est confimé ou s’il est erroné — l’enjeu est de savoir vérifier un oracle sans faire confiance à son auteur, qu’il y ait une erreur ou non.

À retenir

L’IA produit souvent un ensemble de tests plus fourni et mieux structuré que la vôtre mais avec deux défauts : des oracles parfois faux et des tests qui reproduisent les hypothèses du code au lieu de les questionner. Un cas de test dont l’oracle est faux est pire qu’absent : il peut valider un code incorrect. La validité de l’oracle est la seule chose que l’IA ne peut pas garantir à votre place.


Déboguer un code qu’on n’a pas écrit

L’agent IA est un très bon assistant de débogage — à condition de savoir ce qu’on doit lui apporter. Vous allez le vérifier en déroulant, une fois, la boucle de débogage du support sur un code réel.

Ci-dessous vous avez une implémentation différente de compter_jours_ouvres produite par un agent IA. Elle s’exécute sans erreur et renvoie des résultats d’apparence normale — mais elle contient un bug. Vous allez dérouler, une fois, la boucle de débogage décrite dans le support sur l’utilisation d’une IA, et voir concrètement à quel moment l’IA aide et à quel moment elle ne peut pas.

Le code à déboguer
from datetime import date, timedelta

def compter_jours_ouvres(date_debut: date, date_fin: date,
                         feries: set[date] | None = None) -> int:
    if not isinstance(date_debut, date) or not isinstance(date_fin, date):
        raise TypeError("date_debut et date_fin doivent être des objets datetime.date")
    if date_fin < date_debut:
        raise ValueError("date_fin ne peut pas être antérieure à date_debut")

    feries = feries or set()
    total = 0
    jour = date_debut
    while jour < date_fin:
        if jour.weekday() <= 5 and jour not in feries: 
            total += 1
        jour += timedelta(days=1)
    return total

Copiez ce code dans un fichier solution_bug.py.


Répérer que quelque chose ne va pas

Vous ne pouvez décider qu’un résultat est faux que par rapport à la spécification de référence — pas par rapport au code, qui ne peut pas se juger lui-même. Calculez donc à la main, depuis la spécification de référence de l’exercice 1, le résultat attendu pour le cas ci-dessous, puis exécutez le code qui affiche le résultat obtenu :

    compter_jours_ouvres(date(2026, 5, 4), date(2026, 5, 4)) # lundi, même date début et fin

Quel résultat est affiché ? Normalement ce résultat ne correspond pas à ce que vous avez calculé à la main à partir de la spécification : vous avez donc découvert un bug dans l’implémentation.


Confier le débogage à l’agent IA

Donnez à l’agent le code et votre oracle pour le cas précédent, formulé explicitement :

Pour `compter_jours_ouvres(date(2026,5,4), date(2026,5,4))`, 
la spécification impose un résultat de 1 (bornes incluses), or 
la fonction renvoie 0. 
Corrige le code pour respecter cette convention.

Vous ne lui demandez pas de deviner ce qui cloche : vous lui apportez le cas qui échoue et le résultat attendu — c’est-à-dire un test formulé en langage naturel. Avec cette information, l’agent localise et corrige la cause en général sans difficulté.


Vérifier la correction

L’agent affirmera que c’est corrigé. Ne vous en contentez pas : une correction peut régler le cas visé tout en cassant un autre comportement. Ci-dessous vous trouverez un ensemble de tests écrits d’après la spécification de référence de la fonction. Copiez les tests dans un fichier tests_reference.py et lancez leur exécution : uv run python tests_reference.py.

Tests pour la spécification de référence de la fonction
import unittest
from datetime import date
from solution_bug import compter_jours_ouvres


class TestCasNominaux(unittest.TestCase):
    def test_semaine_complete(self):
        # lundi 4 -> vendredi 8 mai 2026 : 5 jours ouvrés
        self.assertEqual(compter_jours_ouvres(date(2026, 5, 4), date(2026, 5, 8)), 5)

    def test_intervalle_traversant_un_weekend(self):
        # lundi 4 -> lundi 11 mai : 6 (5 jours + le lundi suivant, week-end exclu)
        self.assertEqual(compter_jours_ouvres(date(2026, 5, 4), date(2026, 5, 11)), 6)

    def test_mois_entier(self):
        # mai 2026 complet : 21 jours ouvrés (hors fériés)
        self.assertEqual(compter_jours_ouvres(date(2026, 5, 1), date(2026, 5, 31)), 21)


class TestBornesInclusives(unittest.TestCase):
    def test_dates_egales_jour_ouvre(self):
        # même jour, un lundi : 1 (borne de fin incluse)
        self.assertEqual(compter_jours_ouvres(date(2026, 5, 4), date(2026, 5, 4)), 1)

    def test_bornes_debut_et_fin_comptees(self):
        # vendredi 8 -> lundi 11 : vendredi + lundi = 2 (les deux bornes comptent)
        self.assertEqual(compter_jours_ouvres(date(2026, 5, 8), date(2026, 5, 11)), 2)


class TestWeekEnds(unittest.TestCase):
    def test_dates_egales_jour_weekend(self):
        # même jour, un samedi : 0
        self.assertEqual(compter_jours_ouvres(date(2026, 5, 9), date(2026, 5, 9)), 0)

    def test_weekend_complet(self):
        # samedi 9 -> dimanche 10 : 0
        self.assertEqual(compter_jours_ouvres(date(2026, 5, 9), date(2026, 5, 10)), 0)

    def test_samedi_non_compte(self):
        # lundi 4 -> samedi 9 : 5 (le samedi n'ajoute rien)
        self.assertEqual(compter_jours_ouvres(date(2026, 5, 4), date(2026, 5, 9)), 5)

    def test_dimanche_non_compte(self):
        # lundi 4 -> dimanche 10 : 5
        self.assertEqual(compter_jours_ouvres(date(2026, 5, 4), date(2026, 5, 10)), 5)


class TestJoursFeries(unittest.TestCase):
    def test_ferie_en_semaine_exclu(self):
        # 1er mai 2026 (vendredi, férié) sur ven 1 -> lun 4 : seul le lundi compte = 1
        self.assertEqual(
            compter_jours_ouvres(date(2026, 5, 1), date(2026, 5, 4), feries={date(2026, 5, 1)}), 1
        )

    def test_ferie_le_weekend_sans_effet(self):
        # un férié tombant un samedi ne change rien (déjà exclu)
        self.assertEqual(
            compter_jours_ouvres(date(2026, 5, 4), date(2026, 5, 8), feries={date(2026, 5, 9)}), 5
        )

    def test_plusieurs_feries_en_semaine(self):
        # lundi 4 et mardi 5 fériés sur lun 4 -> ven 8 : 5 - 2 = 3
        self.assertEqual(
            compter_jours_ouvres(
                date(2026, 5, 4), date(2026, 5, 8), feries={date(2026, 5, 4), date(2026, 5, 5)}
            ), 3
        )

    def test_ferie_hors_intervalle_sans_effet(self):
        # un férié en dehors de l'intervalle n'a aucun effet
        self.assertEqual(
            compter_jours_ouvres(date(2026, 5, 4), date(2026, 5, 8), feries={date(2026, 12, 25)}), 5
        )

    def test_feries_none_equivaut_aucun(self):
        self.assertEqual(compter_jours_ouvres(date(2026, 5, 4), date(2026, 5, 8), feries=None), 5)


class TestCasDErreur(unittest.TestCase):
    def test_debut_apres_fin_leve_valueerror(self):
        with self.assertRaises(ValueError):
            compter_jours_ouvres(date(2026, 5, 8), date(2026, 5, 4))

    def test_type_invalide_leve_typeerror(self):
        with self.assertRaises(TypeError):
            compter_jours_ouvres("2026-05-04", date(2026, 5, 8))


class TestCasCalendairesSubtils(unittest.TestCase):
    def test_annee_bissextile_29_fevrier(self):
        # 2028 bissextile ; 28 fév (lun) -> 1er mars (mer) inclut le 29 fév (mar) = 3
        self.assertEqual(compter_jours_ouvres(date(2028, 2, 28), date(2028, 3, 1)), 3)

    def test_changement_annee(self):
        # 31 déc 2026 (jeudi) -> 1er jan 2027 (vendredi) : 2 jours ouvrés
        self.assertEqual(compter_jours_ouvres(date(2026, 12, 31), date(2027, 1, 1)), 2)

    def test_annee_complete_2026(self):
        # 2026 compte 261 jours ouvrés (hors fériés)
        self.assertEqual(compter_jours_ouvres(date(2026, 1, 1), date(2026, 12, 31)), 261)


if __name__ == "__main__":
    unittest.main(verbosity=2)

Conclusion

En une séance, vous avez parcouru les trois moments du travail avec un agent IA de codage, et constaté qu’à chacun l’agent fait une partie du chemin — mais jamais la totalité.

  • En spécifiant, vous avez vu que l’agent ne comble pas les silences de votre demande : il les remplit par une hypothèse, souvent tacite. Deux binômes « corrects vis-à-vis de leur propre spec » obtenaient des résultats différents sur la même paire de dates — preuve que la divergence ne venait pas du code, mais de la spécification.

  • En testant, vous avez vu qu’un test ne vaut que par son oracle : une valeur attendue calculée depuis la spécification, indépendamment du code. Un test dont l’oracle est tiré du code épouse ses erreurs et les valide en silence, au lieu de les révéler.

  • En déboguant, vous avez vu que l’agent corrige efficacement un bug — à condition que vous lui apportiez ce que lui ne peut pas produire : le constat qu’un résultat est faux, et le résultat attendu. Décider que 0 était une erreur relevait de la spécification de référence, pas du code ; c’est vous qui déteniez cette information.

Un fil unique traverse les trois : l’agent produit, mais c’est vous qui définissez ce qui est correct. Spécifier ce qui est attendu, calculer un oracle, juger une réponse — ces gestes reposent sur une intention que le code seul ne contient pas, et que l’agent ne peut donc pas deviner à votre place. C’est là, et non dans la vitesse de production, que se situe désormais votre travail d’ingénieur : moins écrire chaque ligne, davantage énoncer avec précision ce que le programme doit faire, et vérifier qu’il le fait.

À retenir

Un agent IA déplace l’effort, il ne le supprime pas. Il rend d’autant plus décisives les deux compétences qu’il ne peut pas remplacer : spécifier ce qui est attendu, et vérifier que le résultat s’y conforme. Vous restez responsable du code produit — que vous l’ayez écrit ou fait écrire.