Programmer et tester
Spécifier une fonction, choisir les constructions adaptées, construire des jeux de tests et expliquer ce que les résultats permettent réellement de conclure.
Environ 35 minutes
- Écrire préconditions et postconditions
- Distinguer paramètres, arguments et résultat
- Choisir des tests ordinaires et limites
- Mettre au point un programme et utiliser une bibliothèque
Un programme qui réussit trois tests est-il forcément correct ?
Pour juger une fonction, il faut savoir ce qu’elle doit recevoir et ce qu’elle doit produire. Une sortie affichée sans résultat attendu ne constitue pas un contrôle. Nous allons formuler des contrats simples, suivre des variables et choisir des cas qui pourraient révéler une erreur. Les tests aident à mettre au point le programme, mais une collection de succès ne remplace pas une justification générale de son fonctionnement.
1. Décomposer et exprimer un traitement
Un programme impératif combine quelques constructions fondamentales : séquence d’instructions, affectation, condition, boucle et appel de fonction. L’affectation remplace la valeur associée à un nom ; la condition choisit une branche ; la boucle répète un traitement. Une boucle bornée parcourt un ensemble prévu de valeurs, comme avec for sur un tableau. Une boucle conditionnelle, comme while, se poursuit tant qu’une condition est vraie ; il faut alors expliquer pourquoi elle finira par devenir fausse.
Une fonction regroupe un traitement sous un nom. Ses paramètres sont les noms utilisés dans sa définition ; ses arguments sont les valeurs fournies lors d’un appel. La fonction peut renvoyer un résultat avec return. Afficher avec print n’est pas renvoyer : un affichage montre un texte, tandis qu’un résultat retourné peut être réutilisé dans une expression ou par une autre fonction. En Python, atteindre la fin d’une fonction sans return explicite renvoie None.
Décomposer un problème en fonctions permet d’isoler des responsabilités et de réutiliser un traitement. Une fonction de conversion peut être testée séparément d’une fonction de calcul. Les noms doivent exprimer leur rôle et les entrées doivent être annoncées. Une variable locale à un appel n’est pas automatiquement disponible ailleurs. Cette organisation limite les dépendances cachées : il est plus facile de comprendre un résultat obtenu à partir d’arguments explicites qu’un résultat dépendant de multiples variables modifiées en dehors de la fonction.
Exemple résolu
On définit def double(x): return 2 * x. Un programme évalue ensuite y = double(7) + 1. Identifie paramètre, argument et valeur finale.
- Étape 1 : Le paramètre est x : c’est le nom employé dans la définition. L’argument fourni à l’appel est l’entier 7.
- Étape 2 : Pendant cet appel, 2 * x vaut 14. return fournit cette valeur au programme appelant.
- Étape 3 : L’expression de l’affectation devient 14 + 1. La variable y reçoit donc 15.
Résultat : Le résultat de double(7) est 14 ; la valeur finale de y est 15. Aucun affichage n’est nécessaire pour que cette valeur existe.
Piège à éviter : Remplacer return par print ferait afficher 14 mais ne fournirait pas 14 à l’addition ; la fonction renverrait None.
2. Spécifier avant de programmer
Une spécification décrit le comportement attendu. Une précondition indique ce que les arguments doivent respecter ; une postcondition décrit le résultat garanti lorsque les préconditions sont satisfaites. Pour une moyenne, la précondition « tableau non vide de nombres » évite la division par zéro et annonce les valeurs admissibles. La postcondition ne dit pas seulement « renvoie un nombre » : elle précise que le nombre obtenu est la somme des valeurs divisée par leur effectif.
La spécification doit aussi annoncer si la fonction modifie un argument. Une fonction peut calculer à partir d’un tableau sans en changer les cases, ou trier ce tableau sur place. Les deux comportements n’offrent pas le même service. Un appelant qui veut conserver l’ordre initial doit le savoir. Pour une entrée hors précondition, on ne peut pas déduire une garantie qui n’a pas été annoncée ; le traitement de l’erreur peut être défini séparément si le cahier des charges le demande.
Exemple résolu
Spécifie une fonction moyenne(t), puis donne une réalisation courte adaptée à un tableau non vide de nombres. Explique l’appel sur [4, 10, 7].
- Étape 1 : Précondition : t contient au moins un nombre et ses valeurs peuvent être additionnées. La fonction ne modifie pas t.
- Étape 2 : Renvoyer le quotient de la somme par le nombre de valeurs.def moyenne(t): return sum(t) / len(t)
- Étape 3 : Pour [4, 10, 7], la somme est 21 et l’effectif vaut 3. La postcondition donne 21 / 3 = 7.
Résultat : La fonction renvoie 7.0 en Python pour ce calcul avec /. Le tableau initial conserve ses trois valeurs dans leur ordre.
Piège à éviter : Accepter un tableau vide sans règle supplémentaire rendrait le dénominateur nul : ce cas doit être exclu ou traité explicitement.
À toi d'essayer
Spécifie une fonction premier(t) qui renvoie la première valeur d’un tableau sans le modifier. Quel cas interdit faut-il annoncer et quelle postcondition précise convient ?
Voir la réponseMasquer la réponse
Précondition : t est un tableau non vide. Postcondition : le résultat est t[0] et les cases de t sont inchangées. Une définition courte est def premier(t): return t[0]. Le tableau vide est exclu, car il ne possède pas de case d’indice 0.
3. Choisir des tests qui peuvent échouer
Un test associe une entrée à un résultat attendu déterminé indépendamment du programme testé. On exécute la fonction puis on compare ce qui est obtenu à cette attente. Les cas ordinaires montrent un comportement courant ; les cas limites visent les frontières : zéro, une seule valeur, seuil exact, répétition ou dernière case. Si une précondition exclut une entrée, il faut le préciser au lieu de présenter son échec comme une contradiction de la spécification.
Pour une fonction de valeur absolue sur les entiers, tester seulement des nombres positifs ne vérifie jamais la branche négative. On choisit au moins un négatif, zéro et un positif. Une erreur peut pourtant subsister ailleurs : tester −2, 0 et 3 ne couvre pas tous les entiers. Un test qui échoue réfute la correction annoncée pour cette entrée ; un test qui réussit confirme seulement ce cas. Une preuve utilise un raisonnement sur toutes les entrées visées par la spécification.
Une assertion exprime une condition que l’on attend vraie à un point du programme. assert double(7) == 14 échoue si le résultat diffère de 14. Une assertion peut aussi exprimer une précondition pendant la mise au point. Elle ne remplace ni une spécification ni les contrôles adaptés à une application réelle ; en Python, certaines options d’exécution peuvent désactiver les assertions. Pour tester des calculs flottants, il peut être nécessaire de comparer un écart à une tolérance plutôt que d’exiger une égalité exacte.
À toi d'essayer
Une fonction censée renvoyer le plus grand de deux entiers passe les tests (3, 8) et (1, 6). Propose deux tests supplémentaires qui explorent des situations absentes de ces essais, et donne les résultats attendus.
Correction détaillée pas à pasDans ce chapitre : 2 corrections détaillées.
4. Chercher la cause d’un écart
Lorsqu’un test échoue, on réduit le cas à une entrée courte et on écrit une trace des variables après chaque instruction. Il faut distinguer l’erreur de syntaxe, qui empêche de comprendre le programme, l’erreur d’exécution, qui interrompt un traitement, et l’erreur logique, qui produit un résultat sans respecter la demande. Modifier plusieurs lignes au hasard ne permet pas d’identifier la cause. Un cas minimal qui échoue aide à relier l’écart à une instruction précise.
Exemple résolu
Pour calculer une somme, un programme initialise total à 0, puis exécute total = x pour chaque valeur x du tableau. Il termine par return total. Pourquoi donne-t-il 7 pour [4, 10, 7] au lieu de 21 ?
- Étape 1 : Après la première valeur, total vaut 4. Après la deuxième, l’affectation remplace ce total par 10. Après la troisième, elle le remplace par 7.
- Étape 2 : La cause est l’écrasement de l’accumulation : total = x ne conserve pas la somme précédente.
- Étape 3 : Dans la boucle, ajouter la valeur à la somme déjà obtenue ; garder l’initialisation avant la boucle et le retour après.total = total + x
- Étape 4 : La trace corrigée donne successivement 4, 14 et 21. Sur un tableau vide, la somme peut rester 0 si la spécification l’autorise.
Résultat : La correction calcule 21 pour [4, 10, 7] parce que chaque étape conserve les valeurs déjà additionnées.
Piège à éviter : Placer total = 0 à l’intérieur de la boucle annulerait de nouveau la somme précédente à chaque tour.
Après une correction, on rejoue le cas qui échouait et les autres cas pertinents pour rechercher une régression. Il est utile de garder le test révélateur avec son résultat attendu. Un test ne doit pas calculer son attente en recopiant exactement le même algorithme fautif : il pourrait reproduire la même erreur. Une petite somme calculée à la main ou une propriété simple fournit une référence plus indépendante.
À toi d'essayer
Une fonction de moyenne renvoie la somme divisée par 3 pour tout tableau non vide. Elle passe le test [2, 4, 6]. Construis un contre-exemple court et explique pourquoi ce test révèle le défaut.
Correction détaillée pas à pasDans ce chapitre : 2 corrections détaillées.
5. Réutiliser sans deviner
Une bibliothèque rassemble des fonctions réutilisables. Avant un appel, on lit la documentation : nom, paramètres, types acceptés, résultat et erreurs possibles. Avec un module, on peut distinguer le nom du module et celui de la fonction appelée. La disponibilité d’une fonction ne dispense pas de comprendre son contrat. Pour un tri, il faut notamment savoir si l’outil renvoie une nouvelle séquence ou modifie celle fournie. Cette lecture évite d’attribuer à une bibliothèque le comportement seulement supposé par son nom.
Des langages différents proposent souvent les mêmes idées de base avec des syntaxes différentes. La condition if de Python et celle de JavaScript organisent toutes deux un choix ; les accolades ou l’indentation ne jouent pas le même rôle dans leur écriture. Pour comparer deux programmes, identifie d’abord l’état, les conditions, les répétitions et les résultats. La ressemblance de mots ne garantit pas l’égalité de tous les comportements, notamment pour les conversions et les types.
Au bac : présenter une fonction et ses tests
- Écrire le contrat avant d’expliquer les instructions : entrées autorisées, résultat et modifications éventuelles.
- Donner des tests avec attente explicite et justifier les cas limites choisis.
- En cas d’écart, produire une trace courte et expliquer la cause avant la correction.
- Borner la conclusion : tests réussis sur ces cas, puis justification distincte du comportement général. Ces méthodes préparent la poursuite éventuelle en terminale.
Erreurs fréquentes : confondre afficher et renvoyer ; oublier le tableau vide ; n’essayer qu’une branche ; fixer une longueur dans un calcul général ; prendre le succès de quelques essais pour une preuve.
Vocabulaire
- Précondition :
- Condition exigée sur les arguments avant l’appel.
- Postcondition :
- Propriété attendue après l’appel lorsque le contrat d’entrée est respecté.
- Jeu de tests :
- Ensemble d’entrées associées à des résultats attendus.
- Régression :
- Défaut introduit dans un comportement qui fonctionnait avant un changement.
Bilan
| Question | Repère |
|---|---|
| Spécifier | Définir le service attendu. |
| Tester | Chercher des cas capables de révéler un écart. |
| Justifier | Raisonner au-delà des seuls exemples exécutés. |
La rubrique S’entraîner est disponible à l’adresse /exercices. Choisis une fonction courte, écris son contrat puis cherche volontairement un cas qui pourrait la mettre en défaut.
À retenir
- Une fonction peut renvoyer un résultat réutilisable avec return.
- La spécification distingue préconditions et postconditions.
- Un jeu de tests compare des résultats obtenus à des attentes explicites.
- Un test réussi ne démontre pas à lui seul une propriété générale.
- Une trace montre l’évolution des variables dans l’ordre des instructions.
Fiche de révision
Avant l’appel
vérifier le domaine et les cas interdits.
Pour les essais
cas courant, frontière, branche opposée et résultat prévu.
Pour une erreur de calcul
réduire l’entrée, tracer l’état, corriger la cause puis rejouer les tests.
Et maintenant ?
Tu as lu le cours. L’étape suivante tient en une action.
Relis le coursLes exercices de ce chapitre arrivent bientôt.Prolonge l’expérience !
Continue à explorer ce chapitre à travers de nouvelles activités, des défis et des expériences interactives. Avec Premium, l’apprentissage continue !
Ici, tu débloques aussi 2 corrections détaillées.
Parent ? Suivez les progrès de votre enfant.
