- Apprendre
- Informatique et numérique
- Programmation
- Java orienté objet : classes, collections, héritage et tests
- Paquetages et tests unitaires Java
Paquetages, diagrammes de classes et tests unitaires en Java
DébutantUnité 10 sur 11 de la formation Formation : Java orienté objet : classes, collections, héritage et tests
Ce que tu vas apprendre
- 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 importationsImporte 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 paquetagesChoisis entre public, protected, l’accès de paquetage et private pour que chaque paquetage n’expose que ce dont les autres paquetages ont besoin.
- Déclare des paquetages et fais-les correspondre aux répertoires
- 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 classeRepré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ésRelie 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 inversementRepré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.
- Dessine et lis la représentation d’une classe
- 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 applicativePlace 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épendancesOrganise 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.
- Sépare l’interface utilisateur de la logique applicative
- É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 JUnitCré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 boguesChoisis 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 à maintenirCré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 TDDSuis 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.
- Écris et exécute un premier test JUnit
- 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 MavenDé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.
- Configure et construis un projet Maven
Paquetages et tests unitaires Java : cours
1Organise 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/javadans 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.langne 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ès | Qui peut utiliser le membre ? |
|---|---|
public | Le code qui peut accéder au type déclarant |
protected | Le même paquetage, ainsi que les sous-classes d’autres paquetages sous certaines conditions |
| Sans modificateur | Le même paquetage exact |
private | La 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.
2Esquisse 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 utiliseType 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 |
|---|---|
1 | Exactement un |
0..1 | Zéro ou un |
* ou 0..* | Un nombre quelconque, zéro compris |
1..* | Au moins un |
2..4 | De 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
- Crée chaque classe ou interface dans son paquetage.
- Traduis les attributs en champs en conservant les noms, les types et la visibilité.
- Traduis les associations navigables en champs contenant une référence ou une collection.
- Ajoute
extendsouimplementsà partir des traits de généralisation ou de réalisation. - 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.
3Structure 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
mainet 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.
4Vé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 grammes | Résultat attendu |
|---|---|
0, -5 | Rejet avec IllegalArgumentException |
1, 2000 | 500 centimes |
2001, 10000 | 900 centimes |
10001, 11000 | 1 000 centimes |
11001 | 1 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 à
assertThrowssous 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
@BeforeEachpour 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.
5Construis 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
targetet 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
1Déplacer une classe de colis dans un paquetage
13 étapes
- 1IntroductionDonner à 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.
- 2ExemplePartir de la racine des sources
La racine des sources est
src/main/java. PourParceldanscom.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épertoiressrc/main/javade la racine correspondent à l’organisation de la construction et ne figurent pas dans la déclaration de paquetage. - 3ExplicationPréserver l’identité de la classe
Le nom simple est
Parcel; le nom pleinement qualifié estcom.example.parcels.domain.Parcel. Déplacer le fichier ne modifie pas cette identité à lui seul.Une classe publique nommée
Parceldoit se trouver dansParcel.java. Exécute une classe principale appartenant à un paquetage depuis la racine de l’arborescence compilée avec une commande commejava -cp out com.example.parcels.Main, afin que Java puisse trouver les répertoires de paquetages sousout. - 4DéfiCorriger un emplacement incohérent
Une classe publique nommée
Receiptdéclarepackage com.example.parcels.domain;, mais se trouve soussrc/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. - 5ExempleFaciliter 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. - 6Point 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 pasjava.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. - 7DéfiDistinguer deux classes List
Un fichier a besoin à la fois de
java.util.Listet dejava.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 écrisjava.awt.Listen 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. - 8ConseilReconnaî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, notammentString,MathetSystem, sont aussi disponibles automatiquement.Une importation statique a un autre rôle : importer
Assertions.assertEqualsde manière statique permet d’appeler directementassertEquals(...). Elle raccourcit une référence à un membre statique, pas un nom de paquetage. - 9ExempleVé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
Parcelgrâ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. - 10DéfiOuvrir l’opération, garder la classe auxiliaire masquée
L’interface utilisateur doit appeler la méthode
feeInCentsd’un calculateur public, actuellement déclarée sans modificateur d’accès. Seul le calculateur utilise sa classe auxiliaireWeightBands, qui a un accès de paquetage. Quel changement d’accès faut-il effectuer ?---reveal---
Rends
feeInCentspublique et laisseWeightBandsavec 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. - 11ExplicationUne 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.
- 12DéfiExaminer tout le chemin d’accès
TextUIimporte correctement une classe publiqueParcel. 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.
- 13Ré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é.
2Lire un modèle de colis et construire son squelette Java
13 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionLire la structure avant d’écrire les corps
- 2ExempleTraduire un champ et une méthode
- 3ExplicationRepérer un membre de classe
- 4DéfiConserver les symboles
- 5ExempleCompter les colis d’une expédition
- 6ExempleChoisir une représentation, puis faire respecter la règle
- 7Point cléSuivre un lien à sens unique
- 8DéfiVérifier un nombre minimal
- 9ExplicationPlacer la propriété à la bonne extrémité
- 10ExempleLire deux traits terminés par un triangle
- 11ConseilConstruire les déclarations dans l’ordre
- 12DéfiAssocier un contrat et une collection
- 13RécapitulatifDistinguer le rôle de chaque symbole
3Suivre une commande à travers des responsabilités distinctes
11 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionSuivre une commande dans le programme
- 2ExempleExtraire la règle du code de la console
- 3ExempleCalculer une tranche entamée
- 4Point cléGarder la validation avec la règle
- 5DéfiRendre un calcul réutilisable
- 6ExempleTransmettre le stockage au service
- 7LienRemplacer les fichiers par un objet factice fonctionnel
- 8ConseilVérifier séparément les appels et les importations
- 9DéfiCorriger un accès qui contourne le service
- 10DéfiMettre en place un test de service contrôlé
- 11RécapitulatifPlacer chaque changement là où sa tâche est prise en charge
4Développer une suite de tests Jupiter utile
19 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionTransformer les exigences en vérifications exécutables
- 2ExemplePréparer, agir et vérifier
- 3ExplicationBien lire les valeurs attendue et obtenue
- 4ConseilAutoriser délibérément l’arrondi numérique
- 5DéfiCorriger un test qui se contente d’afficher
- 6ExempleRepérer les changements de tarif
- 7Point cléGarder le résultat attendu indépendant
- 8ExemplePlacer l’appel rejeté dans la vérification
- 9DéfiChoisir le cas qui révèle le changement
- 10ExempleDonner une nouvelle expédition à chaque test
- 11ExempleExécuter un tableau avec un seul corps de test
- 12LienContrôler le collaborateur, pas l’ordre des tests
- 13DéfiDiagnostiquer les données laissées par un autre test
- 14ExempleCommencer le cycle avec une année ordinaire
- 15ExempleAjouter la règle générale de divisibilité par quatre
- 16ExempleAffiner la condition avec les cas d’années séculaires
- 17DéfiChoisir une étape suivante tout en préservant les vérifications
- 18DéfiAssocier le choix des cas et l’isolation
- 19RécapitulatifSe fier à des vérifications précises et reproductibles
5Du fichier de construction au rapport de test
13 étapes
Enregistre cette unité pour suivre chaque Leçon Interactive pas à pas.
- 1IntroductionRépéter la construction avec une seule commande
- 2ExplicationSéparer les sources des résultats générés
- 3ExempleNommer le projet et sa bibliothèque de test
- 4ExempleDemander la phase nécessaire
- 5ConseilNe pas confondre zéro test et un comportement vérifié
- 6DéfiTrouver un test mal placé
- 7ExplicationSélectionner les tâches Gradle
- 8ExempleGarder les modules Jupiter cohérents
- 9ExempleIndiquer à la tâche de test comment exécuter Jupiter
- 10DéfiCorriger la découverte des tests
- 11ExempleSuivre l’échec d’un test exécuté
- 12DéfiAssocier la découverte à une nouvelle construction
- 13RécapitulatifLire la construction comme une suite d’étapes vérifiables
Paquetages et tests unitaires Java : QCM
Série 1 : Noms, répertoires et accès Java10 Questions Pratiques
Question 1 sur 10
Ticket déclare package org.example.events.model;. Quel chemin doit-elle avoir sous la racine des sources Java principales ?Afficher la réponseMasquer la réponse
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
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 ?Afficher la réponseMasquer la réponse
Question 3 sur 10
import java.util.*; et utilise java.util.function.Predicate par son nom simple. Quelle importation supplémentaire d’un type unique fournit ce nom ?Afficher la réponseMasquer la réponse
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
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 ?Afficher la réponseMasquer la réponse
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
assertTrue(valid) sans le préfixe de classe Assertions. Quelle importation permet cet appel direct ?Afficher la réponseMasquer la réponse
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 ?Afficher la réponseMasquer la réponse
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
class RateTable n’a aucun modificateur d’accès. Où d’autres classes de premier niveau peuvent-elles l’utiliser ?Afficher la réponseMasquer la réponse
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
# threshold: int et + reset(): void souligné. Comment ces membres se traduisent-ils en Java ?Afficher la réponseMasquer la réponse
# 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
public boolean accepts(int grams) ?Afficher la réponseMasquer la réponse
Question 10 sur 10
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 ?Afficher la réponseMasquer la réponse
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
0..* est écrit à côté de Book. À combien de livres une bibliothèque peut-elle être liée ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
List<Session> est initialement vide et aucune méthode ne vérifie sa taille. Que manque-t-il dans l’implémentation ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
Printable. Quelle déclaration de sous-type Java ce trait représente-t-il ?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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
CatalogRepository. Comment un test peut-il éviter la véritable base de données ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
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 ?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
actual et le résultat exigé est 42. Quelle assertion d’égalité Jupiter indique correctement les valeurs ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
reading <= 800 par reading < 800. Quel relevé valide révèle l’erreur ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
IllegalArgumentException. Comment un test Jupiter doit-il exécuter l’appel ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
250 + ((grams - 3000) / 500) * 50 au-dessus de la limite. Quel nouveau test révèle le plus directement son défaut d’arrondi ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
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
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
platform(...) de Gradle. Pourquoi les déclarations des dépendances JUnit associées peuvent-elles omettre les numéros de version individuels ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
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
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 2 sur 10
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é ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 3 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 4 sur 10
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é ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 5 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 6 sur 10
Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 7 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 8 sur 10
< 2500 pour la tranche inférieure. Quelle paire de vérifications supplémentaires révèle les deux défauts ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 9 sur 10
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 ?Afficher la réponseMasquer la réponse
Enregistre cette unité pour voir cette réponse.
Question 10 sur 10
target. Que prouve ce fichier au sujet des sources d’aujourd’hui ?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
A
Accès de paquetage
Accès protégé
Agrégation
Assemblage
Assertion
Assertion sur une exception
assertThrows peut vérifier qu’un poids nul est rejeté et renvoyer l’exception pour vérifier son message.Association
parcels à l’extrémité des colis.Attribut
- weightGrams: int représente un champ entier privé contenant le poids du colis.C
Cas limite
Classe d’équivalence
Composition
Configuration de dépendances
Conflit de noms
List peut importer l’un et utiliser le nom pleinement qualifié de l’autre.Contrat d’interface
save par le contrat du dépôt, que le stockage utilise la mémoire ou un fichier.Coordonnées d’artefact
Couche
Couche d’accès aux données
Couche de présentation
Cycle de dépendances entre paquetages
Cycle de vie de construction
D
Déclaration d’importation
Scanner peut utiliser le nom simple dans sa déclaration de champ au lieu de java.util.Scanner.Déclaration de paquetage
package com.example.parcels.domain; intègre ce paquetage au nom complet de la classe.Dépendance
Parcel crée une dépendance envers la définition du type colis.Dépendance transitive
Dépôt
Développement piloté par les tests
Diagramme de classes UML
E
Expression lambda
() -> calculator.feeInCents(0) permet à JUnit d’exécuter l’appel invalide dans son assertion.F
Fixture de test
G
Généralisation
Letter vers Item se traduit par une déclaration Java dans laquelle Letter étend Item.I
Importation avec caractère générique
java.util.* ne dispense pas d’utiliser une importation propre ou le nom complet de java.util.function.Function.Importation d’un type unique
import java.util.ArrayList; indique exactement quelle classe ArrayList le fichier utilise.Importation statique
assertEquals de manière statique pour que chaque vérification se lise comme un appel direct d’assertion.Indépendance des tests
Injection de dépendances
Interface publique
Interface utilisateur
J
JUnit Jupiter
@Test de Jupiter garde l’annotation de test cohérente avec le moteur sélectionné par la construction.JUnit Platform
useJUnitPlatform() lorsque les tests du projet utilisent les annotations Jupiter.L
Liste des tests
Logique applicative
M
Membre statique
Méthode de cycle de vie
@BeforeEach recrée les objets modifiés, tandis que @AfterEach peut supprimer les ressources de test temporaires.Modificateur d’accès
Multiplicité
1..* à côté de Parcel exige que chaque expédition conserve au moins un colis lié.N
Navigabilité
Nom pleinement qualifié
java.util.List distingue l’interface de collection d’un autre type également appelé List.Nomenclature de dépendances
O
Objet factice
Opération
+ totalWeightGrams(): int décrit une méthode publique sans donner son algorithme.Outil de construction
P
Paquetage
Paquetage sans nom
Plugin
Portée d’une dépendance
src/test/java sans en faire une dépendance du code principal.Préparer–agir–vérifier
R
Racine des sources
src/main/java pour le code de production dans une construction standard.parcels.domain.Parcel, le chemin sous la racine des sources est parcels/domain/Parcel.java.Réalisation
Refactorisation
Régression
Regroupement par paquetage
Résultat attendu
Rouge–vert–refactorisation
S
Séparation des responsabilités
Superclasse
Surefire
ShippingCalculatorTest correspond à un motif d’inclusion Surefire par défaut.Symbole de visibilité
+ public, - private, # protected ou ~ accès de paquetage.- doit garder une visibilité privée lorsque le diagramme est traduit en Java.T
Tâche Gradle
Test instable
Test paramétré
Test unitaire
Tolérance
W
Wrapper
./mvnw test.- 1Premiers programmes Java
- 2Conditions et boucles en Java
- 3Méthodes Java
- 4Listes et tableaux en Java
- 5Classes et objets en Java
- 6Références et égalité en Java
- 7HashMap et collections en Java
- 8Héritage et polymorphisme en Java
- 9Exceptions et fichiers en Java
- 10Paquetages et tests unitaires Java
- 11Généricité et flux en Java
À propos de cette unité
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.