Versandkostenrechner-Übungsaufgabe zur Testfallerstellung nach Whitebox-Systematik
https://bildung.social/@oerinformatik/111523080351425661
https://oer-informatik.de/whitebox-testfall-uebung-shippingcost
tl/dr; (ca. 60 min Bearbeitungszeit): Zu einer gegebenen Funktion (Versandkostenrechner) soll ein Kontrollflussgraph erstellt werden, die McCabe-Zahl berechnet werden (beides optional), die Überdeckungsmetriken für einen bestehenden Test berechnet werden, weitere Tests aus Whitebox-Sicht ergänzt werden und alles mit einem Code-Coverage-Tool implementiert werden. (Zuletzt geändert am 06.12.2023)
Die Aufgaben beziehen sich auf die Inhalte der folgenden Blogposts:
Aufgabe Überdeckungstests Versandkostenrechner
Gegeben ist die folgende 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 in Abhängigkeit von Positionsanzahl und Einzelgewicht die Versandkosten. Die Bestellpositionen haben folgende Struktur (für uns relevant sind nur die Getter für Anzahl (quantity) und Gewicht (weight):

Hinweis: Da einige Antworten bereits Lösungen für die folgenden Fragen enthalten, ist es ratsam, zunächst alle Fragen zu beantworten / bearbeiten und dann die Lösungen anzuschauen. Jedenfalls dann, wenn man auch etwas dazulernen will…
(Optional vorneweg): Erstelle für die Methode
shippingCalculator()
einen Kontrollflussgraphen!Der Kontrollfluss lässt sich wie folgt darstellen (aufwändige 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 unerhelblich, wenn die Bedingungen selbst Methodenaufrufe sind, beeinflusst das den Kontrollfluss jedoch entscheidend.(Optional vorneweg): Berechne für die Methode
shippingCalculator()
die zyklomatisch Komplexität (McCabe-Zahl)!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 / sieht sind es lediglich 4 binäre Verzweigungen.)Welche Anweisungs- und Zweigüberdeckung erreicht folgender Testfall? Gibt die Werte in der Form
xx von yy
an (oder als nicht gekürzter Bruch), und nicht als Prozentzahl!@Test void testShippingCosts() { /* given: Preparation */ ShippingPosition[] positions = {new ShippingPosition(3, 0.5)}; /* when: Execution */ double result = Application.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); }
Es werden die beiden Anweisungen
shippingCost += 0.3;
undshippingCost = 50;
nicht erreicht (Knoten4
und10
im Kontrollflussgraphen). Insgesamt sind es – je nach Zählweise - 11 Anweisungen/Anweisungsblöcke => 9/11=81%.Kontrollflussgraph für die Methode shippingCalculator()
mit 9 (von 11) überdeckten Knoten und überdeckten Zweigen (1 komplett, 3 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 atomare Instruktionen und kommt auf 65/72 = 90%)
Die Übersichts-Ausgabe von JaCoCo zeigt 90% Anweisungsüberdeckung Im Detail werden folgende Zeilen/Zweige nicht erreicht:
Im eingefärbten Code von JaCoCo sind 6 Anweisungen erreicht (grün), eine Verzweigung voll abgedeckt (grüne Raute) und drei Verzweigungen teilweise überdeckt (gelbe Raute) Es werden die Zweige
(pos.getQuantity()*pos.getWeight() < 0.5)
(Kantec
, Knoten3
)undelse
(Kanteh
, Knoten5
) sowie((totalWeight >10) || (shippingCost >50))
nicht erreicht (Kantenj
undl
zu Knoten10
). Insgesamt sind es 8 Zweige (von denen 3 nicht erreicht werden), wenn man die Short-Ciruit-Bedingung (s.u.) bedenkt sind es sogar 10 Branches (von denen 4 nicht erreicht werden) => 5/8 = 62,5% bzw. 6/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 unerhelblich.Es wäre jedoch auch denkbar, dass die Bedingungsabfrage in Methoden ausgelagert wird:
Nach dieser Refaktorisierung ändert sich der Kontrollfluss abhängig davon, ob die erste Bedingung alstrue
ausgewertet wird oder nicht.sindKostenUeberMaximum()
würde ggf. nie aufgerufen und müsste über einen weiteren Test gesondert überdeckt werden.Ergänze so wenige Testfälle wie möglich (zusätzlich zu dem in b) genannten), um 100%ige Anweisungsüberdeckung zu erhalten. Als Antwort langt es, für die Testfälle nur den Parameter
positions
zu definieren (also dengiven
-Abschnitt des obigen Tests). Das erwartete Ergebnis muss nicht berechnet werden. Das Antwortbeispiel für den obigen Testfall wäre:Es fehlen Tests für die beiden Anweisungen
shippingCost += 0.3;
undshippingCost = 50;
(Knoten4
und10
im Kontrollflussgraphen). Um sie zu erreichen benötigen wir eine sehr leichte und eine sehr schwereShippingPosition
.Denkbar wäre, diese in einem einzigen Testfall zu übergeben:
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); }
Ergänze so wenige Testfälle zusätzlich zu denen aus c) und d) wie möglich, um 100%ige Zweigüberdeckung zu erhalten. Als Antwort langt es wieder, für die Testfälle nur den Parameter
positions
zu definieren (also dengiven
-Abschnitt des obigen Tests). Das erwartete Ergebnis muss nicht berechnet werden. Das Antwortbeispiel für den obigen Testfall wäre:Nach dem ersten Testfall in c) fehlten die Zweige
(pos.getQuantity()*pos.getWeight() < 0.5)
(Kantec
, Knoten3
) undelse
(Kanteh
, Knoten5
) sowie((totalWeight >10) || (shippingCost >50))
(Kantenj
undl
zu Knoten10
).Mit den Testfällen aus d) sind beide Zweige von Knoten
3
komplett überdeckt (die leichte Lieferung) sowie die Kantej
undh
(die schwere Lieferung).Wenn wir die Short-Circuit-Bedingung ausser 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 unter 10, die Versandkosten trotzdem über 50 liegen. Das kann durch viele, sehr leichte Artikel erreicht werden:
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; // 500*0.1 + 3.9 = 53.9, aber das wird auf 50 begrenzt double delta = 0.001; // Rundungsgenauigkeit: Schwankung 0.1ct sind ok assertEquals(expected, result, delta); }
Übernimm die Methode in eine IDE und erstelle die entworfenen Testfälle. Überprüfe Deine Ergebnisse mit JaCoCo.
Versuche Äquivalenzklassen und Grenzfälle aus Blackboxsicht für die FUnktion zu erstellen, in dem Du die Anforderungen aus dem Code extrahierst. Welchen logischen Fehler gibt es im Code, der mit Whitebox-Systematik nicht gefunden werden konnte?
Es werden zwar Versandkosten für
Menge*Gewicht < 0.5
undMenge*Gewicht < 2.5
angesetzt, sobald der Wert aber über 2.5 steigt werden keine weitere Kosten fällig (erst ab einem Gesamtgewicht ab 10).Whiteboxtest können derartige Spezifikationsprobleme nicht aufdecken, hierzu wäre eine gesonderte Blackbox-Betrachtung hilfreich: das Aufzeichnen von Äquivalenzklassen deckt derlei Lücken in der Spezifikation auf.
Weiter Übungsaufgaben
Links zu weiteren Übungsaufgaben finden sich über das Menü oder am Ende des Artikels zu Whitebox-Tests/Code-Coverage
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 Hannes 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/code-coverage. Stand: 06.12.2023.
[Kommentare zum Artikel lesen, schreiben] / [Artikel teilen] / [gitlab-Issue zum Artikel schreiben]