Übungsaufgaben zur Fachlogik und zum Komponententest: Regeln für Mitfahrgesuche

https://bildung.social/@oerinformatik/

https://oer-informatik.de/spring-boot-fachlogik-komponententest-uebungsaufgaben

Komponententest, JUnit, Äquivalenzklassen, Grenzwertanalyse, parametrisierter Test, Zustandsübergangstest, BigDecimal, LocalTime, Enum, Übungsaufgaben, Prüfungsvorbereitung

tl/dr; (ca. 41 min Lese- und Bearbeitungszeit): Übungen zur Fachlogik im Mitfahr-Backend: Du bildest Äquivalenzklassen und Grenzwerte für einen Kostenbeitrag, ordnest JUnit-Zusicherungen zu, schreibst einen parametrisierten Test zur Einwilligung, setzt die Zeitfensterregel für Gesuche samt Tests um, testest die Statuswechsel eines Gesuchs vollständig und findest einen Rundungsfehler, den double verursacht [AI-Assisted]. (Zuletzt geändert am 11.11.2026)

Dieser Artikel ist Teil einer Artikelreihe über ein REST-Backend mit Spring Boot: Überblick und Fahrplan / 01 Anforderungen und API-Vertrag / 02 Projekt anlegen / 03 Codegenerierung / 04 Fachlogik und Komponententest / Übungsaufgaben zu 04

Diese Übungsaufgaben gehören zum Artikel Fachlogik in reinem Java: Komponententests für Kostenbeitrag, Einwilligung und Buchungsstatus und setzen den Übungsstrang zu den Mitfahrgesuchen fort. Ausgangspunkt ist das Projekt im Ordner src/04-fachlogik-komponententest des Repositorys. Alle Klassen liegen im Paket de.dbh.mitfahrlabor.domain, alle Tests im gleichnamigen Paket unter src/test/java.

Fahrplan der Artikelreihe im V-Modell, hervorgehoben ist der Komponententest, zu dem Artikel 04 gehört (mit KI erstellt)

Hinweise zur Bearbeitung: Bis zur Teil-1-Prüfung der IT-Berufe solltest Du die Basiskompetenz beherrschen, also Aufgabe 1.1 und Aufgabe 1.2. Als FIA, FIDP oder FIDV musst Du zur Teil-2-Prüfung Aufgaben auf Kernkompetenz-Ebene lösen können, das sind Aufgabe 2.1 und Aufgabe 2.2. Von FIA wird zusätzlich erwartet, dass ihnen Aufgaben auf Profilkompetenz-Ebene (Aufgabe 3.1 und Aufgabe 3.2) keine Probleme bereiten. Notiere Deine Antworten zuerst vollständig, bevor Du die Beispiellösung aufklappst. Code und Tests kannst Du anschließend im Projekt aus dem Repository mit dem Build ausführen; andere fachlich richtige Lösungen sind ebenso richtig.

Du möchtest eigene Aufgaben zum Üben von einem KI-Sprachmodell generieren lassen?

Kopiere den Text in ein KI-Sprachmodell Deiner Wahl und passe Kompetenzstufe, Anzahl, Bearbeitungszeit und Szenario bei Bedarf an. Wie die Aufgaben aufgebaut sind, beschreibt der Artikel Wie sind die Aufgaben aufgebaut?. Prüfe die Ergebnisse mit dem Lehrartikel: KI-Sprachmodelle machen Fehler.

Erstelle neue Übungsaufgaben zum Thema „Fachlogik und Komponententest:
Regeln für Mitfahrgesuche“.

Bauplan: Halte Dich an Aufbau, Kompetenzstufen, Operatoren,
Anforderungsbereiche, Punkte und Zeitschätzung, die im Artikel
https://oer-informatik.de/aufgaben-aufbau beschrieben sind.

Fachliche Grundlage:
- https://oer-informatik.de/spring-boot-fachlogik-komponententest
- https://oer-informatik.de/blackboxtests
- Stilbeispiel: https://oer-informatik.de/spring-boot-fachlogik-komponententest-uebungsaufgaben

Die Aufgaben sollen Inhalte zu diesen Schlagwörtern abdecken:
- Komponententest
- JUnit
- Äquivalenzklassen
- Grenzwertanalyse
- parametrisierter Test
- Zustandsübergangstest
- BigDecimal
- LocalTime
- Enum

Kompetenzstufe: Basiskompetenz (alternativ: Kernkompetenz, Profilkompetenz, Expertise)
Anzahl: 1 Aufgabe mit Teilaufgaben
Bearbeitungszeit in Minuten ca.: <5 bis 30 min>
Szenario: eines der Szenarien aus dem Bauplan

Vorgehen:
1. Lies zuerst die verlinkten Artikel. Kannst Du keine Webseiten
   abrufen, sag mir das, statt zu raten.
2. Steht bei einer Angabe noch ein Platzhalter in <…>, frag mich
   zuerst danach. Richte Punkte und Zahl der Teilaufgaben nach der
   Bearbeitungszeit (ca. 0,9 min je Punkt).
3. Gib je Aufgabe Titel mit Kompetenzstufe, Ausgangssituation und
   Teilaufgaben mit Operator und Punktzahl aus, dazu die geschätzte
   Bearbeitungszeit.
4. Gib die Beispiellösungen mit Punkteverteilung erst aus, wenn ich
   danach frage.

Aufgabe 1.1: Äquivalenzklassen und Grenzwerte für Werther (Basiskompetenz)

Der Haltepunkt Werther liegt 13,1 km von der Schule entfernt. Nach Fachregel 1 darf der Kostenbeitrag je Person höchstens 0,10 Euro je Kilometer betragen, abgerundet auf ganze Cent. Die Kostenregel aus dem Lehrartikel wirft bei negativen Beträgen eine IllegalArgumentException und bei zu hohen eine KostenbeitragZuHochException.

Berechne den Höchstbetrag, bilde die Äquivalenzklassen und bestimme je Klasse einen Vertreter sowie alle Grenzwerte mit dem erwarteten Ergebnis. (6 Punkte)

Höchstbetrag für Werther: ______ Euro

Testfälle für den Kostenbeitrag ab Werther
Äquivalenzklasse (Bereich) Vertreter Grenzwerte erwartetes Ergebnis

Höchstbetrag (1 Punkt): 13,1 km · 0,10 Euro/km = 1,31 Euro, abrunden ändert nichts.

Je Klasse mit Bereich und Vertreter 1 Punkt, Grenzwerte vollständig und richtig zugeordnet 2 Punkte:

Äquivalenzklasse (Bereich) Vertreter Grenzwerte erwartetes Ergebnis
ungültig: kleiner als 0,00 -0,50 -0,01 IllegalArgumentException
gültig: 0,00 bis 1,31 1,00 0,00 und 1,31 keine Exception
ungültig: größer als 1,31 3,00 1,32 KostenbeitragZuHochException

Andere Vertreter sind richtig, wenn sie im Inneren der Klasse liegen. Die Grenzwerte liegen jeweils einen Cent auseinander, weil Beträge in ganzen Cent angegeben werden.

Aufgabe 1.2: Die passende Zusicherung wählen (Basiskompetenz)

Ordne jeder Prüfung die passende JUnit-Zusicherung zu: assertEquals, assertTrue, assertFalse, assertThrows oder assertDoesNotThrow. Notiere die Zeile vollständig. (5 Punkte)

  1. Lina (geboren am 23.11.2008) ist am 22.11.2026 minderjährig.
  2. Ein Gesuch kann nicht von VERMITTELT nach OFFEN wechseln.
  3. Der Höchstbetrag für 6,2 km beträgt 0,62 Euro.
  4. Ein Kostenbeitrag von 0,00 Euro wird für 6,2 km angenommen.
  5. Ein Kostenbeitrag von -0,01 Euro wird mit einer IllegalArgumentException abgelehnt.

Je Zeile 1 Punkt:

assertTrue(Einwilligungsregel.istMinderjaehrig(LocalDate.of(2008, 11, 23), LocalDate.of(2026, 11, 22)));
assertFalse(Gesuchstatus.VERMITTELT.erlaubtWechselZu(Gesuchstatus.OFFEN));
assertEquals(new BigDecimal("0.62"), Kostenregel.hoechstbetrag(new BigDecimal("6.2")));
assertDoesNotThrow(() -> Kostenregel.pruefe(new BigDecimal("0.00"), new BigDecimal("6.2")));
assertThrows(IllegalArgumentException.class,
        () -> Kostenregel.pruefe(new BigDecimal("-0.01"), new BigDecimal("6.2")));

Zu 3: assertEquals vergleicht mit equals(). Das klappt hier, weil hoechstbetrag() immer zwei Nachkommastellen liefert und der erwartete Wert ebenfalls "0.62" lautet; new BigDecimal("0.620") würde scheitern.

Aufgabe 2.1: Linas Einwilligung testen (Kernkompetenz)

Lina ist am 23.11.2008 geboren und hat keine Einwilligung ihrer Eltern hinterlegt. Sie möchte in der Woche ihres 18. Geburtstags mitfahren.

  1. Bestimme die Grenzwerte für das Fahrtdatum und gib an, an welchen Tagen eine Fahrt ohne Einwilligung abgelehnt bzw. erlaubt wird. (3 Punkte)

  2. Schreibe die Testklasse LinaEinwilligungTest mit zwei parametrisierten Tests (@ParameterizedTest, @CsvSource), die Einwilligungsregel.pruefe() für je zwei Fahrtdaten prüfen. Die Testnamen sollen das Datum enthalten. (5 Punkte)

  1. Grenzwerte sind der 22.11.2026 (letzter Tag mit 17 Jahren) und der 23.11.2026 (Geburtstag, ab dann volljährig). Fahrten bis einschließlich 22.11.2026 werden ohne Einwilligung abgelehnt, ab 23.11.2026 sind sie erlaubt. Je richtiger Grenzwert 1 Punkt, Zuordnung 1 Punkt.

  2. Je Test 2 Punkte, Testnamen mit Datum 1 Punkt:

package de.dbh.mitfahrlabor.domain;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.time.LocalDate;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

class LinaEinwilligungTest {

    private static final LocalDate LINA_GEBURTSTAG = LocalDate.of(2008, 11, 23);

    @ParameterizedTest(name = "Fahrt am {0} ohne Einwilligung wird abgelehnt")
    @CsvSource({ "2026-11-20", "2026-11-22" })
    void abgelehnt(LocalDate fahrtdatum) {
        assertThrows(EinwilligungFehltException.class,
                () -> Einwilligungsregel.pruefe(LINA_GEBURTSTAG, null, fahrtdatum));
    }

    @ParameterizedTest(name = "Fahrt am {0} ohne Einwilligung ist erlaubt")
    @CsvSource({ "2026-11-23", "2026-11-24" })
    void erlaubt(LocalDate fahrtdatum) {
        assertDoesNotThrow(() -> Einwilligungsregel.pruefe(LINA_GEBURTSTAG, null, fahrtdatum));
    }
}

JUnit wandelt die Texte 2026-11-20 usw. selbst in LocalDate um. Wichtig sind die beiden Grenzwerte 22.11. und 23.11.; die anderen Daten dürfen abweichen, solange sie auf der richtigen Seite liegen.

Aufgabe 2.2: Die Zeitfensterregel für Gesuche (Kernkompetenz)

Ein Gesuch nennt ein Zeitfenster, in dem die Mitfahrt beginnen soll, etwa von 06:45 bis 07:15 Uhr. Das Projektteam ergänzt eine neue Fachregel: Der Beginn muss vor dem Ende liegen, und das Zeitfenster darf höchstens 90 Minuten lang sein.

  1. Schreibe die Exception ZeitfensterUngueltigException, die sich in die Hierarchie aus dem Lehrartikel einfügt, und die Klasse Zeitfensterregel mit der Methode public static void pruefe(LocalTime von, LocalTime bis). Nutze Duration für die Länge. (5 Punkte)

  2. Bestimme die Grenzwerte und schreibe einen Test ZeitfensterregelTest mit je einem parametrisierten Test für gültige und ungültige Zeitfenster. (5 Punkte)

  1. Die Regel ist eine Fachregel, also erbt die Exception von FachregelVerletztException (1 Punkt). Zeitfensterregel mit privatem Konstruktor, Konstante und beiden Prüfungen (4 Punkte):
package de.dbh.mitfahrlabor.domain;

public class ZeitfensterUngueltigException extends FachregelVerletztException {

    public ZeitfensterUngueltigException(String meldung) {
        super(meldung);
    }
}
package de.dbh.mitfahrlabor.domain;

import java.time.Duration;
import java.time.LocalTime;

public final class Zeitfensterregel {

    public static final Duration HOECHSTENS = Duration.ofMinutes(90);

    private Zeitfensterregel() {
    }

    public static void pruefe(LocalTime von, LocalTime bis) {
        if (!von.isBefore(bis)) {
            throw new ZeitfensterUngueltigException("Das Zeitfenster muss vor seinem Ende beginnen.");
        }
        if (Duration.between(von, bis).compareTo(HOECHSTENS) > 0) {
            throw new ZeitfensterUngueltigException("Das Zeitfenster darf höchstens 90 Minuten lang sein.");
        }
    }
}
  1. Grenzwerte (2 Punkte): Beginn gleich Ende (ungültig) und eine Minute später (gültig); Länge 90 Minuten (gültig) und 91 Minuten (ungültig). Dazu ein Vertreter je Klasse, etwa 06:45–07:15 (gültig) und ein vertauschtes Fenster 07:15–06:45 (ungültig). Test (3 Punkte):
package de.dbh.mitfahrlabor.domain;

import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.time.LocalTime;

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;

class ZeitfensterregelTest {

    @ParameterizedTest(name = "{0} bis {1} ist gültig")
    @CsvSource({ "06:45, 07:15", "06:45, 06:46", "06:00, 07:30" })
    void gueltig(LocalTime von, LocalTime bis) {
        assertDoesNotThrow(() -> Zeitfensterregel.pruefe(von, bis));
    }

    @ParameterizedTest(name = "{0} bis {1} ist ungültig")
    @CsvSource({ "07:15, 06:45", "06:45, 06:45", "06:00, 07:31" })
    void ungueltig(LocalTime von, LocalTime bis) {
        assertThrows(ZeitfensterUngueltigException.class, () -> Zeitfensterregel.pruefe(von, bis));
    }
}

Zeitfenster über Mitternacht kommen bei Schulfahrten nicht vor; die Regel lehnt sie ab, weil 23:30 nicht vor 00:15 liegt.

Aufgabe 3.1: Statuswechsel eines Gesuchs vollständig testen (Profilkompetenz)

Ein Gesuch ist zunächst OFFEN. Es wird VERMITTELT, wenn eine Buchung zustande kommt, ZURUECKGEZOGEN, wenn die Person es zurücknimmt, und ABGELAUFEN, wenn das Fahrtdatum verstrichen ist. Aus den drei letzten Zuständen gibt es keinen Weg mehr zurück.

  1. Setze die Aufzählung Gesuchstatus mit der Methode erlaubtWechselZu(Gesuchstatus ziel) als switch-Ausdruck um. (4 Punkte)

  2. Wie viele Testfälle braucht ein vollständiger Zustandsübergangstest, wie viele davon sind erlaubte Wechsel? Schreibe einen Test GesuchstatusTest, der alle Kombinationen abdeckt. Nutze für die Endzustände @EnumSource. (6 Punkte)

  1. Aufzählung 1 Punkt, switch-Ausdruck mit allen Fällen 3 Punkte:
package de.dbh.mitfahrlabor.domain;

public enum Gesuchstatus {
    OFFEN, VERMITTELT, ZURUECKGEZOGEN, ABGELAUFEN;

    public boolean erlaubtWechselZu(Gesuchstatus ziel) {
        return switch (this) {
            case OFFEN -> ziel == VERMITTELT || ziel == ZURUECKGEZOGEN || ziel == ABGELAUFEN;
            case VERMITTELT, ZURUECKGEZOGEN, ABGELAUFEN -> false;
        };
    }
}

Weil der switch-Ausdruck alle Konstanten abdecken muss, meldet der Compiler einen Fehler, sobald jemand einen fünften Status ergänzt und ihn hier vergisst.

  1. Vier Zustände ergeben 4 · 4 = 16 Kombinationen, davon drei erlaubte Wechsel (2 Punkte). Test (4 Punkte):
package de.dbh.mitfahrlabor.domain;

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

import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.EnumSource;

class GesuchstatusTest {

    @ParameterizedTest(name = "OFFEN -> {0}: erlaubt = {1}")
    @CsvSource({ "OFFEN, false", "VERMITTELT, true", "ZURUECKGEZOGEN, true", "ABGELAUFEN, true" })
    void ausOffen(Gesuchstatus ziel, boolean erwartet) {
        assertEquals(erwartet, Gesuchstatus.OFFEN.erlaubtWechselZu(ziel));
    }

    @ParameterizedTest(name = "Endzustand {0} erlaubt keinen Wechsel")
    @EnumSource(value = Gesuchstatus.class, names = { "VERMITTELT", "ZURUECKGEZOGEN", "ABGELAUFEN" })
    void endzustaende(Gesuchstatus von) {
        for (Gesuchstatus ziel : Gesuchstatus.values()) {
            assertEquals(false, von.erlaubtWechselZu(ziel));
        }
    }
}

Der erste Test prüft vier Kombinationen, der zweite in drei Läufen je vier, zusammen 16. Eine Lösung, die alle 16 Zeilen in einer @CsvSource aufzählt wie im Lehrartikel, ist ebenso richtig.

Aufgabe 3.2: Ein Fehler, der nur an einer Stelle auftritt (Profilkompetenz)

Für einen neuen Haltepunkt 2,3 km vor der Schule hat ein Teammitglied die Kostenregel zum Ausprobieren mit double nachgebaut:

static boolean erlaubt(double beitrag, double entfernungKm) {
    return beitrag <= entfernungKm * 0.10;
}

Ein Test schlägt fehl: erlaubt(0.23, 2.3) liefert false, obwohl 0,23 Euro genau dem Höchstbetrag entsprechen. Für 6,2 km und 0,62 Euro liefert dieselbe Methode true.

  1. Erkläre, warum der Fehler auftritt und warum er nur bei manchen Entfernungen sichtbar wird. (3 Punkte)

  2. Beschreibe, wie die Kostenregel aus dem Lehrartikel den Fehler vermeidet, und welcher Test ihn gefunden hat. (3 Punkte)

  1. double speichert Zahlen im Zweiersystem. Dezimalbrüche wie 2,3 oder 0,10 sind dort nicht exakt darstellbar, sondern nur gerundet (1 Punkt). Das Produkt ergibt deshalb 0.22999999999999998, und 0,23 ist knapp größer (1 Punkt). Ob der Rundungsrest nach oben oder unten fällt, hängt von den Zahlen ab; bei 6,2 · 0,10 liegt das Ergebnis zufällig bei oder über 0,62, deshalb fällt der Fehler dort nicht auf (1 Punkt).

  2. Die Kostenregel rechnet mit BigDecimal, das im Zehnersystem exakt rechnet. Die Werte entstehen aus Texten (new BigDecimal("2.3")), das Ergebnis wird mit setScale(2, RoundingMode.DOWN) auf ganze Cent abgerundet, und verglichen wird mit compareTo() (2 Punkte). Gefunden hat den Fehler ein Grenzwerttest: Der Beitrag liegt genau auf dem Höchstbetrag, der oberen Grenze der gültigen Klasse. Ein Vertreter aus der Mitte der Klasse hätte ihn nicht gezeigt (1 Punkt).


Hinweis zur Nachnutzung als Open Educational Resource (OER)

Dieser Artikel und seine Texte, Bilder, Grafiken, Code und sonstiger Inhalt sind - sofern nicht anders angegeben - lizenziert unter CC BY 4.0. Nennung gemäß TULLU-Regel bitte wie folgt: “Übungsaufgaben zur Fachlogik und zum Komponententest: Regeln für Mitfahrgesuche” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/spring-boot-fachlogik-komponententest-uebungsaufgaben veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/java-springboot/Backend. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 11.11.2026.

[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]

Kommentare gerne per Mastodon, Verbesserungsvorschläge per gitlab issue (siehe oben). Beitrag teilen: