Pakete, Klassendiagramme und Modultests in Java
AnfängerEinheit 10 von 11 in Kurs: Java-Programmierung – Klassen, Vererbung, Collections und Streams
Was du lernst
- Fasse zusammengehörige Klassen in Paketen zusammen, speichere jede Quelldatei dort, wo Compiler und Build-Werkzeuge sie erwarten, und verwende Klassen über Paketgrenzen hinweg mit Importdeklarationen und passenden Zugriffsrechten.
- Deklariere Pakete und ordne sie Verzeichnissen zuSchreibe eine Paketdeklaration, wähle einen üblichen Paketnamen, speichere die Datei im passenden Verzeichnis und verwende Klassen über ihre vollständig qualifizierten Namen.
- Verwende Klassen aus anderen Paketen mit ImportdeklarationenImportiere einzelne Klassen oder ganze Pakete, erkenne Klassen, die keinen Import benötigen, löse Konflikte zwischen gleichnamigen Klassen und verwende statische Importe.
- Steuere den Zugriff über Paketgrenzen hinwegWähle zwischen public, protected, package-private und private, sodass jedes Paket nur das zugänglich macht, was andere Pakete benötigen.
- Deklariere Pakete und ordne sie Verzeichnissen zu
- Lies und zeichne Klassendiagramme, die Klassen, ihre Mitglieder und die Beziehungen zwischen ihnen zeigen, und übertrage Diagramme in Java-Code und Java-Code in Diagramme.
- Zeichne und lies die Darstellung einer einzelnen KlasseStelle den Namen, die Attribute und Operationen einer Klasse mit Typen und Sichtbarkeit dar und markiere statische Mitglieder, abstrakte Klassen und Interfaces.
- Stelle Assoziationen und Multiplizitäten darVerbinde Klassen durch Assoziationen, lies und schreibe Multiplizitäten und Navigierbarkeit und unterscheide Komposition, Aggregation und Abhängigkeit.
- Modelliere Vererbung und übertrage Diagramme in Code und Code in DiagrammeStelle Vererbung und Interface-Implementierung dar, schreibe Java-Code ausgehend von einem Diagramm und zeichne ein Diagramm ausgehend von bestehendem Code.
- Zeichne und lies die Darstellung einer einzelnen Klasse
- Teile ein Programm so auf, dass Benutzeroberfläche, Anwendungslogik und Datenspeicherung in getrennten Klassen und Paketen liegen und die Abhängigkeiten in eine Richtung zeigen.
- Trenne die Benutzeroberfläche von der ProgrammlogikHalte das Einlesen von Eingaben und die Ausgabe in einer Klasse für die Benutzeroberfläche, die Regeln des Programms in Logikklassen und verbinde beide in der main-Methode.
- Ordne Schichten, Pakete und Abhängigkeiten anOrdne Klassen einer Präsentationsschicht, einer Logikschicht und einer Datenzugriffsschicht zu, richte Abhängigkeiten nach unten aus und verbinde die Schichten über Interfaces.
- Trenne die Benutzeroberfläche von der Programmlogik
- Schreibe automatisierte Modultests mit JUnit, wähle Testfälle, die Fehler aufdecken, gestalte Tests unabhängig und lesbar und entwickle neuen Code testgetrieben.
- Schreibe einen ersten JUnit-Test und führe ihn ausErstelle eine Testklasse, schreibe Testmethoden mit Assertions, führe sie aus und lies das Ergebnis eines bestandenen und eines fehlgeschlagenen Tests.
- Wähle Testfälle, die Fehler aufdeckenWähle typische Eingaben, Grenzwerte und ungültige Eingaben, teste erwartete Ausnahmen und gib jedem Test einen klaren Zweck und Namen.
- Gestalte Tests unabhängig und wartbarErzeuge vor jedem Test neue Testobjekte, vermeide gemeinsamen Zustand, führe einen Test mit vielen Eingaben aus und teste die Logik getrennt von Ein- und Ausgabe.
- Entwickle Code testgetrieben mit TDDDurchlaufe ausgehend von einer Testliste den Rot-Grün-Refactoring-Zyklus, gehe in kleinen Schritten vor und verstehe, was testgetriebene Entwicklung verspricht und was nicht.
- Schreibe einen ersten JUnit-Test und führe ihn aus
- Verwende ein Build-Werkzeug zur Verwaltung von Bibliotheken, halte die Standardprojektstruktur ein und kompiliere, teste und paketiere ein Programm mit einem Befehl.
- Richte ein Maven-Projekt ein und baue esBeschreibe ein Projekt in pom.xml mit Koordinaten und Abhängigkeiten, halte die Standardverzeichnisstruktur ein und durchlaufe den Build-Lebenszyklus.
- Richte ein Gradle-Projekt ein und behebe Build-FehlerSchreibe das entsprechende Gradle-Build-Skript, führe JUnit-Tests auf der JUnit Platform aus, vergleiche die beiden Werkzeuge und finde die Ursache eines fehlgeschlagenen Builds.
- Richte ein Maven-Projekt ein und baue es
Modultests in Java einfach erklärt
1Gliedere ein Java-Programm in Pakete
Wenn ein Programm wächst, benötigen Klassennamen und Verzeichnisse eine Struktur, die sowohl Menschen als auch Java nachvollziehen können. Pakete gruppieren zusammengehörige Typen und legen Zugriffsgrenzen fest. Importdeklarationen erleichtern die Verwendung dieser Typen, ohne ihre Interna freizugeben. Du ordnest eine Deklaration ihrem Quellpfad zu, löst Namen über Paketgrenzen hinweg auf und entscheidest, welche Klassen, Konstruktoren und Methoden ein anderer Programmteil aufrufen darf.
1.1 Paketnamen und Quellverzeichnisse
Ein Paket gruppiert zusammengehörige Klassen und Interfaces unter einem Namen. Es trennt außerdem Namen: Zwei Klassen namens Parcel können nebeneinander bestehen, wenn ihre Paketnamen verschieden sind. Ein Paket bietet Java sowohl eine Zugriffsgrenze als auch eine Möglichkeit, Dateien zu ordnen.
Deklariere zuerst das Paket
Eine gewöhnliche Quelldatei beginnt mit ihrer Paketdeklaration, gefolgt von Importdeklarationen und dann der Klasse. Vor der Deklaration dürfen Kommentare stehen. Zum Beispiel:
package com.example.parcels.domain;
public class Parcel {
private int weightGrams;
public Parcel(int weightGrams) {
this.weightGrams = weightGrams;
}
}
Jeder Typ auf oberster Ebene in dieser Datei gehört zu com.example.parcels.domain. Eine Datei hat höchstens eine Paketdeklaration. Die öffentliche Klasse Parcel gehört in Parcel.java; allein durch das Ändern des Verzeichnisses ändert sich das deklarierte Paket nicht.
Wandle Punkte in Verzeichnisse um
Das Quellverzeichnis ist das Verzeichnis, in dem der Paketpfad beginnt. In einem standardmäßigen Maven- oder Gradle-Projekt beginnt der Java-Hauptcode bei src/main/java. Ersetze jeden Punkt im Paketnamen durch einen Schrägstrich und füge dann den Klassennamen hinzu:
src/main/java/
com/example/parcels/domain/Parcel.java
Das Paket heißt com.example.parcels.domain, nicht src.main.java.com.example.parcels.domain. Die ersten drei Verzeichnisse beschreiben die Build-Struktur; sie sind kein Teil des Java-Namens. Meldet eine IDE eine Abweichung beim Paket, vergleiche die Deklaration mit dem Pfad unterhalb des Quellverzeichnisses. Verschiebe die Datei oder korrigiere die Deklaration, sodass beides übereinstimmt.
Paketnamen verwenden Kleinbuchstaben. Organisationen kehren gewöhnlich ihre Domain um: Aus example.com wird com.example, gefolgt vom Projekt und seinem Bereich. Ein Projekt ohne Domain kann parcels.domain verwenden. Enthält ein Teil der umgekehrten Domain einen Bindestrich, ersetze ihn durch einen Unterstrich, wie in org.example.hyphenated_name. Vermeide die Namensräume java und javax der Plattform.
Verwende den vollständigen Klassennamen
Ein vollständig qualifizierter Name verbindet Paketnamen und einfachen Klassennamen: com.example.parcels.domain.Parcel. Er identifiziert die Klasse auch dann, wenn ein anderes Paket ebenfalls eine Klasse Parcel enthält. Im Code kann eine zugängliche Klasse ohne Import vollständig benannt werden:
java.util.ArrayList<String> names = new java.util.ArrayList<>();
Um eine Hauptklasse in einem Paket zu starten, gib ihren vollständigen Namen und einen Klassenpfad an, der auf die Wurzel des kompilierten Paketbaums zeigt. Vom Projektverzeichnis aus:
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
Hier erzeugt javac -d out den Ausgabebaum, und java -cp out sucht von dessen Wurzel aus. Startest du java Main aus einem Paketverzeichnis, fehlt der Paketanteil der Klassenidentität. Build-Werkzeuge und IDEs verwalten diese Befehle normalerweise.
Unterscheide benachbarte Pakete
com.example.parcels und com.example.parcels.domain sind getrennte Pakete. Ein gemeinsamer Namensanfang verleiht keine zusätzlichen Zugriffsrechte. Eine Datei ohne Deklaration gehört zum unbenannten Paket. Benannte Pakete können dessen Klassen nicht importieren. Verwende daher in einem Programm mit mehreren Teilen durchgehend benannte Pakete.
Wichtige Erkenntnisse
- Setze die Paketdeklaration vor Importdeklarationen und die Klasse.
- Stimme den Paketpfad unterhalb des Quellverzeichnisses ab;
src/main/javagehört nicht zum Paketnamen. - Verwende kleingeschriebene Paketnamen und bei Bedarf den vollständig qualifizierten Klassennamen.
- Ein gemeinsamer Anfang von Paketnamen bedeutet keinen gemeinsamen Paketzugriff.
- Starte Klassen in Paketen von der Wurzel des kompilierten Baums aus mit ihren vollständig qualifizierten Namen.
1.2 Importdeklarationen und Konflikte bei Klassennamen
Ein Import ermöglicht es einer Quelldatei, den einfachen Namen eines zugänglichen Typs zu verwenden. Er teilt dem Compiler mit, was der Name bezeichnet. Er kopiert weder die Klasse, lädt eine Bibliothek herunter noch macht er eine verborgene Klasse öffentlich.
Lies den Anfang einer Quelldatei
Die Benutzeroberfläche gehört zu einem Paket und verwendet Klassen aus zwei anderen:
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 und ShippingCalculator stammen aus dem Domain-Paket des Programms; Scanner stammt aus der Java-Bibliothek. Jeder Import gilt nur für diese Datei. Eine andere Datei, die Scanner verwendet, benötigt einen eigenen Import oder den vollständigen Namen java.util.Scanner.
Wähle einen einzelnen Typ oder einen Wildcard-Import
import java.util.Scanner; ist ein Einzeltypimport. Die genaue Klasse ist für den Leser sichtbar. import java.util.*; ist ein Wildcard-Import: Zugängliche Typen in diesem Paket können bei ihrer Verwendung mit ihrem einfachen Namen bezeichnet werden. Der Wildcard-Import lädt keine unbenutzten Klassen in das Programm und verlangsamt es nicht.
Der Wildcard-Import endet bei diesem Paket. Er umfasst weder java.util.function.Function noch java.util.stream.Collectors, die in getrennten Paketen liegen. Dieselbe Regel gilt für eigenen Code: import com.example.parcels.*; erreicht com.example.parcels.domain.Parcel nicht.
In drei Fällen ist kein Import nötig: bei Typen im selben Paket, automatisch verfügbaren Typen in java.lang und vollständig benannten Typen. Deshalb erscheinen String, System, Math, Integer und Object häufig ohne Importdeklaration.
Löse einen gemeinsamen einfachen Namen auf
Sowohl java.util als auch java.awt enthalten eine Klasse namens List. Zwei Einzeltypimporte für diese verschiedenen Klassen verursachen einen Kompilierfehler. Zwei Wildcard-Importe können nebeneinander bestehen, aber der einfache Name List wird bei seiner Verwendung mehrdeutig. Ein Einzeltypimport hat Vorrang vor einem Wildcard-Import.
Werden beide Klassen benötigt, importiere eine und schreibe die andere aus:
import java.util.List;
class NameDisplay {
private List<String> names;
private java.awt.List widget;
}
Java-Importe bieten keine Alias-Syntax wie import ... as .... Mit dem vollständig qualifizierten Namen lässt sich der zweite Typ unterscheiden.
Importiere ein statisches Mitglied
Ein statischer Import macht den Namen der zugehörigen Klasse beim Aufruf eines statischen Mitglieds überflüssig:
import static java.lang.Math.sqrt;
Die Datei kann nun sqrt(16.0) aufrufen. Testcode verwendet häufig import static org.junit.jupiter.api.Assertions.assertEquals;, sodass Assertions als direkte Prüfungen lesbar sind. Setze statische Importe in gewöhnlichem Programmcode gezielt ein, damit Leser weiterhin erkennen können, woher Operationen stammen.
Beachte die Fehlermeldung
„Cannot find symbol“ kann einen fehlenden Import oder einen falsch geschriebenen Typ bedeuten. „Package ... does not exist“ deutet auf ein Problem mit der Schreibweise des Pakets, dem Quellverzeichnis oder einer Abhängigkeit hin. „Is not public ... cannot be accessed from outside package“ bedeutet, dass Java den Typ gefunden hat, der Zugriff aber gesperrt ist. Ein weiterer Import kann diesen Zugriffsfehler nicht beheben; prüfe die Klassendeklaration und die benötigten Mitglieder.
Wichtige Erkenntnisse
- Ein Import löst Namen in einer Datei auf und verleiht keine Zugriffsrechte.
- Ein Wildcard-Import umfasst Typen in einem Paket, ohne dessen Unterpakete.
- Typen im selben Paket und Typen aus
java.langbenötigen keinen Import. - Verwende einen Einzeltypimport und einen vollständig qualifizierten Namen, wenn zwei benötigte Klassen denselben Namen haben.
- Ein statischer Import macht ein statisches Mitglied ohne seinen Klassennamen aufrufbar.
- Unterscheide einen Fehler bei der Namensauflösung von einem Zugriffsfehler.
1.3 Eine kleine öffentliche Schnittstelle
Ein Paket sollte die Klassen und Methoden zugänglich machen, die andere Teile benötigen, und Implementierungsdetails verborgen halten. Zugriffsmodifikatoren setzen diese Grenze durch. Ein Import kann einen Namen verkürzen, aber keine Grenze überschreiten, die Java-Zugriffsregeln schließen.
Wähle den Zugriff für ein Klassenmitglied
| Zugriff | Wer kann das Mitglied verwenden? |
|---|---|
public | Code, der auf den deklarierenden Typ zugreifen kann |
protected | Dasselbe Paket sowie Unterklassen über Paketgrenzen hinweg |
| Kein Modifikator | Exakt dasselbe Paket |
private | Die deklarierende Klasse und ihre verschachtelten Typen |
Kein Modifikator bedeutet für gewöhnliche Klassenmitglieder und Konstruktoren package-private, also Paketzugriff. Es gibt kein entsprechendes Schlüsselwort default. Eine Klasse auf oberster Ebene hat nur zwei Möglichkeiten: public oder package-private. Eine öffentliche Klasse auf oberster Ebene wird in einer Datei mit ihrem Namen gespeichert, etwa ShippingCalculator.java.
Prüfe den Typ und die Operation
Angenommen, die Benutzeroberfläche muss ein Paket erzeugen. Eine öffentliche Klasse allein reicht nicht aus:
package com.example.parcels.domain;
public class Parcel {
private int weightGrams;
Parcel(int weightGrams) {
this.weightGrams = weightGrams;
}
}
Der Konstruktor hat Paketzugriff. Code in com.example.parcels.ui kann Parcel benennen, aber nicht new Parcel(1200) aufrufen. Mache den Konstruktor public, wenn das Erzeugen von Paketen zum öffentlichen Angebot des Pakets gehört. Halte weightGrams privat und stelle stattdessen eine passende Operation bereit, statt das Feld freizugeben.
Beim Paketzugriff wird der gesamte Paketname verglichen. Weder ein übergeordnet aussehendes noch ein untergeordnet aussehendes Paket ist dasselbe Paket. Code in com.example.parcels erhält keinen besonderen Zugriff auf Hilfsklassen in com.example.parcels.domain.
Verstehe geschützten Zugriff
protected erlaubt den Zugriff sowohl aus nicht verwandten Klassen im deklarierenden Paket als auch aus Unterklassen. Über Paketgrenzen hinweg erfolgt der Zugriff auf ein Instanzmitglied im Vererbungskontext der Unterklasse. Eine Unterklasse kann ihr eigenes geerbtes Feld verwenden, aber kein beliebiges Oberklassenobjekt nutzen, um die Paketgrenze zu umgehen.
Wenn zum Beispiel Item das Feld protected int weightGrams deklariert, kann ein Letter extends Item in einem anderen Paket this.weightGrams verwenden. Innerhalb von Letter erlaubt ein Parameter, der lediglich als Item typisiert ist, keinen Zugriff über item.weightGrams; eine als Letter typisierte Referenz erlaubt den geerbten Zugriff dagegen. Diese Einschränkung verhindert, dass die Rechte einer Unterklasse zu einem allgemeinen Zugriff auf jedes Item werden.
Verberge Hilfsklassen bewusst
Mache ShippingCalculator und feeInCents öffentlich, wenn die Benutzeroberfläche sie aufruft. Eine unterstützende Klasse WeightBands kann Paketzugriff behalten. Code außerhalb des Domain-Pakets hängt dann vom Verhalten des Rechners ab und nicht davon, wie er die Tabelle speichert. Wird diese Hilfsklasse ersetzt, sind an anderer Stelle weniger Änderungen nötig.
Tests können denselben Paketnamen wie die getestete Klasse verwenden und dennoch unter src/test/java liegen. Dadurch erhalten sie Paketzugriff, ohne Testcode zwischen den Programmdateien abzulegen. Zugriff auf private Felder erhalten sie dadurch nicht. Jedes Mitglied nur für einen Test öffentlich zu machen, verbreitet Abhängigkeiten von Interna; teste stattdessen öffentliches Verhalten oder eine nützliche Operation mit Paketzugriff.
Wichtige Erkenntnisse
- Klassenmitglieder mit Paketzugriff sind nur im exakt selben Paket zugänglich.
- Aufrufender Code benötigt Zugriff sowohl auf eine Klasse als auch auf den verwendeten Konstruktor oder die verwendete Methode.
- Geschützter Zugriff umfasst Zugriff im selben Paket und eingeschränkten Zugriff für Unterklassen über Paketgrenzen hinweg.
- Halte Felder privat und stelle nur die Operationen bereit, die ein anderes Paket benötigt.
2Skizziere die Programmstruktur mit UML-Klassendiagrammen
Ein Klassendiagramm gibt dir einen kompakten Überblick über ein Programm, bevor du seine Methodenrümpfe liest. Kästen beschreiben Typen und Operationen; Linien beschreiben Verbindungen, Anzahlen und Untertypbeziehungen. Du liest diese Zeichen genau und überträgst sie in Java-Deklarationen. Das Paketprogramm liefert Beispiele für einzelne Klassen, Collections assoziierter Objekte und austauschbare Repository-Implementierungen.
2.1 Was ein UML-Klassenkasten aussagt
Ein UML-Klassendiagramm zeigt die statische Struktur: Klassen, ihre Mitglieder und Beziehungen. Es hilft zu erkennen, welche Arten von Objekten es gibt und was sie anbieten. Es verfolgt nicht die Reihenfolge der Methodenaufrufe während der Ausführung.
Lies die drei Bereiche
Ein Klassenkasten enthält oben den Klassennamen, in der Mitte Attribute und unten Operationen. Ein Attribut stellt gewöhnlich ein Java-Feld dar; eine Operation stellt die sichtbare Signatur einer Methode dar. Eine Skizze kann Bereiche weglassen, die ihrem Zweck nicht dienen.
Für ein Paket sieht eine textuelle Darstellung des Kastens so aus:
Parcel
-----------------------------
- weightGrams: int
- recipient: String
-----------------------------
+ Parcel(weightGrams: int, recipient: String)
+ getWeightGrams(): int
+ isHeavierThan(limitGrams: int): boolean
Der Konstruktor trägt den Namen der Klasse und hat keinen Rückgabetyp. Die Methode isHeavierThan nimmt eine Ganzzahl namens limitGrams entgegen und gibt einen booleschen Wert zurück.
Übertrage die Reihenfolge von Namen und Typen
UML setzt den Namen vor den Typ: weightGrams: int. Java setzt den Typ an die erste Stelle: int weightGrams. Bei einer Operation stehen auch die Parameternamen vor den Typen; der Rückgabetyp folgt nach der schließenden Klammer.
private int weightGrams;
public boolean isHeavierThan(int limitGrams) {
return weightGrams > limitGrams;
}
Dieser Code entspricht - weightGrams: int und + isHeavierThan(limitGrams: int): boolean. Ein Methodenrumpf fehlt gewöhnlich im Diagramm; die Operation gibt an, was aufgerufen werden kann, nicht den Algorithmus.
Beachte Sichtbarkeit und besondere Kennzeichnungen
Die Sichtbarkeitssymbole sind + für public, - für private, # für protected und ~ für Paketzugriff. Setze das Symbol vor den Mitgliedsnamen. Fehlt in einem vorgegebenen Diagramm ein Symbol, erfinde die Sichtbarkeit nicht; ergänze bei der Dokumentation von Code das vorgesehene Symbol.
Ein statisches Mitglied wird unterstrichen. Es gehört zur Klasse und nicht zu jedem einzelnen Objekt. Eine abstrakte Klasse oder Operation wird kursiv geschrieben oder mit {abstract} markiert, wenn Kursivschrift unpraktisch ist. Die Markierung als abstrakt bedeutet, dass eine Unterklasse fehlendes Verhalten bereitstellen muss, bevor ein Objekt aus einer konkreten Klasse erzeugt werden kann.
Ein Interface-Kasten trägt «interface» über seinem Namen. Beispielsweise kann ParcelRepository + save(parcel: Parcel): void und + findAll(): List<Parcel> anbieten. Der Kasten beschreibt einen Vertrag; er legt keine Speicherimplementierung fest.
Wähle den benötigten Detailgrad
Eine Planungsskizze zeigt vielleicht nur Parcel, Shipment und deren wichtigste Operationen. Ein Diagramm zur Implementierung einer Klasse benötigt Typen und Sichtbarkeit. Werden alle gewöhnlichen Getter aufgeführt, können die Beziehungen verdeckt werden, die das Diagramm zeigen sollte. Wähle nützliche Mitglieder und halte sie mit dem Code konsistent: Ein privates Java-Feld sollte nicht mit einem öffentlichen + erscheinen, und eine statische Methode benötigt ihre Unterstreichung.
Wichtige Erkenntnisse
- Ein Klassenkasten trennt Name, Attribute und Operationen.
- UML verwendet
name: Type; Java verwendetType name. - Verwende
+,-,#und~für öffentlichen, privaten, geschützten und Paketzugriff. - Unterstreiche statische Mitglieder; markiere abstrakte Elemente kursiv oder mit
{abstract}. - Ein Interface-Kasten zeigt
«interface»und die Operationen seines Vertrags.
2.2 Lies die Enden von Beziehungslinien
Eine Assoziation zeigt eine strukturelle Verbindung zwischen Objekten. In Java hält ein Objekt häufig eine Referenz auf ein anderes in einem Feld. Die Linie kann auch zeigen, wie viele Objekte zulässig sind und in welcher Richtung der Referenz gefolgt werden kann.
Zähle vom gegenüberliegenden Ende aus
Die Multiplizität steht neben der Klasse, deren Objekte sie zählt. Steht 1..* neben Parcel und 1 neben Shipment, hat eine Sendung ein oder mehrere Pakete, und jedes Paket gehört zu genau einer Sendung. Lies jedes Ende einzeln, ausgehend von einem Objekt am gegenüberliegenden Ende.
| Multiplizität | Bedeutung |
|---|---|
1 | Genau eines |
0..1 | Null oder eines |
* oder 0..* | Beliebig viele, einschließlich null |
1..* | Mindestens eines |
2..4 | Zwei bis vier |
Eine einzelne Referenz kann 1 oder 0..1 darstellen; eine Collection stellt ein mehrwertiges Ende dar. Eine Java-List allein erzwingt keine Untergrenze. Eine Shipment mit der Paketregel 1..* muss eine anfangs leere Liste zurückweisen und verhindern, dass spätere Operationen sie leer zurücklassen. Ebenso benötigt eine vorgeschriebene einzelne Referenz eine Prüfung auf null.
Folge der Referenz
Eine offene Pfeilspitze bei Parcel bedeutet, dass einer Referenz von Shipment zu Parcel gefolgt werden kann. Ein Klassendiagramm kann die Navigierbarkeit offenlassen. In der hier verwendeten Lehrkonvention kennzeichnen Pfeile einseitige Referenzen; eine Linie ohne Pfeil kann beidseitigen Zugriff oder eine nicht festgelegte Richtung bedeuten. Schließe aus einem Pfeil zu Parcel allein nicht auf ein Feld für die Rückreferenz.
Ein Rollenname wie parcels bezeichnet, was die Verbindung aus Sicht der Sendung bedeutet. Zeige diese Referenz entweder als Assoziation oder als - parcels: List<Parcel> im Kasten. Werden ohne Grund beide Formen dargestellt, kann ein Feld wie zwei verschiedene Bestandteile des Zustands aussehen.
Wähle die Bedeutung der Ganzes-Teil-Beziehung
Eine Komposition verwendet eine ausgefüllte Raute am Ganzen. Ein Teil gehört zu einem Zeitpunkt höchstens zu einem zusammengesetzten Ganzen und wird normalerweise mit diesem entfernt, wie eine Bestellung und ihre Bestellpositionen. Eine Aggregation verwendet eine nicht ausgefüllte Raute für eine lockerere Gruppierung, deren Teile unabhängig existieren können. Ein Team kann Spieler gruppieren, ohne deren Lebensdauer zu bestimmen.
Java hat kein Schlüsselwort für die Raute und erzwingt UML-Besitzverhältnisse nicht allein deshalb, weil eine Liste existiert. Modelliere eine Komposition nur, wenn der Entwurf diese Besitzregel enthält. Besagt der Entwurf lediglich, dass ein Objekt ein anderes kennt, ist eine einfache Assoziation klarer als eine unbegründete Raute.
Zeige eine Verwendung, ohne Besitz zu behaupten
Eine Abhängigkeit ist eine gestrichelte Linie mit einer offenen Pfeilspitze zum verwendeten Typ. Ein Rechner mit feeInCents(Parcel parcel) hängt von Parcel ab, weil seine Methodensignatur diesen Typ verwendet. Eine Abhängigkeit ist eine allgemeine Verwendungsbeziehung; sie ist nicht auf Code ohne Felder beschränkt. Eine Assoziation liefert genauere Strukturinformationen, wenn ein Feld für das Modell wichtig ist.
Frag dich vor dem Zeichnen einer Linie, was der Code oder die Anforderung festlegt: eine Referenz, eine zulässige Anzahl, eine Verwendung oder Besitz. Eine Collection legt eine mehrwertige Speicherung fest, aber für sich genommen weder ausschließlichen Besitz noch eine Mindestanzahl.
Wichtige Erkenntnisse
- Die Multiplizität neben einer Klasse zählt deren Objekte für ein Objekt am anderen Ende.
- Ein Collection-Typ erzwingt weder eine minimale noch eine maximale Multiplizität.
- Ein Assoziationspfeil zeigt auf den Typ, zu dem die Ausgangsseite navigieren kann.
- Setze die Raute einer Komposition oder Aggregation an das Ende des Ganzen.
- Eine Abhängigkeit ist ein gestrichelter Verwendungspfeil; eine Assoziation beschreibt eine strukturelle Verbindung.
- Zeige eine Referenz einmal, sofern das Diagramm nicht bewusst beide Darstellungen benötigt.
2.3 Vom Diagramm zu Java-Deklarationen
Sowohl Vererbung als auch Interface-Implementierung verbinden einen spezielleren Typ mit einem allgemeineren. UML verwendet am allgemeinen Typ ein nicht ausgefülltes Dreieck, unterscheidet die beiden Beziehungen aber durch den Linienstil. Dieser Unterschied zeigt dir, welche Java-Deklaration du schreiben musst.
Lies das Dreieck und seine Linie
Eine durchgezogene Linie mit einem nicht ausgefüllten Dreieck an Item bedeutet, dass eine Unterklasse wie Letter Item erweitert. Das ist Generalisierung. Eine gestrichelte Linie mit einem nicht ausgefüllten Dreieck an ParcelRepository bedeutet, dass eine Klasse wie InMemoryParcelRepository dieses Interface implementiert. Das ist Realisierung.
Das Dreieck berührt immer die Oberklasse oder das Interface. Ein gestrichelter offener Pfeil ohne Dreieck bedeutet dagegen eine Abhängigkeit. Linienstil und Pfeilspitze ergeben zusammen die Bedeutung; eine gestrichelte Linie allein reicht nicht aus.
Baue das Grundgerüst in einer sinnvollen Reihenfolge
- Erstelle jede Klasse und jedes Interface im jeweiligen Paket.
- Übertrage Attribute in Felder und behalte Namen, Typen und Sichtbarkeit bei.
- Übertrage navigierbare Assoziationen in Referenz- oder Collection-Felder.
- Ergänze
extendsoderimplementsanhand der Generalisierungs- oder Realisierungslinien. - Übertrage Operationen in Methodenköpfe und ergänze anschließend ihre Rümpfe.
Angenommen, ein Interface bietet save und findAll an. Eine Implementierung hat eine mehrwertige Assoziation zu Parcel mit der Rolle 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);
}
}
Die gestrichelte Linie mit Dreieck liefert implements ParcelRepository. Die Assoziation liefert das Listenfeld. Die Operationssignaturen liefern die beiden öffentlichen Methoden. Die Methodenrümpfe sind eine Implementierungsentscheidung: Hier hängt save ein Objekt an, und findAll gibt eine neue Liste zurück, sodass aufrufender Code keine Einträge direkt in der gespeicherten Liste hinzufügen oder entfernen kann. Die Paketobjekte selbst werden weiterhin gemeinsam genutzt.
Zeichne in die andere Richtung
Beginne mit einem Kasten für jede Klasse und jedes Interface. Ein Feld mit einem einfachen Wert wird zu einem Attribut. Eine Referenz auf eine Klasse im Programm wird zu einer Assoziation; eine Collection legt ein mehrwertiges Ende nahe. Prüfe die Kontrollen in Konstruktoren und Methoden, bevor du eine Untergrenze von eins angibst. Übertrage extends in eine durchgezogene Linie mit Dreieck und implements in eine gestrichelte Linie mit Dreieck.
Geerbte Felder und Operationen bleiben gewöhnlich im Kasten der Oberklasse. Setze neue oder überschriebene Mitglieder in die Unterklasse. Ergänze eine Abhängigkeit für einen Parameter, Rückgabetyp oder eine lokale Verwendung, wenn diese Beziehung den Entwurf erklärt.
Pakete können ihre Klassen in ordnerförmigen Kästen mit einem kleinen Reiter umgeben. Diese Gruppen helfen zu erkennen, welche Bereiche von welchen anderen abhängen. Halte ein Dokumentationsdiagramm bei Codeänderungen aktuell; kennzeichne eine frühe Planungsskizze, damit Leser ihren Zweck verstehen.
Eine Shipment, die Pakete enthält, ist keine Art von Parcel. Ihre Liste beschreibt eine „Hat-ein“-Beziehung, zeichne also eine Assoziation. Verwende Vererbung für eine „Ist-eine-Art-von“-Beziehung, die das Java-Typsystem und das Verhalten tatsächlich unterstützen.
Wichtige Erkenntnisse
- Generalisierung verwendet eine durchgezogene Linie und ein nicht ausgefülltes Dreieck an der Oberklasse.
- Realisierung verwendet eine gestrichelte Linie und ein nicht ausgefülltes Dreieck am Interface.
- Übertrage Felder, Assoziationen, Vererbung und Methodensignaturen, bevor du Methodenrümpfe schreibst.
- Behalte beim Übertragen die Typen und die Sichtbarkeit des Diagramms bei.
- Der deklarierte Typ einer Collection allein kann keine Mindestanzahl festlegen.
- Verwende Assoziationen für enthaltene Objekte und Vererbung für Untertypbeziehungen.
- Halte Dokumentationsdiagramme mit dem Code, den sie beschreiben, konsistent.
3Gliedere ein Programm in Schichten
Ein Programm lässt sich leichter testen und ändern, wenn Dialog, Regeln und Speicherung getrennte Verantwortlichkeiten haben. Du verfolgst einen Paketbefehl durch diese Teile, verlagerst Berechnungen aus dem Konsolencode und übergibst benötigte Partnerobjekte über Konstruktoren. Interfaces ermöglichen es der Logik, sowohl mit echter Speicherung als auch mit einem Ersatz im Arbeitsspeicher zu arbeiten. Anhand der Importe erkennst du genau, von welchen Typen jede Schicht abhängt.
3.1 Halte den Dialog aus Berechnungen heraus
Die Benutzeroberfläche liest Befehle und zeigt Ergebnisse an. Die Anwendungslogik enthält die Regeln und Daten, auf die diese Befehle wirken. Durch die Trennung kann ein Test eine Berechnung direkt aufrufen, und eine andere Oberfläche kann sie wiederverwenden, ohne die Regel zu kopieren.
Gib jedem Teil eine klare Aufgabe
TextUI übernimmt den Dialog: nach einem Gewicht fragen, mit Scanner eine Zeile lesen, den Text in eine Zahl umwandeln, die Logik aufrufen und das Ergebnis ausgeben. ShippingCalculator nimmt ein Gewicht in Gramm entgegen und gibt eine Gebühr in Cent zurück. Die Klasse fragt den Benutzer nichts und gibt keine Antwort aus.
Eine Methode namens printFee vermischt diese Aufgaben, wenn sie sowohl berechnet als auch ausgibt. Ändere die Logikoperation zu feeInCents(int weightGrams). Die Benutzeroberfläche kann dann "Fee: " + calculator.feeInCents(weightGrams) + " cents" ausgeben. Ein Test kann den zurückgegebenen Ganzzahlwert mit einer Assertion prüfen, ohne die Konsolenausgabe abzufangen.
Formuliere die Regel als Rückgabewert
Die Paketregel berechnet 500 Cent für positive Gewichte bis einschließlich 2.000 g, 900 Cent bis einschließlich 10.000 g und danach zusätzliche 100 Cent für je angefangene 1.000 g über 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);
}
Die ersten beiden erfolgreichen Zweige schließen ihre Obergrenzen ein. Bei 12.500 g beträgt der Überschuss 2.500 g. Die Ganzzahldivision nach der Addition von 999 zählt drei angefangene Blöcke, sodass 1.200 Cent entstehen. Die Zwischenrechnung mit long verarbeitet auch große positive int-Gewichte, ohne beim Addieren von 999 überzulaufen.
Ein ungültiges Gewicht löst eine IllegalArgumentException aus: Die Methode weist ein Argument zurück, das gegen ihre Regel verstößt. Die Benutzeroberfläche kann diese Ausnahme abfangen und eine verständliche Meldung wählen. Die Regel gehört zur Logik; die Formulierung der Meldung gehört zur Oberfläche.
Verbinde die Objekte in main
Übergib den Eingabeleser und den Rechner an den Konstruktor der Benutzeroberfläche:
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
ShippingCalculator calculator = new ShippingCalculator();
TextUI ui = new TextUI(scanner, calculator);
ui.start();
}
Erzeuge einen Scanner für den Eingabestrom und verwende ihn gemeinsam mit der Benutzeroberfläche. Mehrere Scanner für System.in können sich bei der gepufferten Eingabe gegenseitig stören. Die main-Methode setzt das Programm zusammen; start führt die Dialogschleife aus.
Achte beim Prüfen einer Klasse auf ihre Verantwortlichkeiten. Ein Scanner oder System.out innerhalb eines Gebührenrechners zeigt, dass Ein- und Ausgabe mit Regeln vermischt werden. Eine Gebührenformel in der Benutzeroberfläche zeigt dasselbe Problem von der anderen Seite. Verschiebe die Berechnung in eine Methode mit ausdrücklicher Eingabe und einem Ergebnis; halte das Parsen von Befehlen und die Anzeigetexte gemeinsam in der Benutzeroberfläche.
Wichtige Erkenntnisse
- Die Benutzeroberfläche liest und gibt aus; Logikmethoden nehmen Werte entgegen und geben Ergebnisse zurück.
- Halte Gebührenregeln und Validierung in der Logik, damit Tests sie direkt aufrufen können.
- Lass die Benutzeroberfläche entscheiden, wie sie ein Ergebnis oder eine zurückgewiesene Eingabe anzeigt.
- Erzeuge benötigte Partnerobjekte in
mainund übergib sie an den Konstruktor der Benutzeroberfläche. - Zähle einen teilweise genutzten Gewichtsblock als angefangenen Block, wenn die Regel es verlangt.
3.2 Verbinde Schichten über ein Repository
Ein Programm mit Schichten gruppiert Klassen nach Verantwortung. Die Präsentationsschicht übernimmt den Dialog, die Logikschicht wendet Regeln an und die Datenzugriffsschicht speichert Informationen und ruft sie ab. Eine Klasse verwendet eine andere Schicht über eine klar definierte Operation, statt auf deren interne Felder zuzugreifen.
Ordne die Paketklassen zu
Das UI-Paket enthält TextUI. Das Domain-Paket enthält Parcel, ShippingCalculator und ParcelService. Das Datenpaket enthält ParcelRepository und dessen Speicherimplementierungen. Main setzt die Objekte oberhalb dieser Schichten zusammen.
Ein Befehl nimmt diesen Weg: Der Benutzer gibt „add 1200 Alex“ ein; die Benutzeroberfläche parst ihn und ruft service.register(1200, "Alex") auf; der Service validiert das Gewicht, erzeugt ein Paket, speichert es über das Repository und gibt 500 Cent zurück; die Benutzeroberfläche gibt die Gebühr aus. Das Lesen von Text, die Entscheidung darüber, was erlaubt ist, und das Speichern des Ergebnisses haben getrennte Zuständigkeiten.
Hänge von einem Vertrag ab
Der Service benötigt Speicherverhalten, kein bestimmtes Dateiformat. Drücke dieses Verhalten durch ein Interface aus:
public interface ParcelRepository {
void save(Parcel parcel);
List<Parcel> findAll();
}
Der Service speichert eine als ParcelRepository typisierte Referenz und erhält sie im Konstruktor:
public class ParcelService {
private final ParcelRepository repository;
public ParcelService(ParcelRepository repository) {
this.repository = repository;
}
}
Das ist Konstruktorinjektion, eine Form der Abhängigkeitsinjektion: Der aufrufende Code stellt ein benötigtes Partnerobjekt bereit. Ein Service, der innerhalb seiner eigenen Methoden new FileParcelRepository(...) erzeugt, entscheidet sich selbst für Dateispeicherung und erschwert isolierte Tests.
Ändere die Implementierung beim Zusammensetzen
Das laufende Programm kann new ParcelService(new FileParcelRepository("parcels.csv")) erzeugen. Ein Test kann new InMemoryParcelRepository() bereitstellen. Beide Implementierungen erfüllen dasselbe Interface. Aufrufe über das Interface erreichen durch Polymorphie die Methoden des tatsächlichen Objekts.
Bleibt der Repository-Vertrag unverändert, erfordert ein Wechsel der Speicherung eine neue Implementierung und eine Änderung beim Zusammensetzen. Gebührenregeln und Dialogcode müssen nicht wissen, wie die Daten gespeichert werden.
Prüfe die Richtung der Abhängigkeiten genau
Der nach unten gerichtete Pfad der Dienstnutzung verläuft von der Benutzeroberfläche über die Logik zur Speicherung. Das Domain-Paket darf keine UI-Klassen importieren oder aufrufen. Muss die Logik dem Benutzer ein Ergebnis mitteilen, gib es an die Benutzeroberfläche zurück. Speichert die Benutzeroberfläche direkt über das Repository, kann sie die Validierung des Service umgehen.
Auch Java-Typreferenzen sind wichtig: Eine Repository-Signatur, die Parcel nennt, hängt von diesem Domain-Typ ab. Liegt ParcelRepository im Datenpaket und der Service im Domain-Paket, verweisen diese Pakete aufeinander. Um diese zyklische Paketabhängigkeit zu beseitigen, platziere das Repository-Interface neben den Domain-Typen und lass es von den Datenimplementierungen importieren. Der Service verwendet weiterhin denselben Vertrag; nur dessen Ablageort ändert sich. Prüfe beim Beurteilen des Entwurfs sowohl den Pfad der Methodenaufrufe als auch die Importe.
Größere Programme können Pakete zunächst nach Funktionalität gruppieren, etwa nach Bestellungen oder Kunden, und dann innerhalb jedes Bereichs die Verantwortlichkeiten trennen. Paketnamen helfen Lesern, Verantwortlichkeiten zu finden; Konstruktorparameter und Methodensignaturen zeigen die tatsächlichen Abhängigkeiten.
Wichtige Erkenntnisse
- Trenne die Verantwortlichkeiten für Präsentation, Logik und Datenzugriff.
- Übergib ein Repository-Interface an den Konstruktor des Service.
- Wähle beim Zusammensetzen des Programms eine Dateiimplementierung oder eine Implementierung im Arbeitsspeicher.
- Leite Aktionen der Benutzeroberfläche durch die Logik, damit die Validierung angewendet wird.
- Gib Ergebnisse an die Benutzeroberfläche zurück, statt UI-Code aus der Logik aufzurufen.
- Unterscheide bei der Prüfung zyklischer Paketabhängigkeiten die Richtung der Dienstnutzung von Java-Typabhängigkeiten.
4Prüfe Code mit JUnit-Tests
Automatisierte Tests machen aus erwartetem Verhalten Prüfungen, die du nach jeder Änderung erneut ausführen kannst. Eine Prüfung zu schreiben, ist erst der Anfang: Geeignete Eingaben decken Grenzwerte auf, neue Fixtures halten Tests unabhängig, und lesbare Fehlermeldungen helfen dir, die Ursache zu finden. Du entwickelst Jupiter-Tests für die Paketlogik, steuerst ihre Daten und Partnerobjekte und erweiterst eine Regel in einem kurzen testgetriebenen Zyklus um jeweils einen Fall.
4.1 Ein Test, der ein Ergebnis prüft
Ein Modultest ruft eine kleine Codeeinheit mit ausgewählter Eingabe auf und prüft das Ergebnis automatisch. Er macht eine Anforderung ausführbar. Tests nach einer Änderung erneut auszuführen, hilft, eine Regression zu finden: einen Fehler in einem Verhalten, das zuvor funktionierte.
Erstelle einen Jupiter-Test
JUnit Jupiter ist das Programmiermodell von JUnit 5 und 6. JUnit 6 benötigt Java 17 oder neuer. Testannotationen und Assertions verwenden org.junit.jupiter.api.
Lege ShippingCalculatorTest.java unter src/test/java/com/example/parcels/domain ab, mit derselben Paketdeklaration wie der Rechner. Das separate Quellverzeichnis hält Tests vom ausgelieferten Programmcode getrennt und erhält zugleich den Zugriff innerhalb desselben Pakets.
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);
}
}
Die Testklasse und die Methode können Paketzugriff haben. Eine Testmethode gibt void zurück und darf nicht privat sein. Verwende das @Test von Jupiter; org.junit.Test aus JUnit 4 gehört zu einem anderen Programmiermodell und wird von der Jupiter-Engine nicht erkannt. JUnit-4-Assertions können funktionieren, wenn ihre Bibliothek verfügbar ist; einheitliche Jupiter-Importe halten die Einrichtung jedoch übersichtlich.
Lies das Arrange-Act-Assert-Muster
Der erste Teil bereitet einen Rechner vor. Der zweite führt die Aktion aus, indem er die Gebühr abfragt. Die abschließende Assertion vergleicht die erwarteten 500 Cent mit dem zurückgegebenen Wert. Leerzeilen machen diese Aufgaben gut erkennbar. Würde nur fee ausgegeben, müsste eine Person entscheiden, ob der Wert korrekt ist; die Assertion macht diese Entscheidung zum Teil des Tests.
Bei assertEquals(expected, actual) steht zuerst die Anforderung und danach das beobachtete Ergebnis. Eine optionale Meldung steht zuletzt, wie in assertEquals(500, fee, "fee for 1,500 g"). Werden die ersten beiden Argumente vertauscht, ändert sich für diese Werte gewöhnlich nicht, ob der Test besteht oder fehlschlägt, aber die Bedeutung der Fehlermeldung wird umgekehrt.
Wähle die passende Assertion
Verwende assertTrue oder assertFalse für einen booleschen Wert und assertNull oder assertNotNull, wenn das Vorhandensein eines Objekts das Ergebnis ist. Prüfungen auf Objektgleichheit verwenden equals; ohne Überschreibung können zwei verschiedene Objekte mit übereinstimmenden Feldern trotzdem als ungleich gelten.
Gleitkommaergebnisse benötigen eine passende Toleranz. Die Rechnung 0.1 + 0.2 ergibt in einem Java-double den Wert 0.30000000000000004:
assertEquals(0.3, 0.1 + 0.2, 1e-9);
Das dritte Argument erlaubt eine absolute Differenz von bis zu 0.000000001. Wähle eine zur Berechnung passende Toleranz; eine zu große Toleranz kann einen bedeutsamen Fehler verbergen. Ganzzahlige Centberechnungen benötigen keine Dezimaltoleranz.
Lies einen Fehlschlag
Führe einen Test über die IDE oder das Build-Werkzeug aus. Ein Bericht wie „expected: <500> but was: <900>“ nennt das geforderte und das beobachtete Ergebnis; der Stacktrace verweist auf die Assertion. Eine unerwartete Ausnahme lässt einen Test ebenfalls fehlschlagen. Grün bedeutet, dass die geprüften Fälle bestanden haben; die Auswahl dieser Fälle bleibt ein eigener Teil des Testens.
Wichtige Erkenntnisse
- Verwende Jupiter-Annotationen und Assertions aus
org.junit.jupiter.api. - Lege Tests in
src/test/javaab, normalerweise im Paket der getesteten Klasse. - Bereite die Objekte vor, führe die Aktion aus und prüfe das Ergebnis mit einer Assertion.
- Setze den erwarteten Wert in
assertEqualsan die erste Stelle. - Verwende für Gleitkommavergleiche eine begründete Toleranz.
- Lies den Namen des fehlgeschlagenen Tests, den erwarteten und tatsächlichen Wert sowie die Stelle der Assertion.
4.2 Wähle Eingaben an den Grenzen von Regeln
Eine nützliche Testauswahl prüft, wie die Spezifikation Eingaben in Fälle aufteilt. Eine gewöhnliche Eingabe testet selten eine ganze Regel. Grenzwertfälle direkt an einer Grenze und unmittelbar daneben eignen sich besonders, um eine Bedingung mit einem falschen Vergleichsoperator aufzudecken.
Teile den Eingabebereich auf
Eine Äquivalenzklasse gruppiert Eingaben, die gleich behandelt werden sollen. Die Versandregel hat ungültige Gewichte bei null oder darunter, eine Gebührenstufe von 500 Cent bis einschließlich 2.000 g, eine Stufe von 900 Cent bis einschließlich 10.000 g und darüber eine Berechnung mit angefangenen Blöcken. Wähle repräsentative Eingaben aus den Klassen und prüfe dann ihre Grenzen.
| Gewicht in Gramm | Erwartetes Ergebnis |
|---|---|
0, -5 | Mit IllegalArgumentException zurückweisen |
1, 2000 | 500 Cent |
2001, 10000 | 900 Cent |
10001, 11000 | 1.000 Cent |
11001 | 1.100 Cent |
Eine Änderung von <= 2000 zu < 2000 wirkt sich bei genau 2.000 g aus. Ein Test bei 1.500 g besteht weiterhin. Teste oberhalb von 10.000 g sowohl einen ganzen Block als auch das erste Gramm des nächsten Blocks, um das Aufrunden zu prüfen.
Erwartete Werte stammen aus der Anforderung und einer Rechnung von Hand. Bei 12.500 g beträgt der Überschuss 2.500 g, sodass drei angefangene Blöcke nötig sind und das erwartete Ergebnis 1.200 Cent beträgt. Würdest du die Methode ausführen und ihre Antwort in den Test kopieren, würdest du auch jeden Fehler übernehmen.
Teste zurückgewiesene Eingaben
Ein Ausnahmetest übergibt JUnit die auszuführende Operation:
assertThrows(IllegalArgumentException.class,
() -> calculator.feeInCents(0));
Der Ausdruck () -> ... ist ein Lambda-Ausdruck: ein kleines Stück Code, das an JUnit übergeben wird. JUnit führt ihn innerhalb der Prüfung aus. Würdest du die Methode aufrufen, bevor du sie an die Assertion übergibst, würde die Ausnahme entweichen, bevor die Prüfung sie untersuchen kann.
assertThrows akzeptiert den angegebenen Ausnahmetyp oder eine Unterklasse. Die Prüfung schlägt fehl, wenn die Operation normal endet oder eine Ausnahme eines inkompatiblen Typs wirft. Sie gibt die Ausnahme zurück, sodass eine geforderte Meldung geprüft werden kann:
IllegalArgumentException error = assertThrows(
IllegalArgumentException.class,
() -> calculator.feeInCents(0));
assertEquals("weight must be positive", error.getMessage());
Gib einem Szenario einen lesbaren Namen
Namen wie zeroWeightIsRejected und feeIsBaseFeeAtExactly2000Grams bezeichnen das vorgesehene Verhalten. Ein Test kann mehrere Assertions haben, wenn sie dasselbe Ergebnis prüfen. Nachdem einer Sendung Pakete mit 1.200 g und 800 g hinzugefügt wurden, beschreibt die Prüfung einer Anzahl von zwei und eines Gesamtgewichts von 2.000 g dieses eine Szenario. assertAll kann Prüfungen gruppieren, sodass alle Fehlschläge gemeldet werden.
Prüfe veränderte Objekte über ihr Verhalten, etwa parcelCount() und totalWeightGrams(), statt private Felder für Tests freizugeben. Ein Refactoring kann dann Speicherdetails ändern und die Prüfungen erhalten. Vollständige Zeilenabdeckung bedeutet, dass die Tests die Zeilen ausgeführt haben; ein falscher Grenzwert oder eine ungetestete ungültige Eingabe kann trotzdem übersehen werden.
Wichtige Erkenntnisse
- Teile Eingaben gemäß der Spezifikation auf und teste repräsentative Fälle sowie Grenzwerte.
- Prüfe einen Grenzwert selbst und benachbarte Werte, um Off-by-one-Fehler aufzudecken.
- Berechne erwartete Ergebnisse unabhängig von der Implementierung.
- Übergib die Operation als Lambda-Ausdruck an
assertThrows. - Benenne ein Szenario pro Test; mehrere zusammengehörige Assertions können dieses Szenario prüfen.
- Teste veränderten Zustand anhand des Verhaltens, das aufrufender Code beobachten kann.
4.3 Tests, die allein und in beliebiger Reihenfolge laufen
Jeder Test sollte alles vorbereiten, was er benötigt. Ein Test, der nur besteht, nachdem ein anderer Test Daten hinzugefügt hat, hängt von einer verborgenen Vorgeschichte ab. Solchen Tests lässt sich schwer vertrauen, wenn das Werkzeug zur Testausführung die Reihenfolge ändert oder nur eine Methode ausführt.
Erzeuge eine neue Fixture
Eine Fixture ist die Gesamtheit der Objekte und Daten, die ein Test verwendet. Lege wiederholte Vorbereitungen pro Test in @BeforeEach ab:
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());
}
}
Die Vorbereitung läuft vor jeder Methode. Der zweite Test verändert nicht die Sendung, die der erste verwendet. Standardmäßig erzeugt Jupiter außerdem für jede Testmethode eine neue Instanz der Testklasse. Veränderliche statische Felder bleiben gemeinsam genutzt, sodass eine statische Liste weiterhin Zustand zwischen diesen Instanzen übertragen kann.
@AfterEach führt Aufräumarbeiten nach jedem Test durch. @BeforeAll und @AfterAll laufen einmal für die Klasse und sind im standardmäßigen Lebenszyklus statisch. Gemeinsam genutzte schreibgeschützte Referenzdaten können sich für eine einmalige Vorbereitung pro Klasse eignen; veränderliche Objekte müssen gewöhnlich für jeden Test neu erzeugt werden. Tests müssen einzeln funktionieren, ohne von der Reihenfolge in der Quelldatei abzuhängen.
Verwende einen Testrumpf für eine Tabelle
Ein parametrisierter Test führt dieselbe Verhaltensprüfung für mehrere Zeilen aus:
@ParameterizedTest
@CsvSource({"1, 500", "2000, 500", "2001, 900",
"10000, 900", "10001, 1000"})
void feeMatchesTable(int grams, int expected) {
assertEquals(expected,
new ShippingCalculator().feeInCents(grams));
}
Jede Zeile ist ein eigener Aufruf mit einem eigenen Bericht. @ValueSource(ints = {0, -5}) liefert für Tests ungültiger Eingaben eine Ganzzahl pro Aufruf. Annotationen für parametrisierte Tests stammen aus org.junit.jupiter.params; CsvSource und ValueSource liegen in dessen Paket provider. Die Sammelabhängigkeit junit-jupiter enthält das Modul für parametrisierte Tests.
@DisplayName ergänzt eine lesbare Bezeichnung im Bericht. @Disabled erfasst einen übersprungenen Test mit einer Begründung; eine übersprungene Prüfung liefert keinen Beleg dafür, dass das zu prüfende Verhalten funktioniert.
Steuere die Umgebung
Ein Service-Test kann ein InMemoryParcelRepository erhalten, einen funktionierenden Fake, der Pakete im Arbeitsspeicher speichert. Das vermeidet echte Dateiinhalte und isoliert die Logik. Übergib einer Textoberfläche einen aus einem bekannten String erzeugten Scanner, wenn du ihre Eingabeverarbeitung testest. Ebenso macht ein festes Datum oder ein Random mit festem Startwert eine externe Eingabe vorhersagbar.
Ein Flaky Test ändert sein Ergebnis zwischen Ausführungen, ohne dass der Code relevant geändert wurde. Gemeinsam genutzte veränderliche Daten, die aktuelle Zeit, unkontrollierte Zufallswerte und zurückgelassene Dateien sind häufige Ursachen. Gib dem Test die Kontrolle über diese Eingaben. Halte Hilfsmethoden klein und die Vorbereitung lesbar; eine Fixture, die viele nicht zusammengehörige Objekte erzeugt, kann zeigen, dass die getestete Klasse zu viele Aufgaben hat.
Wichtige Erkenntnisse
- Verwende
@BeforeEach, um Tests neue Fixtures zu geben. - Eine neue Instanz der Testklasse setzt veränderlichen statischen Zustand nicht zurück.
- Parametrisierte Tests führen eine Methode mit Eingabezeilen aus, über die getrennt berichtet wird.
- Verwende Fakes und übergebene Eingaben, um Logiktests von echter Speicherung und der aktuellen Zeit unabhängig zu halten.
- Gestalte jeden Test einzeln ausführbar, ohne eine erforderliche Methodenreihenfolge.
4.4 Entwickle Verhalten mit Rot, Grün und Refactoring
Testgetriebene Entwicklung (TDD) beginnt eine Verhaltensänderung mit einem Test. Ein kurzer Zyklus macht das fehlende Verhalten sichtbar, implementiert es und verbessert anschließend die Struktur des Codes. Eine Testliste hält noch zu untersuchende Fälle fest, ohne dass sie alle auf einmal geschrieben werden müssen.
Nimm einen Eintrag aus der Testliste
Notiere für eine Prüfung auf Schaltjahre im gregorianischen Kalender vier Verhaltensweisen: ein gewöhnliches Nichtschaltjahr, ein durch vier teilbares Jahr, ein Jahrhundertjahr, das nicht durch 400 teilbar ist, und ein Jahrhundertjahr, das durch 400 teilbar ist. Diese Fälle stehen für verschiedene Teile der Regel.
Rot: Schreibe einen Test und führe ihn aus. Eine fehlende Methode kann einen Kompilierfehler verursachen. Sobald die Methode existiert, prüfe, ob der Test für das Verhalten fehlschlägt, das du ergänzen möchtest. Eine fehlende Assertion, der falsche Methodenaufruf oder ein nicht erkannter Test können ein scheinbar grünes Ergebnis irreführend machen. Ein neuer Fall kann auch bereits durch bestehenden Code erfüllt sein; untersuche dieses Ergebnis, bevor du die Implementierung änderst.
Grün: Ergänze den einfachsten Code, mit dem der neue Test und alle vorherigen Tests bestehen. Refactoring: Verbessere die Struktur, ohne das Verhalten zu ändern, und führe die Tests nach jeder kleinen Änderung erneut aus. Wähle dann den nächsten Fall.
Lass die Fälle die Regel entwickeln
Die erste Prüfung ist assertFalse(LeapYears.isLeapYear(2023)). Erstellst du die Methode mit return false;, besteht dieser Fall. Das ist ein bewusster erster Schritt und noch keine fertige Kalenderregel.
Die nächste Prüfung, assertTrue(LeapYears.isLeapYear(2024)), schlägt fehl. Ändere die Methode zu return year % 4 == 0;. Beide Fälle bestehen nun.
Anschließend deckt assertFalse(LeapYears.isLeapYear(1900)) die Ausnahme für Jahrhundertjahre auf. Änderst du den Ausdruck zu year % 4 == 0 && year % 100 != 0, bestehen diese drei Tests.
Schließlich deckt assertTrue(LeapYears.isLeapYear(2000)) die Ausnahme von der Jahrhundertregel auf. Die Implementierung lautet nun:
public static boolean isLeapYear(int year) {
return year % 400 == 0
|| (year % 4 == 0 && year % 100 != 0);
}
Führe nach der Änderung alle vier Prüfungen aus. Jeder neue Fehlschlag wies auf einen fehlenden Teil des Verhaltens hin, statt dich eine große unvollendete Implementierung nach Fehlern durchsuchen zu lassen.
Verbessere die Struktur in der grünen Phase
Refactoring ändert Namen oder die Struktur und erhält dabei das Verhalten. Eine Methode umzubenennen, eine Hilfsmethode zu extrahieren, wiederholte Logik zu entfernen und eine Klasse in ein besser geeignetes Paket zu verschieben, sind Refactorings, wenn das beobachtbare Verhalten gleich bleibt. Die an einem Grenzwert berechnete Gebühr zu ändern, ist eine Verhaltensänderung und benötigt einen Test, der diese Änderung beschreibt.
Wenn eine Bereinigung einen Test fehlschlagen lässt, stelle das vorgesehene Verhalten wieder her, bevor du eine weitere Funktion ergänzt. Kleine Änderungen helfen, die Ursache eines Fehlschlags zu finden. Ohne Bereinigung bleibt funktionierender Code zurück, der sich zunehmend schwerer erweitern lässt; eine große Implementierung für viele zukünftige Fälle erzeugt mehr Code, den du beim Fehlschlag der nächsten Prüfung untersuchen musst.
TDD hängt von der Qualität der Anforderungen und ausgewählten Fälle ab. Eine missverstandene Regel kann sowohl im Test als auch in der Implementierung erscheinen. Logik mit klaren Ein- und Ausgaben ist ein nützlicher Ausgangspunkt, weil sich das erwartete Ergebnis unabhängig angeben und schnell prüfen lässt.
Wichtige Erkenntnisse
- Führe eine Testliste und implementiere jeweils ein Verhalten.
- Rot macht das fehlende Verhalten sichtbar; Grün lässt alle aktuellen Tests bestehen.
- Untersuche einen unerwartet bestandenen Test, bevor du annimmst, dass er die beabsichtigte Änderung geprüft hat.
- Verbessere die Struktur durch Refactoring, erhalte dabei das Verhalten und führe die Tests erneut aus.
- Decke mit aufeinanderfolgenden Fällen gewöhnliche Regeln und Ausnahmen auf.
5Baue und teste Projekte mit Maven oder Gradle
Ein Projekt benötigt mehr als Quelldateien: Es braucht Bibliotheken, Kompilierungseinstellungen, ein Werkzeug zur Testausführung und eine Möglichkeit, das Ergebnis zu paketieren. Maven und Gradle beschreiben diese Entscheidungen, sodass ein Befehl den Build wiederholen kann. Du verwendest ihre Standardverzeichnisse, deklarierst Jupiter-Abhängigkeiten, führst den mitgelieferten Wrapper aus und unterscheidest anhand der Build-Ausgabe einen Codefehler von einem Problem bei der Testerkennung oder den Abhängigkeiten.
5.1 Baue ein Maven-Projekt anhand seiner Beschreibung
Ein Build-Werkzeug wandelt Quelldateien in ein geprüftes, paketiertes Ergebnis um. Maven liest eine Projektbeschreibung, lädt deklarierte Bibliotheken herunter, kompiliert Code, führt Tests aus und erzeugt ein Artefakt. Dieselbe Beschreibung gibt der Befehlszeile und der IDE eine gemeinsame Sicht auf das Projekt.
Verwende die Standardstruktur
Lege Java-Programmcode in src/main/java, Tests in src/test/java, Programmressourcen in src/main/resources und Testdaten in src/test/resources ab. Paketverzeichnisse beginnen unterhalb des jeweiligen Java-Quellverzeichnisses. Maven schreibt erzeugte Ausgaben unter target; bearbeite die Quelldateien, nicht die kompilierten Kopien in diesem Verzeichnis.
Die Datei pom.xml im Projektwurzelverzeichnis, das Project Object Model, identifiziert das Projekt anhand von Koordinaten: groupId, artifactId und version. Beispielsweise bezeichnet com.example:parcels:1.0-SNAPSHOT eine Entwicklungsversion des Paketprogramms. Auch eine Bibliotheksabhängigkeit hat Koordinaten.
Deklariere JUnit für Tests
Das Folgende ist eine vollständige kleine Maven-Konfiguration. Sie kompiliert für Java 17 und wählt ein Werkzeug zur Testausführung, das Jupiter unterstützt:
<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 ruft Abhängigkeiten aus Repositorys ab, normalerweise aus Maven Central, und speichert sie unter .m2/repository in deinem Benutzerverzeichnis zwischen. Bibliotheken, die eine Abhängigkeit benötigt, sind transitive Abhängigkeiten. Der Test-Abhängigkeitsbereich macht JUnit zum Kompilieren und Ausführen von Tests verfügbar, nicht zum Kompilieren des Hauptcodes oder im Klassenpfad des Programms zur Laufzeit. Eine gewöhnliche JAR-Datei hält die JAR-Dateien ihrer Abhängigkeiten getrennt, sofern die Paketierung nicht so konfiguriert ist, dass sie diese bündelt.
Fordere eine Lebenszyklusphase an
Der Hauptlebenszyklus durchläuft unter anderem die Phasen Kompilieren, Testen, Paketieren und Installieren. Wird package angefordert, laufen auch frühere Phasen. Mit dem Wrapper kompiliert und testet ./mvnw test; ./mvnw package erzeugt außerdem die JAR-Datei unter target; ./mvnw install legt das Artefakt zusätzlich im lokalen Repository ab. Der separate Bereinigungslebenszyklus ./mvnw clean entfernt erzeugte Ausgaben.
Ein Testfehlschlag stoppt den normalen Build, bevor ein neues Artefakt paketiert wird. Eine ältere JAR-Datei kann bis zur Bereinigung noch vorhanden sein; ihr Vorhandensein belegt daher nicht, dass der letzte Build erfolgreich war.
Mache die Testerkennung sichtbar
Zu den Standardmustern von Surefire gehören Namen, die mit Test beginnen oder auf Test, Tests oder TestCase enden. ShippingChecks passt nicht dazu; verwende ShippingCalculatorTest und prüfe die gemeldete Testanzahl. Tests unter src/main/java können keine Abhängigkeit mit Test-Abhängigkeitsbereich verwenden.
Der Maven Wrapper, mvnw oder unter Windows mvnw.cmd, führt die vom Projekt festgelegte Maven-Version aus. Verwende ein Projekt, das seine Wrapper-Dateien enthält; eine POM allein erzeugt die Skripte nicht. Vom Projektwurzelverzeichnis aus stellt der Wrapper einen einheitlichen Build-Befehl ohne separate Maven-Installation bereit.
Wichtige Erkenntnisse
- Halte Hauptcode, Tests und Ressourcen in ihren Standardquellverzeichnissen.
- Identifiziere Projekte und Bibliotheken durch Koordinaten für Gruppe, Artefakt und Version.
- Deklariere JUnit mit Test-Abhängigkeitsbereich und verwende ein kompatibles Surefire-Werkzeug zur Testausführung.
- Eine angeforderte Maven-Phase führt die früheren Phasen ihres Lebenszyklus aus.
- Prüfe die Testanzahl; ein erfolgreicher Build kann einen falsch benannten Test übersehen haben.
- Erzeugte Ausgaben gehören nach
targetund können neu erstellt werden. - Der Wrapper führt die vom Projekt ausgewählte Maven-Version aus.
5.2 Führe Gradle-Tests aus und verfolge Build-Fehler
Gradle beschreibt einen Build mit einem Skript und führt Tasks mit ihren Abhängigkeiten aus. Ein Kotlin-Skript heißt build.gradle.kts; ein Groovy-Skript heißt build.gradle. Plugins stellen Tasks bereit, und Abhängigkeitskonfigurationen geben an, wo Bibliotheken benötigt werden.
Konfiguriere eine Anwendung und Jupiter
Eine settings.gradle.kts im Projektwurzelverzeichnis kann rootProject.name = "parcels" enthalten. Ein passendes Kotlin-Build-Skript lautet:
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()
}
Das Plugin application stellt Java-Build-Tasks bereit und ergänzt run. mavenCentral() wählt ein Repository für Abhängigkeiten aus. Die BOM, eine Bill of Materials, stimmt die Versionen der JUnit-Module aufeinander ab, sodass die Version einmal erscheint. testImplementation stellt Bibliotheken bereit, die zum Kompilieren und Ausführen von Tests benötigt werden. testRuntimeOnly stellt den Launcher nur bei deren Ausführung bereit. Bibliotheken für den Hauptcode verwenden implementation, wodurch sie auch für Testcode verfügbar werden.
useJUnitPlatform() wählt die Plattform aus, auf der Jupiter-Tests ausgeführt werden. Werden Jupiter-Bibliotheken hinzugefügt, ohne das Werkzeug zur Testausführung zu konfigurieren, bleiben diese Tests möglicherweise unentdeckt. Aktuelles Gradle schlägt bei seiner Standardeinstellung fehl, wenn Testquellen existieren, aber keine Tests erkannt werden; prüfe die Ausgabe und Konfiguration des Test-Tasks, statt die Prüfung zu entfernen.
Führe Tasks mit dem Wrapper aus
Verwende den mit dem Projekt gelieferten Wrapper: ./gradlew test führt Tests aus; ./gradlew build kompiliert, testet und paketiert; ./gradlew run startet die konfigurierte Hauptklasse; und ./gradlew clean entfernt build. Unter Windows wird gradlew.bat verwendet. Gradle verwendet dieselbe Struktur für Haupt- und Testquellen wie Maven und schreibt erzeugte Ausgaben nach build.
Bei einem mit UP-TO-DATE markierten Task haben sich die erfassten Ein- und Ausgaben nicht geändert, sodass Gradle sein früheres Ergebnis wiederverwendet. Diese Meldung unterscheidet sich von einem neu ausgeführten Testlauf. Der HTML-Testbericht liegt unter build/reports/tests/test/index.html.
Finde die fehlgeschlagene Phase
Beginne mit dem frühesten hilfreichen Fehler. „Cannot find symbol“ beim Kompilieren des Hauptcodes deutet auf den genannten Code, einen Import oder eine Abhängigkeit hin. Fehlt org.junit.jupiter.api beim Kompilieren der Tests, deutet das auf die Einrichtung der Testabhängigkeit hin; derselbe Fehler beim Kompilieren des Hauptcodes kann bedeuten, dass ein Test im Hauptquellverzeichnis abgelegt wurde. Ein Fehler bei der Auflösung von Abhängigkeiten deutet auf Koordinaten, Repository-Zugriff oder Netzwerkverfügbarkeit hin.
Ein Fehlschlag bei der Testausführung ist etwas anderes: Der Test wurde kompiliert und erkannt, aber seine Prüfung ist fehlgeschlagen. Erscheint „expected: <500> but was: <900>“ am Gebührenwert von 2.000 g, prüfe den Vergleich, der die Gebührenstufe auswählt. Eine Änderung der Abhängigkeiten behebt diese Berechnung nicht.
Maven bietet eine deklarative XML-Beschreibung mit festen Lebenszyklusphasen. Gradle bietet konfigurierbare Skripte und Tasks und überspringt dabei auch Arbeit, deren Ergebnisse aktuell sind. Beide automatisieren wiederholbare Java-Builds; verwende das für das Projekt gewählte Werkzeug und lies den ersten Fehler, den du beheben kannst, bevor du Code änderst.
Wichtige Erkenntnisse
- Plugins ergänzen Gradle-Tasks; Konfigurationen steuern, wo Abhängigkeiten verfügbar sind.
- Stimme JUnit-Versionen mit einer BOM ab und führe Jupiter auf der JUnit Platform aus.
- Verwende den mitgelieferten Gradle Wrapper für Test-, Build-, Ausführungs- und Bereinigungs-Tasks.
- Unterscheide zwischengespeicherte Task-Ergebnisse, Kompilierfehler, Abhängigkeitsfehler und fehlgeschlagene Assertions.
- Lies den genannten fehlgeschlagenen Test sowie den erwarteten und tatsächlichen Wert, bevor du die Implementierung änderst.
Modultests in Java: Beispiele
1Verschiebe eine Paketklasse in ein Paket
13 Schritte
- 1EinführungGib der Klasse einen klaren Platz
Ein Paketprogramm hat einen Rechner und eine Textoberfläche. Wenn diese Klassen in getrennte Pakete verschoben werden, benötigt Java Namen, Verzeichnisse und Zugriffsebenen, die zusammenpassen.
Verfolge den Umzug in drei Schritten: Platziere die Klasse, ermögliche einer anderen Datei, sie zu benennen, und prüfe dann, ob der aufrufende Code ihren Konstruktor und ihre Methoden verwenden kann.
- 2BeispielBeginne unterhalb des Quellverzeichnisses
Das Quellverzeichnis ist
src/main/java. Wandle fürParcelincom.example.parcels.domaindie Punkte des Paketnamens in Verzeichnisse um und hänge den Dateinamen an:src/main/java/com/example/parcels/domain/Parcel.javaSchreibe
package com.example.parcels.domain;an den Anfang der Datei. Die Verzeichnissesrc/main/javades Quellverzeichnisses beschreiben die Build-Struktur und gehören nicht in die Paketdeklaration. - 3ErklärungErhalte die Identität der Klasse
Der einfache Name ist
Parcel; der vollständig qualifizierte Name istcom.example.parcels.domain.Parcel. Das Verschieben der Datei allein ändert diese Identität nicht.Eine öffentliche Klasse namens
Parcelgehört inParcel.java. Starte eine Hauptklasse in einem Paket von der Wurzel des kompilierten Baums aus mit einem Befehl wiejava -cp out com.example.parcels.Main, damit Java die Paketverzeichnisse unterhalb vonoutfinden kann. - 4HerausforderungKorrigiere einen unpassenden Speicherort
Eine öffentliche Klasse namens
Receiptdeklariertpackage com.example.parcels.domain;, ist aber untersrc/main/java/com/example/parcels/ui/gespeichert. Ihr Paket soll unverändert bleiben. Wohin gehört die Datei?---reveal---
Verschiebe sie nach
src/main/java/com/example/parcels/domain/Receipt.java. Der Paketpfad beginnt unterhalb des Quellverzeichnisses, und der Dateiname stimmt mit der öffentlichen Klasse überein. Einen Import an anderer Stelle zu ändern, würde die Quellstruktur nicht korrigieren. - 5BeispielErleichtere die Verwendung des Typnamens
Die Benutzeroberfläche hat ihre eigene Deklaration und importiert dann den Domain-Typ:
package com.example.parcels.ui; import com.example.parcels.domain.Parcel;Ihr Klassenrumpf kann nun den einfachen Namen
Parcelverwenden. Der Import gilt nur für diese Datei. Er löst einen Namen auf; er kopiert keine Klasse und macht keine Abhängigkeit für den Build verfügbar. - 6KernaussageEin Wildcard-Import endet bei einem Paket
import com.example.parcels.*;umfasst das Domain-Paket nicht. Der gemeinsame Namensanfang sieht wie ein Stammbaum aus, aber jeder vollständige Paketname bezeichnet ein eigenes Paket.Dieselbe Regel erklärt, warum
java.util.*nichtjava.util.function.Functionbereitstellt. Verwende für diesen Typ einen Einzeltypimport, den Wildcard-Import des richtigen Pakets oder einen vollständig qualifizierten Namen. - 7HerausforderungUnterscheide zwei Listen
Eine Datei benötigt sowohl
java.util.Listals auchjava.awt.List. Sie hat derzeit Einzeltypimporte für beide, und die Kompilierung schlägt fehl. Wie kann die Datei beide Typen eindeutig benennen?---reveal---
Behalte einen Einzeltypimport bei, etwa
import java.util.List;, und schreibejava.awt.Listüberall dort vollständig aus, wo dieser Typ benötigt wird. Zwei verschiedene Einzeltypimporte können demselben einfachen Namen in einer Datei nicht zwei Bedeutungen geben. - 8TippErkenne Namen, die keinen Import benötigen
Eine Datei kann Typen aus ihrem eigenen Paket verwenden, ohne sie zu importieren. Zugängliche Typen aus
java.lang, darunterString,MathundSystem, sind ebenfalls automatisch verfügbar.Ein statischer Import hat eine andere Aufgabe: Der statische Import von
Assertions.assertEqualserlaubt einen direkten Aufruf vonassertEquals(...). Er verkürzt eine Referenz auf ein statisches Mitglied, keinen Paketnamen. - 9BeispielPrüfe auch den Konstruktor
Der Typ ist öffentlich, aber sein Konstruktor hat keinen Modifikator:
public class Parcel { Parcel(int grams, String recipient) { } }Dieser Konstruktor hat Paketzugriff. Die Benutzeroberfläche kann
Parcelüber den Import auflösen, aber aus dem UI-Paket kein Objekt davon erzeugen. Markiere den Konstruktor als public, wenn die Paketerzeugung zu den vorgesehenen öffentlichen Operationen des Domain-Pakets gehört; halte seine Felder privat. - 10HerausforderungGib die Operation frei und halte die Hilfsklasse verborgen
Die Benutzeroberfläche muss die Methode
feeInCentseines öffentlichen Rechners aufrufen, die derzeit ohne Zugriffsmodifikator deklariert ist. Nur der Rechner verwendet seine HilfsklasseWeightBandsmit Paketzugriff. Welche Zugriffsänderung ist nötig?---reveal---
Mache
feeInCentsöffentlich und belasseWeightBandsmit Paketzugriff. Die Benutzeroberfläche benötigt die Berechnungsoperation, nicht die Hilfsklasse für die Tabelle. Der Import einer der beiden Klassen ändert deren Zugriffsrechte nicht. - 11ErklärungEine Unterklasse hat bestimmte Rechte
Auf geschützte Mitglieder kann das gesamte deklarierende Paket zugreifen sowie Unterklassen gemäß den Regeln für Vererbung über Paketgrenzen hinweg. In einem anderen Paket kann eine Unterklasse ihr eigenes geerbtes Instanzmitglied verwenden.
Sie kann keine als Oberklasse typisierte Referenz verwenden, um auf dieses Mitglied eines beliebigen Oberklassenobjekts zuzugreifen. Geschützter Zugriff ist daher weiter gefasst als Zugriff nur für Unterklassen, aber stärker eingeschränkt als öffentlicher Zugriff.
- 12HerausforderungUntersuche den gesamten Zugriffsweg
TextUIimportiert ein öffentlichesParcelkorrekt. Beim Aufruf vonnew Parcel(800, "Jo")wird gemeldet, dass auf den Konstruktor von außerhalb des Pakets nicht zugegriffen werden kann. Der Konstruktor hat keinen Modifikator. Wie behebst du dies für die vorgesehene Erzeugung über Paketgrenzen hinweg?---reveal---
Mache den Konstruktor öffentlich. Paket und Import lösen den Typ bereits auf, sodass weder das Verschieben der Datei noch ein zusätzlicher Wildcard-Import den gemeldeten Zugriffsfehler behebt. Private Paketfelder können privat bleiben.
- 13ZusammenfassungVerwende drei getrennte Prüfungen
Prüfe den Speicherort, indem du die Deklaration mit den Verzeichnissen unterhalb des Quellverzeichnisses abgleichst. Prüfe die Benennung anhand von Importen oder vollständig qualifizierten Namen. Prüfe den Zugriff auf den Typ und die Operation, die der aufrufende Code tatsächlich verwendet.
Diese Reihenfolge hilft, aus dem vagen Problem „Java kann diese Klasse nicht verwenden“ eine konkrete Korrektur der Quellstruktur, Namensauflösung oder Sichtbarkeit zu machen.
2Lies ein Paketmodell und erstelle sein Java-Grundgerüst
13 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungLies die Struktur, bevor du Methodenrümpfe schreibst
- 2BeispielÜbertrage ein Feld und eine Methode
- 3ErklärungErkenne ein Mitglied auf Klassenebene
- 4HerausforderungBehalte die Zeichen bei
- 5BeispielZähle Pakete ausgehend von einer Sendung
- 6BeispielWähle eine Darstellung und setze die Regel durch
- 7KernaussageFolge einer einseitigen Verbindung
- 8HerausforderungPrüfe eine Mindestanzahl
- 9ErklärungSetze den Besitz an das richtige Ende
- 10BeispielLies zwei Linien mit Dreiecken
- 11TippErstelle die Deklarationen der Reihe nach
- 12HerausforderungVerbinde einen Vertrag und eine Collection
- 13ZusammenfassungHalte die Aufgabe jedes Symbols auseinander
3Verfolge einen Befehl durch getrennte Verantwortlichkeiten
11 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungVerfolge einen Befehl durch das Programm
- 2BeispielExtrahiere die Regel aus dem Konsolencode
- 3BeispielBerechne einen angefangenen Block
- 4KernaussageBelasse die Validierung bei der Regel
- 5HerausforderungMache eine Berechnung wiederverwendbar
- 6BeispielÜbergib die Speicherung an den Service
- 7VerbindungErsetze Dateien durch einen funktionierenden Fake
- 8TippPrüfe Aufrufe und Importe getrennt
- 9HerausforderungKorrigiere einen Umweg um den Service
- 10HerausforderungErmögliche einen kontrollierten Service-Test
- 11ZusammenfassungPlatziere jede Änderung bei der passenden Aufgabe
4Entwickle eine nützliche Jupiter-Testsuite
19 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungMache Anforderungen zu ausführbaren Prüfungen
- 2BeispielVorbereiten, ausführen und prüfen
- 3ErklärungLies erwarteten und tatsächlichen Wert richtig
- 4TippLass Rundungsabweichungen gezielt zu
- 5HerausforderungKorrigiere einen Test, der nur ausgibt
- 6BeispielFinde die Gebührenübergänge
- 7KernaussageLeite den erwarteten Wert unabhängig ab
- 8BeispielSetze den zurückzuweisenden Aufruf in die Prüfung
- 9HerausforderungWähle den Fall, der die Änderung aufdeckt
- 10BeispielGib jedem Test eine neue Sendung
- 11BeispielFühre eine Tabelle mit einem Testrumpf aus
- 12VerbindungSteuere das Partnerobjekt, nicht die Testreihenfolge
- 13HerausforderungUntersuche Daten aus einem anderen Test
- 14BeispielBeginne den Zyklus mit einem gewöhnlichen Jahr
- 15BeispielErgänze die allgemeine Regel zur Teilbarkeit durch vier
- 16BeispielVerfeinere die Bedingung mit Jahrhundertfällen
- 17HerausforderungWähle den nächsten Schritt und erhalte die Prüfungen
- 18HerausforderungVerbinde Fallauswahl und Isolation
- 19ZusammenfassungVertraue konkreten, wiederholbaren Belegen
5Vom Build-Skript zum Testbericht
13 Schritte
Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.
- 1EinführungWiederhole den Build mit einem Befehl
- 2ErklärungTrenne Quelldateien von erzeugten Ausgaben
- 3BeispielBenenne das Projekt und seine Testbibliothek
- 4BeispielFordere die benötigte Phase an
- 5TippVerwechsle null Tests nicht mit geprüftem Verhalten
- 6HerausforderungFinde einen falsch abgelegten Test
- 7ErklärungWähle die Gradle-Tasks
- 8BeispielStimme Jupiter-Module aufeinander ab
- 9BeispielLege fest, wie der Test-Task Jupiter ausführt
- 10HerausforderungKorrigiere die Testerkennung
- 11BeispielVerfolge den Fehlschlag eines ausgeführten Tests
- 12HerausforderungVerbinde Testerkennung mit einem neuen Build
- 13ZusammenfassungLies den Build als Beleg in mehreren Phasen
Modultests in Java: Übungsaufgaben
Set 1: Namen, Verzeichnisse und Java-Zugriff10 Übungsfragen
Frage 1 von 10
Ticket deklariert package org.example.events.model;. Welchen Pfad sollte sie unterhalb des Java-Hauptquellverzeichnisses haben?Antwort zeigenAntwort ausblenden
Ticket.java. Das Quellverzeichnis ist bereits der Ausgangspunkt; ein erneutes Hinzufügen von src/main/java würde daher den Teil der Build-Struktur im Pfad verdoppeln.Frage 2 von 10
shop.ui benötigt eine Hilfsklasse, die derzeit keine Paketdeklaration hat. Wie sollte die Hilfsklasse organisiert werden, damit die Benutzeroberfläche sie importieren kann?Antwort zeigenAntwort ausblenden
Frage 3 von 10
import java.util.*; und verwendet java.util.function.Predicate mit seinem einfachen Namen. Welcher zusätzliche Einzeltypimport stellt diesen Namen bereit?Antwort zeigenAntwort ausblenden
Predicate liegt im getrennten Paket java.util.function, daher wird im Einzeltypimport sein vollständiger Typname benötigt. Der vorhandene Wildcard-Import java.util.* erreicht keine Unterpakete, und ein statischer Import ist für statische Mitglieder bestimmt.Frage 4 von 10
java.util.* und java.awt.* und deklariert dann List<String> names. Wie kann sie dafür sorgen, dass List das Collection-Interface bezeichnet, während beide Wildcard-Importe erhalten bleiben?Antwort zeigenAntwort ausblenden
List als java.util.List auf. Die beiden Wildcard-Importe können bleiben; ein statischer Import würde diesen Typ auf oberster Ebene nicht als statisches Mitglied bezeichnen.Frage 5 von 10
assertTrue(valid) ohne den vorangestellten Klassennamen Assertions auf. Welcher Import ermöglicht diesen direkten Aufruf?Antwort zeigenAntwort ausblenden
assertTrue ist ein statisches Mitglied von Assertions, sodass ein statischer Import das Mitglied direkt aufrufbar macht. Der Import der Klasse allein erlaubt Assertions.assertTrue(...), entfernt aber den Namen der zugehörigen Klasse nicht aus dem Aufruf.Frage 6 von 10
book.data.Record ist öffentlich und hat einen als Record(int id) deklarierten Konstruktor. Eine nicht verwandte Klasse in book.ui importiert die Klasse, kann sie aber nicht erzeugen. Welche Zugriffsänderung ermöglicht diesen Aufruf?Antwort zeigenAntwort ausblenden
book.ui ist ein anderes Paket. Öffentlicher Zugriff ermöglicht den Aufruf durch diese nicht verwandte Klasse; geschützter Zugriff reicht nicht aus, weil die Klasse weder im deklarierenden Paket liegt noch Rechte einer Unterklasse nutzt.Frage 7 von 10
class RateTable deklarierte Hilfsklasse hat keinen Zugriffsmodifikator. Wo können andere Klassen auf oberster Ebene sie verwenden?Antwort zeigenAntwort ausblenden
public hat Paketzugriff und kann nur im exakt selben Paket verwendet werden. Importe lösen Namen auf, ohne Zugriffsrechte zu gewähren; ein ausdrücklicher Import aus einem anderen Paket kann den Zugriff auf diese Hilfsklasse daher nicht öffnen.Frage 8 von 10
# threshold: int und ein unterstrichenes + reset(): void. Wie werden diese Mitglieder in Java übertragen?Antwort zeigenAntwort ausblenden
# kennzeichnet geschützte Sichtbarkeit, während eine Unterstreichung ein statisches Mitglied kennzeichnet. Das Pluszeichen macht reset öffentlich, und der Rückgabetyp ist void; eine Unterstreichung bedeutet nicht abstrakt, wofür Kursivschrift oder eine abstrakte Kennzeichnung verwendet wird.Frage 9 von 10
public boolean accepts(int grams)?Antwort zeigenAntwort ausblenden
Frage 10 von 10
base enthält die Klasse Engine mit Paketzugriff. Ihr ausdrücklich deklarierter Konstruktor und ihre Methode run() haben beide keinen Zugriffsmodifikator. Eine nicht verwandte Klasse Screen in base.ui muss new Engine().run() aufrufen. Welche minimalen Sichtbarkeitsänderungen ermöglichen diesen Aufruf?Antwort zeigenAntwort ausblenden
base.ui ist ein separates Paket. Daher benötigen alle drei für diese nicht verwandte Klasse öffentlichen Zugriff. Werden nur Typ und Methode freigegeben, bleibt die Erzeugung blockiert.Set 2: Klassenbeziehungen und Verantwortlichkeiten in Schichten10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
0..* neben Book. Mit wie vielen Büchern darf eine Bibliothek verbunden sein?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
List<Session> ist anfangs leer, und keine Methode prüft seine Größe. Was fehlt in der Implementierung?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
Printable in einem nicht ausgefüllten Dreieck endet. Welche Java-Untertypdeklaration stellt diese Linie dar?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
Basket enthält private List<Product> products; und erweitert Product nicht. Wie sollte die Hauptbeziehung dargestellt werden?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
CatalogRepository entgegen. Wie kann ein Test die echte Datenbank vermeiden?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
rules.OrderService importiert storage.OrderRepository. Dieses Interface nennt rules.Order in seinen Signaturen, und Speicherimplementierungen implementieren es. Wie kann der Vertrag verlagert werden, um die zyklische Paketabhängigkeit zwischen rules und storage zu beseitigen und austauschbare Speicherung zu erhalten?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Set 3: Assertions, Fallauswahl und unabhängige Tests10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
actual zurück, und ihr gefordertes Ergebnis ist 42. Welche Jupiter-Gleichheits-Assertion bezeichnet die Werte korrekt?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
double-Berechnung weicht um etwa 0,0000000000000001 von 0,6 ab. Die geforderte numerische Genauigkeit beträgt 0,000001. Wie sollte ihr erwartetes Ergebnis geprüft werden?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
reading <= 800 zu reading < 800. Welcher gültige Messwert deckt den Fehler auf?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
IllegalArgumentException zurückweisen. Wie sollte ein Jupiter-Test den Aufruf ausführen?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
250 + ((grams - 3000) / 500) * 50. Welcher neue Test deckt den Rundungsfehler am unmittelbarsten auf?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Set 4: Build-Konfiguration und Fehlerberichte10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
TotalsChecks.java importiert org.junit.Test. Die Einschlussmuster von Surefire sind unverändert. Welche Kombination von Korrekturen lässt die Klasse kompilieren und als erkannten Jupiter-Test ausführen?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
platform(...) deklariert. Warum können zugehörige JUnit-Abhängigkeitsdeklarationen einzelne Versionsnummern weglassen?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
org.junit.jupiter.api fehlt. Seine einzige Jupiter-Deklaration ist testRuntimeOnly("org.junit.jupiter:junit-jupiter"), und der Test-Task verwendet bereits die JUnit Platform. Welche Konfigurationskorrektur behebt das Problem in dieser Phase?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Set 5: Untersuche eine vollständige Java-Änderung10 Übungsfragen
Speichere diese Einheit, um jede Antwort zu sehen und jedes Set zu üben.
Frage 1 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 2 von 10
1 neben Department und 0..* neben Employee. Was erlaubt das Modell für eine Abteilung und was verlangt es für einen Mitarbeiter?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 3 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 4 von 10
base.Item hat ein geschütztes Instanzfeld weight. In einem anderen Paket hat Special extends Item eine Instanzmethode mit den Parametern Item a und Special b. Welche Ausdrücke können innerhalb dieser Methode direkt auf das geerbte Feld zugreifen?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 5 von 10
MemoryArchive das Interface Archive realisiert und über eine navigierbare Assoziation ein oder mehrere Entry-Objekte hält. Eine erzeugte Klasse enthält derzeit extends Archive und erzeugt eine leere ungeprüfte Liste. Welche Kombination von Korrekturen erhält das Modell?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 6 von 10
Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 7 von 10
year % 4 == 0 zurück und besteht Tests für 2031 und 2032. Ein neuer Test für 2100 muss dieses Jahr zurückweisen, und ein Test für 2400 muss es später akzeptieren. Welche Reihenfolge von Verhaltensänderungen passt zu den ausstehenden Fällen?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 8 von 10
< 2500 für die untere Gebührenstufe. Welches zusätzliche Prüfungspaar deckt beide Fehler auf?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 9 von 10
testRuntimeOnly("org.junit.jupiter:junit-jupiter:6.1.3") und tasks.test { }. Eine Jupiter-Testklasse liegt unter src/main/java, und ihre Annotation lässt sich nicht kompilieren. Welche Änderungen ermöglichen es, die Klasse zu kompilieren und unter Jupiter auszuführen?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Frage 10 von 10
target. Was belegt diese Datei über den heutigen Quellcode?Antwort zeigenAntwort ausblenden
Speichere diese Einheit, um diese Antwort zu sehen.
Hängst du bei deiner eigenen Aufgabe fest? Hausaufgabenhilfe
Schlüsselbegriffe
A
Abhängigkeit
Parcel-Parameter erzeugt eine Abhängigkeit von der Definition des Pakettyps.Abhängigkeitsbereich
src/test/java verfügbar, ohne es zu einer Abhängigkeit des Hauptcodes zu machen.Abhängigkeitsinjektion
Abhängigkeitskonfiguration
Aggregation
Anwendungslogik
Arrange-Act-Assert-Muster
Artefaktkoordinaten
Assertion
Assoziation
parcels am Paketende gezeichnet werden.Attribut
- weightGrams: int stellt ein privates Ganzzahlfeld dar, das das Gewicht des Pakets speichert.Ausnahme-Assertion
assertThrows kann prüfen, ob ein Gewicht von null zurückgewiesen wird, und die Ausnahme zur Prüfung ihrer Meldung zurückgeben.Ä
Äquivalenzklasse
B
Benutzeroberfläche
Bill of Materials (BOM)
Build-Lebenszyklus
Build-Werkzeug
D
Datenzugriffsschicht
E
Einzeltypimport
import java.util.ArrayList; bezeichnet genau, welche Klasse ArrayList die Datei verwendet.Erwartetes Ergebnis
F
Fake
Flaky Test
G
Generalisierung
Letter zu Item wird in eine Java-Deklaration übertragen, in der Letter Item erweitert.Geschützter Zugriff
Gradle-Task
Grenzwertfall
I
Importdeklaration
Scanner importiert, kann in ihrer Felddeklaration den einfachen Namen statt java.util.Scanner verwenden.J
JUnit Jupiter
@Test von Jupiter bleibt die Testannotation konsistent mit der vom Build ausgewählten Engine.JUnit Platform
useJUnitPlatform() aus, wenn die Tests des Projekts Jupiter-Annotationen verwenden.K
Komposition
L
Lambda-Ausdruck
() -> calculator.feeInCents(0) lässt JUnit den ungültigen Aufruf innerhalb seiner Assertion ausführen.Lebenszyklusmethode
@BeforeEach baut veränderliche Objekte neu auf, während @AfterEach temporäre Testressourcen entfernen kann.M
Modultest
Multiplizität
1..* neben Parcel verlangt, dass jede Sendung mindestens ein verbundenes Paket behält.N
Namenskonflikt
List benötigt, kann einen importieren und für den anderen den vollständig qualifizierten Namen verwenden.Navigierbarkeit
Ö
Öffentliche Schnittstelle
O
Oberklasse
Objektverdrahtung
Operation
+ totalWeightGrams(): int beschreibt eine öffentliche Methode, ohne ihren Algorithmus anzugeben.P
Paket
Paketdarstellung
Paketdeklaration
package com.example.parcels.domain; wird dieses Paket Teil des vollständigen Klassennamens.Paketzugriff
Parametrisierter Test
Plugin
Präsentationsschicht
Q
Quellverzeichnis
src/main/java für Programmcode in einem Standard-Build.parcels.domain.Parcel lautet der Pfad unterhalb des Quellverzeichnisses parcels/domain/Parcel.java.R
Realisierung
Refactoring
Regression
Repository
Rot-Grün-Refactoring-Zyklus
S
Schicht
Schnittstellenvertrag
save über den Repository-Vertrag aufrufen, unabhängig davon, ob die Speicherung im Arbeitsspeicher oder in einer Datei erfolgt.Sichtbarkeitssymbol
+ öffentlich, - privat, # geschützt oder ~ Paketzugriff.- dargestelltes Feld sollte beim Übertragen des Diagramms in Java seine private Sichtbarkeit behalten.Statischer Import
assertEquals oft statisch, sodass jede Prüfung als direkter Assertion-Aufruf lesbar ist.Statisches Mitglied
Surefire
ShippingCalculatorTest entspricht einem standardmäßigen Surefire-Einschlussmuster.T
Test-Fixture
Testgetriebene Entwicklung
Testliste
Testunabhängigkeit
Toleranz
Transitive Abhängigkeit
Trennung der Verantwortlichkeiten
U
UML-Klassendiagramm
Unbenanntes Paket
V
Vollständig qualifizierter Name
java.util.List lässt sich das Collection-Interface von einem anderen Typ unterscheiden, der ebenfalls List heißt.W
Wildcard-Import
java.util.* benötigt java.util.function.Function weiterhin einen eigenen Import oder den vollständigen Namen.Wrapper
./mvnw test auszuführen.Z
Zugriffsmodifikator
Zyklische Paketabhängigkeit
- 1Java-Programme kompilieren und starten
- 2Bedingungen und Schleifen in Java
- 3Java-Methoden und Problemzerlegung
- 4Listen, Arrays und Strings in Java
- 5Java-Klassen und Objekte
- 6Objektreferenzen in Java
- 7HashMaps und Collections in Java
- 8Vererbung in Java
- 9Ausnahmen und Dateien in Java
- 10Modultests in Java
- 11Lambda-Ausdrücke und Streams in Java
Über diese Einheit
Erstelle deine eigenen Lernmaterialien
Mach aus deinen Notizen, Folien und Dokumenten Lernleitfäden, Übungsfragen, Karteikarten und 6 weitere Tools.