Übungsaufgaben zum Ansichtsmodell: die Mitfahrgesuche im Zeitfenster
https://bildung.social/@oerinformatik/
https://oer-informatik.de/flurmonitor-ansichtsmodell-uebungsaufgaben
Ansichtsmodell, View-Model, record, Clock, Zeitfenster, Sortierung, Komponententest, parametrisierter Test, Grenzwerte, Mitfahrgesuche, Übungsaufgaben, Prüfungsvorbereitung
tl/dr; (Bearbeitungszeit insgesamt 57 min, mehrere Aufgaben): Die Seite für die Mitfahrgesuche bekommt ein eigenes Ansichtsmodell. In diesen Übungen ordnest Du Aufgaben der Service-Schicht, der Aufbereitung, dem Ansichtsmodell und dem Template zu, sagst voraus, welche Fahrten der Monitor am Freitagnachmittag zeigt, baust die Gesuche-Ansicht mit Zeitfenster und Sortierung, prüfst sie mit parametrisierten Tests und einer festen Uhr und findest einen Fehler, der nur zu bestimmten Uhrzeiten auftritt [AI-Assisted]. (Zuletzt geändert am 11.11.2026)
Dieser Artikel ist Teil einer Artikelreihe über den Flurmonitor mit Thymeleaf: Projektübersicht / Scrum-Projektstart / Thymeleaf einbinden / Templates / Ansichtsmodell / Übungsaufgaben zum Ansichtsmodell / Integrationstest der Web-Schicht / Konfiguration und Formatierung / Aktualisierung / Hinweise mit Formularen / Robustheit und Betrieb / GUI-Test mit Selenium / Akzeptanztest / Change Request Keycloak
Diese Übungsaufgaben gehören zum Artikel Ein Ansichtsmodell für den Flurmonitor: Zeitfenster, Sortierung und Komponententests mit fester Uhr. Alle Aufgaben spielen im Projektteam der Mitfahr-App Mitfahrlabor und setzen den Übungsstrang zu den Mitfahrgesuchen fort: Die Seite /flurmonitor/gesuche aus Story MON-13a zeigt bisher alle Gesuche, die der GesuchService liefert, in der Reihenfolge, in der er sie liefert. Jetzt bekommt sie dasselbe Zeitfenster wie die Abfahrten. Ausgangspunkt ist das Projekt im Ordner src/a04-ansichtsmodell des Backend-Repositorys zusammen mit Deiner Lösung der Übungsaufgaben zu Thymeleaf-Templates.
Hinweise zur Bearbeitung: Bis zur Teil-1-Prüfung der IT-Berufe solltest Du die Basiskompetenz beherrschen, also Aufgabe 1.1. 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) keine Probleme bereiten. Notiere Deine Antworten zuerst vollständig, bevor Du die Beispiellösung aufklappst. Code-Aufgaben prüfst Du am besten im Projekt: Lass die Tests laufen und ruf die Seite im Browser auf. Bei Begründungen und Bezeichnern sind andere fachlich richtige Lösungen 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 „Ansichtsmodell: die
Mitfahrgesuche im Zeitfenster“.
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/flurmonitor-ansichtsmodell
- https://oer-informatik.de/szenario-mitfahrlabor
- Stilbeispiel: https://oer-informatik.de/flurmonitor-ansichtsmodell-uebungsaufgaben
Die Aufgaben sollen Inhalte zu diesen Schlagwörtern abdecken:
- Ansichtsmodell
- View-Model
- record
- Clock
- Zeitfenster
- Sortierung
- Komponententest
- parametrisierter Test
- Grenzwerte
- Mitfahrgesuche
Kompetenzstufe: Kernkompetenz (alternativ: Basiskompetenz, 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: Verantwortungen zuordnen und das Zeitfenster vorhersagen (Basiskompetenz)
(ca. 14 min Bearbeitungszeit)
Im Review von Sprint 1 fragt Andrea Wolf, wer im Code eigentlich dafür sorgt, dass auf dem Monitor keine Namen erscheinen. Oksana Melnyk nimmt das zum Anlass, die Verantwortungen rund um die Seite /flurmonitor aufzuschreiben:
| Nr. | Aufgabe | zuständig |
|---|---|---|
| 1 | offene Angebote ab einem Tag aus der Datenbank laden | |
| 2 | die Platzzahl in einem gelben Badge darstellen | |
| 3 | festlegen, ab welcher Zahl freier Plätze eine Fahrt hervorgehoben wird | |
| 4 | entscheiden, ob eine Abfahrt vor dem Ende des nächsten Schultags liegt | |
| 5 | die aktuelle Uhrzeit in deutscher Zeit bestimmen | |
| 6 | die früheste Abfahrt je Richtung nach oben sortieren | |
| 7 | statt einer leeren Tabelle den Satz „Gerade keine offenen Fahrten – schau in die App.“ zeigen | |
| 8 | aus einem Angebot nur Datum, Zeit, Ort, Treffpunkt und Plätze übernehmen |
a) Ergänze für jede Zeile, wer zuständig ist: MonitorService, MonitorAufbereitung (einschließlich Zeitfenster und der Clock), das Ansichtsmodell (MonitorZeile, MonitorAnsicht) oder das Template. (8 Punkte)
| Nr. | Aufgabe | zuständig |
|---|---|---|
| 1 | offene Angebote ab einem Tag aus der Datenbank laden | MonitorService (über das AngebotRepository) |
| 2 | die Platzzahl in einem gelben Badge darstellen | Template (th:classappend im Fragment) |
| 3 | festlegen, ab welcher Zahl freier Plätze eine Fahrt hervorgehoben wird | Ansichtsmodell (MonitorZeile.wenigePlaetze()) |
| 4 | entscheiden, ob eine Abfahrt vor dem Ende des nächsten Schultags liegt | MonitorAufbereitung mit Zeitfenster |
| 5 | die aktuelle Uhrzeit in deutscher Zeit bestimmen | MonitorAufbereitung über die injizierte Clock |
| 6 | die früheste Abfahrt je Richtung nach oben sortieren | MonitorAufbereitung (Comparator) |
| 7 | statt einer leeren Tabelle den Satz zeigen | Template (th:if, #{flurmonitor.keine-fahrten}) |
| 8 | aus einem Angebot nur die angezeigten Werte übernehmen | Ansichtsmodell (MonitorZeile.aus(Angebot)) |
Bewertung: je Zeile 1 Punkt. Bei Zeile 5 ist auch „UhrKonfiguration bzw. Clock-Bean“ richtig, bei Zeile 8 auch „MonitorAufbereitung“, wenn begründet wird, dass sie die Abbildung aufruft.
b) Ordne die Typen Angebot, MonitorEintrag und MonitorZeile jeweils einer Art von Datentyp zu (Entity, DTO, Ansichtsmodell) und nenne zu jedem ein Merkmal, an dem Du die Zuordnung erkennst. (3 Punkte)
Angebotist eine Entity: Sie trägt@Entity, wird von JPA gespeichert, enthält die Fahrerin oder den Fahrer und Fachregeln wiebelegePlatz()(1 Punkt).MonitorEintragist ein DTO: Er wird aus dem API-Vertragopenapi.yamlgeneriert und überGET /monitor/eintraegeals JSON an alle Clients übertragen (1 Punkt).MonitorZeileist ein Ansichtsmodell: Sie enthält nur die Werte, die die Tabelle auf der Seite/flurmonitorzeigt, und eine kleine Darstellungsregel (wenigePlaetze()), aber keine Namen (1 Punkt).
c) Die Uhr steht auf Freitag, 23.10.2026, 15:00 Uhr deutscher Zeit. Der MonitorService liefert diese offenen Angebote in Richtung „nach Hause“:
| Angebot | Datum | Abfahrt | Ort | freie Plätze |
|---|---|---|---|---|
| A | Fr 23.10.2026 | 14:30 | Halle (Westf.) | 2 |
| B | Mo 26.10.2026 | 15:30 | Bielefeld | 1 |
| C | Fr 23.10.2026 | 15:00 | Herford | 3 |
| D | Sa 24.10.2026 | 11:00 | Gütersloh | 2 |
| E | Di 27.10.2026 | 13:15 | Bad Salzuflen | 4 |
Gib an, welche Angebote in der Liste nachHause der MonitorAnsicht stehen, in welcher Reihenfolge, und welches davon hervorgehoben wird. Begründe kurz, warum die übrigen fehlen. (4 Punkte)
Reihenfolge: C (Fr 15:00), D (Sa 11:00), B (Mo 15:30). Hervorgehoben wird B, weil nur noch ein Platz frei ist.
A fehlt, weil die Abfahrt um 14:30 Uhr schon vorbei ist. C fährt genau jetzt ab und gehört noch dazu, weil der Anfang des Zeitfensters einschließlich ist.
Der nächste Schultag nach Freitag ist Montag; das Fenster endet vor Dienstag, 00:00 Uhr. E am Dienstag liegt deshalb außerhalb. D am Samstag liegt innerhalb, denn das Fenster umfasst alle Tage bis zum Ende des nächsten Schultags, auch das Wochenende.
Bewertung: richtige Auswahl C, D, B 2 Punkte (je Fehler 1 Punkt Abzug), Reihenfolge 1 Punkt, Hervorhebung von B 1 Punkt. Im Projekt nachgeprüft mit Zeitfenster.ab(LocalDateTime.of(2026, 10, 23, 15, 0)).
Aufgabe 2.1: Ein Ansichtsmodell für die Mitfahrgesuche (Kernkompetenz)
(ca. 13 min Bearbeitungszeit)
Für MON-13a hat der Product Owner Thomas Becker dieselben Regeln festgelegt wie für die Abfahrten: Ein Gesuch steht auf dem Monitor, solange sein spätester Abfahrtszeitpunkt (Datum und spaetestens) nicht vorbei ist, und nur bis zum Ende des nächsten Schultags. Das früheste Gesuch steht oben, sortiert nach Datum und dann nach fruehestens. Ausgangspunkt sind der Datentyp GesuchZeile und der GesuchService aus den letzten Übungen:
/** Ein offenes Mitfahrgesuch für den Flurmonitor, ohne Namen und ohne Pseudonym. */
public record GesuchZeile(String ort, String treffpunkt, String richtung, LocalDate datum,
LocalTime fruehestens, LocalTime spaetestens, String notiz) {
}Der GesuchService liefert mit offeneGesuche() eine List<GesuchZeile>, bisher mit zwei Beispielgesuchen für Montag, 16.11.2026. Der MitfahrgesucheController legt bisher standort, anzahlGesuche und gesuche ins Model.
a) Schreib im Paket de.dbh.mitfahrlabor.web.monitor einen Record GesucheAnsicht mit den Komponenten standort und gesuche. Die Liste soll nach dem Erzeugen nicht mehr veränderbar sein, und eine Methode anzahl() soll die Zahl der Gesuche liefern. (3 Punkte)
package de.dbh.mitfahrlabor.web.monitor;
import java.util.List;
import de.dbh.mitfahrlabor.service.GesuchZeile;
/** Ansichtsmodell der Seite /flurmonitor/gesuche: Standort und die sichtbaren Gesuche, früheste zuerst. */
public record GesucheAnsicht(String standort, List<GesuchZeile> gesuche) {
public GesucheAnsicht {
gesuche = List.copyOf(gesuche);
}
public int anzahl() {
return gesuche.size();
}
}Bewertung: Record mit beiden Komponenten 1 Punkt, kompakter Konstruktor mit List.copyOf (oder Collections.unmodifiableList auf einer Kopie) 1 Punkt, anzahl() aus der Liste berechnet 1 Punkt.
b) Schreib eine Komponente GesucheAufbereitung mit einer Methode GesucheAnsicht ansicht(String standort), die die Regeln des Product Owners umsetzt. Nutze das Zeitfenster aus dem Lehrartikel und die Clock-Bean. (6 Punkte)
package de.dbh.mitfahrlabor.web.monitor;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.Comparator;
import org.springframework.stereotype.Component;
import de.dbh.mitfahrlabor.service.GesuchService;
import de.dbh.mitfahrlabor.service.GesuchZeile;
/** Bereitet die offenen Gesuche für den Flurmonitor auf (MON-13a). */
@Component
public class GesucheAufbereitung {
private static final Comparator<GesuchZeile> FRUEHESTE_ZUERST = Comparator.comparing(GesuchZeile::datum)
.thenComparing(GesuchZeile::fruehestens);
private final GesuchService gesuchService;
private final Clock uhr;
public GesucheAufbereitung(GesuchService gesuchService, Clock uhr) {
this.gesuchService = gesuchService;
this.uhr = uhr;
}
public GesucheAnsicht ansicht(String standort) {
Zeitfenster fenster = Zeitfenster.ab(LocalDateTime.now(uhr));
var sichtbar = gesuchService.offeneGesuche().stream()
.filter(g -> fenster.enthaelt(g.datum(), g.spaetestens()))
.sorted(FRUEHESTE_ZUERST)
.toList();
return new GesucheAnsicht(standort, sichtbar);
}
}Bewertung: @Component und beide Abhängigkeiten per Konstruktor 1 Punkt, Zeitpunkt aus LocalDateTime.now(uhr) (nicht ohne Uhr) 1 Punkt, Zeitfenster mit enthaelt(datum, spaetestens) 2 Punkte, Sortierung nach Datum und dann fruehestens 1 Punkt, Rückgabe einer GesucheAnsicht 1 Punkt. Im Projekt geprüft: Mit den Tests aus Aufgabe 2.2 ist die Klasse grün, die Abdeckung liegt bei 100 Prozent.
c) Stelle den MitfahrgesucheController und das Template flurmonitor-gesuche.html so um, dass nur noch das Model-Attribut ansicht verwendet wird. Gib die geänderte Methode und die geänderten Template-Stellen an. (3 Punkte)
private final GesucheAufbereitung aufbereitung;
public MitfahrgesucheController(GesucheAufbereitung aufbereitung) {
this.aufbereitung = aufbereitung;
}
@GetMapping("/flurmonitor/gesuche")
public String anzeigen(Model model) {
model.addAttribute("ansicht", aufbereitung.ansicht("Foyer Gebäude A"));
return "flurmonitor-gesuche";
}<p class="lead">Flurmonitor im <span th:text="${ansicht.standort}">Eingang A</span></p>
<p>Offene Gesuche: <strong th:text="${ansicht.anzahl()}">2</strong></p>
<!-- … -->
<p th:if="${ansicht.gesuche.isEmpty()}" class="fs-4" th:text="#{gesuche.keine}">Gerade keine offenen Gesuche.</p>
<div th:unless="${ansicht.gesuche.isEmpty()}" class="table-responsive">
<!-- … -->
<tr th:each="gesuch : ${ansicht.gesuche}" th:object="${gesuch}">Bewertung: Controller mit GesucheAufbereitung statt GesuchService und einem Attribut ansicht 1 Punkt, Standort und Anzahl im Template über ansicht 1 Punkt, Bedingung und Schleife über ansicht.gesuche 1 Punkt. Die Zellen der Zeile bleiben unverändert, weil GesuchZeile weiter verwendet wird. Im Projekt geprüft: Ein Smoke-Test mit fester Uhr auf Montag, 16.11.2026, 07:30 Uhr findet „Offene Gesuche: 2“ und beide Beispielgesuche.
d) Begründe, warum die Aufbereitung enthaelt mit spaetestens aufruft und nicht mit fruehestens. (2 Punkte)
Ein Gesuch ist so lange interessant, wie jemand die suchende Person noch mitnehmen kann, also bis zum Ende ihres Zeitfensters (1 Punkt). Mit fruehestens verschwände ein Gesuch „07:00 bis 08:00“ schon um 07:01 Uhr vom Monitor, obwohl eine Fahrt um 07:45 Uhr noch passen würde (1 Punkt).
Aufgabe 2.2: Die Gesuche-Ansicht mit fester Uhr testen (Kernkompetenz)
(ca. 16 min Bearbeitungszeit)
Jonas Wolff soll die GesucheAufbereitung aus Aufgabe 2.1 mit einem Komponententest absichern. Die Uhr des Tests steht auf Montag, 16.11.2026, 07:30 Uhr deutscher Zeit. Er überlegt sich zuerst Testfälle mit je einem Gesuch:
| Fall | Datum | fruehestens | spaetestens | sichtbar? | Begründung |
|---|---|---|---|---|---|
| 1 | Mo 16.11.2026 | 07:00 | 08:00 | ||
| 2 | Mo 16.11.2026 | 06:30 | 07:30 | ||
| 3 | Mo 16.11.2026 | 06:30 | 07:29 | ||
| 4 | Di 17.11.2026 | 15:30 | 23:59 | ||
| 5 | Mi 18.11.2026 | 00:00 | 00:30 | ||
| 6 | Fr 13.11.2026 | 15:30 | 16:30 |
a) Ergänze die Tabelle. (6 Punkte)
| Fall | Datum | fruehestens | spaetestens | sichtbar? | Begründung |
|---|---|---|---|---|---|
| 1 | Mo 16.11.2026 | 07:00 | 08:00 | ja | Zeitfenster des Gesuchs läuft gerade |
| 2 | Mo 16.11.2026 | 06:30 | 07:30 | ja | Grenzwert: spätester Zeitpunkt ist genau jetzt, Anfang des Fensters einschließlich |
| 3 | Mo 16.11.2026 | 06:30 | 07:29 | nein | Grenzwert: eine Minute vorbei |
| 4 | Di 17.11.2026 | 15:30 | 23:59 | ja | letzte Minute des nächsten Schultags |
| 5 | Mi 18.11.2026 | 00:00 | 00:30 | nein | Grenzwert: Mitternacht nach dem nächsten Schultag, Ende ausschließlich |
| 6 | Fr 13.11.2026 | 15:30 | 16:30 | nein | liegt in der Vergangenheit, obwohl 16:30 Uhr nach 07:30 Uhr liegt |
Bewertung: je Zeile 1 Punkt für die richtige Entscheidung mit passender Begründung.
b) Gib den Instant an, den Jonas an Clock.fixed übergeben muss, und begründe ihn. (2 Punkte)
Clock.fixed(Instant.parse("2026-11-16T06:30:00Z"), ZoneId.of("Europe/Berlin")) (1 Punkt). Ein Instant wird in UTC angegeben; am 16.11.2026 gilt in Deutschland die Normalzeit UTC+1, 07:30 Uhr deutscher Zeit sind also 06:30 Uhr UTC. Die Zone Europe/Berlin sorgt dafür, dass LocalDateTime.now(uhr) wieder 07:30 Uhr liefert (1 Punkt).
c) Schreib die Testklasse GesucheAufbereitungTest mit einem parametrisierten Test, der die sechs Fälle aus a) prüft. Den GesuchService ersetzt ein Mock. (6 Punkte)
@ExtendWith(MockitoExtension.class)
class GesucheAufbereitungTest {
/** Montag, 16.11.2026, 07:30 Uhr in Deutschland; im Winter gilt UTC+1, also 06:30 Uhr UTC. */
private static final Clock MONTAG_0730 = Clock.fixed(Instant.parse("2026-11-16T06:30:00Z"),
ZoneId.of("Europe/Berlin"));
@Mock
private GesuchService gesuchService;
@ParameterizedTest(name = "Gesuch {0} {1}–{2} -> sichtbar: {3}")
@CsvSource({
"2026-11-16, 07:00, 08:00, true", // läuft gerade
"2026-11-16, 06:30, 07:30, true", // Grenzwert: spätestens genau jetzt
"2026-11-16, 06:30, 07:29, false", // eine Minute vorbei
"2026-11-17, 15:30, 23:59, true", // letzte Minute des nächsten Schultags
"2026-11-18, 00:00, 00:30, false", // Mitternacht danach
"2026-11-13, 15:30, 16:30, false" // Freitag davor
})
void zeigtGesucheBisZumEndeDesNaechstenSchultags(LocalDate datum, LocalTime fruehestens, LocalTime spaetestens,
boolean sichtbar) {
given(gesuchService.offeneGesuche()).willReturn(List.of(
new GesuchZeile("Gütersloh", "Hauptbahnhof, ZOB Steig 3", "zur Schule", datum, fruehestens,
spaetestens, "")));
var ansicht = new GesucheAufbereitung(gesuchService, MONTAG_0730).ansicht("Foyer Gebäude A");
assertThat(ansicht.gesuche()).hasSize(sichtbar ? 1 : 0);
}
}Bewertung: feste Uhr mit richtigem Instant und Zone 1 Punkt, Mock des Service mit @Mock und MockitoExtension 1 Punkt, @ParameterizedTest mit @CsvSource und allen sechs Fällen 2 Punkte, Aufbereitung mit new ohne Spring-Kontext 1 Punkt, Prüfung auf ein bzw. kein Gesuch 1 Punkt. Imports: org.junit.jupiter.params.*, org.mockito.*, statisch assertThat und given. Im Projekt ausgeführt: sechs grüne Tests.
d) Ergänze einen Test, der mit drei unsortierten Gesuchen prüft, dass das früheste Gesuch oben steht und anzahl() stimmt. (3 Punkte)
@Test
void sortiertNachDatumUndFruehestemZeitpunkt() {
given(gesuchService.offeneGesuche()).willReturn(List.of(
gesuch("Herford", "2026-11-17", "07:00"),
gesuch("Bielefeld", "2026-11-16", "15:30"),
gesuch("Gütersloh", "2026-11-16", "07:00")));
var ansicht = new GesucheAufbereitung(gesuchService, MONTAG_0730).ansicht("Foyer Gebäude A");
assertThat(ansicht.gesuche()).extracting(GesuchZeile::ort)
.containsExactly("Gütersloh", "Bielefeld", "Herford");
assertThat(ansicht.anzahl()).isEqualTo(3);
}
private static GesuchZeile gesuch(String ort, String datum, String fruehestens) {
var ab = LocalTime.parse(fruehestens);
return new GesuchZeile(ort, "Treffpunkt", "zur Schule", LocalDate.parse(datum), ab, ab.plusHours(1), "");
}Bewertung: Testdaten unsortiert, mit gleichem Datum und verschiedener Uhrzeit 1 Punkt, Prüfung der Reihenfolge mit containsExactly 1 Punkt, Prüfung von anzahl() 1 Punkt. Alle Gesuche müssen im Zeitfenster liegen, sonst prüft der Test den Filter statt der Sortierung.
Aufgabe 3.1: Ein Fehler, der nur zu bestimmten Uhrzeiten auftritt (Profilkompetenz)
(ca. 14 min Bearbeitungszeit)
Bevor Jonas Wolff die Lösung aus Aufgabe 2.1 kannte, hatte er eine eigene Fassung der Aufbereitung in einen Merge Request gestellt. Der folgende Code ist absichtlich fehlerhaft:
public GesucheAnsicht ansicht(String standort) {
LocalDate heute = LocalDate.now(uhr); // Zeile 1
LocalTime jetzt = LocalTime.now(); // Zeile 2
LocalDate letzterTag = Zeitfenster.naechsterSchultag(heute); // Zeile 3
var sichtbar = gesuchService.offeneGesuche().stream()
.filter(g -> !g.datum().isAfter(letzterTag)) // Zeile 4
.filter(g -> g.spaetestens().isAfter(jetzt)) // Zeile 5
.sorted(Comparator.comparing(GesuchZeile::datum)
.thenComparing(GesuchZeile::fruehestens))
.toList();
return new GesucheAnsicht(standort, sichtbar);
}Im Review und auf dem Testmonitor fallen drei Dinge auf:
Symptom A: Am Montag um 15:00 Uhr fehlt auf dem Monitor das Gesuch für Dienstag, 07:00 bis 08:00 Uhr. Morgens um 07:00 Uhr war es noch zu sehen.
Symptom B: Am Montag um 07:30 Uhr steht noch ein Gesuch vom vergangenen Freitag, 15:30 bis 16:30 Uhr, auf dem Monitor.
Symptom C: Der parametrisierte Test aus Aufgabe 2.2 liefert bei Jonas morgens andere Ergebnisse als nachmittags, und in der Pipeline, die in UTC läuft, wieder andere.
a) Ordne jedes Symptom der Zeile zu, die es verursacht, und erkläre die Ursache. (6 Punkte)
Symptom A, Zeile 5: Die Bedingung vergleicht nur die Uhrzeit
spaetestensmit der aktuellen Uhrzeit und ignoriert das Datum. 08:00 Uhr liegt vor 15:00 Uhr, also fliegt das Gesuch für morgen heraus, obwohl es in der Zukunft liegt (2 Punkte).Symptom B, Zeilen 4 und 5: Zeile 4 prüft nur die Obergrenze des Datums, eine Untergrenze fehlt. Zeile 5 lässt das Freitagsgesuch durch, weil 16:30 Uhr nach 07:30 Uhr liegt; dass der Freitag vorbei ist, prüft niemand (2 Punkte).
Symptom C, Zeile 2:
LocalTime.now()liest die Systemuhr in der Standardzeitzone der JVM statt der injiziertenClock. Die feste Uhr des Tests wirkt nur auf das Datum aus Zeile 1, die Uhrzeit kommt vom Rechner. Je nach Tageszeit und Zeitzone des Rechners werden andere Testfälle rot; in UTC ist es eine Stunde früher als in Deutschland (2 Punkte).
Im Projekt nachgeprüft: Mit diesem Code und der Testklasse aus Aufgabe 2.2 waren bei einer Rechnerzeit von 03:41 Uhr zwei Fälle rot (Fall 3 und 6), bei 12:41 Uhr vier Fälle (Fälle 1, 2, 6 und der Sortiertest).
b) Korrigiere die Methode so, dass alle drei Symptome verschwinden. (4 Punkte)
public GesucheAnsicht ansicht(String standort) {
Zeitfenster fenster = Zeitfenster.ab(LocalDateTime.now(uhr));
var sichtbar = gesuchService.offeneGesuche().stream()
.filter(g -> fenster.enthaelt(g.datum(), g.spaetestens()))
.sorted(Comparator.comparing(GesuchZeile::datum)
.thenComparing(GesuchZeile::fruehestens))
.toList();
return new GesucheAnsicht(standort, sichtbar);
}Bewertung: Datum und Uhrzeit aus derselben Uhr (LocalDateTime.now(uhr)) 1 Punkt, Datum und Uhrzeit zusammen vergleichen (datum.atTime(spaetestens)) 1 Punkt, Untergrenze „nicht vor jetzt“ einschließlich 1 Punkt, Obergrenze „vor Mitternacht nach dem nächsten Schultag“ 1 Punkt. Eine Lösung ohne Zeitfenster, die dieselben Bedingungen mit LocalDateTime selbst prüft, ist ebenso richtig.
c) Angenommen, Jonas hätte nur Zeile 2 durch LocalTime.now(uhr) ersetzt. Welche Testfälle aus Aufgabe 2.2 decken den verbliebenen Fehler dann zuverlässig auf? Begründe. (3 Punkte)
Mit fester Uhr ist das Ergebnis jetzt bei jedem Lauf gleich (1 Punkt). Fall 6 (Freitag, 16:30 Uhr) wird zuverlässig rot, weil 16:30 Uhr nach 07:30 Uhr liegt und das Datum nicht nach unten begrenzt ist (1 Punkt). Fall 2 (spätestens genau 07:30 Uhr) wird ebenfalls rot, weil isAfter den Zeitpunkt „genau jetzt“ ausschließt, das Zeitfenster ihn aber einschließt (1 Punkt). Im Projekt nachgeprüft: Mit dieser Fassung sind genau diese beiden Fälle rot, unabhängig von Uhrzeit und Zeitzone des Rechners.
d) Nenne zwei Maßnahmen, mit denen das Team solche Fehler künftig früher findet. (2 Punkte)
Je 1 Punkt, zum Beispiel:
eine Regel in der Definition of Done bzw. im Review: im Hauptcode kein
now()ohneClock; automatisch prüfbar mit einer Checkstyle-Regel (z. B.Regexpauf\.now\(\)) oder einem Architekturtest mit ArchUnit,Grenzwerte um Mitternacht, die Vergangenheit und „genau jetzt“ als feste Testfälle in jede Aufbereitung aufnehmen,
die Tests in der Pipeline zusätzlich mit einer anderen Zeitzone laufen lassen (z. B.
TZ=Asia/Tokyo), damit Abhängigkeiten von der Systemzeit sofort auffallen.
Links und weitere Informationen
zum Artikel: Ein Ansichtsmodell für den Flurmonitor: Zeitfenster, Sortierung und Komponententests mit fester Uhr
zum Szenario: Szenario Mitfahrlabor mit Personas und Flurmonitoren
zum Weiterlesen: Übungsaufgaben zu Thymeleaf-Templates mit der Gesuche-Tabelle
als Nachschlagewerk: Java API:
java.time.Clockals Nachschlagewerk: JUnit User Guide: Parameterized Tests
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 zum Ansichtsmodell: die Mitfahrgesuche im Zeitfenster” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/flurmonitor-ansichtsmodell-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]
