Unit-Tests
Ziel von Unit-Tests
Um fehlerarme Software zu entwickeln, muss diese ausgiebig getestet werden. Testen ist deshalb eine sehr wichtige Arbeit, die allerdings in der Praxis zu oft vernachlässigt wird, da Testen zusätzlichen Aufwand bedeutet, der meist auch nicht besonders viel Spaß macht.
Hier kommen automatisierte Unit-Tests bzw. Modultests ins Spiel: Ziel von Unit-Tests ist es einzelne Teile (Module) von Software zu testen. Im Idealfall erfolgt dies automatisch, damit bei einer Änderung der Software nicht alle durchgeführten Tests per Hand wiederholt werden müssen. In Java gibt es dafür das JUnit-Framework, das einem die Arbeit erleichtert.
Objektzustände in BlueJ speichern
In BlueJ kann man JUnit "missbrauchen", um sich schnell und einfach den Zustand der Objekte der Objektleiste zu speichern. Wie man das macht, ist im Anhang - BlueJ optimal nutzen beschrieben. Das kann sehr praktisch sein, hat aber mit Tests nur bedingt zu tun, weshalb hier nicht weiter darauf eingegangen wird.
Ausgangsbeispiel
Als Basis für eine kleine Beispielsoftware, die getestet werden soll, soll eine einfache Mensch
-Klasse dienen (kein UML sondern Java-spezifisch):
Ein Mensch
hat ein alter
, das man setzen und abfragen kann. Beim Setzen des Alters sollen nur sinnvolle Werte zulässig sein. Andernfalls wird das Alter nicht verändert. Außerdem kann man abfragen, ob ein Mensch volljährig ist.
Ein Mensch
A kann heiraten, und kennt anschließend seinen partner
B. B soll dann automatisch auch mit A verheiratet sein.
Testgetriebene Entwicklung
Ein Grund, warum Tests oft vernachlässigt werden, ist, weil die Tests im Anschluss an die Implementierung erfolgen müssen. Oft wird die Zeit dann knapp und Tests erfolgen nur oberflächlich, da man ja gewissenhaft programmiert hat, und schon hoffentlich alles funktionieren wird.
Wenn man schon bei der Implementierung Testfälle anlegt, können diese mit Hilfe von JUnit automatisch ausgeführt und ausgewertet werden. Testen beschränkt sich dann, vorausgesetzt alle Tests sind erfolgreich, auf einen Mausklick. Ein weiterer Vorteil dieser Vorgehensweise ist, dass man sich schon vor der Implementierung Gedanken zu möglichen Fällen, die eintreten können, macht.
Um Testfälle anlegen zu können, muss man zuerst das Grundgerüst für den zu testenden Code anlegen. Damit man den Code compilieren kann, müssen Methoden mit Rückgabewert Dummy-Werte zurückgeben. Diese müssen zum Rückgabetyp der Methode passen. Bei Klassen als Rückgabetyp kann man z.B. einfach null
zurück geben. Das Grundgerüst kann somit folgende Gestalt haben:
JUnit-Tests in BlueJ interaktiv erstellen
Hat man das Grundgerüst geschrieben und eine syntaktisch korrekte Klasse als Basis, lassen sich schon alle später durchzuführenden Tests formulieren. BlueJ bietet einem besonderen Komfort, da man hier die Tests interaktiv erstellen kann, ohne eine Zeile Code schreiben zu müssen.
Der folgende Schritt ist für BlueJ ab Version 4 nicht mehr notwendig: Zuerst müssen in BlueJ die Testwerkzeuge aktiviert werden. Unter Windows aktivierst Du dazu die Option Teamwerkzeuge anzeigen unter Werkzeuge→ Einstellungen→ Interface. Unter OS X aktivierst Du die Option unter BlueJ→ Einstellungen→ Interface.
Für Klassen, die getestet werden sollen, legt man durch Rechtsklick auf die Klassen entsprechende Testklassen an:
Um klassenübergreifende Tests zu erstellen, kann man alternativ eine neue Klasse vom Typ Unit-Test erstellen:
Die Funktionalität unterscheidet sich nicht, sondern nur die Darstellung in BlueJ:
Anschließend kann man beliebig viele Tests erstellen. Alle interaktiv erzeugten Objekte und Methodenaufrufe werden dabei gespeichert. Bei Methoden mit Rückgabewerten kann man dann die erwarteten Werte eingeben:
Video PlayerDies funktioniert auch, wenn Referenzen auf Objekte zurückgegeben werden sollen:
Video PlayerAnschließend könne alle Tests durch Klick auf Tests starten automatisch ausgeführt werden. Dabei wird dann überprüft, ob die Rückgabewerte der Methoden mit den erwarteten Rückgaben übereinstimmen. Da wir bisher nur Dummywerte zurückgeben, können die Tests hier natürlich noch nicht erfolgreich sein:
Aufgabe 1 - Testfälle erstellen
Probiere das alles wie oben dargestellt aus. Ergänze mindestens einen Test, der überprüft, ob die setAlter
-Methode funktioniert. Diese soll bei sinnvollen Werten das Attribut alter
überschreiben. Ein negatives Alter oder ein Alter über 130 sollen ignoriert werden; das Alter bleibt dann unverändert.
Ergänze die Implementierung, so dass die Tests erfolgreich ablaufen.
JUnit-Tests als Programmcode
BlueJ erstellt aus den interaktiv erstellten Tests Quellcode. In anderen Entwicklungsumgebungen muss der Code für die Tests selbst geschrieben werden. Aber auch innerhalb von BlueJ kann es komfortabler sein, Testcode zu erweitern, anstatt neue Testfälle "zusammenzuklicken". Die im obigen Video interaktiv erstellte Testmethode testHeiraten
wird von BlueJ übersetzt in folgenden Code:
Bei der Angabe @Test
handelt es sich um eine sogenannte Annotation. Wir gehen hier nicht weiter auf Annotations ein, sondern halten nur fest, dass diese vor einem Test angegeben sein muss. Innerhalb der Methode wurden dann genau die Aktionen, die wir per Maus durchgeführt haben, codiert. Um erwartete und reale Rückgabewerte von Methoden zu vergleichen, nutzt man die Methode assertEquals
, die im JUnit-Framework definiert ist.
Dieses Wissen über Testmethoden reicht aus, um Testmethoden leicht abzuändern oder zu ergänzen. Auf diese Art hat man z.B. auch Zugriff auf Attribute (sofern diese nicht als private
definiert sind), oder man kann komplexere Ausdrücke formulieren. Man könnte obige Methode dann z.B. folgendermaßen formulieren:
Ein anderer Anwendungsfall könnte gegeben sein, wenn man auf Attribute zugreifen möchte (und darf):
Weitere Methoden
Neben der Methode assertEquals(...)
gibt es noch einige andere Methoden, um die korrekte Funktionsweise des Programms zu überprüfen:
assertNull(...)
/assertNotNull(...)
: Überprüft, ob die als Parameter übergebene Referenz (un)gleichnull
ist.assertSame(...)
/assertNotSame(..., ...)
: Überprüft, ob die übergebenen Referenzen auf das selbe (ein anderes) Objekt zeigen.assertEquals(..., ...)
/assertNotEquals(..., ...)
: Überprüft, ob die übergebenen Parameter den gleichen (einen anderen) Wert besitzen. Bei Objekten wird die vonObject
geerbte und eventuell überschriebene Methodeequals(...)
zur Überprüfung herangezogen.
Aufgabe 2 - Manueller Code
Erstelle weitere Tests manuell oder erweitere schon vorhandene Tests.