Automatisierte Tests in PHP – PHPUnit und Pest
Erste Schritte: Automatisierte Tests in PHP – PHPUnit und Pest
Automatisierte Tests stellen sicher, dass Änderungen am Code keine unerwünschten
Nebenwirkungen haben.
In der PHP‑Welt sind PHPUnit (De‑facto‑Standard seit Jahren) und
Pest (fluente Syntax + PHPUnit‑Engine) die populärsten
Frameworks.
Dieses Tutorial zeigt Installation, Beispiel‑Tests und CLI‑Workflows –
alle Code‑Snippets stehen im <pre>‑Block.
1. Installation via Composer
# PHPUnit (klassisch) composer require --dev phpunit/phpunit ^10 # Pest (baut intern auf PHPUnit auf) composer require --dev pestphp/pest ^2
- Pakete landen in require-dev – nicht in Produktion.
 - Pest installiert eigene CLI (
vendor/bin/pest
), zieht PHPUnit automatisch mit.
 
2. PHPUnit – Erster Testfall
<?php
// tests/Unit/TaskTest.php
use App\Model\Task;
use PHPUnit\Framework\TestCase;
class TaskTest extends TestCase
{
    public function test_title_must_not_be_empty(): void
    {
        $this->expectException(InvalidArgumentException::class);
        $task = new Task();
        $task->setTitle('');     // sollte Exception werfen
    }
    public function test_status_defaults_to_open(): void
    {
        $task = new Task();
        $this->assertSame('open', $task->status);
    }
}
?>
vendor/bin/phpunit
 durchsucht standardmäßig tests/ und
gibt ein farbiges Ergebnis: grün ✔︎ oder rot ✖︎.
3. PHPUnit Konfiguration – phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunit bootstrap="vendor/autoload.php"
         colors="true"
         stopOnFailure="false">
  <testsuites>
    <testsuite name="Unit">
      <directory suffix="Test.php">tests/Unit</directory>
    </testsuite>
  </testsuites>
</phpunit>
4. Pest – eine expressivere Syntax
# tests/Feature/ExampleTest.php
use App\Model\Task;
it('creates a task', function () {
    $task = new Task();
    $task->title = 'Hello';
    expect($task->title)->toBe('Hello');
});
it('throws when title empty')
    ->expect(fn () => (new Task())->setTitle(''))
    ->toThrow(InvalidArgumentException::class);
CLI‐Aufruf:
vendor/bin/pest
Pest verwendet „higher‑order tests“ & expect()‑Fluent‑API –
weniger Boilerplate.
5. Datenbank‑Tests – Memory‑SQLite (Beispiel)
protected function setUp(): void
{
    $pdo = new PDO('sqlite::memory:');
    $pdo->exec(file_get_contents(__DIR__.'/../../database/create_tasks.sql'));
    App\Core\Database::mock($pdo);  // eigene Helper‑Methode
}
So testest du Models ohne echte MySQL‑Instanz – schnell & isoliert.
6. Test Doubles – Mock & Stub
use PHPUnit\Framework\MockObject\MockObject;
$logger = $this->createMock(LoggerInterface::class);
$logger->expects($this->once())
       ->method('info')
       ->with('Task created');
$service = new TaskService($logger);
$service->create('Demo');
7. CLI‑Shortcuts in composer.json
{
  "scripts": {
    "test": "phpunit",
    "pest": "pest --coverage"
  }
}
Jetzt genügt
composer test
oder
composer pest
.
8. Continuous Integration ( GitHub Actions )
# .github/workflows/test.yml
name: Tests
on: [ push, pull_request ]
jobs:
  phpunit:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: shivammathur/setup-php@v2
        with: { php-version: '8.3', coverage: pcov }
      - run: composer install --no-progress --no-suggest
      - run: composer test       # oder composer pest
9. Code‑Coverage & Mutation‑Tests
- pcov oder xdebug für Coverage (
--coverage-html build/coverage
).
 - Infection PHP prüft Test‑Qualität via Mutation‑Testing.
 
10. Best Practices
- Eine Testklasse pro Produktionsklasse, klare Namenskonvention.
 - Tests unabhängig – keine Reihenfolge, keine globalen States.
 - arrange → act → assert Muster (gegeben / wenn / dann).
 - Schnelle Unit‑Tests lokal, langsamere Integration‑Tests im CI.
 
Fazit
PHPUnit bietet mächtige Assertions, Mocks & Konfig‑Flexibilität,
Pest liefert syntaktischen Zucker oben drauf.
Egal welches Tool – automatisierte Tests sind der Sicherheitsgurt für deine
PHP‑Anwendung: Sie verhindern Regressionen, erleichtern Refactoring und bilden
eine lebendige Dokumentation deines Codes.