Gestaltung von Unit-Tests

Aus MimiPedia

Im Folgenden sind einige Konventionen für das Schreiben von Unit-Tests zusammengestellt. Es gibt viele verschiedene Meinungen und Ansätze an Unit-Tests heranzugehen; die folgenden Regeln haben sich in der Praxis als eine einfache Variante bewährt um schnell vorwärts zu kommen.

Wichtig ist, daß man sich für ein Vorgehen entscheidet und das dann möglichst konsequent durchzieht, am Besten in Absprache mit dem Entwicklungs-Team mit dem man gerade arbeitet.

Merke: Der Unit-Test soll den Entwickler unterstützen und jedem helfen der sie liest.

Regeln für Test-Klassen und Test-Methoden

Für die Beispiele betrachten wir eine einfache Klasse mit einer Methode

class TemperaturRechner {
    public long fahrenheit2Celsius(long f) {
        return (5 * f - 160) / 9;
    }
}
  • Für die zu testende Klasse wird eine eigene Test-Klasse mit dem Zusatz Test erstellt,
Zur Klasse TemperaturRechner erstellt man eine Klasse TemperaturRechnerTest
  • Alle Test-Methoden haben die gleiche Form (Kein Prä- oder Suffix "Test" am Methodennamen)
@Test public void xx(){...}
  • Der Bezeichner der Test-Methoden soll in einem Satz beschreiben was getestet wird und was das erwartete Ergebnis ist.
  • Getestet wird das Interface der zu testenden Klasse:
Alle Methoden mit Ausnahme der private-Methoden werden getestet.
  • Wenn zu viele Tests für eine Klasse zusammenkommen, kann man die Tests auch auf mehrere Testklassen verteilen. Schreibt man etwa für die Methode der obigen Klasse eine eigene Test-Klasse könnte si TemperaturRechnerFahrenheit2Cslsius heißen.

Nach diesen Regeln wird ein Unit-Test erstellt, der prüft, ob 32°F tatsächlich auf 0°C umgerechnet werden. Da der Methodenname nicht mit einer Ziffer beginnen kann, steht dabei die Einheit vor dem Wert. Alternativ kann man die Zahlen auch ausschreiben oder ein Wort voranstellen.

@Test
public void fahrenheit32ErgibtCelsius0() {
    TemperaturRechner rechner = new TemperaturRechner();
    long celsius = rechner.fahrenheit2Celsius(32);
    Assert.assertEquals(0, celsius);
}

Wie immer ist beim Finden passender Namen Kreativität gefragt.

Aufbau eines Unit-Tests

Ein Unit-Test -- also einer einzelnen Testmethode -- hat in der Regel drei Schritte:

Test vorbereiten

In diesem Schritt werden Voraussetzungen für den Test geschaffen, zum Beispiel Test-Objekte oder -Daten erzeugt. in der Literatur wird der Versuchsaufbau auch als Fixture oder Setup bezeichnet. Hier werden ggf. auch Voraussetzungen dokumentiert. Wenn zum Beispiel ein nicht-leeres Objekt benötigt wird, kann man das mit einer Assumption sicherstellen.

Test durchführen

In diesem Schritt wird die zu testende Methode in "geeigneter Weise" aufgerufen.

Ergebnis prüfen

Am Ende des Unit-Tests muß immer eine Prüfung stehen; ohne Assertion ist der Test kein Test! Mit einer geeigneten Assertion prüft man, ob das gewünschte Ergebnis eingetreten ist.

Assertion kann man mit der Assert-Klasse aus JUnit4 formulieren oder mit der Zusatzbibliothek AssertJ

Variationen

Test-Methoden komprimieren

Die drei – im vorangegangenen Abschnitt aufgeführten – Schritte müssen nicht explizit von einander getrennt sein. Kürzere Methoden sind oft leichter zu lesen als lange, erlaubt ist alles, was jeder andere Entwickler ohne Verrenkung verstehen kann. Den Beispiel-Test kann man auch so zusammendampfen:

@Test
public void fahrenheit32ErgibtCelsius0() {
    Assert.assertEquals(0, new TemperaturRechner().fahrenheit2Celsius(32));
}

Gemeinsame Test-Objekte

Die meisten Test-Klassen konzentrieren sich auf eine einzelne zu testende Klasse und benötigen oft nur eine einzige Instanz davon. Man kann sie daher als Feld der Test-Klasse deklarieren, initialisieren und in jedem Test verwenden. Das spart jedesmal eine Deklaration.

Als Bezeichner kann man z.B. testInstanz oder testObjekt verwenden, aber auch einen zur Klasse passenden Begriff wie im Code-Beispiel unten.

Erlaubt ist alles, was leserlich ist, je leichter umso besser. Abkürzungen sollten immer dann vermieden werden, wenn sie für die Leserschaft nicht selbstverstädlich ist. "sut" anstelle von "subjectUnderTest" führt eher zur Verwirrung.

Der Beispiel-Test sieht dann so aus:

public void TemperaturRechnerTest {
    TemperaturRechner rechner = new TemperaturRechner();
    @Test
    public void fahrenheit32ErgibtCelsius0() {
        Assertions.assertThat(rechner.farenheit2Celsius(32)).isEqualTo(0);
    }
}