Paquetages, diagrammes de classes et tests unitaires en Java

Apprends à organiser un programme Java plus conséquent pour qu’il reste facile à comprendre, à modifier et à vérifier. Regroupe les classes en paquetages à l’aide d’importations et de niveaux d’accès adaptés, esquisse leur structure dans des diagrammes de classes UML et sépare l’interface utilisateur, la logique applicative et le stockage des données en couches. Vérifie ensuite la logique avec des tests JUnit, écris les tests avant le code et laisse Maven ou Gradle compiler, tester et empaqueter l’ensemble du projet avec une seule commande.

DébutantUnité 10 sur 11 de la formation Formation : Java orienté objet : classes, collections, héritage et tests

Ce que tu vas apprendre

  1. Regroupe les classes liées en paquetages, stocke chaque fichier source à l’endroit attendu par le compilateur et les outils de construction, et utilise des classes d’autres paquetages grâce aux importations et à un niveau d’accès adapté.
    • Déclare des paquetages et fais-les correspondre aux répertoires
      Écris une déclaration de paquetage, choisis un nom de paquetage conventionnel, stocke le fichier dans le répertoire correspondant et désigne les classes par leur nom pleinement qualifié.
    • Utilise les classes d’autres paquetages grâce aux importations
      Importe des classes individuelles ou des paquetages entiers, identifie les classes qui ne nécessitent aucune importation, résous les conflits entre classes de même nom et utilise les importations statiques.
    • Contrôle les accès entre paquetages
      Choisis entre public, protected, l’accès de paquetage et private pour que chaque paquetage n’expose que ce dont les autres paquetages ont besoin.
  2. Lis et dessine des diagrammes de classes qui montrent les classes, leurs membres et leurs relations, puis passe d’un diagramme au code Java et inversement.
    • Dessine et lis la représentation d’une classe
      Représente le nom, les attributs et les opérations d’une classe avec leurs types et leur visibilité, et indique les membres statiques, les classes abstraites et les interfaces.
    • Représente les associations et les multiplicités
      Relie les classes par des associations, lis et écris les multiplicités et la navigabilité, et distingue la composition, l’agrégation et la dépendance.
    • Modélise l’héritage et passe des diagrammes au code et inversement
      Représente l’héritage et l’implémentation d’interfaces, écris du code Java à partir d’un diagramme et dessine un diagramme à partir de code existant.
  3. Décompose un programme pour placer l’interface utilisateur, la logique applicative et le stockage des données dans des classes et des paquetages distincts, avec des dépendances dirigées dans un seul sens.
    • Sépare l’interface utilisateur de la logique applicative
      Place la lecture des entrées et l’affichage des résultats dans une classe d’interface utilisateur, les règles du programme dans des classes de logique, puis relie les deux dans la méthode main.
    • Organise les couches, les paquetages et les dépendances
      Organise les classes en une couche de présentation, une couche de logique et une couche d’accès aux données, dirige les dépendances vers le bas et relie les couches par des interfaces.
  4. Écris des tests unitaires automatisés avec JUnit, choisis des cas de test qui révèlent des bogues, garde les tests indépendants et lisibles, et développe le nouveau code en écrivant les tests d’abord.
    • Écris et exécute un premier test JUnit
      Crée une classe de test, écris des méthodes de test avec des assertions, exécute-les et lis le résultat d’un test réussi et d’un test échoué.
    • Choisis des cas de test qui révèlent des bogues
      Choisis des entrées typiques, aux limites et invalides, teste les exceptions attendues et donne à chaque test un objectif et un nom précis.
    • Garde les tests indépendants et faciles à maintenir
      Crée de nouveaux objets de test avant chaque test, évite les états partagés, exécute un même test avec plusieurs entrées et teste la logique séparément des entrées et des sorties.
    • Développe du code en écrivant les tests d’abord avec le TDD
      Suis le cycle rouge–vert–refactorisation à partir d’une liste de tests, avance par petites étapes et distingue ce que le développement piloté par les tests promet de ce qu’il ne promet pas.
  5. Utilise un outil de construction pour gérer les bibliothèques, suis l’organisation standard du projet et compile, teste et empaquette un programme avec une seule commande.
    • Configure et construis un projet Maven
      Décris un projet dans pom.xml avec ses coordonnées et ses dépendances, suis l’organisation standard des répertoires et exécute le cycle de vie de construction.
    • Configure un projet Gradle et corrige les échecs de construction
      Écris le script de construction Gradle équivalent, exécute des tests JUnit sur JUnit Platform, compare les deux outils et trouve la cause d’un échec de construction.

Paquetages et tests unitaires Java : cours

5 chapitres

1

Organise un programme Java en paquetages

À mesure qu’un programme grandit, les noms de classes et les répertoires ont besoin d’une structure compréhensible aussi bien pour les personnes que pour Java. Les paquetages regroupent les types liés et définissent des frontières d’accès. Les importations facilitent l’utilisation de ces types sans exposer leurs éléments internes. Tu relieras une déclaration à son chemin source, résoudras les noms entre paquetages et décideras quelles classes, quels constructeurs et quelles méthodes une autre partie du programme doit pouvoir appeler.

1.1 Noms de paquetages et répertoires sources

Un paquetage regroupe des classes et des interfaces liées sous un même nom. Il sépare aussi les noms : deux classes appelées Parcel peuvent coexister lorsque leurs noms de paquetages diffèrent. Un paquetage fournit à Java une frontière d’accès ainsi qu’un moyen d’organiser les fichiers.

Déclarer le paquetage en premier

Un fichier source ordinaire commence par sa déclaration de paquetage, suivie des importations puis de la classe. Des commentaires peuvent précéder la déclaration. Par exemple :

package com.example.parcels.domain;

public class Parcel {
    private int weightGrams;

    public Parcel(int weightGrams) {
        this.weightGrams = weightGrams;
    }
}

Tout type de premier niveau dans ce fichier appartient à com.example.parcels.domain. Un fichier comporte au plus une déclaration de paquetage. La classe publique Parcel doit se trouver dans Parcel.java ; changer uniquement le répertoire ne modifie pas le paquetage déclaré.

Transformer les points en répertoires

La racine des sources est le répertoire où commence le chemin du paquetage. Dans un projet Maven ou Gradle standard, le code Java principal commence dans src/main/java. Remplace chaque point du nom du paquetage par une barre oblique, puis ajoute le nom de la classe :

src/main/java/
  com/example/parcels/domain/Parcel.java

Le paquetage est com.example.parcels.domain, et non src.main.java.com.example.parcels.domain. Les trois premiers répertoires correspondent à l’organisation de la construction ; ils ne font pas partie du nom Java. Lorsqu’un environnement de développement signale une incohérence de paquetage, compare la déclaration au chemin sous la racine des sources. Déplace le fichier ou corrige la déclaration pour les faire correspondre.

Les noms de paquetages utilisent des lettres minuscules. Les organisations inversent généralement leur domaine : example.com devient com.example, suivi du projet et de sa partie concernée. Un projet sans domaine peut utiliser parcels.domain. Si une partie du domaine inversé contient un trait d’union, remplace-le par un trait de soulignement, comme dans org.example.hyphenated_name. Évite l’espace de noms java et javax de la plateforme.

Utiliser le nom complet de la classe

Un nom pleinement qualifié réunit le nom du paquetage et le nom simple de la classe : com.example.parcels.domain.Parcel. Il identifie la classe même lorsqu’un autre paquetage possède aussi une classe Parcel. Le code peut désigner une classe accessible par son nom complet sans importation :

java.util.ArrayList<String> names = new java.util.ArrayList<>();

Pour exécuter une classe principale appartenant à un paquetage, donne son nom complet et un chemin de classes pointant vers la racine de l’arborescence des paquetages compilés. Depuis le répertoire du projet :

javac -d out src/main/java/com/example/parcels/Main.java src/main/java/com/example/parcels/domain/Parcel.java
java -cp out com.example.parcels.Main

Ici, javac -d out crée l’arborescence de sortie et java -cp out recherche à partir de sa racine. Lancer java Main depuis un répertoire de paquetage fait perdre la partie de l’identité de la classe correspondant au paquetage. Les outils de construction et les environnements de développement gèrent normalement ces commandes.

Distinguer les paquetages voisins

com.example.parcels et com.example.parcels.domain sont des paquetages distincts. Le partage d’un préfixe de nom ne donne aucun droit d’accès supplémentaire. Un fichier sans déclaration appartient au paquetage sans nom. Les paquetages nommés ne peuvent pas importer ses classes : utilise donc des paquetages nommés dans tout programme comportant plusieurs parties.

Points clés

  • Place la déclaration de paquetage avant les importations et la classe.
  • Fais correspondre le chemin du paquetage sous la racine des sources ; n’inclus pas src/main/java dans le nom du paquetage.
  • Utilise des noms de paquetages en minuscules et le nom pleinement qualifié de la classe lorsque c’est nécessaire.
  • Un préfixe commun dans les noms de paquetages ne donne pas d’accès de paquetage partagé.
  • Exécute les classes de paquetages depuis la racine de l’arborescence compilée en utilisant leur nom pleinement qualifié.

1.2 Importations et conflits de noms de classes

Une importation permet à un fichier source d’utiliser le nom simple d’un type accessible. Elle indique au compilateur ce que désigne le nom. Elle ne copie pas la classe, ne télécharge pas de bibliothèque et ne rend pas publique une classe masquée.

Lire le début d’un fichier source

L’interface utilisateur appartient à un paquetage et utilise des classes de deux autres :

package com.example.parcels.ui;

import com.example.parcels.domain.Parcel;
import com.example.parcels.domain.ShippingCalculator;
import java.util.Scanner;

public class TextUI {
    private Scanner scanner;
    private ShippingCalculator calculator;
}

Parcel et ShippingCalculator viennent du paquetage du domaine du programme ; Scanner vient de la bibliothèque Java. Chaque importation s’applique uniquement à ce fichier. Un autre fichier qui utilise Scanner doit avoir sa propre importation ou employer le nom complet java.util.Scanner.

Choisir un type unique ou un caractère générique

import java.util.Scanner; est une importation d’un type unique. La classe exacte est visible pour le lecteur. import java.util.*; est une importation avec caractère générique : les types accessibles de ce paquetage peuvent être désignés par leur nom lorsqu’ils sont utilisés. Le caractère générique ne charge pas les classes inutilisées dans le programme et ne le ralentit pas.

Le caractère générique s’arrête à ce paquetage. Il n’inclut pas java.util.function.Function ni java.util.stream.Collectors, qui se trouvent dans des paquetages distincts. La même règle s’applique à ton propre code : import com.example.parcels.*; n’atteint pas com.example.parcels.domain.Parcel.

Trois cas ne nécessitent aucune importation : les types du même paquetage, les types de java.lang disponibles automatiquement et les types désignés par leur nom complet. C’est pourquoi String, System, Math, Integer et Object apparaissent couramment sans importation.

Résoudre un conflit de noms simples

java.util et java.awt possèdent tous deux une classe appelée List. Deux importations d’un type unique pour ces classes différentes provoquent une erreur de compilation. Deux importations avec caractère générique peuvent coexister, mais le nom simple List devient ambigu lorsqu’il est utilisé. Une importation d’un type unique est prioritaire sur une importation avec caractère générique.

Si les deux classes sont nécessaires, importe l’une et écris le nom complet de l’autre :

import java.util.List;

class NameDisplay {
    private List<String> names;
    private java.awt.List widget;
}

Les importations Java ne proposent pas de syntaxe d’alias telle que import ... as .... Le nom pleinement qualifié permet de distinguer le second type.

Importer un membre statique

Une importation statique permet d’appeler un membre statique sans le nom de la classe à laquelle il appartient :

import static java.lang.Math.sqrt;

Le fichier peut maintenant appeler sqrt(16.0). Le code de test utilise souvent import static org.junit.jupiter.api.Assertions.assertEquals; pour que les assertions se lisent comme des vérifications directes. Dans le code ordinaire du programme, utilise les importations statiques avec discernement afin que les lecteurs puissent toujours voir d’où viennent les opérations.

Suivre les indications de l’erreur

« Cannot find symbol » peut indiquer une importation manquante ou un type mal orthographié. « Package ... does not exist » oriente vers un problème d’orthographe du paquetage, de racine des sources ou de dépendance. « Is not public ... cannot be accessed from outside package » signifie que Java a trouvé le type, mais que l’accès est bloqué. Ajouter une autre importation ne peut pas corriger cette erreur d’accès ; examine la déclaration de la classe et les membres dont tu as besoin.

Points clés

  • Une importation résout les noms dans un seul fichier et n’accorde aucun droit d’accès.
  • Un caractère générique inclut les types d’un seul paquetage, sans ses sous-paquetages.
  • Les types du même paquetage et ceux de java.lang ne nécessitent aucune importation.
  • Utilise une importation d’un type unique et un nom pleinement qualifié lorsque deux classes nécessaires portent le même nom.
  • Une importation statique permet d’appeler un membre statique sans le nom de sa classe.
  • Distingue une erreur de résolution de nom d’une erreur d’accès.

1.3 Une interface publique réduite

Un paquetage doit exposer les classes et les méthodes dont les autres parties ont besoin tout en masquant les détails d’implémentation. Les modificateurs d’accès permettent de faire respecter cette frontière. Une importation peut raccourcir un nom, mais elle ne peut pas franchir une frontière fermée par les règles d’accès de Java.

Choisir l’accès à un membre de classe
AccèsQui peut utiliser le membre ?
publicLe code qui peut accéder au type déclarant
protectedLe même paquetage, ainsi que les sous-classes d’autres paquetages sous certaines conditions
Sans modificateurLe même paquetage exact
privateLa classe déclarante et ses types imbriqués

Sans modificateur, les membres ordinaires d’une classe et ses constructeurs ont un accès de paquetage. Ce n’est pas un mot-clé nommé default. Une classe de premier niveau n’a que deux possibilités : publique ou avec un accès de paquetage. Une classe publique de premier niveau est stockée dans un fichier portant son nom, comme ShippingCalculator.java.

Vérifier le type et l’opération

Supposons que l’interface utilisateur doive construire un colis. Une classe publique ne suffit pas à elle seule :

package com.example.parcels.domain;

public class Parcel {
    private int weightGrams;

    Parcel(int weightGrams) {
        this.weightGrams = weightGrams;
    }
}

Le constructeur a un accès de paquetage. Le code de com.example.parcels.ui peut désigner Parcel, mais ne peut pas appeler new Parcel(1200). Rends ce constructeur public si la création de colis fait partie du service public du paquetage. Garde weightGrams privé et expose une opération adaptée au lieu d’ouvrir l’accès au champ.

L’accès de paquetage tient compte du nom complet du paquetage. Un paquetage qui semble parent ou enfant n’est pas le même paquetage. Le code de com.example.parcels n’a aucun accès particulier aux éléments auxiliaires de com.example.parcels.domain.

Comprendre l’accès protégé

protected autorise l’accès depuis les classes sans lien de parenté du paquetage déclarant ainsi que depuis les sous-classes. Entre paquetages, l’accès à un membre d’instance passe par le contexte d’héritage de la sous-classe. Une sous-classe peut utiliser son propre champ hérité, mais ne peut pas utiliser un objet quelconque de la superclasse pour contourner la frontière du paquetage.

Par exemple, si Item déclare protected int weightGrams, une classe Letter extends Item située dans un autre paquetage peut utiliser this.weightGrams. Dans Letter, un paramètre dont le type est simplement Item ne permet pas d’utiliser item.weightGrams ; une référence de type Letter permet l’accès au membre hérité. Cette restriction évite que les privilèges d’une sous-classe deviennent un accès général à chaque Item.

Masquer délibérément les éléments auxiliaires

Rends ShippingCalculator et feeInCents publics si l’interface utilisateur les appelle. Une classe auxiliaire WeightBands peut garder un accès de paquetage. Le code extérieur au paquetage du domaine dépend alors du comportement du calculateur, pas de la façon dont il stocke le tableau. Remplacer cette classe auxiliaire demande moins de modifications ailleurs.

Les tests peuvent utiliser le même nom de paquetage que la classe testée tout en se trouvant sous src/test/java. Cela leur donne un accès de paquetage sans placer le code de test parmi les fichiers de production. Cela ne donne pas accès aux champs privés. Rendre chaque membre public uniquement pour satisfaire un test multiplie les dépendances aux éléments internes ; teste plutôt le comportement public ou une opération utile à l’échelle du paquetage.

Points clés

  • Les membres d’une classe ayant un accès de paquetage ne sont accessibles que dans le même paquetage exact.
  • Le code appelant doit avoir accès à la fois à la classe et au constructeur ou à la méthode qu’il utilise.
  • L’accès protégé inclut l’accès depuis le même paquetage et un accès limité aux sous-classes d’autres paquetages.
  • Garde les champs privés et expose uniquement les opérations dont un autre paquetage a besoin.
2

Esquisse la structure d’un programme avec des diagrammes de classes UML

Un diagramme de classes te donne une vue d’ensemble compacte d’un programme avant que tu lises les corps de ses méthodes. Les boîtes décrivent les types et les opérations ; les traits décrivent les liens, les nombres d’objets et les relations entre types et sous-types. Tu liras ces marques avec précision et les traduiras en déclarations Java. Le programme de colis fournit des exemples de classes individuelles, de collections d’objets associés et d’implémentations de dépôt interchangeables.

2.1 Ce qu’indique une boîte de classe UML

Un diagramme de classes UML montre la structure statique : les classes, leurs membres et leurs relations. Il aide le lecteur à voir quels types d’objets existent et ce qu’ils proposent. Il ne suit pas l’ordre des appels de méthodes pendant l’exécution.

Lire les trois compartiments

Une boîte de classe comporte le nom de la classe en haut, les attributs au milieu et les opérations en bas. Un attribut représente généralement un champ Java ; une opération représente la signature visible d’une méthode. Une esquisse peut omettre les compartiments qui ne servent pas son objectif.

Pour un colis, voici une représentation textuelle de la boîte :

Parcel
-----------------------------
- weightGrams: int
- recipient: String
-----------------------------
+ Parcel(weightGrams: int, recipient: String)
+ getWeightGrams(): int
+ isHeavierThan(limitGrams: int): boolean

Le constructeur porte le nom de la classe et n’a pas de type de retour. La méthode isHeavierThan accepte un entier nommé limitGrams et renvoie un booléen.

Traduire l’ordre des noms et des types

UML place le nom avant le type : weightGrams: int. Java place le type en premier : int weightGrams. Une opération suit le même ordre, avec le nom en premier pour les paramètres, puis place le type de retour après sa parenthèse fermante.

private int weightGrams;

public boolean isHeavierThan(int limitGrams) {
    return weightGrams > limitGrams;
}

Ce code correspond à - weightGrams: int et à + isHeavierThan(limitGrams: int): boolean. Le corps d’une méthode est généralement absent du diagramme ; l’opération indique ce qui peut être appelé, pas l’algorithme.

Préserver la visibilité et les marques particulières

Les symboles de visibilité sont + public, - private, # protected et ~ accès de paquetage. Place le symbole avant le nom du membre. Si un diagramme fourni omet un symbole, n’invente pas sa visibilité ; ajoute le symbole voulu lorsque tu documentes du code.

Un membre statique est souligné. Il appartient à la classe plutôt qu’à chaque objet. Une classe ou une opération abstraite est écrite en italique ou marquée {abstract} lorsque l’italique est peu pratique. La marque abstraite signifie qu’une sous-classe doit fournir le comportement manquant avant qu’un objet puisse être créé à partir d’une classe concrète.

Une boîte d’interface porte «interface» au-dessus de son nom. Par exemple, ParcelRepository peut proposer + save(parcel: Parcel): void et + findAll(): List<Parcel>. La boîte indique un contrat ; elle ne choisit pas d’implémentation du stockage.

Choisir le détail dont le lecteur a besoin

Une esquisse de planification peut ne montrer que Parcel, Shipment et leurs principales opérations. Un diagramme destiné à implémenter une classe doit indiquer les types et la visibilité. Énumérer chaque accesseur courant peut masquer les relations que le diagramme devait montrer. Sélectionne les membres utiles et garde-les cohérents avec le code : un champ Java privé ne doit pas apparaître avec le symbole public + et une méthode statique doit être soulignée.

Points clés

  • Une boîte de classe sépare le nom, les attributs et les opérations.
  • UML utilise name: Type ; Java utilise Type name.
  • Utilise +, -, # et ~ pour les visibilités publique, privée, protégée et de paquetage.
  • Souligne les membres statiques ; marque les éléments abstraits en italique ou avec {abstract}.
  • Une boîte d’interface montre «interface» et les opérations de son contrat.

2.2 Lis les extrémités des traits de relation

Une association montre un lien structurel entre des objets. En Java, un objet conserve généralement une référence vers un autre dans un champ. Le trait peut aussi indiquer combien d’objets sont autorisés et dans quel sens la référence peut être suivie.

Compter depuis l’extrémité opposée

La multiplicité se trouve à côté de la classe dont elle dénombre les objets. Si 1..* apparaît à côté de Parcel et 1 à côté de Shipment, une expédition possède un ou plusieurs colis et chaque colis appartient à exactement une expédition. Lis chaque extrémité séparément, en partant d’un objet situé à l’extrémité opposée.

MultiplicitéSignification
1Exactement un
0..1Zéro ou un
* ou 0..*Un nombre quelconque, zéro compris
1..*Au moins un
2..4De deux à quatre

Une référence unique peut représenter 1 ou 0..1 ; une collection représente une extrémité pouvant comporter plusieurs objets. Une List Java seule n’impose pas de borne inférieure. Un Shipment soumis à une règle de 1..* colis doit rejeter une liste initiale vide et empêcher les opérations ultérieures de la vider. De même, une référence unique obligatoire doit être vérifiée pour exclure null.

Suivre la référence

Une flèche ouverte à l’extrémité Parcel signifie qu’une référence peut être suivie de Shipment vers Parcel. Un diagramme de classes peut laisser la navigabilité non précisée. Dans la convention pédagogique utilisée ici, les flèches indiquent les références à sens unique ; un trait sans symbole peut signifier un accès bidirectionnel ou une direction non précisée. Ne déduis pas l’existence d’un champ inverse de la seule présence d’une flèche vers Parcel.

Un nom de rôle tel que parcels indique ce que signifie le lien depuis l’expédition. Représente cette référence soit par une association, soit par - parcels: List<Parcel> dans la boîte. Montrer les deux sans raison peut donner l’impression qu’un champ correspond à deux éléments d’état différents.

Choisir le sens de la relation tout-partie

La composition utilise un losange plein du côté du tout. Une partie appartient à au plus un tout composite à la fois et est normalement supprimée avec ce tout, comme une commande et ses lignes de commande. L’agrégation utilise un losange creux pour un regroupement plus souple dont les parties peuvent exister indépendamment. Une équipe peut regrouper des joueurs sans que leur durée de vie dépende d’elle.

Java n’a pas de mot-clé pour le losange et n’impose pas la propriété au sens d’UML simplement parce qu’une liste existe. Modélise une composition uniquement lorsque la conception inclut cette règle de propriété. Lorsque la conception indique simplement qu’un objet en connaît un autre, une association simple est plus claire qu’un losange sans fondement.

Montrer une utilisation sans affirmer une propriété

Une dépendance est un trait en pointillés terminé par une flèche ouverte vers le type utilisé. Un calculateur doté de feeInCents(Parcel parcel) dépend de Parcel, car la signature de sa méthode utilise ce type. Une dépendance est une relation générale d’utilisation ; elle ne se limite pas au code sans champs. Une association fournit une information structurelle plus précise lorsqu’un champ est important pour le modèle.

Avant de tracer un trait, demande-toi ce que le code ou l’exigence établit : une référence, un nombre autorisé, une utilisation ou une propriété. Une collection établit un stockage pouvant contenir plusieurs objets, mais elle n’établit pas à elle seule une propriété exclusive ou un nombre minimal.

Points clés

  • La multiplicité à côté d’une classe dénombre ses objets pour un objet à l’autre extrémité.
  • Un type collection n’impose pas de multiplicité minimale ou maximale.
  • Une flèche d’association pointe vers le type auquel la source peut accéder par navigation.
  • Place le losange de composition ou d’agrégation du côté du tout.
  • Une dépendance est une flèche d’utilisation en pointillés ; une association décrit un lien structurel.
  • Ne montre une référence qu’une fois, sauf si le diagramme nécessite délibérément les deux représentations.

2.3 D’un diagramme aux déclarations Java

L’héritage et l’implémentation d’une interface relient tous deux un type spécifique à un type plus général. UML utilise un triangle creux du côté du type général, mais le style du trait distingue les deux relations. Cette distinction indique quelle déclaration Java écrire.

Lire le triangle et son trait

Un trait plein terminé par un triangle creux du côté de Item signifie qu’une sous-classe comme Letter étend Item. C’est une généralisation. Un trait en pointillés terminé par un triangle creux du côté de ParcelRepository signifie qu’une classe comme InMemoryParcelRepository implémente cette interface. C’est une réalisation.

Le triangle touche toujours la superclasse ou l’interface. Une flèche ouverte en pointillés, sans triangle, indique une dépendance. Le style du trait et la pointe de la flèche portent ensemble la signification ; un trait en pointillés seul ne suffit pas.

Construire le squelette dans un ordre utile
  1. Crée chaque classe ou interface dans son paquetage.
  2. Traduis les attributs en champs en conservant les noms, les types et la visibilité.
  3. Traduis les associations navigables en champs contenant une référence ou une collection.
  4. Ajoute extends ou implements à partir des traits de généralisation ou de réalisation.
  5. Traduis les opérations en en-têtes de méthodes, puis complète leurs corps.

Supposons qu’une interface propose save et findAll. Une implémentation possède une association vers plusieurs objets Parcel, avec le rôle parcels :

public class InMemoryParcelRepository implements ParcelRepository {
    private List<Parcel> parcels = new ArrayList<>();

    @Override
    public void save(Parcel parcel) {
        parcels.add(parcel);
    }

    @Override
    public List<Parcel> findAll() {
        return new ArrayList<>(parcels);
    }
}

Le trait en pointillés terminé par un triangle donne implements ParcelRepository. L’association donne le champ contenant la liste. Les signatures des opérations donnent les deux méthodes publiques. Les corps des méthodes relèvent d’un choix d’implémentation : ici, save ajoute un objet et findAll renvoie une nouvelle liste pour que le code appelant ne puisse pas ajouter ou supprimer directement des éléments dans la liste stockée. Les objets colis eux-mêmes restent partagés.

Dessiner dans l’autre sens

Commence par une boîte pour chaque classe ou interface. Un champ contenant une valeur simple devient un attribut. Une référence à une classe du programme devient une association ; une collection suggère une extrémité pouvant comporter plusieurs objets. Examine les vérifications des constructeurs et des méthodes avant d’affirmer que la borne inférieure est de un. Traduis extends par un trait plein terminé par un triangle et implements par un trait en pointillés terminé par un triangle.

Les champs et les opérations hérités restent généralement dans la boîte de la superclasse. Place les membres nouveaux ou redéfinis dans la sous-classe. Ajoute une dépendance pour un paramètre, un type de retour ou une utilisation locale lorsque cette relation aide à expliquer la conception.

Les paquetages peuvent entourer leurs classes dans des boîtes en forme de dossier avec un petit onglet. Ces groupes aident à voir quelles parties dépendent des autres. Tiens à jour un diagramme de documentation lorsque le code change ; indique qu’une esquisse de planification est préliminaire pour que les lecteurs comprennent son objectif.

Un Shipment qui contient des colis n’est pas une sorte de Parcel. Sa liste donne une relation « possède » : dessine donc une association. Utilise l’héritage pour une relation « est une sorte de » que le système de types et le comportement Java prennent réellement en charge.

Points clés

  • La généralisation utilise un trait plein et un triangle creux du côté de la superclasse.
  • La réalisation utilise un trait en pointillés et un triangle creux du côté de l’interface.
  • Traduis les champs, les associations, l’héritage et les signatures de méthodes avant d’écrire les corps.
  • Conserve les types et la visibilité du diagramme lors de la traduction.
  • Le type déclaré d’une collection ne suffit pas à établir un nombre minimal.
  • Utilise des associations pour les objets contenus et l’héritage pour les relations entre types et sous-types.
  • Garde les diagrammes de documentation cohérents avec le code qu’ils décrivent.
3

Structure un programme en couches

Un programme devient plus facile à tester et à modifier lorsque son dialogue, ses règles et son stockage relèvent de responsabilités distinctes. Tu suivras une commande de colis à travers ces parties, déplaceras les calculs hors du code de la console et transmettras les collaborateurs par les constructeurs. Les interfaces permettent à la logique de fonctionner avec un stockage réel ou un substitut en mémoire, tandis qu’une lecture attentive des importations révèle les types dont chaque couche dépend.

3.1 Séparer le dialogue des calculs

L’interface utilisateur lit les commandes et affiche les résultats. La logique applicative contient les règles et les données sur lesquelles portent ces commandes. Leur séparation permet à un test d’appeler directement un calcul et à une autre interface de le réutiliser sans copier la règle.

Donner une tâche précise à chaque partie

TextUI prend en charge le dialogue : demander un poids, lire une ligne avec Scanner, convertir le texte en nombre, appeler la logique et afficher le résultat. ShippingCalculator accepte un poids en grammes et renvoie des frais en centimes. Il ne pose aucune question à l’utilisateur et n’affiche aucune réponse.

Une méthode appelée printFee mélange ces tâches si elle calcule et affiche à la fois. Remplace l’opération de logique par feeInCents(int weightGrams). L’interface peut alors afficher "Fee: " + calculator.feeInCents(weightGrams) + " cents". Un test peut vérifier par une assertion l’entier renvoyé sans capturer la sortie de la console.

Écrire la règle avec une valeur de retour

La règle d’expédition prévoit des frais de 500 centimes pour les poids positifs jusqu’à 2 000 g inclus, de 900 centimes jusqu’à 10 000 g inclus, puis de 100 centimes supplémentaires par tranche de 1 000 g entamée au-dessus de 10 000 g.

public int feeInCents(int weightGrams) {
    if (weightGrams <= 0) {
        throw new IllegalArgumentException("weight must be positive");
    }
    if (weightGrams <= 2000) {
        return 500;
    }
    if (weightGrams <= 10000) {
        return 900;
    }
    long excess = (long) weightGrams - 10000;
    long startedBlocks = (excess + 999) / 1000;
    return (int) (900 + 100 * startedBlocks);
}

Les deux premières branches qui renvoient un résultat incluent leurs bornes supérieures. Pour 12 500 g, l’excédent est de 2 500 g. La division entière après l’ajout de 999 compte trois tranches entamées, ce qui donne 1 200 centimes. Les calculs intermédiaires en long permettent aussi de traiter de grands poids positifs de type int sans dépassement de capacité lors de l’ajout de 999.

Un poids invalide provoque une IllegalArgumentException : la méthode rejette un argument qui ne respecte pas sa règle. L’interface peut intercepter cette exception et choisir un message lisible. La règle appartient à la logique ; la formulation du message appartient à l’interface.

Relier les objets dans main

Transmets le lecteur d’entrée et le calculateur au constructeur de l’interface :

public static void main(String[] args) {
    Scanner scanner = new Scanner(System.in);
    ShippingCalculator calculator = new ShippingCalculator();
    TextUI ui = new TextUI(scanner, calculator);
    ui.start();
}

Crée un seul scanner pour le flux d’entrée et partage-le avec l’interface. Plusieurs scanners utilisant System.in peuvent interférer avec leurs entrées mises en tampon. La méthode main assemble le programme ; start exécute la boucle de dialogue.

Lorsque tu examines une classe, cherche ses responsabilités. Un Scanner ou System.out dans un calculateur de frais révèle un mélange entre les entrées-sorties et les règles. Une formule de calcul des frais dans l’interface révèle le même problème de l’autre côté. Déplace le calcul dans une méthode avec une entrée explicite et un résultat ; garde l’analyse des commandes et le texte affiché ensemble dans l’interface.

Points clés

  • L’interface lit et affiche ; les méthodes de logique acceptent des valeurs et renvoient des résultats.
  • Garde les règles de calcul des frais et la validation dans la logique afin que les tests puissent les appeler directement.
  • Laisse l’interface choisir comment afficher un résultat ou une entrée rejetée.
  • Crée les collaborateurs dans main et transmets-les au constructeur de l’interface.
  • Compte une tranche de poids partiellement utilisée comme une tranche entamée lorsque la règle l’exige.

3.2 Relier les couches par un dépôt

Un programme en couches regroupe les classes selon leur responsabilité. La couche de présentation gère le dialogue, la couche de logique applique les règles et la couche d’accès aux données enregistre et récupère les informations. Une classe utilise une autre couche par une opération clairement définie au lieu d’accéder à ses champs internes.

Placer les classes du programme de colis

Le paquetage de l’interface utilisateur contient TextUI. Celui du domaine contient Parcel, ShippingCalculator et ParcelService. Celui des données contient ParcelRepository et ses implémentations de stockage. Main assemble les objets au-dessus de ces couches.

Une commande suit ce parcours : l’utilisateur saisit « add 1200 Alex » ; l’interface analyse la ligne et appelle service.register(1200, "Alex") ; le service valide le poids, crée un colis, l’enregistre par l’intermédiaire du dépôt et renvoie 500 centimes ; l’interface affiche les frais. La lecture du texte, la décision de ce qui est autorisé et le stockage du résultat sont placés séparément.

Dépendre d’un contrat

Le service a besoin d’un comportement de stockage, pas d’un format de fichier particulier. Exprime ce comportement par une interface :

public interface ParcelRepository {
    void save(Parcel parcel);
    List<Parcel> findAll();
}

Le service stocke une référence de type ParcelRepository et la reçoit dans son constructeur :

public class ParcelService {
    private final ParcelRepository repository;

    public ParcelService(ParcelRepository repository) {
        this.repository = repository;
    }
}

C’est une injection par constructeur, une forme d’injection de dépendances : le code appelant fournit un collaborateur. Un service qui construit new FileParcelRepository(...) dans ses propres méthodes choisit lui-même le stockage dans des fichiers et rend les tests isolés plus difficiles.

Changer l’implémentation lors de l’assemblage

Le programme exécuté peut créer new ParcelService(new FileParcelRepository("parcels.csv")). Un test peut fournir new InMemoryParcelRepository(). Les deux implémentations respectent la même interface. Les appels par l’intermédiaire de l’interface utilisent le polymorphisme pour atteindre les méthodes de l’objet effectif.

Si le contrat du dépôt reste inchangé, changer de stockage nécessite une nouvelle implémentation et une modification lors de l’assemblage. Les règles de calcul des frais et le code du dialogue n’ont pas besoin de connaître la façon dont les données sont stockées.

Vérifier précisément la direction des dépendances

Le parcours descendant d’utilisation des services va de l’interface utilisateur à la logique, puis au stockage. Le domaine ne doit ni importer ni appeler les classes de l’interface utilisateur. Si la logique doit communiquer un résultat à l’utilisateur, renvoie-le à l’interface. Si l’interface enregistre directement par l’intermédiaire du dépôt, elle peut contourner la validation du service.

Les références aux types Java comptent aussi : une signature du dépôt qui nomme Parcel dépend de ce type du domaine. Si ParcelRepository se trouve dans le paquetage des données et le service dans celui du domaine, ces paquetages font référence l’un à l’autre. Pour supprimer ce cycle entre paquetages, place l’interface du dépôt à côté des types du domaine et laisse les implémentations des données l’importer. Le service utilise toujours le même contrat ; seul l’emplacement de celui-ci change. Examine à la fois le parcours des appels de méthodes et les importations lorsque tu vérifies la conception.

Les programmes plus conséquents peuvent d’abord regrouper les paquetages par fonctionnalité, comme les commandes ou les clients, puis séparer les responsabilités au sein de chaque fonctionnalité. Les noms de paquetages aident les lecteurs à localiser les responsabilités ; les paramètres des constructeurs et les signatures des méthodes montrent les dépendances effectives.

Points clés

  • Sépare les responsabilités de présentation, de logique et d’accès aux données.
  • Transmets une interface de dépôt au constructeur du service.
  • Choisis une implémentation sur fichier ou en mémoire à l’endroit où le programme est assemblé.
  • Fais passer les actions de l’interface utilisateur par la logique pour que la validation soit appliquée.
  • Renvoie les résultats à l’interface utilisateur au lieu d’appeler son code depuis la logique.
  • Distingue la direction d’utilisation des services des dépendances entre types Java lorsque tu vérifies les cycles entre paquetages.
4

Vérifie du code avec des tests JUnit

Les tests automatisés transforment le comportement attendu en vérifications que tu peux relancer après chaque modification. Écrire une vérification n’est qu’un début : des entrées bien choisies révèlent les limites, des fixtures recréées gardent les tests indépendants et des échecs lisibles t’aident à trouver la cause. Tu construiras des tests Jupiter pour la logique du programme de colis, contrôleras leurs données et leurs collaborateurs, puis utiliseras un court cycle où les tests sont écrits d’abord pour développer une règle cas par cas.

4.1 Un test qui vérifie un résultat

Un test unitaire appelle une petite unité de code avec une entrée choisie et vérifie automatiquement le résultat. Il rend une exigence exécutable. Relancer les tests après une modification aide à trouver une régression, c’est-à-dire un bogue dans un comportement qui fonctionnait auparavant.

Créer un test Jupiter

JUnit Jupiter est le modèle de programmation utilisé par JUnit 5 et 6. JUnit 6 nécessite Java 17 ou une version ultérieure. Les annotations et les assertions de test utilisent org.junit.jupiter.api.

Place ShippingCalculatorTest.java sous src/test/java/com/example/parcels/domain, avec la même déclaration de paquetage que le calculateur. La racine des sources distincte sépare les tests du code du programme livré tout en conservant l’accès au même paquetage.

package com.example.parcels.domain;

import static org.junit.jupiter.api.Assertions.assertEquals;
import org.junit.jupiter.api.Test;

class ShippingCalculatorTest {
    @Test
    void lightParcelCostsBaseFee() {
        ShippingCalculator calculator = new ShippingCalculator();

        int fee = calculator.feeInCents(1500);

        assertEquals(500, fee);
    }
}

La classe et la méthode de test peuvent avoir un accès de paquetage. Une méthode de test renvoie void et ne doit pas être privée. Utilise @Test de Jupiter ; org.junit.Test de JUnit 4 appartient à un autre modèle de programmation et n’est pas découvert par le moteur Jupiter. Les assertions JUnit 4 peuvent fonctionner lorsque leur bibliothèque est disponible, mais utiliser systématiquement les importations Jupiter rend la configuration plus claire.

Lire les étapes préparer–agir–vérifier

La première partie prépare un calculateur. La deuxième agit en demandant les frais. L’assertion finale compare les 500 centimes attendus à la valeur renvoyée. Les lignes vides permettent de repérer facilement ces tâches. Afficher fee seul laisserait à une personne le soin de décider si la valeur est correcte ; l’assertion intègre cette décision au test.

assertEquals(expected, actual) place l’exigence en premier et le résultat observé en second. Un message facultatif vient en dernier, comme dans assertEquals(500, fee, "fee for 1,500 g"). Inverser les deux premiers arguments ne change généralement pas la réussite ou l’échec pour ces valeurs, mais inverse le sens du rapport d’échec.

Choisir l’assertion adaptée

Utilise assertTrue ou assertFalse pour un booléen, et assertNull ou assertNotNull lorsque le résultat porte sur la présence d’un objet. Les vérifications d’égalité des objets utilisent equals ; sans redéfinition, deux objets distincts dont les champs correspondent peuvent malgré tout être considérés comme différents.

Les résultats en virgule flottante nécessitent une tolérance adaptée. Le calcul 0.1 + 0.2 produit 0.30000000000000004 dans un double Java :

assertEquals(0.3, 0.1 + 0.2, 1e-9);

Le troisième argument autorise une différence absolue allant jusqu’à 0.000000001. Choisis une tolérance adaptée au calcul ; une tolérance trop grande peut masquer une erreur qui a un sens. Les calculs en centimes entiers ne nécessitent aucune tolérance décimale.

Lire un échec

Exécute un test depuis l’environnement de développement ou l’outil de construction. Un rapport tel que « expected: <500> but was: <900> » indique les résultats attendu et observé ; la trace de pile pointe vers l’assertion. Une exception inattendue fait aussi échouer un test. Le vert signifie que les cas vérifiés ont réussi ; leur choix reste une partie distincte du travail de test.

Points clés

  • Utilise les annotations et les assertions Jupiter de org.junit.jupiter.api.
  • Place les tests dans src/test/java, normalement dans le paquetage de la classe testée.
  • Prépare les objets, agis sur eux et vérifie le résultat par une assertion.
  • Place la valeur attendue en premier dans assertEquals.
  • Utilise une tolérance justifiée pour les comparaisons en virgule flottante.
  • Lis le nom du test échoué, les valeurs attendue et obtenue et l’emplacement de l’assertion.

4.2 Choisir des entrées aux limites des règles

Un ensemble de tests utile vérifie la façon dont la spécification répartit les entrées en cas. Une entrée ordinaire teste rarement toute une règle. Les cas limites, à une limite et juste à côté, sont particulièrement efficaces pour détecter une condition écrite avec le mauvais opérateur de comparaison.

Répartir les entrées en classes

Une classe d’équivalence regroupe les entrées qui doivent être traitées de la même façon. La règle d’expédition comporte des poids invalides à zéro ou en dessous, une tranche à 500 centimes jusqu’à 2 000 g inclus, une tranche à 900 centimes jusqu’à 10 000 g inclus et un calcul par tranches entamées au-delà. Choisis des entrées représentatives des classes, puis examine leurs limites.

Poids en grammesRésultat attendu
0, -5Rejet avec IllegalArgumentException
1, 2000500 centimes
2001, 10000900 centimes
10001, 110001 000 centimes
110011 100 centimes

Remplacer <= 2000 par < 2000 affecte exactement 2 000 g. Un test à 1 500 g réussit toujours. Au-dessus de 10 000 g, teste à la fois une tranche entière et le premier gramme de la tranche suivante pour vérifier l’arrondi supérieur.

Les valeurs attendues proviennent de l’exigence et d’un calcul à la main. Pour 12 500 g, l’excédent est de 2 500 g, ce qui représente trois tranches entamées : le résultat attendu est donc de 1 200 centimes. Exécuter la méthode et copier sa réponse dans le test copierait aussi tout bogue éventuel.

Tester une entrée rejetée

Un test d’exception fournit à JUnit l’opération à exécuter :

assertThrows(IllegalArgumentException.class,
    () -> calculator.feeInCents(0));

L’expression () -> ... est une expression lambda : un petit morceau de code fourni à JUnit. JUnit l’exécute pendant la vérification. Appeler la méthode avant de la transmettre à l’assertion laisserait l’exception se propager avant que la vérification puisse l’examiner.

assertThrows accepte le type d’exception indiqué ou une sous-classe. L’assertion échoue si l’opération se termine normalement ou lève un type incompatible. Elle renvoie l’exception, ce qui permet de vérifier un message exigé :

IllegalArgumentException error = assertThrows(
    IllegalArgumentException.class,
    () -> calculator.feeInCents(0));
assertEquals("weight must be positive", error.getMessage());
Donner un nom lisible à un scénario

Des noms comme zeroWeightIsRejected et feeIsBaseFeeAtExactly2000Grams identifient le comportement recherché. Un test peut comporter plusieurs assertions lorsqu’elles vérifient le même résultat. Après l’ajout de colis de 1 200 g et 800 g à une expédition, vérifier un nombre de deux et un poids total de 2 000 g décrit ce même scénario. assertAll peut regrouper les vérifications afin que tous les échecs soient signalés.

Vérifie les objets modifiés par leur comportement, par exemple avec parcelCount() et totalWeightGrams(), au lieu d’exposer les champs privés pour les tests. Une refactorisation peut alors modifier les détails du stockage tout en préservant les vérifications. Une couverture complète des lignes signifie que les tests ont exécuté les lignes ; elle peut malgré tout laisser passer une limite erronée ou une entrée invalide non testée.

Points clés

  • Répartis les entrées selon la spécification et teste des cas représentatifs ainsi que les limites.
  • Vérifie la limite elle-même et les valeurs voisines pour détecter les erreurs de décalage d’une unité.
  • Calcule les résultats attendus indépendamment de l’implémentation.
  • Transmets l’opération à assertThrows sous forme d’expression lambda.
  • Donne à chaque test le nom d’un scénario ; plusieurs assertions liées peuvent vérifier ce scénario.
  • Teste les changements d’état par un comportement observable par le code appelant.

4.3 Des tests exécutables seuls et dans n’importe quel ordre

Chaque test doit préparer tout ce dont il a besoin. Un test qui ne réussit qu’après l’ajout de données par un autre test dépend d’un historique caché. Il devient difficile de faire confiance à de tels tests lorsqu’un exécuteur change l’ordre ou n’exécute qu’une seule méthode.

Créer une nouvelle fixture

Une fixture de test est l’ensemble des objets et des données utilisés par un test. Place la préparation répétée avant chaque test dans @BeforeEach :

class ShipmentTest {
    private Shipment shipment;

    @BeforeEach
    void setUp() {
        shipment = new Shipment();
    }

    @Test
    void newShipmentIsEmpty() {
        assertEquals(0, shipment.parcelCount());
    }

    @Test
    void addingParcelIncreasesCount() {
        shipment.add(new Parcel(1200, "Alex"));
        assertEquals(1, shipment.parcelCount());
    }
}

La préparation s’exécute avant chaque méthode. Le deuxième test ne modifie pas l’expédition utilisée par le premier. Par défaut, Jupiter crée aussi une nouvelle instance de la classe de test pour chaque méthode de test. Les champs statiques mutables restent partagés : une liste statique peut donc toujours transmettre un état entre ces instances.

@AfterEach effectue le nettoyage après chaque test. @BeforeAll et @AfterAll s’exécutent une fois pour la classe et sont statiques dans le cycle de vie par défaut. Des données de référence partagées en lecture seule peuvent convenir à une préparation unique pour la classe ; les objets modifiés doivent généralement être recréés pour chaque test. Les tests doivent fonctionner individuellement sans dépendre de l’ordre du fichier source.

Utiliser un même corps de test pour un tableau

Un test paramétré exécute la même vérification de comportement pour plusieurs lignes :

@ParameterizedTest
@CsvSource({"1, 500", "2000, 500", "2001, 900",
            "10000, 900", "10001, 1000"})
void feeMatchesTable(int grams, int expected) {
    assertEquals(expected,
        new ShippingCalculator().feeInCents(grams));
}

Chaque ligne donne lieu à une invocation distincte avec son propre résultat dans le rapport. @ValueSource(ints = {0, -5}) fournit un entier par invocation pour les tests d’entrées invalides. Les annotations des tests paramétrés proviennent de org.junit.jupiter.params ; CsvSource et ValueSource se trouvent dans son paquetage provider. La dépendance agrégée junit-jupiter inclut le module de tests paramétrés.

@DisplayName ajoute un libellé lisible au rapport. @Disabled indique qu’un test est ignoré et en donne la raison ; une vérification ignorée ne prouve pas que le comportement qu’elle testerait fonctionne.

Contrôler l’environnement

Un test de service peut recevoir un InMemoryParcelRepository, un objet factice fonctionnel qui stocke les colis en mémoire. Cela évite d’utiliser le contenu de vrais fichiers et isole la logique. Transmets à une interface textuelle un Scanner construit à partir d’une chaîne connue lorsque tu testes sa gestion des entrées. Une date fixe ou un Random avec une graine fixe rend de même une entrée extérieure prévisible.

Un test instable change de résultat d’une exécution à l’autre sans modification pertinente du code. Les données mutables partagées, l’heure actuelle, les valeurs aléatoires non contrôlées et les fichiers résiduels sont des causes fréquentes. Donne au test le contrôle de ces entrées. Garde les éléments auxiliaires courts et la préparation lisible ; une fixture qui construit de nombreux objets sans rapport entre eux peut révéler que la classe testée a trop de tâches.

Points clés

  • Utilise @BeforeEach pour fournir aux tests de nouvelles fixtures.
  • Une nouvelle instance de la classe de test ne réinitialise pas l’état statique mutable.
  • Les tests paramétrés exécutent une méthode avec des lignes d’entrée dont les résultats sont présentés séparément.
  • Utilise des objets factices et des entrées fournies au test pour rendre les tests de logique indépendants du stockage réel et de l’horloge.
  • Fais en sorte que chaque test puisse s’exécuter seul, sans ordre imposé entre les méthodes.

4.4 Construire le comportement par le cycle rouge, vert, refactorisation

Le développement piloté par les tests (TDD) commence une modification du comportement par un test. Un cycle court rend visible le comportement manquant, l’implémente, puis améliore la structure du code. Une liste de tests conserve les cas qui restent à explorer sans exiger de les écrire tous en même temps.

Prendre un élément de la liste des tests

Pour un programme qui vérifie les années bissextiles du calendrier grégorien, énumère quatre comportements : une année ordinaire non bissextile, une année divisible par quatre, une année séculaire non divisible par 400 et une année séculaire divisible par 400. Ces cas représentent différentes parties de la règle.

Rouge : écris un test et exécute-le. Une méthode manquante peut provoquer une erreur de compilation. Une fois la méthode créée, vérifie que le test échoue pour le comportement que tu souhaites ajouter. Une assertion manquante, un mauvais appel de méthode ou un test non découvert peuvent rendre trompeur un résultat apparemment vert. Un nouveau cas peut aussi être déjà satisfait par le code existant ; examine ce résultat avant de modifier l’implémentation.

Vert : ajoute le code le plus simple qui fait réussir le nouveau test et tous les tests précédents. Refactorisation : améliore la structure sans changer le comportement, en relançant les tests après chaque petite modification. Choisis ensuite le cas suivant.

Laisser les cas développer la règle

La première vérification est assertFalse(LeapYears.isLeapYear(2023)). Créer la méthode avec return false; fait réussir ce cas. C’est une première étape délibérée, pas une règle calendaire terminée.

La vérification suivante, assertTrue(LeapYears.isLeapYear(2024)), échoue. Remplace le contenu de la méthode par return year % 4 == 0;. Les deux cas réussissent maintenant.

Ensuite, assertFalse(LeapYears.isLeapYear(1900)) révèle l’exception des années séculaires. Remplacer l’expression par year % 4 == 0 && year % 100 != 0 fait réussir ces trois tests.

Enfin, assertTrue(LeapYears.isLeapYear(2000)) révèle l’exception à la règle des années séculaires. L’implémentation devient :

public static boolean isLeapYear(int year) {
    return year % 400 == 0
        || (year % 4 == 0 && year % 100 != 0);
}

Exécute les quatre vérifications après la modification. Chaque nouvel échec a indiqué une partie manquante du comportement au lieu de te demander de déboguer une grande implémentation inachevée.

Améliorer la structure pendant que les tests sont au vert

La refactorisation modifie les noms ou l’organisation tout en préservant le comportement. Renommer une méthode, extraire une méthode auxiliaire, supprimer une logique répétée et déplacer une classe vers un paquetage plus adapté sont des refactorisations lorsque le comportement observable reste le même. Modifier les frais appliqués à une limite change le comportement et nécessite un test qui décrit ce changement.

Si un nettoyage fait échouer un test, rétablis le comportement voulu avant d’ajouter une autre fonctionnalité. De petites modifications aident à localiser la cause d’un échec. Sauter le nettoyage laisse du code qui réussit les tests mais devient plus difficile à enrichir ; écrire une grande implémentation pour de nombreux cas futurs crée davantage de code à examiner lorsque la vérification suivante échoue.

Le TDD dépend de la qualité des exigences et des cas choisis. Une règle mal comprise peut apparaître à la fois dans le test et dans l’implémentation. Une logique dont les entrées et les sorties sont claires constitue un bon point de départ, car le résultat attendu peut être indiqué indépendamment et vérifié rapidement.

Points clés

  • Garde une liste de tests et implémente un comportement à la fois.
  • Le rouge rend visible le comportement manquant ; le vert fait réussir tous les tests actuels.
  • Examine un test qui réussit de façon inattendue avant de supposer qu’il a vérifié le changement voulu.
  • Refactorise la structure tout en préservant le comportement et relance les tests.
  • Utilise des cas successifs pour révéler les règles ordinaires et les exceptions.
5

Construis et teste des projets avec Maven ou Gradle

Un projet a besoin de plus que des fichiers sources : il lui faut des bibliothèques, des paramètres de compilation, un exécuteur de tests et un moyen d’empaqueter le résultat. Maven et Gradle décrivent ces choix afin qu’une commande puisse répéter la construction. Tu utiliseras leurs répertoires standard, déclareras les dépendances Jupiter, exécuteras le wrapper fourni et distingueras une erreur de code d’un problème de découverte des tests ou de dépendance en lisant la sortie de construction.

5.1 Construire un projet Maven à partir de sa description

Un outil de construction transforme des fichiers sources en un résultat vérifié et empaqueté. Maven lit une description du projet, télécharge les bibliothèques déclarées, compile le code, exécute les tests et crée un artefact. La même description donne à la ligne de commande et à l’environnement de développement une vue commune du projet.

Utiliser l’organisation standard

Place le code Java de production dans src/main/java, les tests dans src/test/java, les ressources de production dans src/main/resources et les données de test dans src/test/resources. Les répertoires de paquetages commencent sous chaque racine des sources Java. Maven écrit les résultats générés sous target ; modifie les sources, pas les copies compilées dans ce répertoire.

Le fichier pom.xml à la racine du projet, le Project Object Model, identifie le projet à l’aide de coordonnées : groupId, artifactId et version. Par exemple, com.example:parcels:1.0-SNAPSHOT identifie une version en cours de développement du programme de colis. Une dépendance à une bibliothèque possède aussi des coordonnées.

Déclarer JUnit pour les tests

Voici une petite configuration Maven complète. Elle compile pour Java 17 et sélectionne un exécuteur de tests compatible avec Jupiter :

<project xmlns="http://maven.apache.org/POM/4.0.0">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.example</groupId>
  <artifactId>parcels</artifactId>
  <version>1.0-SNAPSHOT</version>
  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter</artifactId>
      <version>6.1.3</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.14.1</version>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.5.4</version>
      </plugin>
    </plugins>
  </build>
</project>

Maven récupère les dépendances depuis des dépôts, normalement Maven Central, et les met en cache dans .m2/repository sous ton dossier personnel. Les bibliothèques nécessaires à une dépendance sont des dépendances transitives. La portée test rend JUnit disponible pour compiler et exécuter les tests, mais pas pour la compilation du code principal ni dans le chemin de classes du programme à l’exécution. Un JAR ordinaire conserve les JAR de ses dépendances séparément, sauf si l’empaquetage est configuré pour les regrouper.

Demander une phase du cycle de vie

Le cycle de vie principal passe notamment par les phases de compilation, de test, d’empaquetage et d’installation. Demander package exécute aussi les phases précédentes. Avec le wrapper, ./mvnw test compile et teste ; ./mvnw package crée aussi le JAR sous target ; ./mvnw install place en outre l’artefact dans le dépôt local. Le cycle de vie de nettoyage distinct, ./mvnw clean, supprime les résultats générés.

Un échec de test arrête la construction normale avant l’empaquetage d’un nouvel artefact. Un ancien JAR peut encore être présent jusqu’au nettoyage : sa présence ne prouve donc pas que la dernière construction a réussi.

Rendre visible la découverte des tests

Les motifs par défaut de Surefire incluent les noms commençant par Test ou se terminant par Test, Tests ou TestCase. ShippingChecks ne leur correspond pas ; utilise ShippingCalculatorTest et examine le nombre de tests indiqué. Les tests placés sous src/main/java ne peuvent pas utiliser une dépendance de portée test.

Le Maven Wrapper, mvnw ou mvnw.cmd sous Windows, exécute la version de Maven indiquée par le projet. Utilise un projet qui inclut les fichiers de son wrapper ; un POM seul ne crée pas les scripts. Depuis la racine du projet, le wrapper fournit une commande de construction cohérente sans installation séparée de Maven.

Points clés

  • Garde le code principal, les tests et les ressources dans leurs répertoires sources standard.
  • Identifie les projets et les bibliothèques par leurs coordonnées de groupe, d’artefact et de version.
  • Déclare JUnit avec la portée test et utilise un exécuteur Surefire compatible.
  • Une phase Maven demandée exécute les phases précédentes de son cycle de vie.
  • Examine le nombre de tests ; une construction réussie peut malgré tout avoir omis un test mal nommé.
  • Les résultats générés se trouvent dans target et peuvent être reconstruits.
  • Le wrapper exécute la version de Maven choisie par le projet.

5.2 Exécuter les tests Gradle et suivre les erreurs de construction

Gradle décrit une construction avec un script et exécute des tâches avec leurs dépendances. Un script Kotlin se nomme build.gradle.kts ; un script Groovy se nomme build.gradle. Les plugins fournissent des tâches et les configurations de dépendances indiquent où les bibliothèques sont nécessaires.

Configurer une application et Jupiter

Un fichier settings.gradle.kts à la racine du projet peut contenir rootProject.name = "parcels". Un script de construction Kotlin correspondant est :

plugins {
    application
}

repositories {
    mavenCentral()
}

java {
    toolchain {
        languageVersion = JavaLanguageVersion.of(17)
    }
}

dependencies {
    testImplementation(platform("org.junit:junit-bom:6.1.3"))
    testImplementation("org.junit.jupiter:junit-jupiter")
    testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

application {
    mainClass = "com.example.parcels.Main"
}

tasks.test {
    useJUnitPlatform()
}

Le plugin application fournit les tâches de construction Java et ajoute run. mavenCentral() sélectionne un dépôt de dépendances. La nomenclature de dépendances, ou BOM, aligne les versions des modules JUnit : la version n’apparaît donc qu’une fois. testImplementation fournit les bibliothèques nécessaires pour compiler et exécuter les tests. testRuntimeOnly fournit le lanceur uniquement pendant leur exécution. Les bibliothèques du code principal utilisent implementation, ce qui les rend aussi disponibles pour le code de test.

useJUnitPlatform() sélectionne la plateforme qui exécute les tests Jupiter. Ajouter les bibliothèques Jupiter sans configurer l’exécuteur peut empêcher la découverte de ces tests. Avec son réglage par défaut, Gradle dans ses versions actuelles échoue lorsque des sources de test existent mais qu’aucun test n’est découvert ; examine la sortie et la configuration de la tâche de test au lieu de supprimer la vérification.

Exécuter des tâches avec le wrapper

Utilise le wrapper fourni avec le projet : ./gradlew test exécute les tests ; ./gradlew build compile, teste et empaquette ; ./gradlew run démarre la classe principale configurée ; et ./gradlew clean supprime build. Sous Windows, utilise gradlew.bat. Gradle utilise la même organisation des sources principales et de test que Maven et écrit les résultats générés dans build.

Une tâche marquée UP-TO-DATE possède des entrées et des sorties suivies qui n’ont pas changé : Gradle réutilise donc son résultat précédent. Ce message se distingue d’une nouvelle exécution des tests. Le rapport de test HTML se trouve dans build/reports/tests/test/index.html.

Trouver l’étape qui a échoué

Commence par la première erreur exploitable. « Cannot find symbol » pendant la compilation principale indique un problème dans le code nommé, une importation ou une dépendance. L’absence de org.junit.jupiter.api pendant la compilation des tests indique un problème de configuration des dépendances de test ; la même erreur pendant la compilation principale peut signifier qu’un test a été placé dans le répertoire des sources principales. Une erreur de résolution de dépendance oriente vers les coordonnées, l’accès au dépôt ou la disponibilité du réseau.

L’échec d’un test en cours d’exécution est différent : le test a été compilé et découvert, mais sa vérification a échoué. Si « expected: <500> but was: <900> » apparaît à la limite de 2 000 g pour les frais, examine la comparaison qui choisit la tranche. Modifier les dépendances ne corrigera pas ce calcul.

Maven propose une description XML déclarative avec des phases de cycle de vie fixes. Gradle propose des scripts et des tâches configurables, notamment la possibilité de sauter le travail déjà à jour. Les deux automatisent des constructions Java reproductibles ; utilise l’outil choisi pour le projet et lis le premier échec sur lequel tu peux agir avant de modifier le code.

Points clés

  • Les plugins ajoutent des tâches Gradle ; les configurations contrôlent où les dépendances sont disponibles.
  • Aligne les versions de JUnit avec une nomenclature de dépendances et exécute Jupiter sur JUnit Platform.
  • Utilise le Gradle Wrapper fourni pour les tâches de test, de construction, d’exécution et de nettoyage.
  • Distingue les résultats de tâches mis en cache, les erreurs de compilation, les erreurs de dépendance et les échecs d’assertion.
  • Lis le nom du test échoué et les valeurs attendue et obtenue avant de modifier l’implémentation.

Paquetages et tests unitaires Java : exemples

5 Leçons Interactives

1

Déplacer une classe de colis dans un paquetage

13 étapes
Relie un chemin source, des importations et des vérifications d’accès tout en séparant le code de l’interface utilisateur de celui du domaine.
  1. 1
    IntroductionDonner à la classe un emplacement précis

    Un programme de colis possède un calculateur et une interface utilisateur textuelle. Lorsque ces classes sont déplacées dans des paquetages distincts, Java a besoin de noms, de répertoires et de niveaux d’accès cohérents.

    Suis le déplacement en trois étapes : place la classe, permets à un autre fichier de la désigner, puis vérifie si le code appelant peut utiliser son constructeur et ses méthodes.

  2. 2
    ExemplePartir de la racine des sources

    La racine des sources est src/main/java. Pour Parcel dans com.example.parcels.domain, transforme les points du paquetage en répertoires et ajoute le nom du fichier :

    src/main/java/com/example/parcels/domain/Parcel.java
    

    Écris package com.example.parcels.domain; au début du fichier. Les répertoires src/main/java de la racine correspondent à l’organisation de la construction et ne figurent pas dans la déclaration de paquetage.

  3. 3
    ExplicationPréserver l’identité de la classe

    Le nom simple est Parcel ; le nom pleinement qualifié est com.example.parcels.domain.Parcel. Déplacer le fichier ne modifie pas cette identité à lui seul.

    Une classe publique nommée Parcel doit se trouver dans Parcel.java. Exécute une classe principale appartenant à un paquetage depuis la racine de l’arborescence compilée avec une commande comme java -cp out com.example.parcels.Main, afin que Java puisse trouver les répertoires de paquetages sous out.

  4. 4
    DéfiCorriger un emplacement incohérent

    Une classe publique nommée Receipt déclare package com.example.parcels.domain;, mais se trouve sous src/main/java/com/example/parcels/ui/. Son paquetage doit rester inchangé. Où faut-il placer le fichier ?

    ---reveal---

    Déplace-le vers src/main/java/com/example/parcels/domain/Receipt.java. Le chemin du paquetage commence sous la racine des sources et le nom du fichier correspond à celui de la classe publique. Modifier une importation ailleurs ne corrigerait pas l’organisation des sources.

  5. 5
    ExempleFaciliter l’utilisation du nom du type

    L’interface utilisateur possède sa propre déclaration, puis importe le type du domaine :

    package com.example.parcels.ui;
    
    import com.example.parcels.domain.Parcel;
    

    Le corps de sa classe peut maintenant utiliser le nom simple Parcel. L’importation s’applique uniquement à ce fichier. Elle résout un nom ; elle ne copie pas une classe et ne rend pas une dépendance disponible pour la construction.

  6. 6
    Point cléUn caractère générique s’arrête à un seul paquetage

    import com.example.parcels.*; n’inclut pas le paquetage du domaine. Le préfixe commun des noms ressemble à un arbre généalogique, mais chaque nom complet désigne un paquetage distinct.

    La même règle explique pourquoi java.util.* ne fournit pas java.util.function.Function. Utilise une importation d’un type unique, une importation avec caractère générique du bon paquetage ou le nom pleinement qualifié de ce type.

  7. 7
    DéfiDistinguer deux classes List

    Un fichier a besoin à la fois de java.util.List et de java.awt.List. Il comporte actuellement une importation d’un type unique pour chacune et la compilation échoue. Comment le fichier peut-il désigner les deux types sans ambiguïté ?

    ---reveal---

    Garde une seule importation d’un type unique, par exemple import java.util.List;, et écris java.awt.List en entier partout où ce type est nécessaire. Deux importations d’un type unique différentes ne peuvent pas donner deux sens au même nom simple dans un fichier.

  8. 8
    ConseilReconnaître les noms qui ne nécessitent pas d’importation

    Un fichier peut utiliser les types de son propre paquetage sans les importer. Les types accessibles de java.lang, notamment String, Math et System, sont aussi disponibles automatiquement.

    Une importation statique a un autre rôle : importer Assertions.assertEquals de manière statique permet d’appeler directement assertEquals(...). Elle raccourcit une référence à un membre statique, pas un nom de paquetage.

  9. 9
    ExempleVérifier aussi le constructeur

    Le type est public, mais son constructeur n’a aucun modificateur :

    public class Parcel {
        Parcel(int grams, String recipient) { }
    }
    

    Ce constructeur a un accès de paquetage. L’interface utilisateur peut résoudre Parcel grâce à l’importation, mais ne peut pas le construire depuis son paquetage. Marque le constructeur comme public si la création de colis fait partie des opérations publiques prévues du domaine ; garde ses champs privés.

  10. 10
    DéfiOuvrir l’opération, garder la classe auxiliaire masquée

    L’interface utilisateur doit appeler la méthode feeInCents d’un calculateur public, actuellement déclarée sans modificateur d’accès. Seul le calculateur utilise sa classe auxiliaire WeightBands, qui a un accès de paquetage. Quel changement d’accès faut-il effectuer ?

    ---reveal---

    Rends feeInCents publique et laisse WeightBands avec un accès de paquetage. L’interface a besoin de l’opération de calcul, pas de la classe auxiliaire qui gère le tableau. Importer l’une ou l’autre classe ne modifie pas ses droits d’accès.

  11. 11
    ExplicationUne sous-classe dispose de privilèges particuliers

    Les membres protégés sont accessibles à tout le paquetage déclarant et aux sous-classes selon les règles d’héritage entre paquetages. Dans un autre paquetage, une sous-classe peut utiliser son propre membre d’instance hérité.

    Elle ne peut pas utiliser une référence de type superclasse pour accéder à ce membre sur un objet quelconque de la superclasse. L’accès protégé est donc plus large qu’un accès réservé aux sous-classes, tout en étant plus limité que l’accès public.

  12. 12
    DéfiExaminer tout le chemin d’accès

    TextUI importe correctement une classe publique Parcel. Son appel à new Parcel(800, "Jo") indique que le constructeur n’est pas accessible depuis l’extérieur du paquetage. Le constructeur n’a aucun modificateur. Quelle modification permet cette création voulue entre paquetages ?

    ---reveal---

    Rends le constructeur public. Le paquetage et l’importation permettent déjà de résoudre le type : déplacer le fichier ou ajouter une importation avec caractère générique ne corrige donc pas l’échec d’accès signalé. Les champs privés du colis peuvent rester privés.

  13. 13
    RécapitulatifEffectuer trois vérifications distinctes

    Vérifie l’emplacement en faisant correspondre la déclaration aux répertoires sous la racine des sources. Vérifie les noms à l’aide des importations ou des noms pleinement qualifiés. Vérifie l’accès au type et à l’opération que le code appelant utilise réellement.

    Cet ordre aide à transformer un vague problème « Java ne peut pas utiliser cette classe » en une correction précise de l’organisation des sources, de la résolution des noms ou de la visibilité.

2

Lire un modèle de colis et construire son squelette Java

13 étapes
Traduis les boîtes UML, les extrémités des relations et les traits de sous-types en déclarations Java cohérentes.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionLire la structure avant d’écrire les corps
  2. 2
    ExempleTraduire un champ et une méthode
  3. 3
    ExplicationRepérer un membre de classe
  4. 4
    DéfiConserver les symboles
  5. 5
    ExempleCompter les colis d’une expédition
  6. 6
    ExempleChoisir une représentation, puis faire respecter la règle
  7. 7
    Point cléSuivre un lien à sens unique
  8. 8
    DéfiVérifier un nombre minimal
  9. 9
    ExplicationPlacer la propriété à la bonne extrémité
  10. 10
    ExempleLire deux traits terminés par un triangle
  11. 11
    ConseilConstruire les déclarations dans l’ordre
  12. 12
    DéfiAssocier un contrat et une collection
  13. 13
    RécapitulatifDistinguer le rôle de chaque symbole
3

Suivre une commande à travers des responsabilités distinctes

11 étapes
Déplace un calcul de frais hors de l’interface utilisateur et relie un service à un stockage remplaçable.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionSuivre une commande dans le programme
  2. 2
    ExempleExtraire la règle du code de la console
  3. 3
    ExempleCalculer une tranche entamée
  4. 4
    Point cléGarder la validation avec la règle
  5. 5
    DéfiRendre un calcul réutilisable
  6. 6
    ExempleTransmettre le stockage au service
  7. 7
    LienRemplacer les fichiers par un objet factice fonctionnel
  8. 8
    ConseilVérifier séparément les appels et les importations
  9. 9
    DéfiCorriger un accès qui contourne le service
  10. 10
    DéfiMettre en place un test de service contrôlé
  11. 11
    RécapitulatifPlacer chaque changement là où sa tâche est prise en charge
4

Développer une suite de tests Jupiter utile

19 étapes
Écris une première assertion, choisis les limites, isole l’état et développe une règle par de petites modifications précédées de tests.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionTransformer les exigences en vérifications exécutables
  2. 2
    ExemplePréparer, agir et vérifier
  3. 3
    ExplicationBien lire les valeurs attendue et obtenue
  4. 4
    ConseilAutoriser délibérément l’arrondi numérique
  5. 5
    DéfiCorriger un test qui se contente d’afficher
  6. 6
    ExempleRepérer les changements de tarif
  7. 7
    Point cléGarder le résultat attendu indépendant
  8. 8
    ExemplePlacer l’appel rejeté dans la vérification
  9. 9
    DéfiChoisir le cas qui révèle le changement
  10. 10
    ExempleDonner une nouvelle expédition à chaque test
  11. 11
    ExempleExécuter un tableau avec un seul corps de test
  12. 12
    LienContrôler le collaborateur, pas l’ordre des tests
  13. 13
    DéfiDiagnostiquer les données laissées par un autre test
  14. 14
    ExempleCommencer le cycle avec une année ordinaire
  15. 15
    ExempleAjouter la règle générale de divisibilité par quatre
  16. 16
    ExempleAffiner la condition avec les cas d’années séculaires
  17. 17
    DéfiChoisir une étape suivante tout en préservant les vérifications
  18. 18
    DéfiAssocier le choix des cas et l’isolation
  19. 19
    RécapitulatifSe fier à des vérifications précises et reproductibles
5

Du fichier de construction au rapport de test

13 étapes
Configure les dépendances et la découverte des tests, exécute les wrappers et diagnostique les échecs à l’étape où ils se produisent.

Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.

  1. 1
    IntroductionRépéter la construction avec une seule commande
  2. 2
    ExplicationSéparer les sources des résultats générés
  3. 3
    ExempleNommer le projet et sa bibliothèque de test
  4. 4
    ExempleDemander la phase nécessaire
  5. 5
    ConseilNe pas confondre zéro test et un comportement vérifié
  6. 6
    DéfiTrouver un test mal placé
  7. 7
    ExplicationSélectionner les tâches Gradle
  8. 8
    ExempleGarder les modules Jupiter cohérents
  9. 9
    ExempleIndiquer à la tâche de test comment exécuter Jupiter
  10. 10
    DéfiCorriger la découverte des tests
  11. 11
    ExempleSuivre l’échec d’un test exécuté
  12. 12
    DéfiAssocier la découverte à une nouvelle construction
  13. 13
    RécapitulatifLire la construction comme une suite d’étapes vérifiables

Paquetages et tests unitaires Java : QCM

50 Questions Pratiques en 5 séries

Série 1 : Noms, répertoires et accès Java10 Questions Pratiques

Question 1 sur 10

Une classe publique Ticket déclare package org.example.events.model;. Quel chemin doit-elle avoir sous la racine des sources Java principales ?
  1. org/example/events/Ticket/model.java
  2. org/example/events/model/Ticket.java
  3. src/main/java/org/example/events/model/Ticket.java
  4. org.example.events.model/Ticket.java
Afficher la réponseMasquer la réponse
Réponse : B
Chaque point devient un répertoire sous la racine des sources et le nom de la classe publique donne Ticket.java. La racine étant déjà le point de départ, ajouter de nouveau src/main/java dupliquerait la partie du chemin correspondant à l’organisation de la construction.

Question 2 sur 10

Une classe du paquetage shop.ui a besoin d’une classe auxiliaire qui n’a actuellement aucune déclaration de paquetage. Comment organiser cette classe pour que l’interface puisse l’importer ?
  1. La laisser sans nom et ajouter import Helper; au fichier source de l’interface.
  2. La placer dans un répertoire nommé shop sans déclarer de paquetage.
  3. La laisser sans nom et qualifier son nom avec le chemin de la racine des sources principales.
  4. La placer dans un paquetage nommé avec des déclarations accessibles.
Afficher la réponseMasquer la réponse
Réponse : D
Une classe d’un paquetage nommé ne peut pas importer un type du paquetage sans nom. Donne à la classe auxiliaire une déclaration de paquetage et un emplacement correspondant, puis fournis l’accès nécessaire au code appelant ; déplacer le fichier seul ne lui donne pas l’identité d’un paquetage nommé.

Question 3 sur 10

Un fichier contient import java.util.*; et utilise java.util.function.Predicate par son nom simple. Quelle importation supplémentaire d’un type unique fournit ce nom ?
  1. import java.util.function.Predicate;
  2. import java.util.Predicate;
  3. import java.util.function;
  4. import static java.util.function.Predicate;
Afficher la réponseMasquer la réponse
Réponse : A
Predicate se trouve dans le paquetage distinct java.util.function : son nom de type complet est donc nécessaire dans l’importation d’un type unique. Le caractère générique existant java.util.* n’atteint pas les sous-paquetages et une importation statique concerne les membres statiques.

Question 4 sur 10

Un fichier importe java.util.* et java.awt.*, puis déclare List<String> names. Comment faire référence à l’interface de collection avec List tout en conservant les deux importations avec caractère générique ?
  1. Ajouter import static java.util.List;.
  2. Remplacer le type de la déclaration par Object.
  3. Ajouter import java.util.List;.
  4. Placer les deux caractères génériques avant la déclaration de paquetage.
Afficher la réponseMasquer la réponse
Réponse : C
Une importation explicite d’un type unique est prioritaire sur les importations à la demande : List désigne alors java.util.List. Les deux importations avec caractère générique peuvent rester ; une importation statique ne désignerait pas ce type de premier niveau comme membre statique.

Question 5 sur 10

Un test Jupiter appelle assertTrue(valid) sans le préfixe de classe Assertions. Quelle importation permet cet appel direct ?
  1. import org.junit.jupiter.api.Assertions.assertTrue;
  2. import static org.junit.jupiter.api.Test.assertTrue;
  3. import org.junit.jupiter.api.Assertions;
  4. import static org.junit.jupiter.api.Assertions.assertTrue;
Afficher la réponseMasquer la réponse
Réponse : D
assertTrue est un membre statique de Assertions : une importation statique permet donc de l’appeler directement. Importer la classe seule permet d’écrire Assertions.assertTrue(...), mais ne supprime pas le nom de la classe de l’appel.

Question 6 sur 10

book.data.Record est publique et possède un constructeur déclaré Record(int id). Une classe sans lien de parenté dans book.ui l’importe mais ne peut pas la construire. Quel changement d’accès permet cet appel ?
  1. Rendre le constructeur public.
  2. Rendre le constructeur protégé.
  3. Importer aussi book.data.*.
  4. Renommer le paquetage de l’interface book.data.ui.
Afficher la réponseMasquer la réponse
Réponse : A
L’absence de modificateur donne au constructeur un accès de paquetage et book.ui est un paquetage différent. L’accès public permet cet appel depuis une classe sans lien de parenté ; l’accès protégé ne le permettrait pas, car cette classe n’est ni dans le paquetage déclarant ni en train d’utiliser des privilèges de sous-classe.

Question 7 sur 10

Une classe auxiliaire ordinaire déclarée class RateTable n’a aucun modificateur d’accès. Où d’autres classes de premier niveau peuvent-elles l’utiliser ?
  1. Dans les paquetages partageant son nom de premier niveau.
  2. Uniquement dans son paquetage déclarant exact.
  3. Dans tout paquetage après une importation explicite.
  4. Uniquement dans les sous-classes, quel que soit le paquetage.
Afficher la réponseMasquer la réponse
Réponse : B
Une classe de premier niveau sans public a un accès de paquetage, ce qui limite son utilisation au même paquetage exact. Les importations résolvent les noms sans accorder de droits : une importation explicite depuis un autre paquetage ne peut donc pas ouvrir l’accès à cette classe auxiliaire.

Question 8 sur 10

Une boîte de classe contient # threshold: int et + reset(): void souligné. Comment ces membres se traduisent-ils en Java ?
  1. Un champ entier privé et une méthode reset publique abstraite.
  2. Un champ entier à accès de paquetage et une méthode reset publique statique.
  3. Un champ entier protégé et une méthode reset publique statique.
  4. Un champ entier protégé et une méthode reset publique d’instance.
Afficher la réponseMasquer la réponse
Réponse : C
Le symbole # indique une visibilité protégée, tandis que le soulignement indique un membre statique. Le signe plus rend reset publique et son type de retour est void ; le soulignement ne signifie pas abstrait, ce qui est indiqué par l’italique ou une marque abstraite.

Question 9 sur 10

Quelle opération UML décrit public boolean accepts(int grams) ?
  1. + boolean accepts(int: grams)
  2. - accepts(grams: int): boolean
  3. + accepts(grams: boolean): int
  4. + accepts(grams: int): boolean
Afficher la réponseMasquer la réponse
Réponse : D
UML place le nom de l’opération en premier, écrit chaque paramètre avec son nom puis son type, et place le type de retour après les parenthèses. Le signe plus conserve la visibilité publique ; inverser les types du paramètre et du résultat décrirait une autre méthode.

Question 10 sur 10

Le paquetage base contient la classe Engine avec un accès de paquetage. Son constructeur déclaré explicitement et sa méthode run() n’ont tous deux aucun modificateur d’accès. Une classe Screen sans lien de parenté dans base.ui doit appeler new Engine().run(). Quels changements minimaux de visibilité permettent cet appel ?
  1. Rendre Engine publique ; garder le reste avec un accès de paquetage.
  2. Rendre Engine et run publiques, mais pas le constructeur.
  3. Rendre publics Engine, son constructeur et run.
  4. Rendre publics uniquement le constructeur et run.
Afficher la réponseMasquer la réponse
Réponse : C
L’interface doit accéder au type, appeler son constructeur et invoquer sa méthode. Chacun a actuellement un accès de paquetage et base.ui est un paquetage distinct : les trois doivent donc être publics pour cette classe appelante sans lien de parenté. Ouvrir uniquement le type et la méthode laisse la construction bloquée.
Série 2 : Relations entre classes et responsabilités des couches10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Sur une association entre bibliothèque et livre, 0..* est écrit à côté de Book. À combien de livres une bibliothèque peut-elle être liée ?
  1. À au moins un livre.
  2. À exactement un livre.
  3. À zéro ou plusieurs livres.
  4. À zéro ou un livre.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Un cours doit comporter une ou plusieurs séances. Son champ List<Session> est initialement vide et aucune méthode ne vérifie sa taille. Que manque-t-il dans l’implémentation ?
  1. Des vérifications qui établissent et préservent le nombre minimal de séances.
  2. Une autre importation de liste pour que la collection impose un minimum.
  3. Un champ de séances statique partagé par chaque instance de cours.
  4. Un champ de liste protégé permettant aux séances de le remplir directement.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Un modèle de dossier regroupe des documents qui peuvent être partagés et continuent d’exister après la suppression du dossier. Cette exigence justifie-t-elle une composition des documents par ce dossier ?
  1. Oui ; toute collection de références établit une propriété composite.
  2. Oui ; le dossier est le tout, donc la durée de vie des documents ne compte pas.
  3. Non ; la composition est réservée aux classes sans champs de collection.
  4. Non ; les documents partagés et indépendants ne relèvent pas d’une propriété composite.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Une classe possède un trait UML en pointillés terminé par un triangle creux du côté de Printable. Quelle déclaration de sous-type Java ce trait représente-t-il ?
  1. extends Printable
  2. implements Printable
  3. imports Printable
  4. new Printable()
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Basket contient private List<Product> products; et n’étend pas Product. Comment représenter la relation principale ?
  1. Une association vers Product, avec plusieurs objets possibles à l’extrémité Product.
  2. Un trait plein de généralisation partant de Basket, avec son triangle du côté de Product.
  3. Un trait en pointillés de réalisation partant de Basket, avec son triangle du côté de Product.
  4. Une association navigable de Product vers Basket, avec plusieurs objets possibles à l’extrémité Basket.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Un calculateur de notes calcule une moyenne et l’affiche. Une interface web a besoin du calcul. Quel changement rend cette logique directement réutilisable ?
  1. Copier le calcul dans l’interface web.
  2. Ajouter un format d’affichage web dans le calculateur.
  3. Renvoyer la moyenne et laisser les interfaces la formater.
  4. Faire lire d’abord au calculateur le nom de l’interface.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Une interface textuelle rejette les entrées non numériques avant d’appeler un calculateur. Le calculateur doit aussi rejeter les valeurs numériques négatives pour chaque appelant. Où cette règle doit-elle se trouver ?
  1. Uniquement dans l’interface textuelle, avec l’analyse numérique.
  2. Dans le calculateur, les messages étant gérés par l’interface.
  3. Dans la méthode main, après la création du calculateur.
  4. Dans le dépôt, après l’enregistrement du résultat calculé.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Le constructeur d’un service accepte une interface CatalogRepository. Comment un test peut-il éviter la véritable base de données ?
  1. Transmettre un nom de fichier de base de données à l’implémentation réelle existante.
  2. Faire créer le dépôt de base de données par le constructeur du service.
  3. Déplacer le service dans le paquetage de présentation pour le test.
  4. Injecter une implémentation en mémoire de CatalogRepository.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Un écran de réservation enregistre directement par un dépôt. Le service vérifie la limite de places et crée son propre dépôt sur fichier. Les tests doivent vérifier cette limite sans accès au disque. Quelle combinaison de changements convient à la fois à l’écran et aux tests ?
  1. Faire passer les demandes de l’écran par le service et lui injecter le dépôt.
  2. Garder l’enregistrement direct et injecter le dépôt uniquement dans l’écran.
  3. Faire passer les demandes par le service et partager un dépôt statique sur fichier.
  4. Déplacer la vérification des places dans l’écran et laisser le service choisir les fichiers.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

rules.OrderService importe storage.OrderRepository. Cette interface nomme rules.Order dans ses signatures et les implémentations de stockage l’implémentent. Où déplacer le contrat pour supprimer le cycle entre les paquetages rules et storage tout en gardant des stockages interchangeables ?
  1. Déplacer Order dans storage ; OrderService importe un stockage concret.
  2. Déplacer l’interface dans rules ; storage implémente ce contrat.
  3. Déplacer OrderRepository dans l’interface utilisateur ; les deux couches inférieures l’importent.
  4. Garder l’organisation et remplacer les importations par des noms pleinement qualifiés.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 3 : Assertions, choix des cas et tests indépendants10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Une méthode renvoie actual et le résultat exigé est 42. Quelle assertion d’égalité Jupiter indique correctement les valeurs ?
  1. assertEquals(actual, 42);
  2. assertTrue(actual);
  3. assertNotNull(42);
  4. assertEquals(42, actual);
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Un calcul correct avec des double diffère de 0,6 d’environ 0,0000000000000001. La précision numérique exigée est de 0,000001. Comment vérifier son résultat attendu ?
  1. assertEquals(0.6, actual);
  2. assertEquals(0.6, actual, 1.0);
  3. assertEquals(0.6, actual, 1e-6);
  4. assertNotNull(actual);
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Une règle de taille accepte les entiers de 1 à 500 inclus. L’entrée 0 est-elle valide, valide et située à une limite, ou invalide ?
  1. Une entrée invalide.
  2. Une entrée valide située à une limite.
  3. Une entrée valide.
  4. Une entrée non précisée.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Un capteur accepte des relevés entiers jusqu’à 800 inclus. Une personne modifie la vérification de la borne supérieure en remplaçant reading <= 800 par reading < 800. Quel relevé valide révèle l’erreur ?
  1. 799
  2. 800
  3. 801
  4. 400
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Une méthode doit rejeter une chaîne vide avec IllegalArgumentException. Comment un test Jupiter doit-il exécuter l’appel ?
  1. assertThrows(IllegalArgumentException.class, () -> parser.parse(""));
  2. assertThrows(IllegalArgumentException.class, parser.parse(""));
  3. assertEquals(IllegalArgumentException.class, parser.parse(""));
  4. assertNotNull(() -> parser.parse(""));
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Un tarif facture 250 centimes jusqu’à 3 000 g inclus, puis 50 centimes par tranche de 500 g entamée au-delà. Son implémentation utilise 250 + ((grams - 3000) / 500) * 50 au-dessus de la limite. Quel nouveau test révèle le plus directement son défaut d’arrondi ?
  1. 3 500 g doit coûter 300 centimes.
  2. 4 000 g doit coûter 350 centimes.
  3. 3 001 g doit coûter 300 centimes.
  4. 3 000 g doit coûter 250 centimes.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Une nouvelle instance de la classe de test est créée pour chaque test, mais une liste statique mutable conserve des éléments d’un test précédent. Quelle préparation donne à chaque scénario sa propre liste ?
  1. Renommer les méthodes de test pour imposer un ordre alphabétique.
  2. Utiliser une liste non statique initialisée pour chaque test.
  3. Créer une seule liste dans une méthode BeforeAll statique.
  4. Garder la liste statique et ajouter des libellés DisplayName.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Un test de remise lit la date du jour et utilise un dépôt statique contenant des clients précédents. Il réussit seul, mais ses résultats changent d’une exécution de la suite à l’autre. Comment revoir sa préparation ?
  1. Garder la date du jour et exécuter le test en premier dans chaque suite.
  2. Utiliser une date fixe et réutiliser le dépôt partagé déjà rempli.
  3. Recréer uniquement le service, sans changer les deux entrées extérieures.
  4. Fournir une date fixe et un nouveau dépôt pour chaque scénario.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Une règle de frais comporte huit lignes d’entrées et de résultats attendus utilisant la même assertion. Comment Jupiter peut-il présenter séparément les cas sans copier huit corps de méthodes ?
  1. Utiliser un test paramétré avec les lignes comme source d’arguments.
  2. Utiliser un test qui affiche tous les résultats mais ne vérifie que le premier.
  3. Utiliser BeforeAll pour exécuter le calcul des frais une fois pour toute la classe.
  4. Utiliser Disabled sur les méthodes répétées et garder une méthode active.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Tous les tests actuels réussissent. Tu souhaites renommer une méthode auxiliaire et extraire une logique répétée sans changer aucune sortie. De quelle activité du TDD s’agit-il ?
  1. Ajouter un nouveau cas rouge pour une autre fonctionnalité.
  2. Modifier les résultats attendus pour suivre la sortie de la méthode auxiliaire.
  3. Refactoriser en relançant les tests qui réussissent.
  4. Ignorer les tests précédents jusqu’à l’implémentation d’un autre comportement.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 4 : Configuration de construction et rapports d’échec10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Où un projet Maven standard place-t-il les fichiers sources Java des tests unitaires ?
  1. src/test/java
  2. src/main/java
  3. target/test-classes
  4. src/test/resources
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Un projet utilise JUnit uniquement dans les tests. Quelle portée de dépendance Maven le rend disponible pour la compilation et l’exécution des tests sans l’ajouter aux chemins de classes principaux ?
  1. compile
  2. test
  3. runtime
  4. provided
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Avec un cycle de vie Maven normal et sans étape configurée pour être sautée, que se passe-t-il avant que la phase package crée un JAR ?
  1. Seul le code principal compile ; les tests s’exécutent séparément.
  2. Un ancien JAR installé est copié dans les sources.
  3. Le code principal et les tests compilent, puis les tests s’exécutent.
  4. Le cycle de nettoyage supprime toujours d’abord les résultats.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

Un projet Maven Java 17 déclare uniquement JUnit Jupiter 6.1.3, mais configure Surefire 2.12.4. Son fichier de test TotalsChecks.java importe org.junit.Test. Les motifs d’inclusion de Surefire sont inchangés. Quelle combinaison de corrections permet à la classe de compiler et de s’exécuter comme test Jupiter découvert ?
  1. La renommer TotalsTest et utiliser @Test de Jupiter, mais garder Surefire 2.12.4.
  2. Garder TotalsChecks, utiliser @Test de Jupiter et choisir Surefire 3.5.4.
  3. La renommer TotalsTest et choisir Surefire 3.5.4, mais garder org.junit.Test.
  4. La renommer TotalsTest, utiliser @Test de Jupiter et choisir Surefire 3.5.4.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Quel est le nom habituel d’un script de construction Gradle écrit en Kotlin ?
  1. settings.gradle.kts
  2. build.gradle.kts
  3. build.gradle
  4. pom.xml
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Un projet Gradle possède des tests Jupiter et déclare leurs bibliothèques. Quel appel dans la tâche de test sélectionne leur plateforme d’exécution ?
  1. mavenCentral()
  2. testImplementation()
  3. mainClass.set()
  4. useJUnitPlatform()
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Une nomenclature de dépendances JUnit est déclarée avec platform(...) de Gradle. Pourquoi les déclarations des dépendances JUnit associées peuvent-elles omettre les numéros de version individuels ?
  1. Le compilateur Java déduit la version installée la plus récente de chaque module.
  2. Le wrapper remplace chaque version de dépendance par sa version de Gradle.
  3. La nomenclature fournit des contraintes de version cohérentes pour les modules JUnit.
  4. La tâche de test télécharge tout module dont le nom de classe apparaît dans un test.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Un projet Gradle ne peut pas compiler un test car org.junit.jupiter.api manque. Sa seule déclaration Jupiter est testRuntimeOnly("org.junit.jupiter:junit-jupiter") et la tâche de test utilise déjà JUnit Platform. Quelle correction de configuration traite cette étape ?
  1. Déclarer Jupiter avec testImplementation pour rendre son interface visible à la compilation des tests.
  2. Ajouter un autre appel useJUnitPlatform sans changer la dépendance.
  3. Déplacer le test dans les sources principales et garder Jupiter en portée d’exécution seule.
  4. Garder Jupiter en portée d’exécution seule et ajouter une annotation DisplayName au test.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Un test de service réutilise un dépôt statique sur fichier. Il réussit dans l’environnement de développement après une réinitialisation, mais échoue dans la suite complète Gradle parce que des tests précédents ont enregistré des données supplémentaires. Le service accepte déjà une interface de dépôt. Quelle correction isole son comportement ?
  1. Injecter un nouveau dépôt en mémoire contenant uniquement les données de ce test.
  2. Utiliser BeforeAll pour partager le même dépôt sur fichier dans toute la suite.
  3. Exécuter Gradle clean et garder les hypothèses sur les données du dépôt partagé.
  4. Rendre le test public et compter sur l’ordre actuel d’exécution de la suite complète.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Une nouvelle vérification TDD réussit immédiatement, alors que tu voulais qu’elle révèle un comportement manquant. Quelle recherche convient ensuite ?
  1. Ajouter du code de production jusqu’à ce que le test réussi commence à échouer.
  2. Vérifier qu’elle s’exécute, teste le résultat voulu et que le cas est peut-être déjà satisfait.
  3. Supprimer l’assertion, car une réussite prouve que le nouveau comportement est absent.
  4. Refactoriser des classes sans rapport avant de vérifier la méthode appelée par le test.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Série 5 : Diagnostiquer une modification Java complète10 Questions Pratiques

Enregistre cette unité pour voir chaque réponse et t'entraîner sur chaque série.

Question 1 sur 10

Un test exécuté indique « expected: <18> but was: <21> » pour une assertion d’égalité dont les arguments sont dans le bon ordre. Quelle valeur la méthode a-t-elle renvoyée ?
  1. 18
  2. 3
  3. 21
  4. 39
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 2 sur 10

Une association montre 1 à côté de Department et 0..* à côté de Employee. Qu’autorise le modèle pour un département et qu’exige-t-il pour un employé ?
  1. Un département exige un employé ; chaque employé peut n’avoir aucun département.
  2. Un département peut n’avoir aucun employé ; chaque employé a exactement un département.
  3. Un département exige plusieurs employés ; chaque employé a exactement un département.
  4. Un département peut n’avoir aucun employé ; chaque employé peut avoir plusieurs départements.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 3 sur 10

Une méthode renvoie une liste contenant trois valeurs précisées, mais le type de la collection interne peut changer. Comment le test doit-il vérifier le résultat ?
  1. Lire le tableau interne privé et vérifier sa capacité exacte.
  2. Vérifier que sa classe de collection concrète correspond à la classe actuelle.
  3. Afficher les éléments et laisser l’exécuteur les comparer automatiquement.
  4. Comparer le contenu renvoyé aux trois valeurs exigées.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 4 sur 10

La classe base.Item possède un champ d’instance protégé weight. Dans un autre paquetage, Special extends Item possède une méthode d’instance avec les paramètres Item a et Special b. Dans cette méthode, quelles expressions peuvent accéder directement au champ hérité ?
  1. this.weight et b.weight
  2. this.weight et a.weight
  3. a.weight et b.weight
  4. this.weight, a.weight et b.weight
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 5 sur 10

Un diagramme montre MemoryArchive réalisant l’interface Archive et détenant un ou plusieurs objets Entry par une association navigable. Une classe générée indique actuellement extends Archive et crée une liste vide sans vérification. Quelle combinaison de corrections préserve le modèle ?
  1. Utiliser implements Archive mais garder la liste vide sans vérification, car List impose au moins un élément.
  2. Utiliser implements Archive et rejeter les entrées vides du constructeur, mais autoriser la suppression du dernier élément.
  3. Utiliser implements Archive et imposer une collection non vide d’entrées à la construction et lors des mises à jour.
  4. Utiliser implements Archive et remplacer la collection par une référence facultative unique à Entry.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 6 sur 10

Un service de production de rapports importe des outils de formatage de l’interface utilisateur et crée son propre stockage sur fichier. Une seconde interface et des tests sans accès au disque ont besoin de son calcul. Quelle correction de conception traite les deux dépendances ?
  1. Renvoyer du texte formaté pour la console et laisser le service créer le stockage sur fichier.
  2. Déplacer le stockage sur fichier dans le paquetage de l’interface et garder les appels aux outils de formatage dans le service.
  3. Rendre statiques les outils de formatage et le stockage sur fichier du service pour les partager entre les interfaces.
  4. Renvoyer les données du rapport, garder le formatage dans les interfaces et injecter un contrat de stockage.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 7 sur 10

Un programme de vérification développé avec le TDD renvoie actuellement year % 4 == 0 et réussit les tests pour 2031 et 2032. Un nouveau test pour 2100 doit rejeter cette année, puis un test pour 2400 doit l’accepter. Quelle succession de changements de comportement convient aux cas en attente ?
  1. Exclure les années divisibles par 400 pour 2100, puis rétablir celles divisibles par 100 pour 2400.
  2. Exclure les années divisibles par 100 pour 2100, puis rétablir celles divisibles par 400 pour 2400.
  3. Exclure d’abord les années divisibles par 100, puis exiger la divisibilité par 400 pour toute année bissextile.
  4. Exclure d’abord les années divisibles par 100, puis autoriser les multiples de 400 seulement s’ils ne sont pas divisibles par 100.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 8 sur 10

Une règle d’emballage rejette les poids inférieurs ou égaux à zéro, facture 400 centimes jusqu’à 2 500 g inclus et 700 centimes au-delà. Ses tests ne couvrent que 1 000 g. Une modification accepte accidentellement zéro et utilise < 2500 pour la tranche inférieure. Quelle paire de vérifications supplémentaires révèle les deux défauts ?
  1. Zéro doit lever une exception ; 2 500 g doit renvoyer 400 centimes.
  2. Zéro doit lever une exception ; 2 501 g doit renvoyer 700 centimes.
  3. 1 g doit renvoyer 400 centimes ; 2 501 g doit renvoyer 700 centimes.
  4. −1 g doit lever une exception ; 2 499 g doit renvoyer 400 centimes.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 9 sur 10

Un script Gradle contient testRuntimeOnly("org.junit.jupiter:junit-jupiter:6.1.3") et tasks.test { }. Une classe de test Jupiter se trouve sous src/main/java et son annotation ne compile pas. Quels changements permettent à la classe de compiler et de s’exécuter avec Jupiter ?
  1. La garder dans les sources principales, utiliser testImplementation pour Jupiter et sélectionner JUnit Platform.
  2. La déplacer dans les sources de test, garder Jupiter pour l’exécution seule et sélectionner JUnit Platform.
  3. La déplacer dans les sources de test, utiliser testImplementation pour Jupiter et garder l’exécuteur par défaut.
  4. La déplacer dans les sources de test, utiliser testImplementation pour Jupiter et sélectionner JUnit Platform.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Question 10 sur 10

Une exécution normale de la phase package de Maven s’arrête sur un test échoué, mais un JAR datant d’hier reste dans target. Que prouve ce fichier au sujet des sources d’aujourd’hui ?
  1. Il prouve que les sources d’aujourd’hui ont réussi leurs tests.
  2. Il prouve que l’empaquetage a ignoré l’échec.
  3. Il prouve seulement qu’un ancien artefact existe.
  4. Il prouve que Maven a reconstruit les sources d’hier.
Afficher la réponseMasquer la réponse

Enregistre cette unité pour voir cette réponse.

Tu bloques sur ton propre exercice ? Aide aux Devoirs

Mots-Clés

73 Mots-Clés, chacun avec sa définition

A

Accès de paquetage
L’accès de paquetage permet d’utiliser un membre ordinaire de classe ou un type de premier niveau uniquement depuis son paquetage déclarant exact.
Une classe auxiliaire sans modificateur public reste inaccessible à une classe d’interface d’un paquetage voisin, même avec une importation.
Accès protégé
L’accès protégé autorise l’utilisation dans le paquetage déclarant et un accès hérité limité aux sous-classes d’autres paquetages.
Une sous-classe d’un autre paquetage peut lire son propre champ protégé hérité sans rendre ce champ public.
Agrégation
L’agrégation est un regroupement tout-partie souple, représenté par un losange creux, dont le sens détaillé dépend de la conception modélisée.
Une équipe peut regrouper des joueurs qui existent aussi indépendamment, sans que leur durée de vie dépende d’elle.
Assemblage
L’assemblage est la création et la connexion des objets collaborateurs d’un programme, généralement effectuées près du point d’entrée de l’application.
Une méthode main crée un scanner et un calculateur, les transmet à un objet d’interface et démarre son dialogue.
Assertion
Une assertion dans un test est une vérification exécutable qui fait échouer le test lorsque le résultat observé ne satisfait pas l’attente indiquée.
Une assertion d’égalité fait automatiquement échouer le test si les frais sont incorrects, au lieu de simplement les afficher.
Assertion sur une exception
Une assertion sur une exception exécute une opération fournie et vérifie si elle lève une exception du type exigé ou d’un sous-type accepté.
assertThrows peut vérifier qu’un poids nul est rejeté et renvoyer l’exception pour vérifier son message.
Association
Une association est une relation structurelle qui relie des objets de types différents, généralement implémentée par des champs contenant des références.
La liste des colis d’une expédition peut être représentée par une association portant le nom de rôle parcels à l’extrémité des colis.
Attribut
Un attribut est une propriété nommée représentée dans une boîte de classe UML, correspondant généralement à un champ Java doté d’un type et d’une visibilité.
L’entrée - weightGrams: int représente un champ entier privé contenant le poids du colis.

C

Cas limite
Un cas limite utilise une entrée située à la frontière d’une règle spécifiée ou juste à côté, là où un petit changement peut modifier le comportement attendu.
Lorsqu’une tranche tarifaire se termine à 2 000 g, tester exactement 2 000 g révèle une comparaison incorrectement stricte qu’un test à 1 500 g laisserait passer.
Classe d’équivalence
Une classe d’équivalence en test est un groupe d’entrées que la spécification traite de la même façon, représenté par des cas et des limites utiles.
Plusieurs poids positifs d’une même tranche partagent les mêmes frais, tandis que zéro appartient à une classe d’entrées invalides.
Composition
La composition est une association tout-partie dans laquelle une partie appartient exclusivement à un seul tout composite à la fois ; elle est représentée par un losange plein du côté du tout.
Une commande et ses lignes peuvent utiliser la composition lorsque la suppression de la commande supprime aussi ces parties.
Configuration de dépendances
Une configuration de dépendances définit où une dépendance Gradle est disponible, par exemple pour l’implémentation principale, la compilation des tests ou leur exécution.
Un lanceur de plateforme peut utiliser testRuntimeOnly, car les sources de test n’en ont pas besoin pendant la compilation.
Conflit de noms
Un conflit de noms se produit lorsqu’un nom simple peut désigner des déclarations concurrentes, ce qui oblige le code à distinguer le type voulu.
Un fichier qui a besoin de deux types appelés List peut importer l’un et utiliser le nom pleinement qualifié de l’autre.
Contrat d’interface
Un contrat d’interface spécifie les opérations offertes par une implémentation, ce qui permet aux appelants d’utiliser le type interface sans choisir son implémentation.
Un service peut appeler save par le contrat du dépôt, que le stockage utilise la mémoire ou un fichier.
Coordonnées d’artefact
Les coordonnées d’artefact identifient un projet ou une bibliothèque Maven par son groupe, son nom d’artefact et sa version afin que la construction puisse résoudre l’artefact voulu.
Les coordonnées distinguent une version d’une bibliothèque JUnit d’une autre sans copier les fichiers JAR à la main.
Couche
Une couche regroupe des classes ayant un même type de responsabilité et offre des opérations aux autres parties à travers une frontière définie.
Un service de colis applique la validation avant de demander à une couche de stockage d’enregistrer un nouveau colis.
Couche d’accès aux données
La couche d’accès aux données implémente l’enregistrement et la récupération des données persistantes, en séparant les détails du stockage des règles applicatives.
Un dépôt sur fichier gère le format du fichier tandis qu’un service de colis gère les poids valides des colis.
Couche de présentation
La couche de présentation traduit les interactions de l’utilisateur en demandes adressées au programme et présente à l’utilisateur les résultats renvoyés.
Une interface textuelle peut formater les frais dans une phrase sans placer cette phrase dans le code qui calcule les frais.
Cycle de dépendances entre paquetages
Un cycle de dépendances entre paquetages existe lorsqu’une chaîne de dépendances entre types part d’un paquetage et revient à celui-ci, reliant leurs définitions dans les deux sens.
Placer l’interface du dépôt à côté des types du domaine peut supprimer un cycle d’importations entre domaine et données.
Cycle de vie de construction
Un cycle de vie de construction est une suite ordonnée de phases Maven dans laquelle demander une phase ultérieure exécute les phases précédentes de ce cycle.
Demander package exécute normalement la phase test avant de produire l’artefact empaqueté du projet.

D

Déclaration d’importation
Une déclaration d’importation permet à un fichier source Java d’utiliser un type ou un membre statique sous un nom plus court sans modifier son accessibilité.
Une classe d’interface qui importe Scanner peut utiliser le nom simple dans sa déclaration de champ au lieu de java.util.Scanner.
Déclaration de paquetage
Une déclaration de paquetage associe les types d’un fichier source à un paquetage nommé et apparaît avant ses importations et ses déclarations de classes.
Écrire package com.example.parcels.domain; intègre ce paquetage au nom complet de la classe.
Dépendance
Une dépendance est une relation d’utilisation dans laquelle un élément dépend d’un autre ; elle est représentée par un trait en pointillés terminé par une flèche ouverte.
Une opération de calcul des frais qui accepte un paramètre Parcel crée une dépendance envers la définition du type colis.
Dépendance transitive
Une dépendance transitive est une bibliothèque requise par une autre dépendance, que l’outil de construction résout avec l’artefact demandé directement.
Déclarer l’agrégat Jupiter apporte aussi les modules nécessaires à ses fonctionnalités de test.
Dépôt
Un dépôt offre des opérations de stockage et de récupération d’objets du domaine derrière un contrat qui peut avoir différentes implémentations.
Un test fournit un dépôt en mémoire pour éviter de dépendre d’un véritable fichier de données de colis.
Développement piloté par les tests
Le développement piloté par les tests est une méthode de développement qui utilise un test échoué pour guider une petite modification du comportement avant d’améliorer la structure du code.
Un cas échoué portant sur une année séculaire guide la modification suivante d’un programme de vérification des années bissextiles au lieu d’une grande réécriture.
Diagramme de classes UML
Un diagramme de classes UML modélise la structure statique d’un logiciel à l’aide de boîtes de types, d’attributs, d’opérations et de relations entre types.
Un diagramme de colis montre ce qu’une expédition stocke sans suivre l’ordre des commandes saisies par un utilisateur.

E

Expression lambda
Une expression lambda fournit une petite opération sous forme de valeur afin qu’un autre code puisse décider quand et comment l’exécuter.
L’expression lambda () -> calculator.feeInCents(0) permet à JUnit d’exécuter l’appel invalide dans son assertion.

F

Fixture de test
Une fixture de test est l’ensemble des objets et des données préparés pour un test, qui fournit l’état initial contrôlé nécessaire à son scénario.
Une nouvelle expédition vide avant chaque test évite que des ajouts de colis se répercutent sur un autre scénario.

G

Généralisation
La généralisation est une relation UML entre type et sous-type, représentée par un trait plein terminé par un triangle creux pointant vers le type le plus général.
Un trait de Letter vers Item se traduit par une déclaration Java dans laquelle Letter étend Item.

I

Importation avec caractère générique
Une importation avec caractère générique rend disponibles à la demande les types accessibles d’un paquetage, sans rendre disponibles ceux de ses sous-paquetages.
Importer java.util.* ne dispense pas d’utiliser une importation propre ou le nom complet de java.util.function.Function.
Importation d’un type unique
Une importation d’un type unique rend un type accessible disponible par son nom simple et indique explicitement le paquetage qui le fournit.
La déclaration import java.util.ArrayList; indique exactement quelle classe ArrayList le fichier utilise.
Importation statique
Une importation statique permet à un fichier source de désigner directement un membre statique accessible en omettant le nom de la classe qui le déclare.
Les tests JUnit importent souvent assertEquals de manière statique pour que chaque vérification se lise comme un appel direct d’assertion.
Indépendance des tests
L’indépendance des tests signifie que chaque test fournit l’état dont il a besoin et peut réussir ou échouer correctement lorsqu’il est exécuté seul ou dans n’importe quel ordre.
Un test de comptage crée ses propres colis au lieu de compter sur une méthode précédente pour les ajouter.
Injection de dépendances
L’injection de dépendances fournit les collaborateurs d’un objet depuis l’extérieur, ce qui permet à l’appelant de choisir leurs implémentations ou leurs entrées.
Transmettre un dépôt au constructeur du service permet au test de choisir un stockage contrôlé.
Interface publique
L’interface publique d’un paquetage est l’ensemble des types et des opérations accessibles qu’il offre délibérément au code extérieur au paquetage.
Un paquetage du domaine peut exposer un calculateur d’expédition tout en masquant la classe auxiliaire qui stocke ses tranches de poids.
Interface utilisateur
Une interface utilisateur est la partie d’un programme qui reçoit les actions de l’utilisateur et présente les résultats, par exemple un dialogue par commandes textuelles.
L’interface textuelle d’un calculateur de frais demande le poids en grammes, lit la réponse, appelle le calculateur et affiche les frais renvoyés.

J

JUnit Jupiter
JUnit Jupiter est le modèle de programmation et le moteur de test JUnit qui fournit des annotations, des assertions et des extensions modernes pour les tests Java.
Utiliser @Test de Jupiter garde l’annotation de test cohérente avec le moteur sélectionné par la construction.
JUnit Platform
JUnit Platform est la plateforme de base qui découvre et lance les moteurs de test pris en charge, notamment le moteur des tests Jupiter.
Gradle la sélectionne avec useJUnitPlatform() lorsque les tests du projet utilisent les annotations Jupiter.

L

Liste des tests
Une liste des tests recense les comportements et les cas qui restent à couvrir, ce qui permet de choisir une petite étape suivante et d’ajouter les découvertes au besoin.
Une liste pour les années bissextiles inclut les années ordinaires, les années divisibles par quatre et les deux cas d’années séculaires.
Logique applicative
La logique applicative est le code qui implémente les règles d’un programme et ses opérations sur les données, indépendamment de son dialogue avec l’utilisateur.
Un calculateur de frais choisit la bonne tranche tarifaire sans demander d’entrée ni écrire dans la console.

M

Membre statique
Un membre statique appartient à une classe plutôt qu’à un objet individuel et se représente en soulignant son entrée d’attribut ou d’opération UML.
Souligner une constante de poids maximal distingue une valeur de classe partagée du poids de chaque colis.
Méthode de cycle de vie
Une méthode de cycle de vie est une méthode que le framework de test exécute à un moment défini, par exemple avant chaque test ou une fois avant toute la classe.
@BeforeEach recrée les objets modifiés, tandis que @AfterEach peut supprimer les ressources de test temporaires.
Modificateur d’accès
Un modificateur d’accès est un mot-clé qui contrôle quel code peut utiliser une déclaration, comme une classe, un constructeur, un champ ou une méthode.
Une classe publique peut malgré tout avoir un constructeur qu’une classe d’interface située dans un autre paquetage ne peut pas appeler.
Multiplicité
La multiplicité indique le nombre autorisé d’objets liés à une extrémité d’une relation pour un objet à l’extrémité opposée.
Écrire 1..* à côté de Parcel exige que chaque expédition conserve au moins un colis lié.

N

Navigabilité
La navigabilité indique si un objet peut suivre une relation vers un autre objet ; elle est souvent représentée par une flèche ouverte.
Une flèche de l’expédition vers le colis montre qu’une expédition peut atteindre ses colis par des références.
Nom pleinement qualifié
Un nom pleinement qualifié identifie un type Java en associant son nom de paquetage et son nom simple, ce qui élimine l’ambiguïté entre paquetages.
Utiliser java.util.List distingue l’interface de collection d’un autre type également appelé List.
Nomenclature de dépendances
Une nomenclature de dépendances est un ensemble de métadonnées qui aligne les versions de modules associés afin qu’une construction puisse garder cohérente une famille de bibliothèques.
La nomenclature JUnit fournit des versions de modules compatibles après la sélection d’une version commune.

O

Objet factice
Un objet factice est une implémentation simple et fonctionnelle d’une dépendance utilisée dans les tests, qui fournit un comportement exploitable avec des ressources contrôlées.
Un dépôt en mémoire stocke des colis pour les tests de service sans ouvrir ni modifier de véritable fichier.
Opération
Une opération est un comportement déclaré dans un type UML, décrit par son nom, les types de ses paramètres, sa visibilité et son type de retour.
L’opération + totalWeightGrams(): int décrit une méthode publique sans donner son algorithme.
Outil de construction
Un outil de construction automatise des tâches de projet comme la résolution des dépendances, la compilation du code source, l’exécution des tests et l’empaquetage des artefacts.
Une commande Maven peut compiler un projet de colis, exécuter ses tests et produire le JAR résultant.

P

Paquetage
Un paquetage est un groupe nommé de types Java qui organise le code lié, sépare les noms des types et fournit une frontière d’accès.
Des paquetages distincts pour l’interface utilisateur et le domaine séparent le traitement des commandes des classes qui calculent les frais des colis.
Paquetage sans nom
Le paquetage sans nom contient les types des fichiers sources sans déclaration de paquetage ; les paquetages nommés ne peuvent pas importer ces types.
Déplacer les éléments auxiliaires partagés dans un paquetage nommé permet aux autres parties nommées d’un projet d’y faire référence.
Plugin
Un plugin ajoute des capacités et une configuration de construction, en fournissant souvent des tâches et des conventions pour un type de projet particulier.
Le plugin application de Gradle ajoute une tâche run pour la classe principale configurée du programme de colis.
Portée d’une dépendance
La portée d’une dépendance contrôle les chemins de classes Maven qui incluent une bibliothèque, en séparant les besoins propres aux tests de ceux du programme principal.
La portée test rend JUnit disponible sous src/test/java sans en faire une dépendance du code principal.
Préparer–agir–vérifier
Préparer–agir–vérifier est un schéma d’écriture de tests qui sépare la préparation d’un scénario, l’exécution de son action et la vérification de son résultat.
Créer une expédition, ajouter un colis et vérifier le nombre de colis donnent au test trois parties distinctes.

R

Racine des sources
Une racine des sources est le répertoire sous lequel commencent les chemins des paquetages Java, comme src/main/java pour le code de production dans une construction standard.
Pour parcels.domain.Parcel, le chemin sous la racine des sources est parcels/domain/Parcel.java.
Réalisation
La réalisation est une relation dans laquelle un type respecte un contrat d’interface, représentée par un trait en pointillés terminé par un triangle creux.
Un dépôt en mémoire réalise la même interface de dépôt qu’un dépôt sur fichier.
Refactorisation
La refactorisation modifie l’organisation interne du code tout en préservant son comportement observable, par exemple en renommant ou en extrayant une méthode.
Déplacer une méthode auxiliaire de calcul des frais vers un paquetage plus adapté est une refactorisation si le calculateur donne toujours les mêmes résultats.
Régression
Une régression est un défaut introduit dans un comportement qui fonctionnait auparavant, souvent après une modification dans une autre partie du code.
Un test existant de limite tarifaire détecte une régression lorsqu’une comparaison inclusive devient stricte.
Regroupement par paquetage
Un regroupement par paquetage est une zone en forme de dossier dans un diagramme UML qui organise les boîtes de types selon leur appartenance à un paquetage.
Regrouper séparément les classes de l’interface utilisateur et du domaine facilite l’examen des références entre ces zones.
Résultat attendu
Un résultat attendu est le résultat ou le comportement exigé par la spécification, déterminé indépendamment du code testé.
Lorsque les colis lourds sont facturés par tranche de poids entamée, les frais attendus proviennent du décompte des tranches, pas d’une copie de la sortie de la méthode.
Rouge–vert–refactorisation
Rouge–vert–refactorisation est le cycle TDD qui consiste à révéler un comportement manquant, à faire réussir tous les tests actuels et à améliorer la structure tout en préservant le comportement.
Une fois le nouveau cas de frais réussi, l’extraction du code de calcul répété appartient à l’étape de refactorisation.

S

Séparation des responsabilités
La séparation des responsabilités attribue des tâches différentes à différentes parties d’un programme afin que chacune soit plus facile à comprendre, à modifier et à tester.
Modifier la formulation de l’invite ne devrait nécessiter aucune modification de la méthode qui calcule les frais d’un colis.
Superclasse
Une superclasse est la classe dont une sous-classe hérite les membres et le comportement ; UML place le triangle de généralisation du côté de cette classe.
Les lettres et les colis peuvent hériter d’un comportement commun d’article sans le répéter dans chaque boîte de sous-classe.
Surefire
Surefire est le plugin Maven qui exécute les tests unitaires pendant la phase test selon les frameworks de test et les règles de découverte configurés.
Nommer une classe de test ShippingCalculatorTest correspond à un motif d’inclusion Surefire par défaut.
Symbole de visibilité
Un symbole de visibilité est un préfixe UML qui indique l’accès : + public, - private, # protected ou ~ accès de paquetage.
Un champ représenté avec - doit garder une visibilité privée lorsque le diagramme est traduit en Java.

T

Tâche Gradle
Une tâche Gradle est une unité de travail de construction, comme la compilation ou les tests, qui peut dépendre d’autres tâches et réutiliser des résultats inchangés.
Exécuter la tâche build entraîne aussi la compilation et les tests nécessaires à cette construction.
Test instable
Un test instable donne des résultats incohérents sans modification pertinente du code, souvent parce que le temps, l’état partagé ou des données extérieures ne sont pas contrôlés.
Un test qui lit la date du jour peut changer de résultat lorsque la date change pendant la nuit.
Test paramétré
Un test paramétré exécute une méthode de test pour plusieurs ensembles d’arguments fournis et présente chaque invocation comme un cas distinct.
Un tableau de poids et de frais peut vérifier plusieurs limites sans répéter le corps de la méthode d’assertion.
Test unitaire
Un test unitaire est une vérification automatisée d’une petite unité de code qui compare le comportement observé à un résultat spécifié indépendamment.
Un test de calculateur appelle la méthode de calcul des frais et vérifie le nombre de centimes attendu sans démarrer l’interface utilisateur.
Tolérance
Une tolérance est une différence numérique autorisée qu’utilise une assertion lorsque l’égalité exacte ne convient pas à un calcul en virgule flottante.
Une petite tolérance justifiée accepte un écart d’arrondi sans accepter un résultat nettement erroné.

W

Wrapper
Un wrapper est un lanceur fourni par le projet qui récupère et exécute la version de l’outil de construction choisie par le projet afin de rendre les commandes reproductibles.
Un wrapper Maven enregistré dans le projet permet aux contributeurs d’exécuter la version de Maven indiquée avec ./mvnw test.

À propos de cette unité

Par StudeliSignaler une erreur

Crée ton propre Matériel d'Étude

Transforme tes notes, diapos et documents en Guides d'Étude, Questions Pratiques, Flashcards et 6 autres outils.

Commencer