Versandkostenrechner-Übungsaufgabe zur Testfallerstellung nach Whitebox-Systematik

https://bildung.social/@oerinformatik/111523080351425661

https://oer-informatik.de/whitebox-testfall-uebung-shippingcost

Whitebox-Test, Anweisungsüberdeckung, Zweigüberdeckung, Kontrollflussgraph, McCabe-Zahl, Code-Coverage, JaCoCo, JUnit, Short-Circuit-Evaluation, Übungsaufgaben, Prüfungsvorbereitung

tl/dr; (ca. 34 min Lese- und Bearbeitungszeit): Zu einer gegebenen Java-Methode (Versandkostenrechner) erstellst Du einen Kontrollflussgraphen und berechnest die McCabe-Zahl (beides optional), bestimmst Anweisungs- und Zweigüberdeckung eines vorhandenen Tests, ergänzt weitere Testfälle nach Whitebox-Systematik, prüfst alles mit dem Code-Coverage-Werkzeug JaCoCo und suchst einen logischen Fehler, den die Whitebox-Sicht übersieht [AI-Assisted]. (Zuletzt geändert am 29.10.2026)

Diese Übungsaufgabe gehört zum Artikel Überdeckungsmetriken zur Bestimmung der Testgüte (Whitebox-Testing); für die optionale Teilaufgabe b) brauchst Du außerdem die McCabe-Zahl. Weitere Übungen findest Du unter Leitfragen zur Whitebox-Systematik, in den Übungen Rabatt und Sortierung sowie in den Übungsaufgaben zu Whitebox-Tests und Überdeckungsmaßen.

Hinweise zur Bearbeitung: Von FIA wird erwartet, dass ihnen Aufgaben auf Profilkompetenz-Ebene (Aufgabe 3.1) keine Probleme bereiten. Die Teilaufgaben a) und b) sind optional. Da einige Beispiellösungen schon Lösungen für spätere Teilaufgaben enthalten, bearbeite zuerst alle Teilaufgaben und klappe erst danach die Beispiellösungen auf – jedenfalls dann, wenn Du dabei etwas lernen willst.

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 „Versandkostenrechner-
Übungsaufgabe zur Testfallerstellung nach Whitebox-Systematik“.

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/codecoverage
- https://oer-informatik.de/zyklomatische-komplexitaet
- Stilbeispiel: https://oer-informatik.de/whitebox-testfall-uebung-shippingcost

Die Aufgaben sollen Inhalte zu diesen Schlagwörtern abdecken:
- Whitebox-Test
- Anweisungsüberdeckung
- Zweigüberdeckung
- Kontrollflussgraph
- McCabe-Zahl
- Code-Coverage
- JaCoCo
- JUnit
- Short-Circuit-Evaluation

Kompetenzstufe: Profilkompetenz (alternativ: Basiskompetenz, Kernkompetenz, 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 3.1: Versandkostenrechner mit Whitebox-Tests prüfen (Profilkompetenz)

Gegeben ist die folgende Java-Methode zur Berechnung von Versandkosten:

public static double shippingCalculator(ShippingPosition[] positions){

        double shippingCost = 3.9;
        double totalWeight = 0;

        for (ShippingPosition pos : positions) {

            shippingCost += pos.getQuantity()*0.1; // 10ct pro Artikel

            if (pos.getQuantity()*pos.getWeight() < 0.5){ // Kleinartikel
                shippingCost += 0.3;
            }else if (pos.getQuantity()*pos.getWeight() < 2.5){ //größer
                shippingCost += 1.0;
            }

            totalWeight += pos.getQuantity()*pos.getWeight();

            if ((totalWeight >10) || (shippingCost >50)){  //Sperrgut
                shippingCost = 50;
            }
        }
        return shippingCost;
    }

Die Methode erhält ein Array von Bestellpositionen und errechnet die Versandkosten in Abhängigkeit von Stückzahl und Einzelgewicht der Positionen. Die Bestellpositionen haben folgende Struktur (relevant sind nur die Getter für die Anzahl quantity und das Gewicht weight):

UML-Klassendiagramm: Die Klasse App nutzt mit der statischen Methode shippingCalculator() ein Array von ShippingPosition-Objekten; ShippingPosition hat die Attribute price, quantity, ean und weight, einen Konstruktor sowie die Getter getWeight() und getQuantity() (mit KI erstellt)

a) (optional) Erstelle für die Methode shippingCalculator() einen Kontrollflussgraphen. (6 Punkte)

Der Kontrollfluss lässt sich wie folgt darstellen (ausführlich kommentierte Fassung – eigentlich sind nur Knoten und Kanten erforderlich):

Kontrollflussgraph für die Methode shippingCalculator() (11 Knoten, 5 Verzweigungen)

Eine kleine Gemeinheit hier ist die Short-Circuit-Evaluation (die zusammengesetzte Bedingung mit dem ||-Operator): Bei der Bedingung ((totalWeight >10) || (shippingCost >50)) wird der zweite Teil (shippingCost >50) nur ausgewertet, wenn der erste Teil (totalWeight >10) false ergibt. Wenn sich die Bedingungen lediglich aus Werten zusammensetzen, ist das unerheblich; sind die Bedingungen selbst Methodenaufrufe, beeinflusst das den Kontrollfluss jedoch entscheidend.

b) (optional) Berechne für die Methode shippingCalculator() die zyklomatische Komplexität (McCabe-Zahl). (2 Punkte)

Der Kontrollfluss weist 5 binäre Verzweigungen auf, somit ergibt sich die McCabe-Zahl zu

M = b + 1 = 5 + 1 = 6

(Hinweis: Wenn man die Short-Circuit-Evaluation nicht einbezieht, sind es lediglich 4 binäre Verzweigungen und damit M = 5. JaCoCo kommt in der Spalte „Cxty“ ebenfalls auf 6, weil es jede Teilbedingung als eigene Verzweigung zählt.)

c) Bestimme, welche Anweisungs- und Zweigüberdeckung der folgende Testfall erreicht. Gib die Werte in der Form xx von yy (oder als ungekürzten Bruch) an, nicht als Prozentzahl. (6 Punkte)

    @Test
    void testShippingCosts() {
        /* given: Preparation */
        ShippingPosition[] positions = {new ShippingPosition(3, 0.5)};

        /* when: Execution */
        double result = App.shippingCalculator(positions);

        /* then: Verification */
        double expected = 5.2;   // 3.9 + 3*0.1 + 1.0
        double delta = 0.001;    // Rundungsgenauigkeit: Schwankung 0.1ct sind ok
        assertEquals(expected, result, delta);
    }

Die beiden Anweisungen shippingCost += 0.3; und shippingCost = 50; werden nicht erreicht (Knoten 4 und 10 im Kontrollflussgraphen). Insgesamt sind es – je nach Zählweise – 11 Anweisungen bzw. Anweisungsblöcke, die Anweisungsüberdeckung beträgt also 9 von 11 (9/11 ≈ 82 %).

Kontrollflussgraph für die Methode shippingCalculator() mit 9 (von 11) überdeckten Knoten und überdeckten Zweigen (Verzweigung an Knoten 2 komplett, an den Knoten 3, 5, 8 und 9 teilweise)

Wie bei allen Überdeckungsmetriken gilt auch hier: Der Bruch ist aussagekräftiger als die Prozentzahl, weil man Einblicke in die Berechnung bekommt.

JaCoCo zählt die einzelnen Bytecode-Instruktionen und kommt auf 65 von 72 Instruktionen (≈ 90 %), bei den Zweigen auf 6 von 10 (60 %):

Die Übersichtsausgabe von JaCoCo zeigt für shippingCalculator() 90 % Instruktions- und 60 % Zweigüberdeckung

Im Detail werden folgende Zeilen/Zweige nicht erreicht:

Im eingefärbten Code von JaCoCo sind alle Zeilen außer shippingCost += 0.3 und shippingCost = 50 (rot) erreicht (grün), die Schleife ist voll abgedeckt (grüne Raute), drei Verzweigungen sind teilweise überdeckt (gelbe Raute)

Nicht erreicht werden die Zweige (pos.getQuantity()*pos.getWeight() < 0.5) (Kante c, Knoten 3) und else (Kante h, Knoten 5) sowie ((totalWeight >10) || (shippingCost >50)) (Kanten j und l zu Knoten 10). Insgesamt sind es 8 Zweige, von denen 3 nicht erreicht werden; wenn man die Short-Circuit-Bedingung (s. u.) bedenkt, sind es sogar 10 Zweige, von denen 4 nicht erreicht werden. Die Zweigüberdeckung beträgt also 5 von 8 (62,5 %) bzw. 6 von 10 (60 %).

Eine kleine Gemeinheit hier ist, dass bei der Bedingung ((totalWeight >10) || (shippingCost >50)) der zweite Teil (shippingCost >50) nur ausgewertet wird, wenn der erste Teil (totalWeight >10) false ergibt. Wenn sich die Bedingungen wie hier lediglich aus Werten zusammensetzen, ist das unerheblich.

Es wäre jedoch auch denkbar, dass die Bedingungsabfrage in Methoden ausgelagert wird:

if (istGewichtZuGross() || sindKostenUeberMaximum()) {...}

Nach dieser Refaktorisierung ändert sich der Kontrollfluss abhängig davon, ob die erste Bedingung als true ausgewertet wird oder nicht. sindKostenUeberMaximum() würde ggf. nie aufgerufen und müsste über einen weiteren Test gesondert überdeckt werden.

d) Ergänze so wenige Testfälle wie möglich (zusätzlich zu dem in c) genannten), um 100 % Anweisungsüberdeckung zu erreichen. (4 Punkte)

Als Antwort genügt es, für die Testfälle nur den Parameter positions zu definieren (also den given-Abschnitt des obigen Tests). Das erwartete Ergebnis musst Du nicht berechnen. Für den Testfall aus c) sähe die Antwort so aus:

ShippingPosition[] positions = {new ShippingPosition(3, 0.5)};

Es fehlen Tests für die beiden Anweisungen shippingCost += 0.3; und shippingCost = 50; (Knoten 4 und 10 im Kontrollflussgraphen). Um sie zu erreichen, benötigen wir eine sehr leichte und eine sehr schwere ShippingPosition.

Denkbar wäre, diese in einem einzigen Testfall zu übergeben:

ShippingPosition[] positions = {new ShippingPosition(2, 0.2), new ShippingPosition(1, 11)};

Jedoch überschreibt der Sperrgut-Versandpreis den Preis für leichte Lieferungen. Sinnvoller ist es also, beides gesondert zu testen (auch wenn die Aufgabenstellung hier eine Lösung in einem Test zuließe).

Zwei komplett ausgeschriebene Testfälle mit erwartetem Ergebnis wären:

    @Test
    void testShippingCostsLight() {
        //given
        ShippingPosition[] positions = {new ShippingPosition(2, 0.2)};

        //when
        double result = App.shippingCalculator(positions);

        //then
        double expected = 4.4;   // 3.9 + 0.3 + 0.2
        double delta = 0.001;    // Rundungsgenauigkeit: Schwankung 0.1ct sind ok
        assertEquals(expected, result, delta);
    }

    @Test
    void testShippingCostsHeavy() {
        //given
        ShippingPosition[] positions = {new ShippingPosition(1, 11)};

        //when
        double result = App.shippingCalculator(positions);

        //then
        double expected = 50;    // Sperrgut
        double delta = 0.001;    // Rundungsgenauigkeit: Schwankung 0.1ct sind ok
        assertEquals(expected, result, delta);
    }

e) Ergänze zusätzlich zu den Testfällen aus c) und d) so wenige Testfälle wie möglich, um 100 % Zweigüberdeckung zu erreichen. (4 Punkte)

Als Antwort genügt es wieder, für die Testfälle nur den Parameter positions zu definieren. Das erwartete Ergebnis musst Du nicht berechnen. Für den Testfall aus c) sähe die Antwort so aus:

ShippingPosition[] positions = {new ShippingPosition(3, 0.5)};

Nach dem ersten Testfall in c) fehlten die Zweige (pos.getQuantity()*pos.getWeight() < 0.5) (Kante c, Knoten 3) und else (Kante h, Knoten 5) sowie ((totalWeight >10) || (shippingCost >50)) (Kanten j und l zu Knoten 10).

Mit den Testfällen aus d) sind beide Zweige von Knoten 3 komplett überdeckt (die leichte Lieferung) sowie die Kanten h und j (die schwere Lieferung).

Wenn wir die Short-Circuit-Bedingung außer Acht lassen, wären wir also bereits fertig, denn es fehlt nur noch diese Kante (l).

Wir benötigen also einen Testfall, bei dem das Gesamtgewicht höchstens 10 beträgt, die Versandkosten aber trotzdem über 50 liegen. Das gelingt mit vielen, sehr leichten Artikeln:

ShippingPosition[] positions = {new ShippingPosition(500, 0.001)};

Als ausgeschriebener Testfall mit erwartetem Ergebnis wäre das:

    @Test
    void testShippingManyPositions() {
        //given
        ShippingPosition[] positions = {new ShippingPosition(500, 0.001)};

        //when
        double result = App.shippingCalculator(positions);

        //then
        double expected = 50;    // 3.9 + 500*0.1 + 1.0 = 54.9 (Positionsgewicht 0.5 ist nicht < 0.5), begrenzt auf 50
        double delta = 0.001;    // Rundungsgenauigkeit: Schwankung 0.1ct sind ok
        assertEquals(expected, result, delta);
    }

f) Übernimm die Methode in eine IDE, implementiere die entworfenen Testfälle mit JUnit und überprüfe Deine Ergebnisse mit JaCoCo. (10 Punkte)

Ein fertiges Maven-Projekt mit der Methode, den vier Testfällen aus c), d) und e) und eingebundenem JaCoCo-Plugin findest Du im Repository unter jacoco-metrik-shippingCost. Mit mvn test laufen die Tests, den Bericht legt JaCoCo unter target/site/jacoco/index.html ab.

Mit allen vier Testfällen meldet JaCoCo für shippingCalculator() 72 von 72 Instruktionen (100 %) und 10 von 10 Zweigen (100 %); die zyklomatische Komplexität (Spalte „Cxty“) beträgt 6. Nur der leere Standardkonstruktor App() und die leere main()-Methode werden nicht durchlaufen, deshalb zeigt die Klasse insgesamt 72 von 76 Instruktionen (≈ 94 %).

g) Leite aus dem Code die Anforderungen ab und erstelle daraus Äquivalenzklassen und Grenzwerte aus Blackbox-Sicht. Erläutere, welchen logischen Fehler der Code enthält, den die Whitebox-Systematik nicht finden konnte. (5 Punkte)

Aus dem Code ergeben sich je Position (Positionsgewicht = Menge * Gewicht) diese Äquivalenzklassen: Positionsgewicht unter 0.5 (Zuschlag 0.30), von 0.5 bis unter 2.5 (Zuschlag 1.00) und ab 2.5 (kein Zuschlag); dazu für die ganze Bestellung: Gesamtgewicht über 10 oder Versandkosten über 50 (Sperrgutpreis 50). Grenzwerte liegen bei 0.5, 2.5, 10 und 50.

Es werden zwar Versandkosten für Menge*Gewicht < 0.5 und Menge*Gewicht < 2.5 angesetzt, sobald der Wert aber 2.5 erreicht oder übersteigt, werden keine weiteren Kosten fällig (erst bei einem Gesamtgewicht über 10 gilt der Sperrgutpreis). Eine Bestellung mit einem Artikel des Gewichts 5 kostet so 4.00 Versand, eine mit einem Artikel des Gewichts 2 dagegen 5.00.

Whitebox-Tests können derartige Spezifikationsprobleme nicht aufdecken, hierzu ist eine gesonderte Blackbox-Betrachtung hilfreich: Das Aufstellen von Äquivalenzklassen deckt solche Lücken in der Spezifikation auf.


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: “Versandkostenrechner-Übungsaufgabe zur Testfallerstellung nach Whitebox-Systematik” von oer-informatik.de (H. Stein), Lizenz: CC BY 4.0. Der Artikel wurde unter https://oer-informatik.de/whitebox-testfall-uebung-shippingcost veröffentlicht, die Quelltexte sind in weiterverarbeitbarer Form verfügbar im Repository unter https://gitlab.com/oer-informatik/qs/testen. Erstellt mit KI-Assistenz; von mir umfassend redaktionell überarbeitet, geprüft und freigegeben. Stand: 29.10.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: