Unit Testing PHPUnit Grundlagen
PHPUnit: Unit Testing für PHP
Tests sind kein Luxus, sondern Notwendigkeit. Mit PHPUnit schreiben Sie automatisierte Tests, die Bugs verhindern und Refactoring ermöglichen.
Installation
# Als Dev-Dependency composer require --dev phpunit/phpunit # Version prüfen ./vendor/bin/phpunit --version
Projektstruktur
projekt/ ├── src/ │ ├── Calculator.php │ └── User.php ├── tests/ │ ├── CalculatorTest.php │ └── UserTest.php ├── composer.json └── phpunit.xml
phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<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="Unit">
<directory>tests</directory>
</testsuite>
</testsuites>
<coverage>
<include>
<directory suffix=".php">src</directory>
</include>
</coverage>
</phpunit>
Erster Test
Zu testende Klasse
<?php
// src/Calculator.php
namespace App;
class Calculator
{
public function add(int $a, int $b): int
{
return $a + $b;
}
public function divide(int $a, int $b): float
{
if ($b === 0) {
throw new \InvalidArgumentException('Division durch Null');
}
return $a / $b;
}
}
Test-Klasse
<?php
// tests/CalculatorTest.php
namespace Tests;
use App\Calculator;
use PHPUnit\Framework\TestCase;
class CalculatorTest extends TestCase
{
private Calculator $calculator;
protected function setUp(): void
{
$this->calculator = new Calculator();
}
public function testAddReturnsCorrectSum(): void
{
$result = $this->calculator->add(2, 3);
$this->assertEquals(5, $result);
}
public function testAddWithNegativeNumbers(): void
{
$this->assertEquals(-1, $this->calculator->add(2, -3));
}
public function testDivideReturnsCorrectResult(): void
{
$this->assertEquals(2.5, $this->calculator->divide(5, 2));
}
public function testDivideByZeroThrowsException(): void
{
$this->expectException(\InvalidArgumentException::class);
$this->expectExceptionMessage('Division durch Null');
$this->calculator->divide(10, 0);
}
}
Tests ausführen
# Alle Tests ./vendor/bin/phpunit # Bestimmte Datei ./vendor/bin/phpunit tests/CalculatorTest.php # Bestimmter Test ./vendor/bin/phpunit --filter testAddReturnsCorrectSum # Mit Coverage-Report ./vendor/bin/phpunit --coverage-html coverage/
Assertions
| Assertion | Prüft |
|---|---|
assertEquals($expected, $actual) |
Werte sind gleich |
assertSame($expected, $actual) |
Identisch (Typ + Wert) |
assertTrue($condition) |
Ist true |
assertFalse($condition) |
Ist false |
assertNull($value) |
Ist null |
assertNotNull($value) |
Ist nicht null |
assertCount($count, $array) |
Array hat n Elemente |
assertContains($needle, $haystack) |
Array enthält Wert |
assertInstanceOf($class, $object) |
Objekt ist Instanz von |
assertEmpty($value) |
Ist leer |
Data Providers
Mehrere Testfälle mit unterschiedlichen Daten:
<?php
class CalculatorTest extends TestCase
{
/**
* @dataProvider additionProvider
*/
public function testAdd(int $a, int $b, int $expected): void
{
$calculator = new Calculator();
$this->assertEquals($expected, $calculator->add($a, $b));
}
public static function additionProvider(): array
{
return [
'positive numbers' => [1, 2, 3],
'negative numbers' => [-1, -2, -3],
'mixed numbers' => [-1, 2, 1],
'with zero' => [5, 0, 5],
];
}
}
Mocking
<?php
class UserServiceTest extends TestCase
{
public function testCreateUserSendsEmail(): void
{
// Mock erstellen
$emailService = $this->createMock(EmailService::class);
// Erwartung definieren
$emailService->expects($this->once())
->method('send')
->with(
$this->equalTo('user@example.com'),
$this->stringContains('Willkommen')
);
// Service mit Mock testen
$userService = new UserService($emailService);
$userService->createUser('user@example.com', 'password');
}
}
Setup und Teardown
<?php
class DatabaseTest extends TestCase
{
private PDO $db;
// Vor jedem Test
protected function setUp(): void
{
$this->db = new PDO('sqlite::memory:');
$this->db->exec('CREATE TABLE users (id INTEGER, name TEXT)');
}
// Nach jedem Test
protected function tearDown(): void
{
$this->db = null;
}
// Einmal vor allen Tests
public static function setUpBeforeClass(): void
{
// z.B. Test-Datenbank erstellen
}
// Einmal nach allen Tests
public static function tearDownAfterClass(): void
{
// z.B. Aufräumen
}
}
Test-Kategorien mit Gruppen
<?php
class SlowTest extends TestCase
{
/**
* @group slow
* @group integration
*/
public function testSlowOperation(): void
{
// ...
}
}
# Nur bestimmte Gruppen ./vendor/bin/phpunit --group slow # Gruppen ausschließen ./vendor/bin/phpunit --exclude-group slow
Best Practices
💡 Empfehlungen:
- Ein Konzept pro Test (Single Assertion Rule)
- Sprechende Testnamen:
testUserCannotLoginWithWrongPassword - Arrange-Act-Assert Pattern
- Tests unabhängig voneinander
- Keine Produktionsdatenbank in Tests
- Tests bei jedem Commit ausführen (CI/CD)