mitmario.dev

Was man testet und was nicht

PHP Sandbox 4 Min Lesezeit 3 BeispieleLektion 5 von 6

Nach dem ersten grünen Lauf kommt bei fast allen derselbe Gedanke: Jetzt alles testen. Das ist gut gemeint und der schnellste Weg, das Testen wieder aufzugeben. Eine Sammlung, die zu groß ist, wird langsam, bricht bei jeder Umbenennung und wird irgendwer irgendwann abschalten. Die Frage ist also nicht, ob man testet, sondern was.

Der wertvollste Test ist der zu einem gefundenen Fehler

Ein Test, der einen echten Fehler festhält
<?php

declare(strict_types=1);

use App\Versand;
use PHPUnit\Framework\TestCase;

final class VersandTest extends TestCase
{
    public function testUnterFuenfzigKostetDerVersand(): void
    {
        $this->assertSame(4.95, Versand::kosten(49.99));
    }

    public function testUeberFuenfzigIstErFrei(): void
    {
        $this->assertSame(0.0, Versand::kosten(80.0));
    }

    public function testAnDerGrenzeIstErSchonFrei(): void
    {
        // Genau 50 Euro. Das ist der Fall, den die Kundin gemeldet hat.
        $this->assertSame(0.0, Versand::kosten(50.0));
    }
}

Die Regel lautet: ab 50 Euro versandkostenfrei. Im Code steht > statt >=, und damit zahlt ausgerechnet die Kundin mit exakt 50 Euro noch 4,95. Zwei der drei Tests sind grün, der dritte ist rot, und er sagt in einer Zeile, was los ist: Failed asserting that 4.95 is identical to 0.0. Im Reiter Debug siehst du die drei Aufrufe durch dieselbe Zeile laufen, wenn src/Versand.php vorn liegt: summe steht auf 49.99, dann 80, dann 50, und beim dritten Mal sagt das > nein.

Derselbe Test nach der Behebung
<?php

declare(strict_types=1);

use App\Versand;
use PHPUnit\Framework\TestCase;

final class VersandTest extends TestCase
{
    public function testUnterFuenfzigKostetDerVersand(): void
    {
        $this->assertSame(4.95, Versand::kosten(49.99));
    }

    public function testUeberFuenfzigIstErFrei(): void
    {
        $this->assertSame(0.0, Versand::kosten(80.0));
    }

    public function testAnDerGrenzeIstErSchonFrei(): void
    {
        // Genau 50 Euro. Das ist der Fall, den die Kundin gemeldet hat.
        $this->assertSame(0.0, Versand::kosten(50.0));
    }
}

Ein Zeichen geändert, alle drei grün. Und was wichtiger ist: Dieser Fehler kommt nicht wieder. Wer in einem halben Jahr an der Versandregel arbeitet, bekommt sofort gesagt, wenn die Grenze wieder kippt.

Das ist die beste Investition, die es beim Testen gibt. Ein Fehler, der einmal passiert ist, ist kein Zufall: Die Stelle ist offenbar leicht misszuverstehen. Mach es deshalb zur Gewohnheit, zu jedem gemeldeten Fehler zuerst den Test zu schreiben, der ihn festhält, ihn rot zu sehen, und ihn erst dann zu beheben. Rot zu sehen ist dabei kein Ritual: Ein Test, den du nie hast reißen sehen, könnte genauso gut nichts prüfen.

Wo Tests sich lohnen

  • Rechnungen. Alles mit Geld, Mengen, Prozenten, Steuern. Fehler sind hier teuer und fallen spät auf.
  • Regeln. Wer darf was, ab wann gilt welcher Preis, welcher Status folgt auf welchen. Das ist der Teil deiner Anwendung, den es nur bei dir gibt.
  • Grenzfälle. Genau 50 Euro. Der leere Korb. Der letzte Tag im Monat. Null Treffer. Die meisten Fehler wohnen an den Rändern, nicht in der Mitte.
  • Alles, was schon einmal kaputt war. Siehe oben.

Wo sie wenig bringen

Drei Tests, die nichts beweisen
<?php

declare(strict_types=1);

use App\Kunde;
use App\Versand;
use PHPUnit\Framework\TestCase;

final class OhneWertTest extends TestCase
{
    public function testDerGetterGibtZurueckWasHineingegebenWurde(): void
    {
        // Prueft, dass PHP eine Eigenschaft speichern kann.
        $this->assertSame("Mia", (new Kunde("Mia"))->name());
    }

    public function testStrtolowerMachtKleinbuchstaben(): void
    {
        // Prueft die Standardbibliothek von PHP, nicht diesen Code.
        $this->assertSame("mia", strtolower("MIA"));
    }

    public function testRuftNurAufUndBehauptetNichts(): void
    {
        // Laeuft durch, prueft aber nichts. Fuer eine Abdeckungszahl
        // zaehlt diese Zeile trotzdem als geprueft.
        Versand::kosten(60.0);
    }
}

Diese drei sind das Gegenstück, und sie sind absichtlich so gebaut, dass sie sich nützlich anfühlen:

  • Der erste prüft einen Getter, der nichts tut, als zurückzugeben, was hineingegeben wurde. Er kann gar nicht fehlschlagen, solange PHP funktioniert.
  • Der zweite prüft strtolower(). Das ist die Standardbibliothek. Fremder Code hat seine eigenen Tests, und wenn nicht, ist ein eigener Test dafür die falsche Antwort auf das Problem.
  • Der dritte ruft nur auf und behauptet nichts. Sieh dir an, was PHPUnit dazu sagt: Der Punkt in der Fortschrittszeile wird zu einem R für riskant, und darunter steht This test did not perform any assertions. Am Ende steht dann OK, but there were issues!, und der Lauf endet mit einem Exit-Code von null; vendor/bin/phpunit; echo $? im Terminal zeigt die 0. Er gilt also als bestanden. Genau das macht diese Sorte Test so tückisch. Wer das nicht durchgehen lassen will, hängt --fail-on-risky an den Aufruf, dann endet derselbe Lauf mit 1.

Ebenfalls selten die Mühe wert: Vorlagen. Ob im HTML ein <li> an der richtigen Stelle steht, ändert sich bei jeder Gestaltungsrunde. Solche Tests reißen ständig, ohne dass je ein Fehler dahintersteckte, und genau davon lernt man, das rote Ergebnis nicht mehr ernst zu nehmen.

Testabdeckung ist eine Zahl zum Ansehen

Es gibt Werkzeuge, die messen, welcher Anteil deiner Zeilen beim Testlauf ausgeführt wurde. PHPUnit kann das, es braucht dafür allerdings eine zusätzliche PHP-Erweiterung, die hier im Kurs nicht eingebaut ist. Tipp vendor/bin/phpunit --coverage-text ins Terminal, dann sagt es das selbst: No code coverage driver available, und der Lauf endet mit einer Warnung.

Wichtiger als die Zahl ist ohnehin, wie man sie liest. Der dritte Test oben führt kosten() aus, ohne irgendetwas zu behaupten. Für die Abdeckung zählt die Zeile damit als erledigt. Eine Abdeckung von 100 Prozent heißt also: Jede Zeile ist einmal gelaufen. Sie heißt nicht: Jede Zeile tut das Richtige.

Brauchbar ist die Zahl in genau einer Richtung. Sieh dir an, was nicht abgedeckt ist. Wenn dort die Rabattrechnung steht, hast du etwas gelernt. Als Ziel taugt sie nicht: Eine Vorgabe von 90 Prozent erzeugt Tests wie den dritten oben, und die sind schlimmer als keine, weil sie Sicherheit vortäuschen.

Die einfache Faustregel

Frag dich bei jedem Test: Welcher Fehler würde hier auffallen? Fällt dir keiner ein, schreib ihn nicht. Fällt dir einer ein, der schon passiert ist, schreib ihn sofort.

Zum Mitnehmen

Nach dem ersten grünen Lauf will man alles testen. Das ist der schnellste Weg zu einer Sammlung, die niemand mehr pflegt. Die brauchbare Frage lautet bei jedem einzelnen Test: Welcher Fehler würde hier auffallen?

Jetzt du

Basis Konto, kostenlos

Zu dieser Lektion gehört eine Aufgabe. Du schreibst den Code selbst, und nach jedem Lauf sagt dir eine Prüfliste, was schon stimmt.

Dafür brauchst du das Basis Konto. Es kostet nichts, und ein Passwort gibt es auch nicht.

In diesem Kurs läuft dein Code auf einem Server. Dafür hat das Basis Konto 1 Stunde im Monat, mehr Zeit gibt es mit dem Premium Konto.

Was in dieser Lektion steckt

  • Artikel mit 3 Beispielen zum Ausprobieren

    Steht hier, ohne Konto lesbar.

  • Aufgabe, dein Code läuft auf einem Server

    Öffnet sich mit dem Basis Konto.