Software lässt sich auf verschiedene Arten testen. In BlueJ hat man z.B. die Möglichkeit über den Objektinspektor den Zustand von Objekten zu verfolgen. Man kann damit sehr gut verfolgen, ob sich der Zustand eines Objekts erwartungsgemäß ändert. Man kann damit allerdings keine Funktionalität überprüfen, die den Zustand eines Objekts nicht ändert. Soll z.B. ein Würfel sein Volumen berechnen und als Ergebnis zurückliefern, lässt sich anhand des Objektinspektors nicht erkennen, ob das richtige Ergebnis berechnet wurde, da sich der Zustand des Objekts dadurch nicht verändert hat. Zusätzlich ist der Objektinspektor eine Spezialität von BlueJ, so dass das Testen anhand des Objektinspektors in einigen Fällen sehr sinnvoll und praktikabel ist, aber keine allgemein anwendbare Teststrategie sein kann.
Eine andere Möglichkeit des Testens stellen Ausgabeanweisungen dar. In praktisch jeder Programmiersprache lassen sich einfache Ausgabeanweisungen - wie in Java mit System.out.println() - formulieren. Mit Hilfe dieser Anweisungen lassen sich beliebig viele Werte von lokalen Variablen, Attributen oder Rückgabewerten protokollieren. Tatsächlich stellen solche Ausgabeanweisungen ein oft verwendetes Mittel dar, um schnell und einfach kleinere Tests durchzuführen.
Möchte man den genauen Ablauf der Programmausführung beobachten, bietet sich ein Debugger an. Mit Hilfe eines Debuggers kann man ein Programm schrittweise ausführen und dabei den Wert verschiedener Variablen beobachten.
Meist besteht ein Test nicht nur darin, irgendeine Methode aufzurufen und das Ergebnis mit dem erwarteten Ergebnis zu vergleichen. Zusätzlich müssen oft Objekte erzeugt und diese in einen definierten Zustand gebracht werden, bevor ein Test durchgeführt werden kann. Will man z.B. eine Methode testen, die einen Termin in einen Terminplaner einfügt, so müssen zuerst andere Termine eingetragen werden, um festzustellen, ob die Methode auch bei Terminüberschneidungen korrekt funktioniert. Um dies nicht bei jedem Test per Hand machen zu müssen, kann man Testklassen schreiben, die für jeden Test die entsprechenden Voraussetzungen schaffen. Beim oben genannten Beispiel würde man dann z.B. eine Methode schreiben, die einen neuen und leeren Terminplan erstellt, diesen mit einigen Terminen füllt und dann einen weiteren, kollidierenden Termin einfügt. Soll der Test durchgeführt werden, wird dann nur diese eine Methode der Testklasse aufgerufen, die dann selbstständig alle Voraussetzungen schafft.
Ein Test läuft im Prinzip immer nach dem folgenden Schema ab:
- Voraussetzungen schaffen (Oft: Objekte erzeugen und in einen definierten Zustand versetzen).
- Zu testende Funktionalität ausführen.
- Rückgabewerte, Ausgaben oder neuen Objektzustand mit den Erwartungen vergleichen.
Typischerweise müssen Tests einige male wiederholt werden, nämlich immer dann, wenn am Code Veränderungen vorgenommen wurden. Da Veränderungen an einer einzigen Stelle im Code die Funktionalität verschiedener Methoden - auch anderer Klassen - beeinflussen kann, müssten bei jeder Änderung idealerweise alle Tests wiederholt werden. Folglich ist es von hohem Nutzen Tests möglichst stark zu automatisieren. Mit Hilfe von Testklassen lassen sich immerhin die ersten beiden der obigen Schritte recht einfach wiederholen. Ein Nachteil aller oben genannten Methoden ist, dass der dritte Schritt - der Vergleich zwischen tatsächlichem Verhalten und erwartetem Verhalten - manuell durch einen Menschen erfolgen muss. Glücklicherweise gibt es Testframeworks, die auch diesen Schritt automatisieren und beliebig viele Tests, nachdem sie einmal eingerichtet wurden, automatisch ausführen können, und den Benutzer über den Erfolg oder Misserfolg der Tests unterrichten.
Debugger in BlueJ
Das folgende Video demonstriert die Benutzung des Debuggers in BlueJ. (Achtung: Einbettung funktioniert momentan nicht. Bitte Link unten anklicken)
Aufgabe - Debugger
Erstelle eine einfache Klasse, die z.B. mathematische Berechnungen durchführt und teste mit Hilfe des Debuggers.
Testframeworks - JUnit
JUnit ist ein Framework zum Testen von Java-Programmen, das besonders für automatisierte Unit-Tests einzelner Units (meist Klassen oder Methoden) geeignet ist. Es basiert auf Konzepten, die ursprünglich unter dem Namen SUnit für Smalltalk entwickelt wurden. Mittlerweile existieren JUnit-ähnliche Frameworks auch für viele andere Programmiersprachen. Oft werden diese Programme unter dem Namen xUnit zusammengefasst. (Nach Wikipedia)
In BlueJ ist die JUnit-Unterstützung recht elegant und einfach gelöst. Andere Werkzeuge wie Netbeans oder Eclipse unterstützen aber ebenfalls automatische Tests mit JUnit.
Aufgabe - JUnit-Tutorial
Bearbeite das Tutorial unter http://bluej.org/tutorial/testing-tutorial.pdf
Aufgabe - Mathe-Klasse
Besonders einfach lassen sich Tests auf mathematische Funktionen anwenden, da diese einem einfachen Eingabe-Verarbeitung-Ausgabe Schema folgen. Erstelle eine Klasse, die wichtige mathematische Funktionen bereit stellt. Dies können bspw. Funktionen sein, die den
- Betrag einer Zahl
- Rauminhalt und die Fläche von geometrischen Figuren
- Zins und Zinseszins
- Wert für verschiedene Konstanten wie Pi oder die Eulersche Zahl anhand von Näherungen
- Lösungswert bzw. die Werte einer quadratischen Gleichung
berechnen.
Erstelle sinnvoll ausgewählte Testfälle.
Aufgabe - Terminplaner
Interessanter wird es, wenn die Tests auch abhängig vom Zustand mehrerer Objekte sind. Als Beispiel soll ein einfacher Terminplaner gemäß den folgenden Anforderungen implementiert werden.
- Zur Vereinfachung soll dieser nur Termine für einen einzelnen Tag verwalten.
- Ein einzelner Termin hat eine Bezeichnung, einen Start-Zeitpunkt und einen Endzeitpunkt. Es reicht aus die Zeiten als volle Stunden zu speichern.
- Ein Terminplaner soll einen Termin hinzufügen können, falls die entsprechende Zeit noch frei ist.
- Termine eines bestimmten Zeitraums sollen gelöscht werden können.
- Ein Termin soll über dessen Bezeichnung gelöscht werden können.
- Die Gesamtzahl der Termine soll zurückgegeben werden.
- Erstelle ein UML-Diagramm der benötigten Klassen und erzeuge den Quellcode.
- Implementiere die geforderte Funktionalität.
- Wähle geeignete Testfälle, dokumentiere diese und teste den Terminplaner.
Vergleich der Testtechniken
Du hast im Wesentlichen die Möglichkeit von Ausgabeanweisungen, den Debugger und JUnit kennengelernt, um Software zu testen. Jede Technik hat ihre Stärken.
Aufgabe - Vergleich
Stelle die verschiedenen Möglichkeiten des Testens einander gegenüber. Möglichkeiten der Darstellung sind z.B. eine Tabelle oder eine Mindmap.
Konstruktion von Testmengen
Die Auswahl von Testfällen ist kein trivialer Prozess und entscheidend für die Qualität der Tests. Es existieren dabei zwei grundsätzlich verschiedene Herangehensweisen:
- Black-Box Tests
- White-Box Tests
Aufgabe
Informiere Dich über die wesentlichen Unterschiede zwischen Black- und White-Box Tests und beschreibe diese. Diskutiere Vor- und Nachteile der beiden Verfahren. Von wem können Testfälle formuliert werden?
Black-Box-Tests
Wichtige Techniken zum Ableiten von Testfällen bei Black-Box Tests sind u.a.:
- Abdecken von Äquivalenzklassen
- Testen von Grenzfällen
- Testen von typischen und untypischen Fällen
- Positives und negatives Testen
Aufgabe
Überlege anhand der Kurzbeschreibungen, was sich hinter den Techniken bei Black-Box Tests verbirgt und finde Beispiele. (Zur Not: Recherchiere im Internet)
White-Box-Tests
Bei White-Box Tests sind u.a. folgende Strategien üblich:
- Anweisungsüberdeckung
- Entscheidungsüberdeckung
- Pfadüberdeckung
Um White-Box-Tests durchzuführen, ist es hilfreich die zu testenden Algorithmen so darzustellen, dass der Kontrollfluss deutlich ist. Dazu eignen sich unter anderem Aktivitätsdiagramme, die - ebenso wie Klassendiagramme - Teil der UML sind.
Aufgabe
Finde heraus, worin sich die oben genannten Testmaße (Anweisungsüberdeckung, ...) unterscheiden, beschreibe die Unterschiede und gib anhand von Aktivitätsdiagrammen Beispiele.
Aufgabe
Erweitere die definierten Tests für den Terminplaner so, dass sowohl White- als auch Black-Box Tests ausreichend berücksichtigt sind. (Was heißt "ausreichend"...?)
Aufgabe
Schreibe eine Methode, die überprüft, ob eine als Parameter übergebene Zahl eine Primzahl ist, und true oder false zurück gibt.
Teste die Methode mit Hilfe von Black- und White-Box-Tests.
Testgetriebene Entwicklung
siehe Abschnitt auf inf-schule.de
Exkurs - Korrektheitsbeweise
siehe Abschnitt auf inf-schule.de