Station MinuitPortail personnel de révision

IFT-1111 — Pratique de l’informatiqueAutomne 2026

Semaine 6 — ION/Algosys — Christian Desrosiers : TDD et évolution du logiciel

8. Démonstration : construire `DitBonjour` par les tests

LECTURE 11 / 169 repèresCouverture déclarée complète

8. Démonstration : construire DitBonjour par les tests

8.1 Le problème, le squelette et les outils

Le conférencier choisit un exemple volontairement simple pour un groupe aux expériences de programmation différentes. Il utilise C#, Visual Studio, MSTest et FluentAssertions. La démarche peut être transposée avec d’autres langages ou frameworks; il ne s’agit pas d’une prescription d’outils pour tous les projets.

Une classe Toto possède DitBonjour(string name). name représente le nom reçu, et le résultat attendu pour un nom valide est une chaîne de salutation. Au départ, la méthode retourne null uniquement pour disposer d’un squelette compilable. La classe de tests est vide.

La succession des comportements est importante : on ne commence pas par écrire tout le code et ajouter ensuite les vérifications. Chaque attente supplémentaire doit provoquer un rouge significatif, puis un petit changement.

8.2 Rejeter le nom nul

Le premier test, DitBonjour_WithNullName_ShouldThrow, prépare un Toto et exige qu’un appel avec null lance une ArgumentNullException. La méthode initiale retourne simplement null; le test doit donc échouer.

L’implémentation minimale ajoute une condition sur name is null et lance l’exception. Après exécution, le test devient vert. Ce résultat ne prouve pas que la méthode sait déjà dire bonjour : il prouve le scénario de rejet qui vient d’être défini.

La structure Arrange–Act–Assert distingue préparation, action et vérification. Quand on attend une exception, l’appel doit être confié à une invocation que l’outil exécutera et vérifiera. L’extrait affiché exprime cette attente :

Code (csharp) · sélectionner pour copier

toto.Invoking(t => t.DitBonjour(null))
    .Should().Throw<ArgumentNullException>();

Le test d’une classe précède son intégration. Une fonctionnalité complète peut ensuite mobiliser plusieurs classes et une vérification d’acceptation plus large.

8.3 Rejeter la chaîne vide sans perdre le cas nul

Le deuxième test, DitBonjour_WithEmptyName_ShouldThrow, transmet string.Empty et attend une ArgumentException. Avant son implémentation, le cas nul reste vert et ce nouveau cas est rouge.

On ajoute la condition sur la chaîne vide et l’exception attendue. Les deux tests deviennent verts. Les différences entre nul et vide restent explicites; pour les autres noms, la méthode peut encore retourner null tant qu’aucun test n’a exigé la salutation.

Le message d’exception n’est pas détaillé dans cette petite étape. Le conférencier reconnaît que l’omission sert à avancer dans la démonstration et n’est pas une bonne pratique générale. Il faut distinguer cette simplification de présentation d’une recommandation de conception d’erreurs.

8.4 Refactorer la validation au stade vert

Les deux conditions explicites peuvent être remplacées par :

Code (csharp) · sélectionner pour copier

ArgumentException.ThrowIfNullOrEmpty(name);

Les tests déjà présents donnent une protection pour vérifier que le remplacement garde les attentes sur nul et vide. Après la modification, on rejoue la suite. C’est la phase de refactoring : le changement rend le code plus compact ou plus moderne, sans ajouter un nouveau comportement.

Le raccourci syntaxique n’est pas le principe principal à mémoriser. Le raisonnement est : disposer des scénarios pertinents, obtenir leur réussite, changer la structure, puis vérifier le maintien du résultat. Le premier tour d’une méthode encore minime peut ne justifier aucun refactoring; il ne faut pas en fabriquer un uniquement pour avoir effectué une étape.

8.5 Refactorer aussi la préparation des tests

La construction new Toto() est répétée. Le conférencier la déplace dans Setup() avec [TestInitialize] et conserve un membre privé _toto. La préparation commune crée une nouvelle instance avant chaque test, plutôt que de faire dépendre les scénarios d’une instance altérée par le précédent.

Si le constructeur reçoit demain un paramètre ou plusieurs dépendances, cette préparation factorisée limite le nombre de lieux à modifier. Le bénéfice est plus visible sur des objets complexes que sur Toto, mais l’exemple montre le mécanisme.

La critique du copier-coller est illustrée par des triangles qu’il faudrait modifier chacun et par la multiplication des copies de code. Les copies augmentent les lieux à corriger et les risques d’oublier une modification. Le code des tests demande donc lui aussi un bon découpage et une vérification après refactoring.

Le panneau des résultats appartient à Visual Studio, distinct de Visual Studio Code. Les outils modernes permettent généralement de visualiser les succès et les échecs; leur interface précise ne définit pas le TDD.

8.6 Une réponse codée en dur suffit à un test trop étroit

Le cas valide appelle DitBonjour("Gontrand") et attend "Bonjour Gontrand!". Le code qui ne produit pas encore de salutation fait échouer le nouveau test. Le conférencier retourne alors volontairement cette réponse en dur; les tests deviennent verts.

La méthode reste pourtant incorrecte pour Joseph, qui recevrait le nom de Gontrand. Le test ne peut pas exiger un comportement qu’il n’a pas distingué. Le vert a une portée limitée au jeu de scénarios effectivement choisi.

Ce contre-exemple complète la vérification du rouge : un test peut réellement détecter une absence, puis laisser passer une implémentation trop particulière. Le choix intelligent des valeurs fait partie du travail de test. À propos des laboratoires universitaires, l’intervenant rappelle que les jeux fournis peuvent différer de ceux utilisés pour corriger : coder les sorties des exemples en dur ne résout pas le problème général.

8.7 Deux noms dans un test paramétré

Pour demander une généralisation, le test reçoit string name et deux DataRow, avec Gontrand et Joseph. L’action doit transmettre ce paramètre et l’attente doit dépendre de la même entrée. Un oubli temporaire — l’action appelait encore Gontrand — est corrigé pendant la séance.

Code (csharp) · sélectionner pour copier

[TestMethod]
[DataRow("Gontrand")]
[DataRow("Joseph")]
public void DitBonjour_WithValidName_ShouldSayHello(string name)
{
    var result = _toto.DitBonjour(name);
    result.Should().BeEquivalentTo($"Bonjour {name}!");
}

Chaque ligne de données provoque une exécution du même corps. La réponse de production constante échoue pour Joseph; une salutation construite à partir du nom reçu satisfait les deux valeurs. La suite protège toujours les anciennes validations de nul et vide.

L’assertion affichée est celle utilisée dans la démonstration. L’échange sur BeEquivalentTo rappelle que les notions d’équivalence et d’égalité ne doivent pas être confondues pour tous les types d’objets ou de collections. La séance n’enseigne pas la spécification complète de toutes les variantes de cette bibliothèque.

8.8 Ce qui a été vérifié, et ce qui ne l’a pas été

Tableau 1 — Scénario effectivement démontré · Attente
Scénario effectivement démontré Attente
Nom nul ArgumentNullException
Chaîne vide ArgumentException
Gontrand Salutation Bonjour Gontrand!
Joseph Salutation Bonjour Joseph!

Le conférencier évoque ensuite des valeurs atypiques, valides mais extrêmes, des noms longs et des caractères spéciaux. Ces cas ne sont pas implémentés pendant la démonstration. On ne peut donc pas attribuer à la petite suite une couverture exhaustive de toutes les entrées.

Le code de l’attente et celui de la salutation se ressemblent particulièrement parce que le problème est trivial. Cela ne signifie pas qu’un test complexe doit recopier l’implémentation qu’il cherche à vérifier. Il doit exprimer le comportement attendu de façon pertinente.

La leçon complète est de définir un petit comportement, observer un échec significatif, écrire le minimum, rejouer les contrôles et améliorer la structure. La répétition de cette boucle protège l’avancement; la qualité du choix des scénarios détermine la valeur de la protection.

Retour au sommaire de l’unitéHaut de page
Récupérer manuellement les données locales

Copier séparément les données ci-dessous dans des fichiers texte avant de fermer la page ou de modifier le stockage. Une valeur illisible ou incompatible reste en place. Aucun effacement ni rétablissement écrasant n’est effectué automatiquement. L’export/import JSON est disponible sur la page Progression du cours. Cette récupération séparée reste utile pour une valeur brute ou un conflit.

Clé de ce cours : . Format v1 porté par cette clé; dates UTC et identifiants durables.

Si l’accès est refusé, la valeur persistée peut rester inaccessible. Réautoriser le stockage dans le navigateur puis rouvrir ce cours permet de la relire. Une réinitialisation exige une décision explicite après récupération; aucune commande de suppression n’est proposée ici. Les données restent sur cette origine et cet appareil, sans transfert automatique.