PHP Unit-Tests ohne Framework
Wer noch nie Tests geschrieben hat fängt oft nicht an weil er denkt er braucht erst ein Framework, eine Ordnerstruktur, einen Test-Runner. Man braucht nichts davon für den Anfang.
Das Prinzip
Ein Test führt Code aus und prüft ob das Ergebnis stimmt.
// test_math.php
function assert_equals(mixed $expected, mixed $actual, string $label): void
{
if ($expected !== $actual) {
echo "[FAIL] $label\n";
echo " Expected: " . var_export($expected, true) . "\n";
echo " Got: " . var_export($actual, true) . "\n";
} else {
echo "[OK] $label\n";
}
}
// Code der getestet wird
function add(int $a, int $b): int { return $a + $b; }
// Tests
assert_equals(5, add(2, 3), 'add(2, 3) = 5');
assert_equals(0, add(-1, 1), 'add(-1, 1) = 0');
assert_equals(-3, add(-1, -2),'add(-1, -2) = -3');
php test_math.php
Das ist ein Test-Runner. Simpel, direkt, ohne Abhängigkeiten.
Wenn es mehr werden: PHPUnit
composer require --dev phpunit/phpunit
// tests/MathTest.php
use PHPUnit\Framework\TestCase;
class MathTest extends TestCase
{
public function test_add_positive_numbers(): void
{
$this->assertSame(5, add(2, 3));
}
public function test_add_negative_numbers(): void
{
$this->assertSame(-3, add(-1, -2));
}
}
vendor/bin/phpunit tests/
Was testen
Nicht alles. Zu viele Tests sind so ein Problem wie zu wenige.
- Gute Kandidaten:
- Funktionen mit nicht-trivialer Logik (Berechnungen, Transformationen, Validierung)
- Code der Randfälle hat (leer, null, negative Zahlen, Unicode)
- Code der sich oft ändert
- Nicht sinnvoll:
- Getter und Setter
- Framework-Glue-Code
- Dinge die nur Datenbankzugriff machen
Datenbank-Abhängigkeit isolieren
// Schlecht testbar: direkte PDO-Abhängigkeit
function getUser(int $id): ?array
{
global $pdo;
return $pdo->query("SELECT * FROM users WHERE id = $id")->fetch();
}
// Testbar: Dependency Injection
function getUser(PDO $pdo, int $id): ?array
{
$stmt = $pdo->prepare('SELECT * FROM users WHERE id = ?');
$stmt->execute([$id]);
return $stmt->fetch() ?: null;
}
Mit Dependency Injection kann man im Test eine SQLite-In-Memory-Datenbank übergeben:
$pdo = new PDO('sqlite::memory:');
$pdo->exec('CREATE TABLE users (id INTEGER, email TEXT)');
$pdo->exec("INSERT INTO users VALUES (1, 'alice@example.com')");
$user = getUser($pdo, 1);
assert($user['email'] === 'alice@example.com');
Data Provider für Parametrisierung
public static function additionProvider(): array
{
return [
[2, 3, 5],
[-1, 1, 0],
[0, 0, 0],
];
}
/** @dataProvider additionProvider */
public function test_add(int $a, int $b, int $expected): void
{
$this->assertSame($expected, add($a, $b));
}
Ein Test, viele Eingaben — ohne Duplikation.