Comme l’année dernière avec le sujet pagination 7 mes collègues et moi même avons rédigé un sujet de DS sur table (i.e sans ordinateur) pour valider la bonne compréhension du TDD de nos étudiants de seconde année de BUT. L’exercice de cette année était le suivant :

Le sujet

Nous allons chercher à tester une classe “compte en banque”, sur laquelle il est possible de faire les opérations suivantes :

  • static CompteEnBanque creer(String name)
  • void depot(Double montant)
  • void retrait(Double montant)
  • Double solde()
  • List<Transaction> historique()

Rédigez et ordonnez les tests que vous feriez dans une approche TDD. Pour chaque test, donnez l’intention de code qu’il fait émerger.

après correction je reformulerais bien en : Rédigez en français et ordonnez les testes que vous feriez dans une approche TDD. Pour C chaque test, vous décrirez l’intention fonctionnelle du test, c’est la phase Test du TDD, puis les modifications de code qu’il faudrait faire durant la phase implémentation. Enfin vous pourrez décrire la phase de restructuration.

Une solution en français

Voilà ce que je m’attendait à avoir comme proposition de solution :

Test Intention de code
Le solde doit être de 0 à la création du compte Création de la classe CompteEnBanque, de la méthode statique creer et de la méthode solde. Pour l’instant la méthode solde retourne la valeur 0
un dépôt incrémente le solde Création de la méthode depot et d’un attribut valeurDuCompte de type Double qui sera retourné par la méthode solde. La méthode depot incrémente l’attribut valeurDuCompte
un retrait décrémente le solde Création de la méthode retrait qui décrémente l’attribut valeurDuCompte
il doit être impossible de faire un retrait avec un solde insuffisant Ajout du test si (valeurDuCompte - SommeÀRetirer) < 0 alors déclanche une Exception
la création doit être enregistrée dans l’historique Ajout d’un attribut de type List<Transaction> qui va contenir l’ensemble des transactions, ajout de la méthode historique et modification de la factory creer pour ajouter une entrée dans la liste des transactions
le dépôt doit être enregistré dans l’historique Modification de la méthode depot pour ajouter une entrée dans l’historique
le retrait doit être enregistré dans l’historique Modification de la méthode retrait pour ajouter une entrée dans l’historique
il n est pas possible de créer un compte avec le nom d’un compte existant Ajout d’un attribut statique contenant la liste de tous les noms de compte et modification de la fabrique creer pour tester l’unicité du nom de compte

Une solution en Java

Après relecture du sujet et surtout de certaines copies, je m’aperçois que le mot “rédigez” est ambigüe, en effet, rien ne disait que je n’attendais pas de code mais du français. Je vous partage donc aussi une version Java tout à fait acceptable.

Tout le code est disponible sur mon gitlab.

Création

@Test
void le_solde_doit_etre_de_0_a_la_creation() {
    CompteEnBanque compteEnBanqueDeBob = CompteEnBanque.creer("Bob");
    Assertions.assertEquals(0.0, compteEnBanqueDeBob.solde());
}
public class CompteEnBanque {
    public static CompteEnBanque creer(String nom) {
        return new CompteEnBanque();
    }

    public double solde() {
        return 0;
    }
}

Dépôt

@Test
void un_depot_doit_incrementer_le_solde() {
    CompteEnBanque compteDeBob = CompteEnBanque.creer("Bob");
    compteDeBob.depot(12.2);
    Assertions.assertEquals(12.2, compteDeBob.solde());
}
 public class CompteEnBanque {
+    private double valeurDuCompte = 0;
+
     public static CompteEnBanque creer(String nom) {
         return new CompteEnBanque();
     }

     public double solde() {
-        return 0;
+        return valeurDuCompte;
+    }
+
+    public void depot(double montant) {
+        this.valeurDuCompte += montant;
     }
}

Retrait

@Test
void un_retrait_doit_decrementer_le_solde () {
    CompteEnBanque compteDeBob = CompteEnBanque.creer("Bob").with(12.2);
    compteDeBob.retrait(2.2);
    Assertions.assertEquals(10.0, compteDeBob.solde());
}
     }
+
+    public void retrait(double montant) {
+        this.valeurDuCompte -= montant;
+    }
 }

Solde insuffisant

@Test
void il_est_impossible_de_faire_un_retrait_avec_un_solde_insuffisant() {
    CompteEnBanque compteDeBob = CompteEnBanque.creer("Bob");
    Assertions.assertThrows(RuntimeException.class, () -> {
        compteDeBob.retrait(2.2);
    });
}
     public void retrait(double montant) {
+        if (this.valeurDuCompte - montant < 0)
+            throw new RuntimeException("Il manque de l'argent");
         this.valeurDuCompte -= montant;
     }

Création dans l’historique

@Test
void la_creation_doit_etre_enregistree_dans_l_historique() {
    CompteEnBanque compteDeBob = CompteEnBanque.creer("Bob");
    assertEquals(List.of(Transaction.of("Création")), compteDeBob.historique());
}
 public class CompteEnBanque {
     private double valeurDuCompte = 0;
+    private List<Transaction> transactions = new ArrayList<>();

     public static CompteEnBanque creer(String nom) {
-        return new CompteEnBanque();
+        CompteEnBanque compteEnBanque = new CompteEnBanque();
+        compteEnBanque.transactions.add(Transaction.of("Création"));
+        return compteEnBanque;
     }

     ...
+
+    public List<Transaction> historique() {
+        return transactions;
+    }
 }

Dépôt dans l’historique

@Test
void le_depot_doit_etre_enregistre_dans_l_historique() {
    CompteEnBanque compteDeBob = CompteEnBanque.creer("Bob");
    compteDeBob.depot(42);
    assertEquals(List.of(Transaction.of("Création"), Transaction.of("Dépôt de 42.00 €")), compteDeBob.historique());
}
     public void depot(double montant) {
         this.valeurDuCompte += montant;
+        this.transactions.add(Transaction.of(String.format(Locale.US, "Dépôt de %.2f €", montant)));
     }

Retrait dans l’historique

@Test
void le_retrait_doit_etre_enregistre_dans_l_historique() {
    CompteEnBanque compteDeBob = CompteEnBanque.creer("Bob").with(50);
    compteDeBob.retrait(8);
    assertEquals(Transaction.of("Création", "Dépôt de 50.00 €", "Retrait de 8.00 €"), compteDeBob.historique());
}
     public void retrait(double montant) {
         if (this.valeurDuCompte - montant < 0)
             throw new RuntimeException("Il manque de l'argent");
+        this.transactions.add(Transaction.of(String.format(Locale.US, "Retrait de %.2f €", montant)));
         this.valeurDuCompte -= montant;
     }

Pas de duplication de nom

@Test
void il_n_est_pas_possible_de_creer_un_compte_avec_le_nom_d_un_compte_existant() {
    CompteEnBanque.creer("Bob");
    assertThrows(RuntimeException.class, () -> {
        CompteEnBanque.creer("Bob");
    });
}
 public class CompteEnBanque {
+    private static final List<String> nomsDesComptes = new ArrayList<>();
     private final List<Transaction> transactions = new ArrayList<>();
     private double valeurDuCompte = 0;

     public static CompteEnBanque creer(String nom) {
+        if (nomsDesComptes.contains(nom)) throw new RuntimeException("Le compte existe déjà");
         CompteEnBanque compteEnBanque = new CompteEnBanque();
         compteEnBanque.transactions.add(Transaction.of("Création"));
+        nomsDesComptes.add(nom);
         return compteEnBanque;
     }