Notes S1

1. Tests Unitaires (Vidéo 1)

Configuration :

  1. Créer un répertoire test à la racine du projet.
  2. Marquer ce répertoire comme source de tests : Clic droit Mark Directory as Test Sources Root.
  3. Le package des tests doit être identique à celui du code source (ex: cs108).

Création d’un test (sans JUnit) :

  • Créer une classe de test, par convention NomClasseTest (ex: ArraysTest).
  • Chaque test est une méthode private static boolean qui retourne true si le test passe.
  • Gérer les exceptions avec des blocs try-catch.
  • Utiliser une méthode main pour lancer les tests et afficher les résultats.

Un test de méthode doit couvrir: (1) au moins un cas normal, (2) les cas limites (idéalement tous), (3) les cas d’erreur (idéalement tous).

2. JUnit (Vidéo 2)

Principes :

  1. Utilisation de la bibliothèque JUnit (framework standard de test).
  2. Les méthodes de test :
  • Doivent être annotées @Test.
  • Doivent être des méthodes d’instance (donc non static), non privées, et retourner void.
  • Ne prennent (en général) pas d’arguments. Attention: l’ordre d’exécution des tests est quelconque, donc aucun test ne doit dépendre de l’exécution d’un autre.
  1. Utiliser des assertions pour vérifier les résultats :
  • assertEquals(expected, actual) : Vérifie l’égalité. Pour les double, on peut ajouter une tolérance : assertEquals(expected, actual, delta). Pour des calculs en double, il faut souvent un delta (p.ex. 1e-10) à cause des erreurs d’arrondi (ex: 333.40000000000003).
  • assertTrue(condition) / assertFalse(condition) : Vérifie une condition booléenne.
  • assertThrows(Exception.class, () -> { ... }) : Vérifie qu’une exception est bien levée (le 2e argument est une lambda () -> { ... }).
  1. Plus besoin de méthode main pour tester, ni de vérifications visuelles. L’IDE gère l’exécution.

3. Immuabilité (Vidéos 3-4)

Définition : Un objet est immuable si son état ne peut pas être modifié après sa création.

Règle générale: autant que possible, définissez des classes immuables.

Règles pour créer une classe immuable :

  1. Classe final : Empêche l’héritage (pour éviter les sous-classes mutables).
  2. Attributs private et final :
  • private : Empêche l’accès direct depuis l’extérieur.
  • final : Assure que la référence/valeur ne change pas après l’initialisation dans le constructeur.
  1. Pas de setters : Aucune méthode ne doit permettre de modifier les attributs. Les méthodes comme withYear doivent retourner une nouvelle instance de l’objet.
  2. Initialisation complète : Le constructeur doit initialiser tous les champs.
  3. Copies défensives (voir ci-dessous) si la classe contient des valeurs mutables.

Immuable vs “non modifiable” (read-only): immuable = l’objet ne change jamais; read-only = un code donné ne peut pas modifier l’objet, mais l’objet pourrait changer par ailleurs (p.ex. via sous-classe).

Avantages :

  • Thread-safe : Peut être partagé entre plusieurs threads sans risque.
  • Prévisible : Le code est plus simple à raisonner, car les objets ne changent pas d’état.
  • Clé de Map sûre : Idéal pour être utilisé comme clé dans une HashMap ou HashSet.

4. Immuabilité Avancée (Cours 2)

Copies Défensives

Si une classe immuable contient des attributs qui sont des objets mutables (ex: tableaux, ArrayList), il faut se protéger contre les modifications externes.

Conditions (rappel) pour qu’une classe soit immuable: (1) tous les attributs sont final et jamais modifiés, (2) toute valeur non immuable reçue au constructeur est copiée en profondeur avant stockage, (3) aucune valeur non immuable interne n’est exposée telle quelle (on retourne une copie profonde ou une vue non modifiable).

Cas 1 : Attribut de type tableau (ex: Vector.java) Les tableaux sont toujours mutables en Java.

  • Constructeur : Il faut cloner le tableau reçu en argument pour que la classe possède sa propre version. java public Vector(double[] v) { this.v = v.clone(); // Crée une copie interne }
  • Getter : Il faut retourner un clone du tableau interne pour empêcher l’appelant de le modifier. java public double[] asArray() { return v.clone(); // Retourne une copie }

Cas 2 : Attribut de type List (ex: Vector2.java)

  • Constructeur : On utilise List.copyOf(v). Cette méthode est très pratique car elle crée une copie immuable de la liste fournie. java public Vector2(List<Double> v) { this.v = List.copyOf(v); }
  • Getter : Comme this.v est déjà une liste immuable, on peut la retourner directement. Il n’y a aucun risque que l’appelant la modifie. java public List<Double> asArray() { return v; // Pas besoin de copie ! }

Pattern Builder (Bâtisseur)

Problème : Comment construire un objet immuable complexe, surtout si certains attributs sont optionnels ou si on ne les connaît pas tous au moment de la création ?

Solution : Le pattern Builder.

  1. On crée une classe auxiliaire, souvent static et imbriquée, appelée Builder.
  2. Cette classe Builder est mutable et possède des “setters” pour chaque attribut.
  3. Une fois le Builder configuré, on appelle une méthode build() qui utilise les données du builder pour appeler le constructeur de la classe principale et retourner une instance immuable.

Exemple (inspiré de Date.java) :

// Utilisation
Date.Builder b = new Date.Builder();
b.setY(1903);
b.setM(12);
b.setD(28);
Date d = b.build(); // Crée l'objet Date immuable
 
// --- Définition dans la classe Date ---
public static final class Builder {
    private int y, m, d;
    // ... setters pour y, m, d ...
    public Date build() {
        return new Date(y, m, d);
    }
}

Principe clé : Le Builder est un objet temporaire et local. L’objet immuable qu’il construit est celui qui est stocké et utilisé à long terme.