Unit Tests mit PHPUnit
Unit Tests mit PHPUnit
~17 Min. Lesezeit Zuletzt aktualisiert am 8. August 2026
Zeit für automatisierte Qualitätssicherung – wir testen ProjectStatistikService aus Kapitel 33 ISOLIERT, OHNE echte Datenbank.
Das Test-Pack installieren
composer require --dev symfony/test-packDie Recipe (Kapitel 6) installiert PHPUnit UND legt phpunit.dist.xml sowie tests/bootstrap.php an.
Unit-Tests vs. funktionale Tests: der Unterschied
| Test-Art | Eigenschaften |
|---|---|
| Unit-Tests (dieses Kapitel) | Testen EINE Klasse ISOLIERT, OHNE Datenbank, OHNE HTTP-Kernel – SCHNELL (Millisekunden), Abhängigkeiten werden durch Fakes/Mocks ersetzt. |
| Funktionale Tests (Kapitel 43) | Testen das ZUSAMMENSPIEL mehrerer Komponenten über eine ECHTE HTTP-Anfrage – LANGSAMER, aber realistischer. |
Den ersten Unit-Test schreiben
php bin/console make:test TestCase ProjectStatistikServiceTest<?php
declare(strict_types=1);
namespace App\Tests\Service;
use App\Entity\Project;
use App\Repository\TaskRepository;
use App\Service\ProjectStatistikService;
use PHPUnit\Framework\TestCase;
class ProjectStatistikServiceTest extends TestCase
{
public function testBerechneStatistikOhneAufgaben(): void
{
$taskRepository = $this->createMock(TaskRepository::class);
$taskRepository->method('zaehleAufgabenNachStatus')->willReturn([]);
$service = new ProjectStatistikService($taskRepository);
$ergebnis = $service->berechneStatistik(new Project());
self::assertSame(0, $ergebnis['offen']);
self::assertSame(0.0, $ergebnis['fortschritt']);
}
public function testBerechneStatistikMitFortschritt(): void
{
$taskRepository = $this->createMock(TaskRepository::class);
$taskRepository->method('zaehleAufgabenNachStatus')->willReturn([
['status' => 'erledigt', 'anzahl' => 3],
['status' => 'offen', 'anzahl' => 1],
]);
$service = new ProjectStatistikService($taskRepository);
$ergebnis = $service->berechneStatistik(new Project());
self::assertSame(75.0, $ergebnis['fortschritt']);
}
}createMock(TaskRepository::class) erzeugt ein FAKE-Repository, das NIEMALS eine echte Datenbank berührt – method(...)->willReturn(...) definiert, WAS die Fake-Methode zurückgeben soll, wenn sie aufgerufen wird. GENAU DAS ist der praktische Wert von Dependency Injection aus Kapitel 32: ProjectStatistikService weiß NICHT, ob es mit einem echten oder einem Fake-Repository arbeitet.
Die Tests ausführen
php bin/phpunit
php bin/phpunit tests/Service/ProjectStatistikServiceTest.php # nur eine Datei
php bin/phpunit --filter testBerechneStatistikMitFortschritt # nur eine MethodeWichtige Assertions im Überblick
assertSame($erwartet, $tatsaechlich)– strikte Gleichheit (===), IMMER bevorzugen gegenüberassertEquals().assertTrue(...)/assertFalse(...)– Boolean-Prüfung.assertCount($anzahl, $array)– Array-/Collection-Länge.assertInstanceOf(Klasse::class, $objekt)– Typ-Prüfung.expectException(Klasse::class)– erwartet, dass eine bestimmte Exception geworfen wird.
public function testWirftFehlerBeiUngueltigemStatus(): void
{
$this->expectException(\InvalidArgumentException::class);
$aufgabe = new Task();
$aufgabe->setStatus('ungueltiger-status');
}Test Coverage messen
php bin/phpunit --coverage-html var/coverageErzeugt einen HTML-Bericht, der ZEILENGENAU zeigt, welcher Code von Tests abgedeckt ist – NÜTZLICH, um blinde Flecken zu finden, aber KEIN Selbstzweck: 100% Coverage garantiert NICHT automatisch fehlerfreien Code, nur dass jede Zeile MINDESTENS EINMAL ausgeführt wurde.
Tipp: Faustregel: Unit-Tests eignen sich HERVORRAGEND für Services mit klarer, isolierbarer Logik wie ProjectStatistikService – für Code, der stark von der Datenbank oder dem HTTP-Kontext abhängt (Controller, Repositories selbst), sind funktionale Tests (Kapitel 43) oft der PASSENDERE Ansatz.