i

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):

Mensch - Klassendiagramm

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:

class Mensch
{
    int alter;
    Mensch partner;

    int getAlter() {
        return 0;
    }

    void setAlter(int a) {
    }

    boolean istVolljaehrig() {
        return true;
    }

    void heiraten(Mensch p) {
    }

    Mensch getPartner() {
        return null;
    }
}

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:

Erstellen einer Testklasse

Um klassenübergreifende Tests zu erstellen, kann man alternativ eine neue Klasse vom Typ Unit-Test erstellen:

Allgemeine Testklasse erstellen

Die Funktionalität unterscheidet sich nicht, sondern nur die Darstellung in BlueJ:

Darstellung der Testklassen

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:

Dies funktioniert auch, wenn Referenzen auf Objekte zurückgegeben werden sollen:

Anschließ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:

Erfolglose Tests

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.

  • class Mensch
  • {
  • int alter;
  • Mensch partner;
  • boolean istVolljaehrig() {
  • if(alter >= 18)
  • return true;
  • else
  • return false;
  • }
  • void setAlter(int a) {
  • if(a >= 0 && a < 130)
  • alter = a;
  • }
  • int getAlter() {
  • return alter;
  • }
  • void heiraten(Mensch p) {
  • partner = p;
  • p.partner = this;
  • }
  • Mensch getPartner() {
  • return partner;
  • }
  • }

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:

@Test
public void testHeiraten()
{
    Mensch mensch1 = new Mensch();
    Mensch mensch2 = new Mensch();
    mensch1.heiraten(mensch2);
    assertEquals(mensch2, mensch1.getPartner());
    assertEquals(mensch1, mensch2.getPartner());
}

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:

@Test
public void testHeiratenManuell()
{
    Mensch a = new Mensch();
    Mensch b = new Mensch();
    a.heiraten(b);
    assertEquals(b, a.getPartner());
    assertEquals(a, a.getPartner().getPartner());
}

Ein anderer Anwendungsfall könnte gegeben sein, wenn man auf Attribute zugreifen möchte (und darf):

@Test
public void testSetAlter()
{
    Mensch m = new Mensch();
    m.setAlter(10);
    m.setAlter(200);
    m.setAlter(-5);
    assertEquals(10, m.alter); // Hier wird auf Attribut zugegriffen
}

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)gleich null 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 von Object geerbte und eventuell überschriebene Methode equals(...) zur Überprüfung herangezogen.
@Test
public void beispieltests()
{
    String s1 = new String("Hallo");
    String s2 = new String("Hallo");
    assertNotNull(s1);      // erfüllt, da s1 nicht null
    assertNotSame(s1, s2);  // erfüllt, da s1 und s2 unterschiedliche Objekte sind
    assertEquals(s1, s2);   // erfüllt, da s1 und s2 "Hallo" als Wert haben
}

Aufgabe 2 - Manueller Code

Erstelle weitere Tests manuell oder erweitere schon vorhandene Tests.

Suche

v
7.1.3.2.3
inf-schule.de/oop/java/beziehungen/keeporthrow/unittests
inf-schule.de/7.1.3.2.3
inf-schule.de/@/page/0JOV3yioY31fCPOy

Rückmeldung geben