"Wir haben keine Tests" – das höre ich regelmäßig. Meistens folgt dann: "Aber das System ist zu komplex, da kann man keine Tests schreiben." Das stimmt so nicht. Was stimmt: Das System ist so gewachsen, dass es schwer zu testen ist. Das ist ein Unterschied. Hier beschreibe ich, wie ich in solchen Systemen trotzdem anfange – und warum der erste Test nicht perfekt sein muss.

Warum der erste Test der schwerste ist

In einem System ohne Tests gibt es keine Test-Infrastruktur. PHPUnit ist nicht installiert, es gibt keine phpunit.xml, keinen Autoloader, der konsistent funktioniert. Der Code selbst ist oft eng verzahnt: Datenbankzugriffe direkt in Funktionen, globale Variablen, require-Kaskaden beim Start. Bevor der erste Test grün ist, muss man also erst einmal eine Umgebung bauen, die Tests überhaupt ermöglicht.

PHPUnit installieren

Falls Composer noch nicht vorhanden ist, fange ich damit an. Dann:

composer require --dev phpunit/phpunit ^11
./vendor/bin/phpunit --version

Eine minimale phpunit.xml im Projektstamm:

<?xml version="1.0"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="vendor/phpunit/phpunit/phpunit.xsd"
         bootstrap="vendor/autoload.php"
         colors="true">
  <testsuites>
    <testsuite name="Application">
      <directory>tests</directory>
    </testsuite>
  </testsuites>
</phpunit>

Welche Funktion zuerst testen?

Die Antwort ist immer: die langweiligste. Keine Datenbankverbindung, keine globalen Variablen, keine HTTP-Requests. Eine reine Funktion, die Input nimmt und Output zurückgibt. In jedem System gibt es solche Funktionen – Preisberechnungen, Validierungslogik, Formatierungsfunktionen.

Angenommen, es gibt diese Funktion irgendwo im Code:

function berechneMwst(float $netto, float $satz = 0.19): float {
    return round($netto * $satz, 2);
}

Der erste Test dafür:

use PHPUnit\Framework\TestCase;

class MwstTest extends TestCase
{
    public function testStandardsatz(): void
    {
        $this->assertSame(19.0, berechneMwst(100.0));
    }

    public function testReduzierterSatz(): void
    {
        $this->assertSame(3.5, berechneMwst(50.0, 0.07));
    }
}

Simpel. Offensichtlich. Und trotzdem wertvoll: Die Funktion ist dokumentiert, ihr Verhalten ist festgehalten, und jede künftige Änderung kann dagegen geprüft werden.

Wenn der erste Test unmöglich zu schreiben ist – weil jede Funktion die Datenbank anspricht, Globals liest oder globale Seiteneffekte hat – dann liegt das Problem nicht am Entwickler und nicht an PHPUnit. Es liegt an der Struktur des Codes. Das ist keine Kritik, sondern eine Diagnose: Der Code muss refactored werden, bevor er testbar wird.

Fazit

Testbarkeit ist keine Eigenschaft, die Code zufällig hat oder nicht hat. Sie folgt aus der Struktur. Klare Eingaben, klare Ausgaben, keine versteckten Abhängigkeiten – das sind die Voraussetzungen. Der erste Test ist deshalb auch ein Spiegel: Er zeigt, wie der Code wirklich strukturiert ist. Wer damit anfängt, fängt gleichzeitig an, den Code besser zu verstehen – und das ist, ehrlich gesagt, schon die halbe Arbeit.