Übungsaufgaben zur Build-Qualität und CI-Pipeline: Prüfregeln und Abdeckung für Mitfahrgesuche

https://bildung.social/@oerinformatik/

https://oer-informatik.de/spring-boot-build-qualitaet-ci-uebungsaufgaben

Continuous Integration, GitLab CI, Pipeline, Checkstyle, Spotless, JaCoCo, Testabdeckung, Zweigabdeckung, Grenzwertanalyse, Übungsaufgaben, Prüfungsvorbereitung

tl/dr; (ca. 37 min Lese- und Bearbeitungszeit): Übungen zur Qualitätssicherung im Build des Mitfahr-Backends: Du ordnest Werkzeuge und Pipeline-Begriffe zu, deutest Checkstyle-Meldungen zur Zeitfensterregel der Mitfahrgesuche, liest Anweisungs- und Zweigabdeckung aus einem JaCoCo-Bericht ab, ergänzt eine Regel gegen „magische Zahlen“, passt die GitLab-Pipeline an und zeigst, warum 100 % Abdeckung einen Fehler an der Grenze nicht findet [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 / 05 Build-Qualität und CI / Übungsaufgaben zu 05

Diese Übungsaufgaben gehören zum Artikel Qualität im Build und eine CI-Pipeline: Checkstyle, Spotless, JaCoCo und GitLab CI und setzen den Übungsstrang zu den Mitfahrgesuchen fort. Ausgangspunkt ist das Projekt im Ordner src/05-build-qualitaet-ci des Repositorys. Mehrere Aufgaben nutzen die Zeitfensterregel aus den Übungen zu Artikel 04: Ein Zeitfenster muss vor seinem Ende beginnen und darf höchstens 90 Minuten lang sein, sonst wirft pruefe(von, bis) eine ZeitfensterUngueltigException.

Fahrplan der Artikelreihe im V-Modell; unter der Implementierung ist Artikel 05 „CI-Pipeline prüft jeden Push“ hervorgehoben (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 „Build-Qualität und CI-Pipeline:
Prüfregeln und Abdeckung 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-build-qualitaet-ci
- https://oer-informatik.de/ci-cd-pipeline
- https://oer-informatik.de/codecoverage
- Stilbeispiel: https://oer-informatik.de/spring-boot-build-qualitaet-ci-uebungsaufgaben

Die Aufgaben sollen Inhalte zu diesen Schlagwörtern abdecken:
- Continuous Integration
- GitLab CI
- Pipeline
- Checkstyle
- Spotless
- JaCoCo
- Testabdeckung
- Zweigabdeckung
- Grenzwertanalyse

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: Werkzeuge und Begriffe zuordnen (Basiskompetenz)

  1. Ordne jede Frage dem Werkzeug zu, das sie im Build des Mitfahrlabors beantwortet: Checkstyle, Spotless, JaCoCo oder OpenAPI Generator (openApiValidate). (3 Punkte)
  1. Ist die Datei openapi.yaml ein gültiger Vertrag?
  2. Wird der Konstruktor von ZeitfensterUngueltigException von einem Test aufgerufen?
  3. Ist der Name der Konstanten hoechstens zulässig?
  4. Wurden in ZeitfensterregelTest.java Tabulatoren statt Leerzeichen verwendet?
  5. Welche Zweige des zweiten if in Zeitfensterregel.pruefe() wurden durchlaufen?
  6. Steht nach einem if ein Block mit geschweiften Klammern?
  1. Erläutere die Begriffe Stufe (stage), Job, Artefakt und Cache am Beispiel der Pipeline des Mitfahrlabors in je einem Satz. (4 Punkte)
  1. Je zwei richtige Zuordnungen 1 Punkt:
Nr. Werkzeug
1 OpenAPI Generator (openApiValidate)
2 JaCoCo
3 Checkstyle (Regel ConstantName)
4 Spotless (Checkstyle könnte es mit der Regel FileTabCharacter ebenfalls, im Projekt ist sie nicht eingeschaltet)
5 JaCoCo (Zweigabdeckung)
6 Checkstyle (Regel NeedBraces)
  1. Je Begriff 1 Punkt:
  • Stufe: Abschnitt der Pipeline; die Stufen build, test, quality und package laufen nacheinander, und eine rote Stufe verhindert die folgenden.
  • Job: Aufgabe mit eigenen Befehlen in einem eigenen Container, etwa der Job test, der ./gradlew test ausführt.
  • Artefakt: Ergebnis eines Jobs, das GitLab aufhebt oder auswertet, etwa der JUnit-Testbericht, der JaCoCo-Bericht oder die JAR-Datei aus package.
  • Cache: Dateien, die zwischen Pipeline-Läufen erhalten bleiben, hier die heruntergeladenen Bibliotheken unter .gradle/caches, damit nicht jeder Lauf sie erneut lädt.

Aufgabe 1.2: Checkstyle-Meldungen zur Zeitfensterregel deuten (Basiskompetenz)

Ein Teammitglied hat die Zeitfensterregel für die Gesuche so in das Projekt übernommen:

package de.dbh.mitfahrlabor.domain;

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

public class Zeitfensterregel {

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

    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.");
        }
    }
}

Die Tests laufen, aber die Aufgabe checkstyleMain meldet (Pfade gekürzt):

[ant:checkstyle] [ERROR] …/domain/Zeitfensterregel.java:6:1: Utility classes should not have a public or default constructor. [HideUtilityClassConstructor]
[ant:checkstyle] [ERROR] …/domain/Zeitfensterregel.java:8:34: Name 'hoechstens' must match pattern '^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$'. [ConstantName]
[ant:checkstyle] [ERROR] …/domain/Zeitfensterregel.java:11: Line is longer than 120 characters (found 122). [LineLength]
[ant:checkstyle] [ERROR] …/domain/Zeitfensterregel.java:11:9: 'if' construct must use '{}'s. [NeedBraces]
  1. Erkläre für jede Meldung in einem Satz, was die Regel verlangt. (4 Punkte)

  2. Notiere die korrigierte Klasse. (2 Punkte)

  1. Je Meldung 1 Punkt:
  • HideUtilityClassConstructor (Zeile 6): Eine Klasse mit ausschließlich statischen Methoden soll keinen öffentlichen Konstruktor haben; ohne eigenen Konstruktor erzeugt Java einen öffentlichen Standardkonstruktor.
  • ConstantName (Zeile 8): Konstanten (static final) werden in Großbuchstaben mit Unterstrichen geschrieben.
  • LineLength (Zeile 11): Eine Zeile darf höchstens 120 Zeichen lang sein, diese hat 122.
  • NeedBraces (Zeile 11): Nach if muss ein Block in geschweiften Klammern stehen, auch wenn er nur eine Anweisung enthält.
  1. Die beiden Meldungen zu Zeile 11 verschwinden zusammen, sobald das throw in einem eigenen Block steht (2 Punkte):
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() {
        // nur statische Methoden
    }

    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.");
        }
    }
}

Das final an der Klasse ist keine Forderung von Checkstyle, verhindert aber Unterklassen, die den privaten Konstruktor ohnehin nicht nutzen könnten.

Aufgabe 2.1: Abdeckung der Zeitfensterregel deuten (Kernkompetenz)

Zur korrigierten Zeitfensterregel aus Aufgabe 1.2 gibt es zunächst nur diesen Test:

class ZeitfensterregelTest {

    @Test
    void gueltigesZeitfenster() {
        assertDoesNotThrow(() -> Zeitfensterregel.pruefe(LocalTime.of(6, 45), LocalTime.of(7, 15)));
    }

    @Test
    void vertauschtesZeitfenster() {
        assertThrows(ZeitfensterUngueltigException.class,
                () -> Zeitfensterregel.pruefe(LocalTime.of(7, 15), LocalTime.of(6, 45)));
    }
}

Der JaCoCo-Bericht nennt für die Klasse Zeitfensterregel: 5 von 25 Anweisungen nicht ausgeführt, 1 von 4 Zweigen nicht durchlaufen.

  1. Gib an, welche Zeilen der Methode pruefe() im Bericht grün, gelb und rot hinterlegt sind, und begründe. (3 Punkte)

  2. Berechne die Anweisungs- und die Zweigabdeckung der Klasse in Prozent. (2 Punkte)

  3. Ergänze einen Testfall, mit dem beide Werte 100 % erreichen, und nenne einen weiteren Testfall, den die Grenzwertanalyse verlangt, obwohl er die Abdeckung nicht mehr erhöht. (3 Punkte)

  1. (3 Punkte)
  • Grün: das erste if und das erste throw (das vertauschte Fenster wirft die Exception) sowie die schließende Klammer der Methode, die nach dem gültigen Fenster erreicht wird.
  • Gelb: das zweite if. Es wird zwar ausgeführt, aber nur mit dem Ergebnis false, weil kein Test ein zu langes Fenster prüft; einer von zwei Zweigen fehlt.
  • Rot: das zweite throw, das kein Test erreicht.
  1. Anweisungsabdeckung: 20 / 25 = 80 % (1 Punkt). Zweigabdeckung: 3 / 4 = 75 % (1 Punkt). Den privaten Konstruktor zählt JaCoCo nicht mit, weil es private, leere Konstruktoren herausfiltert.

  2. Ein zu langes Zeitfenster, etwa 06:00 bis 08:00 (1 Punkt):

@Test
void zuLangesZeitfenster() {
    assertThrows(ZeitfensterUngueltigException.class,
            () -> Zeitfensterregel.pruefe(LocalTime.of(6, 0), LocalTime.of(8, 0)));
}

Damit erreichen Anweisungs- und Zweigabdeckung 100 % (1 Punkt). Die Grenzwertanalyse verlangt zusätzlich Fenster direkt an den Grenzen, etwa genau 90 Minuten (06:00 bis 07:30, gültig), 91 Minuten oder Beginn gleich Ende (1 Punkt für einen davon). Warum das wichtig ist, zeigt Aufgabe 3.2.

Aufgabe 2.2: Eine Regel gegen magische Zahlen ergänzen (Kernkompetenz)

Im Team fällt eine andere Fassung der Zeitfensterregel auf, die die Länge direkt mit toMinutes() > 90 vergleicht. Das Team möchte solche „magischen Zahlen“ künftig von Checkstyle melden lassen und ergänzt im TreeWalker die Regel <module name="MagicNumber"/>. Der nächste Lauf von checkstyleMain und checkstyleTest meldet im Hauptcode einen Verstoß und in den Tests zahlreiche (Auszug):

[ant:checkstyle] [ERROR] …/main/java/de/dbh/mitfahrlabor/domain/Zeitfensterregel.java:18:54: '90' is a magic number. [MagicNumber]
[ant:checkstyle] [ERROR] …/test/java/de/dbh/mitfahrlabor/domain/EinwilligungsregelTest.java:38:81: '2008' is a magic number. [MagicNumber]
[ant:checkstyle] [ERROR] …/test/java/de/dbh/mitfahrlabor/domain/EinwilligungsregelTest.java:38:90: '29' is a magic number. [MagicNumber]
[ant:checkstyle] [ERROR] …/test/java/de/dbh/mitfahrlabor/domain/ZeitfensterregelTest.java:14:71: '6' is a magic number. [MagicNumber]
[ant:checkstyle] [ERROR] …/test/java/de/dbh/mitfahrlabor/domain/ZeitfensterregelTest.java:14:74: '45' is a magic number. [MagicNumber]
  1. Erkläre, was eine „magische Zahl“ ist, und warum die Regel bei Duration.ofMinutes(90) in der Konstanten HOECHSTENS nichts meldet. (2 Punkte)

  2. Beurteile, ob die Meldungen in den Testklassen behoben werden sollten. (2 Punkte)

  3. Ergänze die Checkstyle-Konfiguration so, dass MagicNumber nur für den Hauptcode gilt, und behebe den Verstoß in der Zeitfensterregel. (3 Punkte)

  1. Eine magische Zahl ist ein Zahlenwert mitten im Code, dessen Bedeutung sich nur aus dem Zusammenhang erschließt; ändert sich die Regel, muss man alle Stellen finden (1 Punkt). Die Regel meldet keine Werte in Konstantendefinitionen, denn dort bekommt die Zahl einen Namen; HOECHSTENS ist static final (1 Punkt). Kleine Zahlen wie -1, 0, 1 und 2 meldet sie standardmäßig ebenfalls nicht.

  2. In Tests sind feste Werte gewollt: LocalDate.of(2008, 2, 29) oder LocalTime.of(6, 45) sind die Testdaten selbst und lesen sich direkt besser als Konstanten mit Namen wie SECHS (1 Punkt). Die Meldungen sollten also nicht behoben, sondern die Regel für Tests abgeschaltet werden (1 Punkt).

  3. Ein Filter im Checker unterdrückt die Regel für alle Dateien unter src/test (2 Punkte); er wirkt in Gradle und Maven gleich:

<!-- in Tests sind feste Werte (Daten, Uhrzeiten) gewollt -->
<module name="SuppressionSingleFilter">
    <property name="checks" value="MagicNumber"/>
    <property name="files" value="[\\/]src[\\/]test[\\/]"/>
</module>

Der Ausdruck [\\/] passt auf / und \, damit der Filter unter Windows und Linux greift. In der Zeitfensterregel wird wieder die Konstante verwendet: Duration.between(von, bis).compareTo(HOECHSTENS) > 0 (1 Punkt). Danach meldet Checkstyle keine Verstöße mehr.

Aufgabe 3.1: Die Pipeline anpassen (Profilkompetenz)

Das Team arbeitet mit Feature-Branches und Merge Requests. Zwei Wünsche gibt es an die Pipeline aus dem Lehrartikel:

  1. Formatierungs- und Regelfehler sollen schneller gemeldet werden. Bisher läuft der Job quality erst, wenn test fertig ist.
  2. Die JAR-Datei soll nur auf dem Hauptzweig (main) gebaut werden, nicht in jedem Feature-Branch.
  1. Ändere die Jobs quality und package in der .gitlab-ci.yml so, dass beide Wünsche erfüllt sind. (4 Punkte)

  2. Bewerte einen Nachteil, den Wunsch 1 mit sich bringt. (1 Punkt)

  3. Erläutere, warum die Jobs ihr Ergebnis nicht über einen gemeinsamen Ordner build/ austauschen, sondern jeweils neu kompilieren. (2 Punkte)

  1. Für Wunsch 1 genügt es, quality in die Stufe test zu verschieben, dann laufen beide Jobs parallel; die Stufe quality kann entfallen (2 Punkte). Für Wunsch 2 bekommt package eine Regel, die den Job nur auf dem Standardzweig anlegt (2 Punkte):
stages:
  - build
  - test
  - package

quality:
  stage: test
  script:
    - ./gradlew checkstyleMain checkstyleTest spotlessCheck openApiValidate
  artifacts:
    when: on_failure
    paths:
      - build/reports/checkstyle/

package:
  stage: package
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
  script:
    - ./gradlew bootJar
  artifacts:
    paths:
      - build/libs/*.jar
    expire_in: 1 week

$CI_DEFAULT_BRANCH enthält den Standardzweig des Projekts, meist main; ein fester Text "main" ist ebenso richtig. Eine Lösung mit needs: [build] im Job quality ist ebenfalls richtig: Dann startet der Job, sobald build fertig ist, unabhängig von der Stufe.

  1. Laufen beide Jobs parallel, meldet die Pipeline bei roten Tests womöglich gleichzeitig Formatierungsfehler. Die Fehlermeldungen mischen sich, und der Blick wandert leicht zum harmlosen Fehler statt zum fachlichen. Außerdem belegen zwei Jobs gleichzeitig Runner (1 Punkt für einen begründeten Nachteil).

  2. Jeder Job startet in einem frischen Container; Dateien aus einem anderen Job sind dort nur vorhanden, wenn sie als Artefakt weitergegeben werden (1 Punkt). Neu zu kompilieren kostet etwas Zeit, macht die Jobs aber unabhängig: Ein Job kann einzeln wiederholt werden, und kein Job arbeitet mit einem veralteten Zwischenstand. Der Cache hält nur die Bibliotheken bereit, nicht die Ergebnisse (1 Punkt).

Aufgabe 3.2: 100 % Abdeckung und trotzdem ein Fehler (Profilkompetenz)

Nach Aufgabe 2.1 besteht ZeitfensterregelTest aus drei Tests: gültiges Fenster 06:45–07:15, vertauschtes Fenster 07:15–06:45 und zu langes Fenster 06:00–08:00. JaCoCo meldet für die Zeitfensterregel 100 % Anweisungs- und 100 % Zweigabdeckung.

Bei einer Überarbeitung ändert jemand versehentlich die zweite Bedingung:

if (Duration.between(von, bis).compareTo(HOECHSTENS) >= 0) {

gradlew check bleibt grün, die Abdeckung bleibt bei 100 %.

  1. Erkläre, welches Verhalten sich geändert hat und warum weder Tests noch Abdeckung es bemerken. (3 Punkte)

  2. Ergänze einen Test, der den Fehler findet, und beschreibe, was bei gradlew test passiert. (2 Punkte)

  3. Beurteile die Forderung „Jede Klasse muss 100 % Abdeckung haben, dann sind keine Fehler mehr möglich“. (1 Punkt)

  1. Ein Zeitfenster von genau 90 Minuten war erlaubt und wird jetzt abgelehnt; die Grenze ist um eins verschoben (1 Punkt). Kein Test prüft genau diese Grenze: Das gültige Fenster dauert 30 Minuten, das zu lange 120, beide verhalten sich mit > und >= gleich (1 Punkt). Die Abdeckung bleibt bei 100 %, weil alle Anweisungen und beide Zweige des if weiterhin durchlaufen werden; JaCoCo misst, ob Code ausgeführt wird, nicht ob die Tests das richtige Ergebnis an den Grenzen prüfen (1 Punkt).

  2. Ein Grenzwerttest mit genau 90 Minuten (1 Punkt):

@Test
void genauNeunzigMinuten() {
    assertDoesNotThrow(() -> Zeitfensterregel.pruefe(LocalTime.of(6, 0), LocalTime.of(7, 30)));
}

Mit der fehlerhaften Bedingung schlägt der Test fehl, weil eine ZeitfensterUngueltigException geworfen wird; Gradle meldet ZeitfensterregelTest > genauNeunzigMinuten() FAILED und bricht den Build ab (1 Punkt). In der Pipeline wird die Stufe test rot.

  1. Die Forderung ist nicht haltbar: 100 % Abdeckung ist notwendig, damit jeder Code überhaupt einmal läuft, aber nicht hinreichend. Fehler an Grenzen, fehlende Zusicherungen oder vergessene Anforderungen bleiben unentdeckt; Testfälle müssen deshalb mit Äquivalenzklassen und Grenzwerten entworfen werden (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 Build-Qualität und CI-Pipeline: Prüfregeln und Abdeckung für Mitfahrgesuche” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/spring-boot-build-qualitaet-ci-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: