Testen, 'this', Kommentare

in Arbeit ...

1. Testklassen

1.1. Idee des Testens

  • Stufe 0 …​ Lauffähigkeit ausprobieren

  • Stufe 1 …​ Testfälle überlegen, Methoden ausführen, Ergebnis mit Erwartungswert vergleichen

  • Stufe 2 …​ wie Stufe 1, allerdings Erwartungswert neben/über produziertem Ergebnis, sodass visueller Vergleich einfach und zuverlässig möglich

  • Stufe 3 …​ programmtechnsiches Vergleichen von Erwartungswert und produziertem Ergebnis

Welche Art von Ergebnissen gibt es:

Alle Nicht-Void-Methoden liefern einen Rückgabewert, der mit dem erwarteten Wert verglichen werden kann. Bei Diskrepanz liegt irgendwo ein Fehler vor.

Methoden, die eine Bildschirm-Ausgabe liefern, ermöglichen ebenfalls den Vergleich der produzierten Bildschirmausgabe mit der erwarteten.

Methoden, die "still" ihre Arbeit verrichten (z.B. die Setter, die keinen Rückgabewert und im Normalfall auch keine Bildschirmausgabe liefern), können nur indirekt hinsichtlich ihrer Wirkung geprüft werden - die Setter ändern ein Attribut, diese Änderung kann z.B. mit dem Getter geprüft werden (oder direkt im BlueJ-Objekt-Inspektor). Recht oft müssen also Methodenkombinationen für Tests genutzt werden (wobei sehr oft eine der Methoden der Getter ist).

1.2. Testklassen

Testklassen erlauben die Automatisierung des händischen Testens in BlueJ. Bei komplexeren Klassen ist dies eine große Erleichterung und ermöglicht gründliches Testen bei jeder Code-Änderung ohne nennenswerten Aufwand.

Auf der anderen Seite erfordert das Erstellen einer sinnvollen Menge von Tests einigen Aufwand. Sobald diese Tests aber erstellt sind, ist je nach realisierter Stufe (siehe oben) der Aufwand erheblich bis enorm reduziert.

Wir werden später eine direkt in BlueJ (und alle besseren IDEs) eingebaute Test-Funktionalität kennenlernen, die den Test-Aufwand nach Erstellung der Tests nahezu vernachlässigbar macht und sehr effizientes Auffinden des Fehlers ermöglicht: JUnit

Sinnvollerweise gliedert man die Tests in mehrere "sprechend" benannte Methoden.

1.3. Erstellen von Objekten im Code

Bisher haben wir neue Objekte in BlueJ mittels Rechtsklick auf das passende Klassensymbol erzeugt, indem wir einen der Einträge, die mit "new" beginnen und danach die vorhandenen Konstruktoren zeigen, ausgewählt haben.

Normalerwewise werden Objekte aber direkt im Code (häufig anderer Klassen) erzeugt/instanziert und häufig in Objektvariablen zur weiteren Verwendung "gespeichert" (eigentlich wird eine Refernenz auf das Objekt gespeichert – weiteres siehe in Kürze unter "Assoziationen"):

    // ...
    Person person1 = new Person("Evi", 1999);
    System.out.println("Evis Geburtsjahr: " + person1.getGebJahr());
    // ...

Wie oben ersichtlich, kann an der Objektreferenz person1 mit Punkt + Methodenaufruf jede (public) Methode der Klasse Person für das referenzierte Objekt aufgerufen werden.

1.4. Beispiel zum Testen

Hier der komplette Source-Code eines Beispiels:

public class RechtWinkelDreieck {
    private float seiteA = 3.0f;  // m
    private float seiteB = 4.0f;  // m

    public RechtWinkelDreieck() {
        // kann leer bleiben, die Initialwerte passen.
    }
    public RechtWinkelDreieck(float seiteA, float seiteB) {
        if (seiteA <= 0 || seiteB <= 0) {
            System.out.println("ERR: Seiten a <= 0 || b <= 0: a=" + seiteA + ", b=" + seiteB);
        } else {
            this.seiteA = seiteA;
            this.seiteB = seiteB;
        }
    }
    public float getSeiteA() {
        return this.seiteA;  // 'this.' optional
    }
    public float getSeiteB() {
        return seiteB;
    }
    public float calcSeiteC() {
        // phytagoras; sqrt = SquareRoot = QuadratWurzel
        return (float)/*type cast double->float*/ Math.sqrt(seiteA*seiteA + seiteB*seiteB);
    }
    public float calcUmfang() {
        return seiteA + seiteB + calcSeiteC();
    }
    public float calcFlaeche() {
        return seiteA*seiteB/2;
    }
    public void printInfo() {
        System.out.println("==== RechtWinkelDreieck: ====");
        System.out.println("  Seite a (Kathethe 1)  = " + seiteA);
        System.out.println("  Seite b (Kathethe 2)  = " + seiteB);
        System.out.println("  Seite c (Hypothenuse) = " + calcSeiteC());
        System.out.println("  Berechnete Fläche     = " + calcFlaeche());
    }
}
public class TestRwDreieck {
    public void testNiceCases() {
        RechtWinkelDreieck rwd0 = new RechtWinkelDreieck();
        System.out.println("==== Default-RechtWinkelDreieck() 'nice cases': ====");
        System.out.println("  Seite a - erwartet/erhalten: 3.0/" + rwd0.getSeiteA());
        System.out.println("  Seite b - erwartet/erhalten: 4.0/" + rwd0.getSeiteB());
        System.out.println("  Seite c - erwartet/erhalten: 5.0/" + rwd0.calcSeiteC());
        System.out.println("  Umfang - erwartet/erhalten: 12.0/" + rwd0.calcUmfang());
        System.out.println("  Flaeche - erwartet/erhalten: 6.0/" + rwd0.calcFlaeche());
        RechtWinkelDreieck rwd1 = new RechtWinkelDreieck(6.0f, 8.0f);
        // ...
    }
    public void testBadCases() {
        System.out.println("==== RechtWinkelDreieck 'bad cases': ====");
        System.out.println("Erzeuge RechtWinkelDreieck(0.0, 1.0):");
        System.out.println("  Erwartet: ERR: Seiten a <= 0 || b <= 0: a=0.0, b=1.0");
        System.out.print("  Erhalten: "); // <- print (ohne ln): Fortsetzen in gleicher Zeile!
        RechtWinkelDreieck rwd1 = new RechtWinkelDreieck(0.0f, 1.0f);
        System.out.println("  Seite a - erwartet/erhalten: 3.0/" + rwd1.getSeiteA());
        System.out.println("  Seite b - erwartet/erhalten: 4.0/" + rwd1.getSeiteB());
    }
}

Methode testNiceCases() liefert:

==== Default-RechtWinkelDreieck() 'nice cases': ====
  Seite a - erwartet/erhalten: 3.0/3.0
  Seite b - erwartet/erhalten: 4.0/4.0
  Seite c - erwartet/erhalten: 5.0/5.0
  Umfang - erwartet/erhalten: 12.0/12.0
  Flaeche - erwartet/erhalten: 6.0/6.0

Naturgemäß sollten wie angedeutet mehrere Fälle (Nicht-Default-Konstruktor) analog getestet werden.

Methode testBadCases() liefert:

==== RechtWinkelDreieck 'bad cases': ====
Erzeuge RechtWinkelDreieck(0.0, 1.0):
  Erwartet: ERR: Seiten a <= 0 || b <= 0: a=0.0, b=1.0
  Erhalten: ERR: Seiten a <= 0 || b <= 0: a=0.0, b=1.0
  Seite a - erwartet/erhalten: 3.0/3.0
  Seite b - erwartet/erhalten: 4.0/4.0

Hier werden Fehlerausgaben untereinander ausgegeben, was einen visuellen Text-Vergleich sehr einfach macht.
Bei mehrzeiligen Fehlerausgaben ist die naturgemäß etwas schwieriger, aber immer noch die beste Möglichkeit (Später werden wir den Soll/ist-Vergleich komfortabler im Programm erledigen). Zahlenvergleiche lassen sich nebeneinander sehr gut durchführen.

2. Verwenden von 'this'

Mithilfe des Schlüsselwortes this kann explizit auf die Attribute der Klasse zugegriffen werden (auch auf die Methoden - also auf die Instanz-Elemente der obersten Ebene). Damit wird es möglich, in den Settern den Übergabeparameter gleich wie das Attribut zu nennen, was den Code einfacher zu schreiben und mit etwas Routine auch einfacher zu lesen macht:

// Alt:
public void setName(String neuerName) {
    name = neuerName;
}
// Neu:
public void setName(String name) {
    this.name = name;
}

Das Schlüsselwort 'this' ist für Zugriff auf eigene Attribute und Methoden immer erlaubt, aber im Fall von Attributen erforderlich, sobald es Namensgleichheiten mit lokalen Variablen gibt.

Bei Namensgleichheiten nimmt der Compiler die "näheste" Deklaration (also die lokalen Variablen u.ä.).

Wenn es nicht explizit angegeben ist und keine Namensgleichheiten bestehen "denkt sich der Compiler das 'this.' dazu".

3. Kommentare

In Java gibt es drei Arten von Kommentaren:

  • Einzeilen-Kommentar …​ Beginnend bei // (außerhalb von String-Literalen) bis zum Zeilenende

  • Block-Kommentar …​ von /* bis zum nächsten */ innerhalb einer Zeile oder auch über mehrere Zeilen (wieder /* außerhalb von String-Literalen).
    Ein Block-Kommentar kann überall dort stehen, wo ein Leerzeichen erlaubt ist – also z.B. in int z = 15; System.out.println("Zahl z=" + z);`ist für den Compiler gleichwertig mit `int z = 15; /*Kommentar1 */ System.out.println(/*Kommentar2 */ "Zahl z=" + z);

  • Javadoc-Kommentar …​ Beginnend mit /** bis zum ersten */ über eine oder mehrere Zeilen unmittelbar über Klassendeklaration, Instanzvariablen, Konstruktoren oder Methoden.
    Für den Compiler sind das normale Block-Kommentare, deren Inhalt mit einem '*' beginnt.

3.1. Javadoc-Kommentare

Das Werkzeug javadoc erzeugt aus der Top-Level-Struktur (Klassendeklaration, Attribute, Konstruktoren, Methoden) und den jeweils direkt darüber definierten Javadoc-Kommentaren (samt HTML-5 Elementen darin) eine standardisierte Klassendokumentation, wie sie z.B. für die Klasse String unter https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/String.html angesehen werden kann.

Details dazu siehe auch unter Java Dokumentations-Kommentare: Javadoc.