Pakete, Klassendiagramme und Modultests in Java

Lerne, ein größeres Java-Programm so zu gliedern, dass es leicht verständlich, änderbar und überprüfbar bleibt. Fasse Klassen mit Importdeklarationen und passenden Zugriffsrechten in Paketen zusammen, skizziere die Struktur in UML-Klassendiagrammen und trenne Benutzeroberfläche, Programmlogik und Datenspeicherung in Schichten. Prüfe anschließend die Logik mit JUnit-Tests, schreibe Code testgetrieben und lass Maven oder Gradle das gesamte Projekt mit einem Befehl kompilieren, testen und paketieren.

AnfängerEinheit 10 von 11 in Kurs: Java-Programmierung – Klassen, Vererbung, Collections und Streams

Was du lernst

  1. 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 zu
      Schreibe 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 Importdeklarationen
      Importiere 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 hinweg
      Wähle zwischen public, protected, package-private und private, sodass jedes Paket nur das zugänglich macht, was andere Pakete benötigen.
  2. 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 Klasse
      Stelle 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 dar
      Verbinde 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 Diagramme
      Stelle Vererbung und Interface-Implementierung dar, schreibe Java-Code ausgehend von einem Diagramm und zeichne ein Diagramm ausgehend von bestehendem Code.
  3. 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 Programmlogik
      Halte 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 an
      Ordne Klassen einer Präsentationsschicht, einer Logikschicht und einer Datenzugriffsschicht zu, richte Abhängigkeiten nach unten aus und verbinde die Schichten über Interfaces.
  4. 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 aus
      Erstelle 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 aufdecken
      Wä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 wartbar
      Erzeuge 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 TDD
      Durchlaufe ausgehend von einer Testliste den Rot-Grün-Refactoring-Zyklus, gehe in kleinen Schritten vor und verstehe, was testgetriebene Entwicklung verspricht und was nicht.
  5. 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 es
      Beschreibe 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-Fehler
      Schreibe 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.

Modultests in Java einfach erklärt

5 Kapitel

1

Gliedere 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/java gehö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.lang benö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
ZugriffWer kann das Mitglied verwenden?
publicCode, der auf den deklarierenden Typ zugreifen kann
protectedDasselbe Paket sowie Unterklassen über Paketgrenzen hinweg
Kein ModifikatorExakt dasselbe Paket
privateDie 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.
2

Skizziere 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 verwendet Type 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ätBedeutung
1Genau eines
0..1Null oder eines
* oder 0..*Beliebig viele, einschließlich null
1..*Mindestens eines
2..4Zwei 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
  1. Erstelle jede Klasse und jedes Interface im jeweiligen Paket.
  2. Übertrage Attribute in Felder und behalte Namen, Typen und Sichtbarkeit bei.
  3. Übertrage navigierbare Assoziationen in Referenz- oder Collection-Felder.
  4. Ergänze extends oder implements anhand der Generalisierungs- oder Realisierungslinien.
  5. Ü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.
3

Gliedere 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 main und ü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.
4

Prü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/java ab, 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 assertEquals an 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 GrammErwartetes Ergebnis
0, -5Mit IllegalArgumentException zurückweisen
1, 2000500 Cent
2001, 10000900 Cent
10001, 110001.000 Cent
110011.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.
5

Baue 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 target und 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

5 Interaktive Lektionen

1

Verschiebe eine Paketklasse in ein Paket

13 Schritte
Verbinde Quellpfad, Importe und Zugriffsprüfungen, während du UI-Code von Domain-Code trennst.
  1. 1
    Einfü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.

  2. 2
    BeispielBeginne unterhalb des Quellverzeichnisses

    Das Quellverzeichnis ist src/main/java. Wandle für Parcel in com.example.parcels.domain die Punkte des Paketnamens in Verzeichnisse um und hänge den Dateinamen an:

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

    Schreibe package com.example.parcels.domain; an den Anfang der Datei. Die Verzeichnisse src/main/java des Quellverzeichnisses beschreiben die Build-Struktur und gehören nicht in die Paketdeklaration.

  3. 3
    ErklärungErhalte die Identität der Klasse

    Der einfache Name ist Parcel; der vollständig qualifizierte Name ist com.example.parcels.domain.Parcel. Das Verschieben der Datei allein ändert diese Identität nicht.

    Eine öffentliche Klasse namens Parcel gehört in Parcel.java. Starte eine Hauptklasse in einem Paket von der Wurzel des kompilierten Baums aus mit einem Befehl wie java -cp out com.example.parcels.Main, damit Java die Paketverzeichnisse unterhalb von out finden kann.

  4. 4
    HerausforderungKorrigiere einen unpassenden Speicherort

    Eine öffentliche Klasse namens Receipt deklariert package com.example.parcels.domain;, ist aber unter src/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.

  5. 5
    BeispielErleichtere 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 Parcel verwenden. 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.

  6. 6
    KernaussageEin 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.* nicht java.util.function.Function bereitstellt. Verwende für diesen Typ einen Einzeltypimport, den Wildcard-Import des richtigen Pakets oder einen vollständig qualifizierten Namen.

  7. 7
    HerausforderungUnterscheide zwei Listen

    Eine Datei benötigt sowohl java.util.List als auch java.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 schreibe java.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.

  8. 8
    TippErkenne 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, darunter String, Math und System, sind ebenfalls automatisch verfügbar.

    Ein statischer Import hat eine andere Aufgabe: Der statische Import von Assertions.assertEquals erlaubt einen direkten Aufruf von assertEquals(...). Er verkürzt eine Referenz auf ein statisches Mitglied, keinen Paketnamen.

  9. 9
    BeispielPrü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.

  10. 10
    HerausforderungGib die Operation frei und halte die Hilfsklasse verborgen

    Die Benutzeroberfläche muss die Methode feeInCents eines öffentlichen Rechners aufrufen, die derzeit ohne Zugriffsmodifikator deklariert ist. Nur der Rechner verwendet seine Hilfsklasse WeightBands mit Paketzugriff. Welche Zugriffsänderung ist nötig?

    ---reveal---

    Mache feeInCents öffentlich und belasse WeightBands mit 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.

  11. 11
    Erklä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.

  12. 12
    HerausforderungUntersuche den gesamten Zugriffsweg

    TextUI importiert ein öffentliches Parcel korrekt. Beim Aufruf von new 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.

  13. 13
    ZusammenfassungVerwende 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.

2

Lies ein Paketmodell und erstelle sein Java-Grundgerüst

13 Schritte
Übertrage UML-Kästen, Beziehungsenden und Untertyplinien in konsistente Java-Deklarationen.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungLies die Struktur, bevor du Methodenrümpfe schreibst
  2. 2
    BeispielÜbertrage ein Feld und eine Methode
  3. 3
    ErklärungErkenne ein Mitglied auf Klassenebene
  4. 4
    HerausforderungBehalte die Zeichen bei
  5. 5
    BeispielZähle Pakete ausgehend von einer Sendung
  6. 6
    BeispielWähle eine Darstellung und setze die Regel durch
  7. 7
    KernaussageFolge einer einseitigen Verbindung
  8. 8
    HerausforderungPrüfe eine Mindestanzahl
  9. 9
    ErklärungSetze den Besitz an das richtige Ende
  10. 10
    BeispielLies zwei Linien mit Dreiecken
  11. 11
    TippErstelle die Deklarationen der Reihe nach
  12. 12
    HerausforderungVerbinde einen Vertrag und eine Collection
  13. 13
    ZusammenfassungHalte die Aufgabe jedes Symbols auseinander
3

Verfolge einen Befehl durch getrennte Verantwortlichkeiten

11 Schritte
Verschiebe eine Gebührenberechnung aus der Benutzeroberfläche und verbinde einen Service mit austauschbarer Speicherung.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungVerfolge einen Befehl durch das Programm
  2. 2
    BeispielExtrahiere die Regel aus dem Konsolencode
  3. 3
    BeispielBerechne einen angefangenen Block
  4. 4
    KernaussageBelasse die Validierung bei der Regel
  5. 5
    HerausforderungMache eine Berechnung wiederverwendbar
  6. 6
    BeispielÜbergib die Speicherung an den Service
  7. 7
    VerbindungErsetze Dateien durch einen funktionierenden Fake
  8. 8
    TippPrüfe Aufrufe und Importe getrennt
  9. 9
    HerausforderungKorrigiere einen Umweg um den Service
  10. 10
    HerausforderungErmögliche einen kontrollierten Service-Test
  11. 11
    ZusammenfassungPlatziere jede Änderung bei der passenden Aufgabe
4

Entwickle eine nützliche Jupiter-Testsuite

19 Schritte
Schreibe eine erste Assertion, wähle Grenzwerte, isoliere Zustand und entwickle eine Regel durch kleine testgetriebene Änderungen.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungMache Anforderungen zu ausführbaren Prüfungen
  2. 2
    BeispielVorbereiten, ausführen und prüfen
  3. 3
    ErklärungLies erwarteten und tatsächlichen Wert richtig
  4. 4
    TippLass Rundungsabweichungen gezielt zu
  5. 5
    HerausforderungKorrigiere einen Test, der nur ausgibt
  6. 6
    BeispielFinde die Gebührenübergänge
  7. 7
    KernaussageLeite den erwarteten Wert unabhängig ab
  8. 8
    BeispielSetze den zurückzuweisenden Aufruf in die Prüfung
  9. 9
    HerausforderungWähle den Fall, der die Änderung aufdeckt
  10. 10
    BeispielGib jedem Test eine neue Sendung
  11. 11
    BeispielFühre eine Tabelle mit einem Testrumpf aus
  12. 12
    VerbindungSteuere das Partnerobjekt, nicht die Testreihenfolge
  13. 13
    HerausforderungUntersuche Daten aus einem anderen Test
  14. 14
    BeispielBeginne den Zyklus mit einem gewöhnlichen Jahr
  15. 15
    BeispielErgänze die allgemeine Regel zur Teilbarkeit durch vier
  16. 16
    BeispielVerfeinere die Bedingung mit Jahrhundertfällen
  17. 17
    HerausforderungWähle den nächsten Schritt und erhalte die Prüfungen
  18. 18
    HerausforderungVerbinde Fallauswahl und Isolation
  19. 19
    ZusammenfassungVertraue konkreten, wiederholbaren Belegen
5

Vom Build-Skript zum Testbericht

13 Schritte
Konfiguriere Abhängigkeiten und Testerkennung, führe Wrapper aus und untersuche Fehler dort, wo sie auftreten.

Speichere diese Einheit, um jede Interaktive Lektion Schritt für Schritt durchzugehen.

  1. 1
    EinführungWiederhole den Build mit einem Befehl
  2. 2
    ErklärungTrenne Quelldateien von erzeugten Ausgaben
  3. 3
    BeispielBenenne das Projekt und seine Testbibliothek
  4. 4
    BeispielFordere die benötigte Phase an
  5. 5
    TippVerwechsle null Tests nicht mit geprüftem Verhalten
  6. 6
    HerausforderungFinde einen falsch abgelegten Test
  7. 7
    ErklärungWähle die Gradle-Tasks
  8. 8
    BeispielStimme Jupiter-Module aufeinander ab
  9. 9
    BeispielLege fest, wie der Test-Task Jupiter ausführt
  10. 10
    HerausforderungKorrigiere die Testerkennung
  11. 11
    BeispielVerfolge den Fehlschlag eines ausgeführten Tests
  12. 12
    HerausforderungVerbinde Testerkennung mit einem neuen Build
  13. 13
    ZusammenfassungLies den Build als Beleg in mehreren Phasen

Modultests in Java: Übungsaufgaben

50 Übungsfragen in 5 Sets

Set 1: Namen, Verzeichnisse und Java-Zugriff10 Übungsfragen

Frage 1 von 10

Eine öffentliche Klasse Ticket deklariert package org.example.events.model;. Welchen Pfad sollte sie unterhalb des Java-Hauptquellverzeichnisses haben?
  1. org/example/events/Ticket/model.java
  2. org/example/events/model/Ticket.java
  3. src/main/java/org/example/events/model/Ticket.java
  4. org.example.events.model/Ticket.java
Antwort zeigenAntwort ausblenden
Antwort: B
Jeder Punkt wird unterhalb des Quellverzeichnisses zu einem Verzeichnis, und der Name der öffentlichen Klasse liefert 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

Eine Klasse im Paket shop.ui benötigt eine Hilfsklasse, die derzeit keine Paketdeklaration hat. Wie sollte die Hilfsklasse organisiert werden, damit die Benutzeroberfläche sie importieren kann?
  1. Im unbenannten Paket belassen und import Helper; zur UI-Quelldatei hinzufügen.
  2. In einem Verzeichnis namens shop ohne Paketdeklaration ablegen.
  3. Im unbenannten Paket belassen und den Namen mit dem Hauptquellpfad qualifizieren.
  4. In einem benannten Paket mit zugänglichen Deklarationen ablegen.
Antwort zeigenAntwort ausblenden
Antwort: D
Eine Klasse in einem benannten Paket kann keinen Typ aus dem unbenannten Paket importieren. Gib der Hilfsklasse eine Paketdeklaration und einen passenden Speicherort und gewähre dann den benötigten Zugriff; das Verschieben der Datei allein gibt ihr keine Identität in einem benannten Paket.

Frage 3 von 10

Eine Datei enthält import java.util.*; und verwendet java.util.function.Predicate mit seinem einfachen Namen. Welcher zusätzliche Einzeltypimport stellt diesen Namen bereit?
  1. import java.util.function.Predicate;
  2. import java.util.Predicate;
  3. import java.util.function;
  4. import static java.util.function.Predicate;
Antwort zeigenAntwort ausblenden
Antwort: A
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

Eine Datei importiert 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?
  1. import static java.util.List; hinzufügen.
  2. Den Typ der Deklaration durch Object ersetzen.
  3. import java.util.List; hinzufügen.
  4. Beide Wildcard-Importe vor die Paketdeklaration verschieben.
Antwort zeigenAntwort ausblenden
Antwort: C
Ein ausdrücklicher Einzeltypimport hat Vorrang vor Wildcard-Importen und löst 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

Ein Jupiter-Test ruft assertTrue(valid) ohne den vorangestellten Klassennamen Assertions auf. Welcher Import ermöglicht diesen direkten Aufruf?
  1. import org.junit.jupiter.api.Assertions.assertTrue;
  2. import static org.junit.jupiter.api.Test.assertTrue;
  3. import org.junit.jupiter.api.Assertions;
  4. import static org.junit.jupiter.api.Assertions.assertTrue;
Antwort zeigenAntwort ausblenden
Antwort: D
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?
  1. Den Konstruktor öffentlich machen.
  2. Den Konstruktor geschützt machen.
  3. Zusätzlich book.data.* importieren.
  4. Das UI-Paket in book.data.ui umbenennen.
Antwort zeigenAntwort ausblenden
Antwort: A
Der fehlende Modifikator gibt dem Konstruktor Paketzugriff, und 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

Eine gewöhnliche, als class RateTable deklarierte Hilfsklasse hat keinen Zugriffsmodifikator. Wo können andere Klassen auf oberster Ebene sie verwenden?
  1. In Paketen mit demselben obersten Namen.
  2. Nur in ihrem exakt deklarierenden Paket.
  3. Nach einem ausdrücklichen Import in jedem Paket.
  4. Nur in Unterklassen, in beliebigen Paketen.
Antwort zeigenAntwort ausblenden
Antwort: B
Eine Klasse auf oberster Ebene ohne 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

Ein Klassenkasten enthält # threshold: int und ein unterstrichenes + reset(): void. Wie werden diese Mitglieder in Java übertragen?
  1. Ein privates Ganzzahlfeld und eine öffentliche abstrakte reset-Methode.
  2. Ein Ganzzahlfeld mit Paketzugriff und eine öffentliche statische reset-Methode.
  3. Ein geschütztes Ganzzahlfeld und eine öffentliche statische reset-Methode.
  4. Ein geschütztes Ganzzahlfeld und eine öffentliche reset-Instanzmethode.
Antwort zeigenAntwort ausblenden
Antwort: C
Das Zeichen # 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

Welche UML-Operation beschreibt public boolean accepts(int grams)?
  1. + boolean accepts(int: grams)
  2. - accepts(grams: int): boolean
  3. + accepts(grams: boolean): int
  4. + accepts(grams: int): boolean
Antwort zeigenAntwort ausblenden
Antwort: D
UML setzt den Operationsnamen an die erste Stelle, schreibt bei jedem Parameter zuerst den Namen und dann den Typ und setzt den Rückgabetyp hinter die Klammern. Das Pluszeichen erhält die öffentliche Sichtbarkeit; vertauschte Parameter- und Ergebnistypen würden eine andere Methode beschreiben.

Frage 10 von 10

Das Paket 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?
  1. Engine öffentlich machen; den Rest mit Paketzugriff belassen.
  2. Engine und run öffentlich machen, den Konstruktor nicht.
  3. Engine, den Konstruktor und run öffentlich machen.
  4. Nur den Konstruktor und run öffentlich machen.
Antwort zeigenAntwort ausblenden
Antwort: C
Die Benutzeroberfläche muss auf den Typ zugreifen, seinen Konstruktor aufrufen und seine Methode ausführen. Alle drei haben derzeit Paketzugriff, und 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

An einer Assoziation zwischen Bibliothek und Buch steht 0..* neben Book. Mit wie vielen Büchern darf eine Bibliothek verbunden sein?
  1. Mit mindestens einem Buch.
  2. Mit genau einem Buch.
  3. Mit keinem oder mehreren Büchern.
  4. Mit keinem oder einem Buch.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Ein Kurs muss eine oder mehrere Sitzungen haben. Sein Feld List<Session> ist anfangs leer, und keine Methode prüft seine Größe. Was fehlt in der Implementierung?
  1. Prüfungen, die die Mindestsitzungszahl herstellen und erhalten.
  2. Ein anderer Listenimport, damit die Collection ein Minimum erzwingt.
  3. Ein statisches Sitzungsfeld, das alle Kursinstanzen gemeinsam nutzen.
  4. Ein geschütztes Listenfeld, das Sitzungen direkt befüllen können.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Ein Ordnermodell gruppiert Dokumente, die gemeinsam genutzt werden dürfen und nach dem Entfernen des Ordners weiterbestehen. Stützt die Anforderung eine Komposition der Dokumente durch diesen Ordner?
  1. Ja; jede Collection von Referenzen begründet zusammengesetzten Besitz.
  2. Ja; ein Ordner ist das Ganze, daher ist die Lebensdauer der Dokumente unwichtig.
  3. Nein; Komposition ist Klassen ohne Collection-Felder vorbehalten.
  4. Nein; gemeinsam genutzten unabhängigen Dokumenten fehlt zusammengesetzter Besitz.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Eine Klasse hat eine gestrichelte UML-Linie, die an Printable in einem nicht ausgefüllten Dreieck endet. Welche Java-Untertypdeklaration stellt diese Linie dar?
  1. extends Printable
  2. implements Printable
  3. imports Printable
  4. new Printable()
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?
  1. Als Assoziation zu Product mit einem mehrwertigen Product-Ende.
  2. Als durchgezogene Generalisierungslinie von Basket mit Dreieck an Product.
  3. Als gestrichelte Realisierungslinie von Basket mit Dreieck an Product.
  4. Als navigierbare Assoziation von Product zu Basket mit einem mehrwertigen Basket-Ende.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Ein Notenrechner berechnet einen Mittelwert und gibt ihn aus. Eine Weboberfläche benötigt die Berechnung. Welche Änderung macht diese Logik direkt wiederverwendbar?
  1. Die Berechnung in die Weboberfläche kopieren.
  2. Ein Webausgabeformat im Rechner ergänzen.
  3. Den Mittelwert zurückgeben und Oberflächen ihn formatieren lassen.
  4. Den Rechner zuerst den Namen der Oberfläche einlesen lassen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Eine Textoberfläche weist nicht numerische Eingaben zurück, bevor sie einen Rechner aufruft. Der Rechner muss außerdem negative Zahlenwerte für jeden Aufrufer zurückweisen. Wo sollte diese Regel liegen?
  1. Nur in der Textoberfläche, zusammen mit dem Parsen von Zahlen.
  2. Im Rechner, während die Oberfläche die Meldungen übernimmt.
  3. In der main-Methode, nach dem Erzeugen des Rechners.
  4. Im Repository, nach dem Speichern des berechneten Ergebnisses.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Ein Service-Konstruktor nimmt das Interface CatalogRepository entgegen. Wie kann ein Test die echte Datenbank vermeiden?
  1. Der bestehenden echten Implementierung einen Datenbankdateinamen übergeben.
  2. Den Service-Konstruktor das Datenbank-Repository erzeugen lassen.
  3. Den Service für den Test in das Präsentationspaket verschieben.
  4. Eine CatalogRepository-Implementierung im Arbeitsspeicher übergeben.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Eine Buchungsoberfläche speichert direkt über ein Repository. Der Service enthält eine Prüfung der Platzgrenze und erzeugt sein eigenes Dateirepository. Tests müssen die Grenze ohne Festplattenzugriff prüfen. Welche Kombination von Änderungen unterstützt sowohl die Oberfläche als auch die Tests?
  1. Anfragen der Oberfläche durch den Service leiten und ihm das Repository übergeben.
  2. Direktes Speichern beibehalten und das Repository nur der Oberfläche übergeben.
  3. Anfragen durch den Service leiten und ein statisches Dateirepository gemeinsam nutzen.
  4. Die Platzprüfung in die Oberfläche verschieben und den Service weiter Dateien wählen lassen.
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?
  1. Order nach storage verschieben; OrderService importiert einen konkreten Speicher.
  2. Das Interface nach rules verschieben; storage implementiert den Vertrag.
  3. OrderRepository zur Oberfläche verschieben; beide unteren Schichten importieren es.
  4. Die Struktur beibehalten; Importe durch vollständig qualifizierte Namen ersetzen.
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

Eine Methode gibt actual zurück, und ihr gefordertes Ergebnis ist 42. Welche Jupiter-Gleichheits-Assertion bezeichnet die Werte korrekt?
  1. assertEquals(actual, 42);
  2. assertTrue(actual);
  3. assertNotNull(42);
  4. assertEquals(42, actual);
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Eine korrekte 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?
  1. assertEquals(0.6, actual);
  2. assertEquals(0.6, actual, 1.0);
  3. assertEquals(0.6, actual, 1e-6);
  4. assertNotNull(actual);
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Eine Größenregel akzeptiert Ganzzahlen von 1 bis einschließlich 500. Ist 0 eine gültige Eingabe, eine gültige Eingabe am Grenzwert oder eine ungültige Eingabe?
  1. Eine ungültige Eingabe.
  2. Eine gültige Eingabe am Grenzwert.
  3. Eine gültige Eingabe.
  4. Eine nicht spezifizierte Eingabe.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Ein Sensor akzeptiert ganzzahlige Messwerte bis einschließlich 800. Ein Entwickler ändert die obere Prüfung von reading <= 800 zu reading < 800. Welcher gültige Messwert deckt den Fehler auf?
  1. 799
  2. 800
  3. 801
  4. 400
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Eine Methode muss einen leeren String mit IllegalArgumentException zurückweisen. Wie sollte ein Jupiter-Test den Aufruf ausführen?
  1. assertThrows(IllegalArgumentException.class, () -> parser.parse(""));
  2. assertThrows(IllegalArgumentException.class, parser.parse(""));
  3. assertEquals(IllegalArgumentException.class, parser.parse(""));
  4. assertNotNull(() -> parser.parse(""));
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Ein Tarif berechnet 250 Cent bis einschließlich 3.000 g und danach 50 Cent je angefangene 500 g darüber. Seine Implementierung verwendet oberhalb der Grenze 250 + ((grams - 3000) / 500) * 50. Welcher neue Test deckt den Rundungsfehler am unmittelbarsten auf?
  1. 3.500 g müssen 300 Cent kosten.
  2. 4.000 g müssen 350 Cent kosten.
  3. 3.001 g müssen 300 Cent kosten.
  4. 3.000 g müssen 250 Cent kosten.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Für jeden Test wird eine neue Instanz der Testklasse erzeugt, aber eine statische veränderliche Liste behält Einträge aus einem früheren Test. Welche Vorbereitung gibt jedem Szenario eine eigene Liste?
  1. Die Testmethoden für eine alphabetische Reihenfolge umbenennen.
  2. Eine für jeden Test initialisierte nicht statische Liste verwenden.
  3. Eine Liste in einer statischen BeforeAll-Methode erzeugen.
  4. Die statische Liste behalten und DisplayName-Bezeichnungen ergänzen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Ein Rabatttest liest das aktuelle Datum und verwendet ein statisches Repository mit früheren Kunden. Er besteht allein, liefert aber bei verschiedenen Ausführungen der Testsuite unterschiedliche Ergebnisse. Wie sollte seine Vorbereitung umgestaltet werden?
  1. Das heutige Datum beibehalten und den Test in jeder Suite zuerst ausführen.
  2. Ein festes Datum verwenden und das gemeinsame gefüllte Repository wiederverwenden.
  3. Nur den Service neu erzeugen und beide externen Eingaben unverändert lassen.
  4. Für jedes Szenario ein festes Datum und ein neues Repository bereitstellen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Eine Gebührenregel hat acht Zeilen mit Eingaben und erwarteten Ergebnissen, die dieselbe Assertion verwenden. Wie kann Jupiter getrennt ausgewiesene Fälle ohne acht kopierte Methodenrümpfe erhalten?
  1. Einen parametrisierten Test mit den Zeilen als Argumentquelle verwenden.
  2. Einen Test verwenden, der alle Ergebnisse ausgibt, aber nur das erste prüft.
  3. BeforeAll verwenden, um die Gebühr einmal für die gesamte Klasse zu berechnen.
  4. Disabled für die wiederholten Methoden und eine aktive Methode verwenden.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Alle aktuellen Tests bestehen. Du möchtest eine Hilfsmethode umbenennen und wiederholte Logik extrahieren, ohne eine Ausgabe zu ändern. Welche TDD-Aktivität ist das?
  1. Einen neuen roten Fall für eine andere Funktion ergänzen.
  2. Erwartete Ergebnisse an die Ausgabe der Hilfsmethode anpassen.
  3. Refactoring durchführen und die bestehenden Tests erneut ausführen.
  4. Frühere Tests auslassen, bis ein anderes Verhalten implementiert ist.
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

Wo legt ein standardmäßiges Maven-Projekt Java-Quelldateien für Modultests ab?
  1. src/test/java
  2. src/main/java
  3. target/test-classes
  4. src/test/resources
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Ein Projekt verwendet JUnit nur in Tests. Welcher Maven-Abhängigkeitsbereich hält es für die Kompilierung und Ausführung von Tests verfügbar, ohne es zu den Klassenpfaden des Hauptprogramms hinzuzufügen?
  1. compile
  2. test
  3. runtime
  4. provided
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Was geschieht bei einem normalen Maven-Lebenszyklus ohne konfigurierte Überspringungen, bevor die Package-Phase eine JAR-Datei erzeugt?
  1. Nur der Hauptcode wird kompiliert; Tests laufen separat.
  2. Eine ältere installierte JAR-Datei wird in die Quellen kopiert.
  3. Haupt- und Testcode werden kompiliert und die Tests ausgeführt.
  4. Der Clean-Lebenszyklus löscht immer zuerst die Ausgabe.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Ein Maven-Projekt mit Java 17 deklariert nur JUnit Jupiter 6.1.3, konfiguriert aber Surefire 2.12.4. Seine Testdatei 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?
  1. In TotalsTest umbenennen und Jupiter-@Test verwenden, aber Surefire 2.12.4 behalten.
  2. TotalsChecks behalten, Jupiter-@Test verwenden und Surefire 3.5.4 wählen.
  3. In TotalsTest umbenennen und Surefire 3.5.4 wählen, aber org.junit.Test behalten.
  4. In TotalsTest umbenennen, Jupiter-@Test verwenden und Surefire 3.5.4 wählen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Wie lautet der übliche Dateiname für ein in Kotlin geschriebenes Gradle-Build-Skript?
  1. settings.gradle.kts
  2. build.gradle.kts
  3. build.gradle
  4. pom.xml
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Ein Gradle-Projekt hat Jupiter-Tests und deklariert deren Bibliotheken. Welcher Aufruf innerhalb des Test-Tasks wählt ihre Ausführungsplattform?
  1. mavenCentral()
  2. testImplementation()
  3. mainClass.set()
  4. useJUnitPlatform()
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Eine JUnit-BOM wird mit Gradles platform(...) deklariert. Warum können zugehörige JUnit-Abhängigkeitsdeklarationen einzelne Versionsnummern weglassen?
  1. Der Java-Compiler ermittelt die neueste installierte Version jedes Moduls.
  2. Der Wrapper ersetzt jede Abhängigkeitsversion durch seine Gradle-Version.
  3. Die BOM liefert abgestimmte Versionsvorgaben für die JUnit-Module.
  4. Der Test-Task lädt jedes Modul herunter, dessen Klassenname in einem Test steht.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Ein Gradle-Projekt kann einen Test nicht kompilieren, weil 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?
  1. Jupiter mit testImplementation deklarieren, damit die Testkompilierung seine API sieht.
  2. Einen weiteren useJUnitPlatform-Aufruf ergänzen, ohne die Abhängigkeit zu ändern.
  3. Den Test in die Hauptquellen verschieben und Jupiter nur zur Laufzeit verfügbar lassen.
  4. Jupiter nur zur Laufzeit verfügbar lassen und dem Test eine DisplayName-Annotation geben.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Ein Service-Test verwendet ein statisches Dateirepository mehrfach. Nach einem Zurücksetzen besteht er in der IDE, schlägt aber in der vollständigen Gradle-Suite fehl, weil frühere Tests zusätzliche Datensätze gespeichert haben. Der Service nimmt bereits ein Repository-Interface entgegen. Welche Korrektur isoliert sein Verhalten?
  1. Ein neues Repository im Arbeitsspeicher mit nur den Datensätzen dieses Tests übergeben.
  2. Mit BeforeAll dasselbe Dateirepository in der gesamten Suite gemeinsam nutzen.
  3. Gradle clean ausführen und die Datenannahmen zum gemeinsamen Repository behalten.
  4. Den Test öffentlich machen und auf die aktuelle Ausführungsreihenfolge vertrauen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Eine neue TDD-Prüfung besteht sofort, obwohl sie fehlendes Verhalten aufdecken sollte. Was solltest du als Nächstes untersuchen?
  1. Programmcode ergänzen, bis der bestehende Test fehlschlägt.
  2. Prüfen, ob sie läuft, das beabsichtigte Ergebnis prüft und bereits erfüllt sein könnte.
  3. Die Assertion löschen, weil ein bestandener Test das fehlende Verhalten beweist.
  4. Andere Klassen überarbeiten, bevor der Methodenaufruf des Tests geprüft wird.
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

Ein ausgeführter Test meldet bei einer korrekt angeordneten Gleichheits-Assertion „expected: <18> but was: <21>“. Welchen Wert hat die Methode zurückgegeben?
  1. 18
  2. 3
  3. 21
  4. 39
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 2 von 10

Eine Assoziation zeigt 1 neben Department und 0..* neben Employee. Was erlaubt das Modell für eine Abteilung und was verlangt es für einen Mitarbeiter?
  1. Eine Abteilung benötigt einen Mitarbeiter; jeder Mitarbeiter darf keine Abteilung haben.
  2. Eine Abteilung darf keine Mitarbeiter haben; jeder Mitarbeiter hat genau eine Abteilung.
  3. Eine Abteilung benötigt viele Mitarbeiter; jeder Mitarbeiter hat genau eine Abteilung.
  4. Eine Abteilung darf keine Mitarbeiter haben; jeder Mitarbeiter darf viele Abteilungen haben.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 3 von 10

Eine Methode gibt eine Liste mit drei vorgegebenen Werten zurück, aber der interne Collection-Typ kann sich ändern. Wie sollte der Test das Ergebnis prüfen?
  1. Das private zugrunde liegende Array lesen und seine genaue Kapazität prüfen.
  2. Prüfen, ob die konkrete Collection-Klasse der derzeitigen entspricht.
  3. Die Elemente ausgeben und das Testwerkzeug sie automatisch vergleichen lassen.
  4. Den zurückgegebenen Inhalt mit den drei geforderten Werten vergleichen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 4 von 10

Die Klasse 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?
  1. this.weight und b.weight
  2. this.weight und a.weight
  3. a.weight und b.weight
  4. this.weight, a.weight und b.weight
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 5 von 10

Ein Diagramm zeigt, dass 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?
  1. implements Archive verwenden, aber die ungeprüfte leere Liste behalten, weil List mindestens einen Eintrag erzwingt.
  2. implements Archive verwenden und leere Konstruktoreingaben zurückweisen, aber das Entfernen des letzten Eintrags erlauben.
  3. implements Archive verwenden und bei Konstruktion und Aktualisierungen eine nicht leere Eintrags-Collection erzwingen.
  4. implements Archive verwenden und die Collection durch eine optionale Entry-Referenz ersetzen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 6 von 10

Ein Berichtservice importiert Formatierer der Benutzeroberfläche und erzeugt seinen eigenen Dateispeicher. Seine Berechnung wird von einer zweiten Oberfläche und von Tests ohne Festplattenzugriff benötigt. Welche Entwurfsänderung behebt beide Abhängigkeiten?
  1. Formatierten Konsolentext zurückgeben und den Service weiter den Dateispeicher erzeugen lassen.
  2. Den Dateispeicher ins UI-Paket verschieben und Formatierer der Oberfläche im Service aufrufen.
  3. Formatierer und Dateispeicher des Service statisch machen, damit beide Oberflächen sie teilen.
  4. Berichtsdaten zurückgeben, in den Oberflächen formatieren und einen Speichervertrag übergeben.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 7 von 10

Eine mit TDD entwickelte Prüfung gibt derzeit 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?
  1. Für 2100 durch 400 teilbare Jahre ausschließen, dann für 2400 durch 100 teilbare Jahre wieder zulassen.
  2. Für 2100 durch 100 teilbare Jahre ausschließen, dann für 2400 durch 400 teilbare Jahre wieder zulassen.
  3. Zuerst durch 100 teilbare Jahre ausschließen, dann für jedes Schaltjahr Teilbarkeit durch 400 verlangen.
  4. Zuerst durch 100 teilbare Jahre ausschließen, dann Vielfache von 400 nur zulassen, wenn sie nicht durch 100 teilbar sind.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 8 von 10

Eine Verpackungsregel weist nicht positive Gewichte zurück, berechnet 400 Cent bis einschließlich 2.500 g und darüber 700 Cent. Ihre Tests decken nur 1.000 g ab. Eine Änderung akzeptiert versehentlich null und verwendet < 2500 für die untere Gebührenstufe. Welches zusätzliche Prüfungspaar deckt beide Fehler auf?
  1. Null muss eine Ausnahme auslösen; 2.500 g müssen 400 Cent ergeben.
  2. Null muss eine Ausnahme auslösen; 2.501 g müssen 700 Cent ergeben.
  3. 1 g muss 400 Cent ergeben; 2.501 g müssen 700 Cent ergeben.
  4. −1 g muss eine Ausnahme auslösen; 2.499 g müssen 400 Cent ergeben.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 9 von 10

Ein Gradle-Skript enthält 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?
  1. In den Hauptquellen behalten, testImplementation für Jupiter verwenden und die JUnit Platform wählen.
  2. In die Testquellen verschieben, Jupiter nur zur Laufzeit belassen und die JUnit Platform wählen.
  3. In die Testquellen verschieben, testImplementation für Jupiter verwenden und die Standardausführung behalten.
  4. In die Testquellen verschieben, testImplementation für Jupiter verwenden und die JUnit Platform wählen.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Frage 10 von 10

Ein normaler Maven-Package-Lauf stoppt bei einem fehlgeschlagenen Test, aber eine JAR-Datei von gestern bleibt in target. Was belegt diese Datei über den heutigen Quellcode?
  1. Sie belegt, dass der heutige Quellcode seine Tests bestanden hat.
  2. Sie belegt, dass die Paketierung den Fehlschlag ignoriert hat.
  3. Sie belegt nur, dass ein älteres Artefakt existiert.
  4. Sie belegt, dass Maven den gestrigen Quellcode neu gebaut hat.
Antwort zeigenAntwort ausblenden

Speichere diese Einheit, um diese Antwort zu sehen.

Hängst du bei deiner eigenen Aufgabe fest? Hausaufgabenhilfe

Schlüsselbegriffe

73 Schlüsselbegriffe, jeweils mit Definition

A

Abhängigkeit
Eine Abhängigkeit ist eine Verwendungsbeziehung, bei der ein Element auf ein anderes angewiesen ist. Sie wird als gestrichelte Linie mit offener Pfeilspitze gezeichnet.
Eine Gebührenoperation mit einem Parcel-Parameter erzeugt eine Abhängigkeit von der Definition des Pakettyps.
Abhängigkeitsbereich
Ein Abhängigkeitsbereich steuert, welche Maven-Klassenpfade eine Bibliothek enthalten, und trennt Anforderungen nur für Tests von Anforderungen des Hauptprogramms.
Der Test-Abhängigkeitsbereich macht JUnit unter src/test/java verfügbar, ohne es zu einer Abhängigkeit des Hauptcodes zu machen.
Abhängigkeitsinjektion
Abhängigkeitsinjektion stellt einem Objekt seine Partnerobjekte von außen bereit, sodass der aufrufende Code deren Implementierungen oder Eingaben auswählen kann.
Wird ein Repository an den Service-Konstruktor übergeben, kann der Test eine kontrollierte Speicherung wählen.
Abhängigkeitskonfiguration
Eine Abhängigkeitskonfiguration legt fest, wo eine Gradle-Abhängigkeit verfügbar ist, etwa für die Hauptimplementierung, die Testkompilierung oder die Testlaufzeit.
Ein Plattform-Launcher kann testRuntimeOnly verwenden, weil die Testquellen ihn während der Kompilierung nicht benötigen.
Aggregation
Eine Aggregation ist eine lockere Ganzes-Teil-Gruppierung, die durch eine nicht ausgefüllte Raute dargestellt wird und deren genaue Bedeutung vom modellierten Entwurf abhängt.
Ein Team kann Spieler gruppieren, die auch unabhängig existieren, ohne deren Lebensdauer zu bestimmen.
Anwendungslogik
Die Anwendungslogik ist der Code, der die Regeln und Operationen eines Programms auf Daten unabhängig vom Benutzerdialog implementiert.
Ein Gebührenrechner wählt die richtige Gebührenstufe, ohne nach Eingaben zu fragen oder auf die Konsole zu schreiben.
Arrange-Act-Assert-Muster
Das Arrange-Act-Assert-Muster ist ein Muster zum Schreiben von Tests, das die Vorbereitung eines Szenarios, die Ausführung seiner Aktion und die Prüfung seines Ergebnisses trennt.
Eine Sendung zu erzeugen, ein Paket hinzuzufügen und die Anzahl zu prüfen, ergibt drei klare Testteile.
Artefaktkoordinaten
Artefaktkoordinaten identifizieren ein Maven-Projekt oder eine Bibliothek anhand von Gruppe, Artefaktname und Version, damit der Build das gewünschte Artefakt auflösen kann.
Die Koordinaten unterscheiden eine Version einer JUnit-Bibliothek von einer anderen, ohne JAR-Dateien von Hand zu kopieren.
Assertion
Eine Assertion in einem Test ist eine ausführbare Prüfung, die den Test fehlschlagen lässt, wenn das beobachtete Ergebnis die angegebene Erwartung nicht erfüllt.
Eine Gleichheits-Assertion lässt eine falsche Gebühr automatisch fehlschlagen, statt sie lediglich in der Ausgabe stehen zu lassen.
Assoziation
Eine Assoziation ist eine strukturelle Beziehung, die Objekte verschiedener Typen verbindet und häufig durch gespeicherte Referenzfelder implementiert wird.
Die Paketliste einer Sendung kann als Assoziation mit dem Rollennamen parcels am Paketende gezeichnet werden.
Attribut
Ein Attribut ist eine benannte Eigenschaft in einem UML-Klassenkasten, die häufig einem Java-Feld mit Typ und Sichtbarkeit entspricht.
Der Eintrag - weightGrams: int stellt ein privates Ganzzahlfeld dar, das das Gewicht des Pakets speichert.
Ausnahme-Assertion
Eine Ausnahme-Assertion führt eine übergebene Operation aus und prüft, ob sie den geforderten Ausnahmetyp oder einen akzeptierten Untertyp wirft.
assertThrows kann prüfen, ob ein Gewicht von null zurückgewiesen wird, und die Ausnahme zur Prüfung ihrer Meldung zurückgeben.

Ä

Äquivalenzklasse
Eine Äquivalenzklasse beim Testen ist eine Gruppe von Eingaben, die die Spezifikation gleich behandelt und die durch geeignete Beispielfälle und Grenzwerte repräsentiert wird.
Mehrere positive Gewichte derselben Gewichtsstufe haben dieselbe Gebühr, während null zu einer Klasse ungültiger Eingaben gehört.

B

Benutzeroberfläche
Eine Benutzeroberfläche ist der Teil eines Programms, der Benutzeraktionen entgegennimmt und Ergebnisse präsentiert, etwa ein Dialog mit Textbefehlen.
Eine Textoberfläche für einen Gebührenrechner fragt nach Gramm, liest die Antwort, ruft den Rechner auf und gibt die zurückgegebene Gebühr aus.
Bill of Materials (BOM)
Eine Bill of Materials ist ein Satz von Abhängigkeitsmetadaten, der Versionen zusammengehöriger Module aufeinander abstimmt, sodass ein Build eine Bibliotheksfamilie konsistent halten kann.
Die JUnit-BOM liefert passende Modulversionen, nachdem eine gemeinsame Version ausgewählt wurde.
Build-Lebenszyklus
Ein Build-Lebenszyklus ist Mavens geordnete Folge von Phasen, bei der die Anforderung einer späteren Phase die früheren Phasen dieses Lebenszyklus ausführt.
Die Anforderung von package führt normalerweise die Testphase aus, bevor das paketierte Projektartefakt erzeugt wird.
Build-Werkzeug
Ein Build-Werkzeug automatisiert Projektaufgaben wie das Auflösen von Abhängigkeiten, das Kompilieren von Quellcode, das Ausführen von Tests und das Paketieren von Artefakten.
Ein Maven-Befehl kann ein Paketprojekt kompilieren, seine Tests ausführen und die resultierende JAR-Datei erzeugen.

D

Datenzugriffsschicht
Die Datenzugriffsschicht implementiert das Speichern und Abrufen persistenter Daten und hält Speicherdetails von Anwendungsregeln getrennt.
Ein Dateirepository verarbeitet das Dateiformat, während ein Paketservice gültige Paketgewichte behandelt.

E

Einzeltypimport
Ein Einzeltypimport macht einen zugänglichen Typ über seinen einfachen Namen verfügbar und gibt ausdrücklich an, aus welchem Paket er stammt.
Die Deklaration import java.util.ArrayList; bezeichnet genau, welche Klasse ArrayList die Datei verwendet.
Erwartetes Ergebnis
Ein erwartetes Ergebnis ist das von der Spezifikation geforderte Resultat oder Verhalten, das unabhängig vom getesteten Code bestimmt wird.
Werden schwere Pakete je angefangenen Gewichtsblock berechnet, wird die erwartete Gebühr durch das Zählen der Blöcke bestimmt und nicht aus der Ausgabe der Methode kopiert.

F

Fake
Ein Fake ist eine einfache funktionierende Implementierung einer Abhängigkeit für Tests, die nutzbares Verhalten mit kontrollierten Ressourcen bereitstellt.
Ein Repository im Arbeitsspeicher speichert Pakete für Service-Tests, ohne eine echte Datei zu öffnen oder zu ändern.
Flaky Test
Ein Flaky Test liefert ohne relevante Codeänderung uneinheitliche Ergebnisse, oft weil Zeit, gemeinsamer Zustand oder externe Daten nicht kontrolliert werden.
Ein Test, der das aktuelle Datum liest, kann sein Ergebnis ändern, wenn das Datum über Nacht wechselt.

G

Generalisierung
Generalisierung ist eine UML-Untertypbeziehung, die als durchgezogene Linie mit einem nicht ausgefüllten Dreieck zum allgemeineren Typ gezeichnet wird.
Eine Linie von Letter zu Item wird in eine Java-Deklaration übertragen, in der Letter Item erweitert.
Geschützter Zugriff
Geschützter Zugriff erlaubt die Verwendung innerhalb des deklarierenden Pakets und eingeschränkten geerbten Zugriff durch Unterklassen in anderen Paketen.
Eine Unterklasse in einem anderen Paket kann ihr eigenes geerbtes geschütztes Feld lesen, ohne dieses Feld öffentlich zu machen.
Gradle-Task
Ein Gradle-Task ist eine Einheit von Build-Arbeit, etwa Kompilierung oder Testausführung, die von anderen Tasks abhängen und unveränderte Ergebnisse wiederverwenden kann.
Die Ausführung des Build-Tasks umfasst auch die für diesen Build erforderliche Kompilierung und Testausführung.
Grenzwertfall
Ein Grenzwertfall verwendet eine Eingabe an oder neben der Grenze einer spezifizierten Regel, wo eine kleine Änderung das erwartete Verhalten ändern kann.
Endet eine Gebührenstufe bei 2.000 g, deckt ein Test bei genau 2.000 g einen fälschlich strengen Vergleich auf, den ein Test bei 1.500 g übersehen würde.

I

Importdeklaration
Eine Importdeklaration ermöglicht es einer Java-Quelldatei, einen Typ oder ein statisches Mitglied mit einem kürzeren Namen zu verwenden, ohne dessen Zugänglichkeit zu ändern.
Eine UI-Klasse, die Scanner importiert, kann in ihrer Felddeklaration den einfachen Namen statt java.util.Scanner verwenden.

J

JUnit Jupiter
JUnit Jupiter ist das JUnit-Programmiermodell und die Test-Engine, die moderne Java-Testannotationen, Assertions und Erweiterungen bereitstellen.
Mit dem @Test von Jupiter bleibt die Testannotation konsistent mit der vom Build ausgewählten Engine.
JUnit Platform
Die JUnit Platform ist die Grundlage, die unterstützte Test-Engines erkennt und startet, darunter die Engine für Jupiter-Tests.
Gradle wählt sie mit useJUnitPlatform() aus, wenn die Tests des Projekts Jupiter-Annotationen verwenden.

K

Komposition
Eine Komposition ist eine Ganzes-Teil-Assoziation, bei der ein Teil zu einem Zeitpunkt exklusiv einem zusammengesetzten Ganzen gehört. Sie wird durch eine ausgefüllte Raute am Ganzen dargestellt.
Eine Bestellung und ihre Bestellpositionen können als Komposition dargestellt werden, wenn beim Entfernen der Bestellung auch diese Teile entfernt werden.

L

Lambda-Ausdruck
Ein Lambda-Ausdruck stellt eine kleine Operation als Wert bereit, sodass anderer Code entscheiden kann, wann und wie er diese Operation ausführt.
Der Lambda-Ausdruck () -> calculator.feeInCents(0) lässt JUnit den ungültigen Aufruf innerhalb seiner Assertion ausführen.
Lebenszyklusmethode
Eine Lebenszyklusmethode wird vom Testframework zu einem festgelegten Zeitpunkt ausgeführt, etwa vor jedem Test oder einmal vor der gesamten Klasse.
@BeforeEach baut veränderliche Objekte neu auf, während @AfterEach temporäre Testressourcen entfernen kann.

M

Modultest
Ein Modultest ist eine automatisierte Prüfung einer kleinen Codeeinheit, die beobachtetes Verhalten mit einem unabhängig festgelegten Ergebnis vergleicht.
Ein Rechnertest ruft die Gebührenmethode auf und prüft die erwartete Anzahl Cent mit einer Assertion, ohne die Benutzeroberfläche zu starten.
Multiplizität
Die Multiplizität gibt für ein Objekt am gegenüberliegenden Ende die zulässige Anzahl verbundener Objekte an einem Beziehungsende an.
Die Angabe 1..* neben Parcel verlangt, dass jede Sendung mindestens ein verbundenes Paket behält.

N

Namenskonflikt
Ein Namenskonflikt entsteht, wenn ein einfacher Name auf sich widersprechende Deklarationen verweisen kann und der Code den gemeinten Typ unterscheiden muss.
Eine Datei, die zwei Typen namens List benötigt, kann einen importieren und für den anderen den vollständig qualifizierten Namen verwenden.
Navigierbarkeit
Navigierbarkeit beschreibt, ob ein Objekt einer Beziehung zu einem anderen Objekt folgen kann; sie wird oft mit einer offenen Pfeilspitze dargestellt.
Ein Pfeil von der Sendung zum Paket zeigt, dass eine Sendung ihre Pakete über Referenzen erreichen kann.

Ö

Öffentliche Schnittstelle
Die öffentliche Schnittstelle eines Pakets ist die Gesamtheit der zugänglichen Typen und Operationen, die es Code außerhalb des Pakets bewusst anbietet.
Ein Domain-Paket kann einen Versandrechner bereitstellen und zugleich die Hilfsklasse verbergen, die seine Gewichtsstufen speichert.

O

Oberklasse
Eine Oberklasse ist die Klasse, von der eine Unterklasse Mitglieder und Verhalten erbt; UML setzt das Generalisierungsdreieck an diese Klasse.
Sowohl Briefe als auch Pakete können gemeinsames Artikelverhalten erben, ohne es in jedem Unterklassenkasten zu wiederholen.
Objektverdrahtung
Die Objektverdrahtung ist das Erzeugen und Verbinden zusammenarbeitender Programmobjekte, das häufig nahe dem Einstiegspunkt der Anwendung erfolgt.
Eine main-Methode erzeugt einen Scanner und einen Rechner, übergibt sie an ein UI-Objekt und startet dessen Dialog.
Operation
Eine Operation ist ein in einem UML-Typ deklariertes Verhalten, das durch Namen, Parametertypen, Sichtbarkeit und Rückgabetyp beschrieben wird.
Die Operation + totalWeightGrams(): int beschreibt eine öffentliche Methode, ohne ihren Algorithmus anzugeben.

P

Paket
Ein Paket ist eine benannte Gruppe von Java-Typen, die zusammengehörigen Code ordnet, Typnamen voneinander trennt und eine Zugriffsgrenze bildet.
Getrennte UI- und Domain-Pakete halten die Befehlsverarbeitung von den Klassen getrennt, die Paketgebühren berechnen.
Paketdarstellung
Eine Paketdarstellung ist ein ordnerförmiger Bereich in einem UML-Diagramm, der Typkästen nach ihrer Paketzugehörigkeit ordnet.
Die getrennte Gruppierung von UI- und Domain-Klassen erleichtert es, Referenzen zwischen diesen Bereichen zu untersuchen.
Paketdeklaration
Eine Paketdeklaration ordnet die Typen einer Quelldatei einem benannten Paket zu und steht vor deren Import- und Klassendeklarationen.
Mit package com.example.parcels.domain; wird dieses Paket Teil des vollständigen Klassennamens.
Paketzugriff
Paketzugriff erlaubt die Verwendung eines gewöhnlichen Klassenmitglieds oder eines Typs auf oberster Ebene nur aus dem exakt selben deklarierenden Paket.
Eine Hilfsklasse ohne öffentlichen Modifikator bleibt für eine UI-Klasse in einem benachbarten Paket auch mit einem Import unzugänglich.
Parametrisierter Test
Ein parametrisierter Test führt eine Testmethode für mehrere bereitgestellte Argumentsätze aus und weist jeden Aufruf als separaten Fall aus.
Eine Tabelle mit Gewichten und Gebühren kann mehrere Grenzwerte prüfen, ohne den Rumpf der Assertion-Methode zu wiederholen.
Plugin
Ein Plugin ergänzt Build-Funktionen und Konfiguration und stellt oft Tasks und Konventionen für eine bestimmte Projektart bereit.
Gradles Application-Plugin ergänzt einen Run-Task für die konfigurierte Paket-Hauptklasse.
Präsentationsschicht
Die Präsentationsschicht übersetzt Benutzerinteraktionen in Anfragen an das Programm und präsentiert dem Benutzer die zurückgegebenen Ergebnisse.
Eine Textoberfläche kann eine Gebühr in einem Satz formatieren, ohne diesen Satz in den Code für die Gebührenberechnung aufzunehmen.

Q

Quellverzeichnis
Ein Quellverzeichnis ist das Verzeichnis, unterhalb dessen Java-Paketpfade beginnen, etwa src/main/java für Programmcode in einem Standard-Build.
Für parcels.domain.Parcel lautet der Pfad unterhalb des Quellverzeichnisses parcels/domain/Parcel.java.

R

Realisierung
Realisierung ist eine Beziehung, bei der ein Typ einen Interface-Vertrag erfüllt. Sie wird durch eine gestrichelte Linie und ein nicht ausgefülltes Dreieck dargestellt.
Ein Repository im Arbeitsspeicher realisiert dasselbe Repository-Interface wie ein dateibasiertes Repository.
Refactoring
Refactoring ändert die interne Organisation von Code und erhält dabei sein beobachtbares Verhalten, etwa durch Umbenennen oder Extrahieren einer Methode.
Eine Gebührenhilfsklasse in ein besser geeignetes Paket zu verschieben, ist ein Refactoring, wenn der Rechner weiterhin dieselben Ergebnisse liefert.
Regression
Eine Regression ist ein neu eingeführter Fehler in einem zuvor funktionierenden Verhalten, häufig nach einer Änderung an anderer Stelle im Code.
Ein bestehender Test für einen Gebührengrenzwert deckt eine Regression auf, wenn ein einschließender Vergleich zu einem strengen Vergleich geändert wird.
Repository
Ein Repository bietet Operationen zum Speichern und Abrufen von Domain-Objekten hinter einem Vertrag an, der verschiedene Implementierungen haben kann.
Ein Test stellt ein Repository im Arbeitsspeicher bereit, um nicht von einer echten Paketdatendatei abzuhängen.
Rot-Grün-Refactoring-Zyklus
Der Rot-Grün-Refactoring-Zyklus ist der TDD-Zyklus, bei dem fehlendes Verhalten sichtbar gemacht wird, alle aktuellen Tests zum Bestehen gebracht werden und die Struktur unter Beibehaltung des Verhaltens verbessert wird.
Nachdem der neue Gebührenfall besteht, gehört das Extrahieren wiederholten Berechnungscodes in den Refactoring-Schritt.

S

Schicht
Eine Schicht gruppiert Klassen mit einer gemeinsamen Art von Verantwortung und bietet anderen Teilen über eine definierte Grenze Operationen an.
Ein Paketservice führt die Validierung durch, bevor er eine Speicherschicht anweist, ein neues Paket zu speichern.
Schnittstellenvertrag
Ein Schnittstellenvertrag legt Operationen fest, die eine Implementierung anbietet, sodass aufrufender Code den Interface-Typ verwenden kann, ohne dessen Implementierung auszuwählen.
Ein Service kann save über den Repository-Vertrag aufrufen, unabhängig davon, ob die Speicherung im Arbeitsspeicher oder in einer Datei erfolgt.
Sichtbarkeitssymbol
Ein Sichtbarkeitssymbol ist ein vorangestelltes UML-Zeichen für den Zugriff: + öffentlich, - privat, # geschützt oder ~ Paketzugriff.
Ein mit - dargestelltes Feld sollte beim Übertragen des Diagramms in Java seine private Sichtbarkeit behalten.
Statischer Import
Ein statischer Import ermöglicht es einer Quelldatei, ein zugängliches statisches Mitglied direkt zu benennen und dabei den Namen der deklarierenden Klasse wegzulassen.
JUnit-Tests importieren assertEquals oft statisch, sodass jede Prüfung als direkter Assertion-Aufruf lesbar ist.
Statisches Mitglied
Ein statisches Mitglied gehört zu einer Klasse statt zu einem einzelnen Objekt und wird in UML durch Unterstreichen seines Attribut- oder Operationseintrags dargestellt.
Das Unterstreichen einer Konstante für das Höchstgewicht unterscheidet einen gemeinsam genutzten Klassenwert vom Gewicht jedes einzelnen Pakets.
Surefire
Surefire ist das Maven-Plugin, das während der Testphase Modultests anhand konfigurierter Testframeworks und Erkennungsregeln ausführt.
Ein Testklassenname wie ShippingCalculatorTest entspricht einem standardmäßigen Surefire-Einschlussmuster.

T

Test-Fixture
Eine Test-Fixture ist die Gesamtheit der für einen Test vorbereiteten Objekte und Daten, die den kontrollierten Anfangszustand für sein Szenario bereitstellt.
Eine neue leere Sendung vor jedem Test verhindert, dass hinzugefügte Pakete in ein anderes Szenario gelangen.
Testgetriebene Entwicklung
Testgetriebene Entwicklung ist eine Entwicklungsmethode, bei der ein fehlschlagender Test eine kleine Verhaltensänderung leitet, bevor die Codestruktur verbessert wird.
Ein fehlschlagender Fall für ein Jahrhundertjahr leitet die nächste Änderung einer Schaltjahrprüfung anstelle einer großen Neufassung.
Testliste
Eine Testliste hält noch abzudeckende Verhaltensweisen und Fälle fest, sodass der Entwickler einen kleinen nächsten Schritt auswählen und bei Bedarf neue Erkenntnisse ergänzen kann.
Eine Schaltjahrliste enthält gewöhnliche Jahre, durch vier teilbare Jahre und die beiden Jahrhundertfälle.
Testunabhängigkeit
Testunabhängigkeit bedeutet, dass jeder Test seinen benötigten Zustand selbst bereitstellt und allein oder in beliebiger Reihenfolge korrekt bestehen oder fehlschlagen kann.
Ein Test der Anzahl erzeugt seine eigenen Pakete, statt darauf zu bauen, dass eine frühere Methode sie hinzugefügt hat.
Toleranz
Eine Toleranz ist eine erlaubte numerische Abweichung, die eine Assertion verwendet, wenn exakte Gleichheit für eine Gleitkommaberechnung ungeeignet ist.
Eine kleine begründete Toleranz lässt eine Rundungsabweichung zu, ohne ein erheblich falsches Ergebnis zu akzeptieren.
Transitive Abhängigkeit
Eine transitive Abhängigkeit ist eine Bibliothek, die von einer anderen Abhängigkeit benötigt und vom Build-Werkzeug zusammen mit dem direkt angeforderten Artefakt aufgelöst wird.
Die Deklaration des Jupiter-Sammelartefakts bringt auch die für seine Testfunktionen benötigten Module mit.
Trennung der Verantwortlichkeiten
Die Trennung der Verantwortlichkeiten weist unterschiedliche Aufgaben verschiedenen Programmteilen zu, sodass jeder Teil leichter verstanden, geändert und getestet werden kann.
Eine Änderung der Formulierung einer Eingabeaufforderung sollte keine Änderung an der Methode erfordern, die eine Paketgebühr berechnet.

U

UML-Klassendiagramm
Ein UML-Klassendiagramm modelliert die statische Struktur von Software durch Typkästen, Attribute, Operationen und Beziehungen zwischen Typen.
Ein Klassendiagramm des Paketprogramms zeigt, was eine Sendung speichert, ohne die Reihenfolge der vom Benutzer eingegebenen Befehle nachzuverfolgen.
Unbenanntes Paket
Das unbenannte Paket enthält Typen aus Quelldateien ohne Paketdeklaration; benannte Pakete können diese Typen nicht importieren.
Werden gemeinsam verwendete Hilfsklassen in ein benanntes Paket verschoben, können andere benannte Teile eines Projekts auf sie verweisen.

V

Vollständig qualifizierter Name
Ein vollständig qualifizierter Name identifiziert einen Java-Typ, indem er dessen Paketnamen mit seinem einfachen Namen verbindet und so Mehrdeutigkeit zwischen Paketen auflöst.
Mit java.util.List lässt sich das Collection-Interface von einem anderen Typ unterscheiden, der ebenfalls List heißt.

W

Wildcard-Import
Ein Wildcard-Import macht zugängliche Typen eines Pakets bei Bedarf verfügbar, ohne Typen aus dessen Unterpaketen verfügbar zu machen.
Nach dem Import von java.util.* benötigt java.util.function.Function weiterhin einen eigenen Import oder den vollständigen Namen.
Wrapper
Ein Wrapper ist ein vom Projekt bereitgestelltes Startprogramm, das für wiederholbare Befehle die vom Projekt gewählte Version des Build-Werkzeugs abruft und ausführt.
Ein im Projekt eingecheckter Maven Wrapper ermöglicht Mitwirkenden, die festgelegte Maven-Version mit ./mvnw test auszuführen.

Z

Zugriffsmodifikator
Ein Zugriffsmodifikator ist ein Schlüsselwort, das steuert, welcher Code eine Deklaration wie eine Klasse, einen Konstruktor, ein Feld oder eine Methode verwenden darf.
Eine öffentliche Klasse kann trotzdem einen Konstruktor haben, den eine UI-Klasse in einem anderen Paket nicht aufrufen darf.
Zyklische Paketabhängigkeit
Eine zyklische Paketabhängigkeit besteht, wenn eine Kette von Typabhängigkeiten von einem Paket zu ihm selbst zurückführt und die Definitionen dadurch in beide Richtungen verknüpft.
Wird das Repository-Interface neben den Domain-Typen platziert, kann ein Importzyklus zwischen Domain- und Datenpaket entfallen.

Über diese Einheit

Von StudeliFehler melden

Erstelle deine eigenen Lernmaterialien

Mach aus deinen Notizen, Folien und Dokumenten Lernleitfäden, Übungsfragen, Karteikarten und 6 weitere Tools.

Jetzt starten