Übungsaufgaben zur Testfallerstellung
https://oer-informatik.de/
tl/dr; (ca. 90 min Bearbeitungszeit): Beispielfragen rund um Testgetriebene Entwicklung (Testdriven Development, TDD), Testfallerstellung aus Whitebox- und Blackbox-Systematik.
Die Aufgaben beziehen sich auf die Inhalte der folgenden Blogposts:
Verständnisfragen:
A) Leitfragen Testgetriebene Entwicklung:
- Was versteht man unter Testgetriebener Entwicklung (TDD)?
Bei Testgetriebener Entwicklung wird zunächst ein Test erstellt, der einen Teil der Spezifikation überprüft und erst im zweiten Schritt die zugehörige Implementierung, die den Test bestehen lässt.
- Welche drei Phasen gibt es in der Testgetriebenen Entwicklung, wie werden Sie genannt, und welche Aufgaben haben Sie als Entwicker*in in dieser Phase?
TDD umfass diese drei sich permanent wiederholenden Phasen: * Red: Erstelle einen Test, der einen kleinen (!) Bereich der Spezifikation umfasst. Dieser soll zunächst scheitern. * Green: Implementiere gerade so viel Code, damit der zuvor erstellte Test bestanden wird. * Refactor: Verbessere - wenn nötig - den Code des Tests und der Implementierung, ohne die Funktionalität zu verändern.
- Testgetriebenen Entwicklung wurde v.a. im Umfeld eines bestimmten Vorgehensmodells bekannt. Welches Vorgehensmodell ist dies?
Testgetriebene Entwicklung wurde v.a. durch Kent Beck bekannt, der es im Rahmen des Vorgehensmodells extreme programming (XP) entwickelt hat.
- In der Softwareentwicklung verwenden wir bei der Problemlösung häufig die Strategien Atomisieren (Aufteilen in kleinere, lösbare Probleme) und Abstrahieren (Ähnlichkeiten zu bekannten Problemlösungen finden). Beschreiben Sie auf welche Art Testgetriebene Entwicklung eine dieser Strategien nutzt.
Testgetriebene Entwicklung geht davon aus, dass jeweils nur kleine Abschnitte der Spezifikation getestet und entwickelt werden. Durch diese Kleinschrittigkeit ist der Fokus auf ein Teilproblem gesetzt. Die anderen bereits realisierten Abschnitte geraten durch die dafür bereits realisierten Tests jedoch nicht in Vergessenheit, sondern werden von der Testsuite weiterhin abgedeckt. Das Lösen von kleinen Teilen der Spezifikation als zentraler Bestandteil von TDD steht für die Atomisierung von Problemstellungen.
- Zur Testfallerstellung für Software können Whitebox- und Blackbox-Verfahren genutzt werden. Beschreiben Sie, welches dieser beiden Verfahren bei der Testgetriebenen Entwicklung eingesetzt wird - und warum das jeweils andere nicht genutzt wird.
Bei der Testgetriebenen Entwicklung entsteht der Test vor der eigentlichen Implementierung. Daher können diese Tests nur gegen die Spezifikation erstellt werden, also aus Blackboxsicht. Whitebox-Tests könnten im Nachgang aufgrund der Abdeckungsmetriken ergänzt werden, das ist jedoch nicht (mehr) Bestandteil des TDD-Zyklus.
- Für ein Projekt, an dem Ihr Betrieb seit einem halben Jahr arbeitet, wurden für den letzten Sprint das Sprintziel “Code mit Unit-Tests abdecken” festgelegt. Die meisten Module besitzen bislang nur wenige Integrations- und keinerlei Unittests. Nach Abschluss des Sprints konnte das Team tatsächlich eine Codeüberdeckung von 100% erreichen. Wurde das Projekt nach dieser Phase testgetrieben entwickelt? Falls nein: Welche Änderungen wären erforderlich, damit es als “testgetrieben entwickelt” bezeichnet werden kann?
Der Ablauf, Tests erst während oder nach der Implementierung zu erstellen widerspricht den Grundsätzen der Testgetriebenen Entwicklung. Testgetrieben wäre das Vorgehen nur dann, wenn für kleine Spezifikationsabschnitte zunächst ein Test, danach der zugehörige Code implementiert würde, gemäß TDD-Zyklus.
- Ab welchem Überdeckungsgrad darf man von “testgetriebener Entwicklung” sprechen?
Der Überdeckungsgrad hat keinerlei bezug zur Testgetriebenen Entwicklung. Daher gibt es auch keine Festlegung, welche Testabdeckung erreicht werden soll. Die Überdeckungsmetriken können genutzt werden, um nach der Erstellung von Blackboxtests und nach der darauf folgenden Implementierung Hinweise darauf zu erhalten, welche Testfälle man ggf. noch ergänzen sollte.
- Bei testgetriebener Entwicklung wird in Phasen vorgegangen. Benennen Sie alle Phasen, in denen der eigentliche Programmcode angepasst wird!
In den Phasen Green und Refactor wird der Programmcode bearbeitet, wobei nur in Green neue Funktionalitäten hinzugefügt werden.
- Benennen Sie alle Phasen, in denen der Testcode angepasst wird!
In den Phasen Red und Refactor wird der Testcode bearbeitet, wobei nur in Red neue Tests für Funktionalitäten entstehen.
B) Leitfragen Whitebox-Tests:
- Was versteht man unter Whitebox-Tests?
Bei Whitebox-Tests handelt es sich um Tests, die gegen die Implementierung erstellt werden. Whitebox-Testfälle werden so erstellt, dass die Testsuite einen möglichst großen Bereich des Codes erreicht.
- Welche Systematiken können zur Generierung von Whitebox-Testfällen genutzt werden?
Für Whitebox-Tests werden Überdeckungsmetriken herangezogen. Es wird dabei überprüft, wie viel Prozent der vorhandenen Zeilen, Anweisungen, Zweige, Pfade oder Bedingungen durch die Testfälle erreicht wird. Häufig helfen Unittest-Frameworks mit Codeabdeckungstools bei der Berechnung und Visualisierung der nicht abgedeckten Codeabschnitte.
- Welche Aussagen können aus den Überdeckungs-Metriken abgeleitet werden?
Aus Überdeckungsmetriken können Aussagen über die Güte der Testsuite abgeleitet werden: Werden wenige Anweisungen oder Zweige durch die Tests erreicht, so ist die Wahrscheinlichkeit größer, dass sich in den nicht erreichten Zeilen Fehler befinden. Eine umgekehrte Aussage ist nicht möglich: auch eine Funktion, in der alle Überdeckungsgrade 100% betragen, kann voller logischer Fehler sein.
- Eine Testsuite für eine Funktion verfügt über Testfälle mit 100% Anweisungsüberdeckung. Wann und warum kann es sinnvoll sein, diese Testsuite mit Testfällen nach Blackbox-Systematik zu ergänzen?
Fehler passieren oft am Rand des Definitionsbereichs oder bei unerwarteten oder fehlerhaften Eingaben. Mit der Grenzfallanalyse bieten Blackbox-Testfälle eine Systematik, die dabei hilft, solche Szenarien mit in die Testsuite aufzunehmen.
- “Verfügt eine Testsuite über 100% Zweigabdeckung, so ist automatisch auch die Anweisungsüberdeckung und die Pfadüberdeckung bei 100% gegeben.” Bewerten sie diese Aussage!
Zweigüberdeckung setzt voraus, dass an allen bedingten Anweisungen alle Zweige durchlaufen wurden. Das beinhaltet, dass alle (erreichbaren) Anweisungen auch durchlaufen wurden. Sofern also kein “toter Code” im getesteten Modul vorhanden ist (nicht erreichbare Zeilen, etwa if (false) {nichtErreichbar()}
) folgt aus eine Zweigüberdeckung von 100% auch eine Anweisungsüberdeckung. Die Pfadüberdeckung wiederum basiert auf der Kombination unterschiedlicher Zweige. Eine Pfadüberdeckung von 100% würde zwar die Zweigüberdeckung beinhalten. Umgekehrt gilt dies aber nicht.
C) Leitfragen Blackbox-Tests:
- Was versteht man unter Blackbox-Tests?
Blackbox-Tests sind Testfälle, die gegen die Anforderungen implementiert werden. Unabhängig davon, ob der Code bekannt oder unbekannt, ob er bereits existiert oder noch nicht implementiert wurde werden lediglich die in der Spezifikation beschriebenen Eingabe- und Ausgabewerte zur Erstellung der Testfälle herangezogen. siehe Artikel zu Blackbox-Testfällen
- Welche Systematiken können zur Generierung von Blackbox-Testfällen genutzt werden?
Blackbox-Testfälle werden durch die beiden Systematiken Grenzfallanalyse und Äquivalenzklassenbildung gebildet.
- Was versteht man unter Äquivalenzklassenbildung?
Eine Äquivalenzklasse fasst ein Set von Eingabewerten zusammen, bei dem wir ein ähnliches Verhalten unseres getesteten Systems (system under test - SUT) erwarten. Die Menge aller möglichen Eingabewerte für unser SUT wird so in möglichst wenige Gruppen aufgeteilt.
- Was versteht man unter Grenzfall-Analyse?
Grenzfälle sind diejenigen Eingabewerte für unser SUT, bei dem sich das Verhalten ändert. Häufig finden sich Grenzfälle an den Grenzen des Definitionsbereichs oder an den Grenzen von Äquivalenzklassen. Da hier besonders häufig Fehler passieren, wird im Rahmen der Grenzfallanalyse der Grenzwert selbst sowie die direkten Nachbarwerte getestet.
- Die Methode
int anzahlDerPunkteImString(String text)
soll systematisch getestet werden. Welche Systematiken legen Sie an? Welche Testfälle wären möglich?
Da keine Implementierung vorliegt, aber die Spezifikation im Wesentlichen aus dem Namen hervorgeht, bietet sich die Blackboxsystematik (Äquivalenzklassen, Grenzfälle) an:
Testfall Nr. |
Beschreibender Name der Testklasse / des Testfalls | Vor- bedingungen |
Eingabewerte (Parameter text ) |
Erwartetes Resultat |
Nach- bedingungen |
Tatsächliches Resultat |
bestanden / nicht bestanden |
---|---|---|---|---|---|---|---|
1. | ÄK: mehrere Kommata | keine | anzahlDerPunkteImString("1,2,3") |
2 | - | ?? | ?? |
2. | ÄK: kein Komma | keine | anzahlDerPunkteImString("nix") |
0 | - | ?? | ?? |
3. | GW: Leerer String | keine | anzahlDerPunkteImString("") | 0 | - | ?? | ?? |
4. | GW: Ungültige Werte | keine | anzahlDerPunkteImString(NULL) | Exception | - | ?? | ?? |
- Die Methode
int einzurichtendeKlassen(long anzahlSchueler; long schuelerProKlasseMax)
berechnet die Anzahl der Klassen, die bei einer Schülerzahl eingerichtet werden muss – unter Berücksichtigung einer Obergrenze an Schülern pro Klasse. Die Methode soll systematisch getestet werden. Welche Testfälle wären möglich? Dokumentieren Sie je Systematik 2 Testfälle!
Testfall Nr. |
Beschreibender Name der Testklasse / des Testfalls | Vor- bedingungen |
Eingabewerte (Parameter anzahlSchueler , schuelerProKlasseMax ) |
Erwartetes Resultat |
Nach- bedingungen |
Tatsächliches Resultat |
bestanden / nicht bestanden |
---|---|---|---|---|---|---|---|
1. | GW: Anzahl Schüler geht auf | keine | anzahlSchueler=60 , schuelerProKlasseMax=30 |
2 | - | ?? | ?? |
2. | GW: 0 Schüler | keine | anzahlSchueler=0 , schuelerProKlasseMax=30 |
0 | - | ?? | ?? |
2. | ÄK: Anzahl Schüler geht nicht auf | keine | anzahlSchueler=61 , schuelerProKlasseMax=30 |
3 | - | ?? | ?? |
2. | ÄK: Weniger Schüler als max | keine | anzahlSchueler=28 , schuelerProKlasseMax=30 |
1 | - | ?? | ?? |
Übungsaufgaben
D) Aufgabe Überdeckungstests
Gegeben ist die folgende Methode, die Rabatte errechnet in Abhängigkeit der Positionskosten (über 100€ -> 5€ Rabatt) und Gesamtkosten (über 1000€ -> 5% Rabatt auf die rabattierte Gesamtsumme):
public static int rabattAusgeben(int[] kosten){
int rabatt = 0;
int summe = 0;
for(int position:kosten){
if (position > 100) {
rabatt += 5;
}
summe += position;
}
if (summe-rabatt > 1000){
rabatt +=(int)((summe-rabatt)*0.05);
}
return rabatt;
}
- Welchen Anweisungsüberdeckungsgrad und Zweigüberdeckungsgrad erhalten Sie mit dem Testfall
@Test
public void rabattAusgeben_1000_10_Test() {
// given: Preparation
int[] kosten = { 1000, 10 };
// when: Execution
int result = App.rabattAusgeben(kosten);
// then: Verification
int expected = 55;
assertEquals(result, expected);
}
Testfall im Kontrollflußgraph: 1->2->3->4->5->6->3->4->6->3->7->8->9
Anweisung 1 + 2 kann zusammengefasst werden.
C0 = durchlaufende Anweisungen / mögliche Anweisungen = 9/9 = 100% (bei Zusammenfassung von 1+2: 8/8 = 100%)
C1 = durchlaufende Zweige / mögliche Zweige = 5/6 = (83%)
Berechnung über Kanten statt Zweige:
C1 = durchlaufende Kanten / mögliche Kanten = 9/10 = 90%
- Ergänzen Sie so wenige Testfälle wie möglich, um eine 100%ige Zweigüberdeckung zu erhalten. (Erwartete Ergebnisse sind nicht erforderlich – lediglich die zu testenden Parameter sollen angegeben werden).
Bei den folgenden Fragen können die Testfälle abgekürzt werden nach dem Muster assertEquals(result, expected)
, für den obigen Testfall also - weil das erwartete Resultat ja nicht berechnet werden muss, z.B.:
summe-rabatt muss unter 1000 bleiben, damit else-Zweig genommen wird, also:
Testfall im Kontrollflußgraph: 1->2->3->4->6->3->7->9
assertEquals(rabattAusgeben({10 }), ??);
E) Aufgabe Überdeckungstests
Im Folgenden ist eine Beispielimplementierung für einen SelectionSort-Algorithmus abgebildet:
public static int[] selectionsort(int[] sortieren) {
for (int i = 0; i < sortieren.length - 1; i++) {
for (int j = i + 1; j < sortieren.length; j++) {
if (sortieren[i] > sortieren[j]) {
int temp = sortieren[i];
sortieren[i] = sortieren[j];
sortieren[j] = temp;
}
}
}
return sortieren;
}
Erstellen Sie für diese Methode einen Kontrollflussgraphen!
Berechnen Sie die Zyklomatische Komplexität (McCabe-Zahl)! Die McCabe-Zahl M lässt sich am einfachsten über die Anzahl der binären Verzweigungen (b) berechnen: im Kontrollflußgraph oben haben die Knoten 2, 5 und 7 je zwei ausgehende Kanten (b=3)
M = b + 1 = 4 Hintergrund: Artikel zur McCabe-ZahlWelche Testfälle erhalten Sie aus Blackbox-Sicht für diesen Algorithmus? Erstellen Sie für jede Blackbox-Systematik zwei Testfälle und dokumentieren Sie die erforderlichen Informationen für die Testfälle tabellarisch!
Testfall Nr. |
Beschreibender Name der Testklasse / des Testfalls | Vor- bedingungen |
Eingabewerte (Parameter sortieren ) |
Erwartetes Resultat |
Nach- bedingungen |
Tatsächliches Resultat |
bestanden / nicht bestanden |
---|---|---|---|---|---|---|---|
1. | ÄK: Unsortiertes Array | keine | selectionsort([2;3;1]) |
[1;2;3] | - | ?? | ?? |
2. | ÄK: bereits sortiertes Array | keine | selectionsort([3;4;5]) |
[3;4;5] | - | ?? | ?? |
3. | GW: Leeres Array | keine | selectionsort([]) |
[] | - | ?? | ?? |
4. | GW: Ungültige Werte | keine | selectionsort(NULL) |
Exception | - | ?? | ?? |
- Es liegt folgender Testfall vor:
Berechnen Sie den Anweisungsüberdeckungsgrad und den Zweigüberdeckungsgrad! Anhand des Kontrollflussgraphen - am besten selbst nach zeichnen, alle durchlaufenen Anweisungen und Kanten kennzeichnen:
Anweisungsüberdeckung: C_0 = \frac{9}{9} = 100%
Über Zweige: Zweig 7-6 wird nicht durchlaufen,
Zweigüberdeckung: C_1 = \frac{5}{6} = 100% Über Kanten: Kante 7-6 wird nicht durchlaufen,
Kantenüberdeckung: C_1 = \frac{10}{11} = 100%
F) Aufgabe Blackbox-Tests
Testfallerstellung: Es soll eine einfache Funktion getestet werden, die ganzzahliges Teilen wie in der Grundschule durchführt und das Ergebnis als Zeichenkette ausgibt:
def dividiere(divident:int, divisor:int)-> str:
ganzzahl_ergebnis = divident // divisor
rest_ergebnis = divident % divisor
aufgabe = str(divident)+ " : " +str(divisor)+" = "
ergebnis = str(ganzzahl_ergebnis) + " Rest " + str(rest_ergebnis)
return aufgabe + ergebnis
Ausgabe: 10 : 3 = 3 Rest 1
Testfall Nr. |
Beschreibender Name der Testklasse / des Testfalls | Vor- bedingungen |
Eingabewerte (Parameter divident , divisor ) |
Erwartetes Resultat gemäß Spezifikation |
Nach- bedingungen |
Tatsächliches Resultat |
bestanden / nicht bestanden |
---|---|---|---|---|---|---|---|
1. | ÄQ: Ergebnis ist Ganzzahl | keine | divident=8 divisor=4 |
2 | - | 2 | ok |
2. | ÄK: Ergebnis ist Gleitkommazahl | keine | divident=1 divisor=10 |
0.1 auf sechs signifikante Stellen genau |
- | 0.1 | ok |
3. | GW: Teilen durch 0 | keine | divident=1 divisor=0 |
UI muss zur Korrektur der Werte auffordern | - | Exception | fail |
4. | GW: Teilen mit 0 | keine | divident=0 divisor=1 |
0 | - | 0 | ok |
Links und weitere Informationen
Quellen und offene Ressourcen (OER)
Die Ursprungstexte (als Markdown), Grafiken und zugrunde liegende Diagrammquelltexte finden sich (soweit möglich in weiterbearbeitbarer Form) in folgendem git-Repository:
http://gitlab.com/oer-informatik/db-sql/dbms.
Sofern nicht explizit anderweitig angegeben sind sie zur Nutzung als Open Education Resource (OER) unter Namensnennung (H. Stein, oer-informatik.de) freigegeben gemäß der Creative Commons Namensnennung 4.0 International Lizenz (CC BY 4.0).