„Unsere Tests laufen durch, aber was testen sie eigentlich?“ Diese Frage höre ich regelmäßig in Code Reviews. Mit über 15 Jahren Erfahrung in Softwarequalität und Open Source Consulting weiß ich: Tests ohne klare Coverage-Dokumentation sind wie ein Auto ohne Tacho – es läuft, aber keiner weiß wie schnell.
PHPUnit 10 hat mit den neuen Coverage Attributes endlich eine saubere Lösung geschaffen. Schluss mit kryptischen DocBlocks. Zeit für moderne PHP 8 Syntax, die euer IDE versteht und euer Team lieben wird.
Warum ihr von @covers Annotations zu #[CoversClass] Attributes wechseln solltet
Die alte Welt der DocBlock Annotations stirbt aus. PHPUnit 12 wird sie nicht mehr unterstützen. Aber das ist nicht der einzige Grund für den Wechsel:
Vorteile der neuen Attributes:
- Native PHP 8 Syntax mit IDE-Support
- Type-Safety und Autocompletion
- Bessere Performance beim Test-Parsing
- Klare Trennung von Dokumentation und Metadaten
Ein praktisches Beispiel aus unseren Projekten:
// Alt: DocBlock Annotation
/**
* @covers AppServicePaymentProcessor
*/
class PaymentProcessorTest extends TestCase
{
// Tests...
}
// Neu: PHP 8 Attribute
#[CoversClass(PaymentProcessor::class)]
class PaymentProcessorTest extends TestCase
{
// Tests...
}
Der Unterschied? Eure IDE kennt jetzt die Klasse, kann refactoren und findet Fehler vor dem Test-Run.
Wie migriert ihr bestehende @covers Annotations zu PHP 8 Attributes?
Migration muss nicht schmerzhaft sein. In unseren Remote Consulting Sessions nutzen wir Rector für automatische Migrationen:
// rector.php
use RectorPHPUnitSetPHPUnitSetList;
return static function (RectorConfig $rectorConfig): void {
$rectorConfig->sets([
PHPUnitSetList::ANNOTATIONS_TO_ATTRIBUTES,
]);
};
Schritt-für-Schritt Migration:
- Rector installieren und konfigurieren
- Dry-Run durchführen zur Vorschau
- Team-Review der Änderungen
- Schrittweise Migration pro Modul
Nach über 50 erfolgreichen Migrationen wissen wir: Plant 2-4 Stunden für ein mittelgroßes Projekt ein.
Was ist der Unterschied zwischen CoversClass und UsesClass?
Diese Frage sorgt regelmäßig für Verwirrung. Lasst es uns klar trennen:
#[CoversClass] – Das testet ihr wirklich:
#[CoversClass(InvoiceCalculator::class)]
class InvoiceCalculatorTest extends TestCase
{
public function testCalculateTotalAmount(): void
{
// Dieser Test zählt zur Coverage von InvoiceCalculator
}
}
#[UsesClass] – Das nutzt ihr nur:
#[CoversClass(InvoiceCalculator::class)]
#[UsesClass(Money::class)]
#[UsesClass(TaxRate::class)]
class InvoiceCalculatorTest extends TestCase
{
// Money und TaxRate werden genutzt, aber nicht getestet
}
Pro-Tipp aus unserer Praxis: UsesClass verhindert, dass Hilfsklassen eure Coverage-Metriken verfälschen.
Wie funktioniert CoversMethod im Vergleich zu CoversClass?
PHPUnit 11 hat endlich CoversMethod eingeführt. Der Unterschied ist granulare Kontrolle:
#[CoversClass(UserService::class)]
#[CoversMethod(UserService::class, 'register')]
#[CoversMethod(UserService::class, 'sendWelcomeEmail')]
class UserRegistrationTest extends TestCase
{
// Testet nur register() und sendWelcomeEmail()
// NICHT die anderen Methoden von UserService
}
Wann was nutzen?
- CoversClass: Für Unit Tests einer kompletten Klasse
- CoversMethod: Für spezifische Feature-Tests
- Kombination: Für präzise Coverage-Reports
Kann man CoversClass auf Methoden-Ebene verwenden?
Kurze Antwort: Nein. Und das ist gut so.
PHPUnit erlaubt CoversClass nur auf Klassen-Ebene. Das zwingt euch zu sauberer Test-Organisation:
// ❌ Funktioniert nicht
class MixedTest extends TestCase
{
#[CoversClass(ServiceA::class)] // Nicht erlaubt!
public function testServiceA(): void { }
#[CoversClass(ServiceB::class)] // Nicht erlaubt!
public function testServiceB(): void { }
}
// ✅ Bessere Struktur
#[CoversClass(ServiceA::class)]
class ServiceATest extends TestCase
{
public function testFeatureOne(): void { }
public function testFeatureTwo(): void { }
}
Unsere Empfehlung nach 15 Jahren Erfahrung: Ein Test-Klasse pro Production-Klasse. Klare Struktur, bessere Wartbarkeit.
Wie verhindert ihr unbeabsichtigte Code Coverage mit UsesClass?
Unbeabsichtigte Coverage ist der stille Killer eurer Qualitätsmetriken. Ein Beispiel aus einem unserer Enterprise-Projekte:
// Ohne UsesClass: 95% Coverage - aber falsch!
#[CoversClass(OrderProcessor::class)]
class OrderProcessorTest extends TestCase
{
public function testProcessOrder(): void
{
// Nutzt intern: Logger, Mailer, Repository
// Alle bekommen Coverage - obwohl nicht getestet!
}
}
// Mit UsesClass: Ehrliche 75% Coverage
#[CoversClass(OrderProcessor::class)]
#[UsesClass(Logger::class)]
#[UsesClass(Mailer::class)]
#[UsesClass(OrderRepository::class)]
class OrderProcessorTest extends TestCase
{
// Jetzt wisst ihr: Logger, Mailer, Repository brauchen eigene Tests
}
Best Practice Check-Liste:
- Listet ALLE genutzten Dependencies
- Nutzt PHPUnit im Strict-Coverage-Mode
- Reviewed Coverage-Reports im Team
Was bedeutet CoversNothing und wann solltet ihr es einsetzen?
CoversNothing ist euer Freund für Integration Tests:
#[CoversNothing]
class DatabaseIntegrationTest extends TestCase
{
public function testCompleteUserRegistrationFlow(): void
{
// Testet das Zusammenspiel mehrerer Komponenten
// Soll NICHT zur Unit-Test-Coverage zählen
}
}
Wann CoversNothing nutzen:
- End-to-End Tests
- Integration Tests
- Performance Tests
- Smoke Tests
Aus unserer Consulting-Praxis: Trennt Unit- von Integration-Test-Coverage. Zwei Metriken sind besser als eine verfälschte.
Wie kombiniert ihr mehrere Coverage Attributes in einer Testklasse?
Komplexe Projekte brauchen flexible Coverage-Dokumentation:
#[CoversClass(ShoppingCart::class)]
#[CoversClass(CartItem::class)]
#[UsesClass(Product::class)]
#[UsesClass(PriceCalculator::class)]
#[UsesClass(DiscountService::class)]
class ShoppingCartTest extends TestCase
{
// Testet ShoppingCart UND CartItem
// Nutzt die anderen Klassen nur
}
Struktur-Tipps aus unseren Workshops:
- Hauptklasse zuerst mit CoversClass
- Eng gekoppelte Klassen als weitere CoversClass
- Alle Dependencies mit UsesClass
- Alphabetisch sortieren für bessere Übersicht
Wie wirken sich Coverage Attributes auf Performance und Laufzeit aus?
Die gute Nachricht: Moderne Coverage Attributes sind schneller als alte Annotations.
Performance-Vergleich aus unseren Messungen:
- Parsing ohne Coverage: Baseline
- Mit @covers Annotations: +8-12% Overhead
- Mit #[CoversClass] Attributes: +3-5% Overhead
- Mit Coverage-Generierung: +40-60% Overhead
Optimierungs-Tipps:
<!-- phpunit.xml -->
<phpunit>
<coverage>
<include>
<directory suffix=".php">src</directory>
</include>
<exclude>
<directory>src/Generated</directory>
<directory>src/Migrations</directory>
</exclude>
</coverage>
</phpunit>
Unser Rat: Coverage nur in CI/CD, nicht lokal. Spart Zeit bei der Entwicklung.
Wie enforced ihr Coverage Attributes im Team mit CI/CD?
Standards ohne Enforcement sind nur Wünsche. So macht ihr es richtig:
1. PHP-CS-Fixer Configuration:
// .php-cs-fixer.php
return (new Config())
->setRules([
'php_unit_test_class_requires_covers' => true,
]);
2. PHPUnit Strict Mode:
<phpunit
requireCoverageMetadata="true"
beStrictAboutCoverageMetadata="true">
3. CI/CD Pipeline Check:
# .github/workflows/tests.yml
- name: Check Test Coverage Attributes
run: |
vendor/bin/phpunit --coverage-text
vendor/bin/php-cs-fixer fix --dry-run
4. Pre-Commit Hook:
#!/bin/sh
# .git/hooks/pre-commit
php vendor/bin/php-cs-fixer fix --dry-run
Nach über 15 Jahren Remote Consulting wissen wir: Automatisierung ist der Schlüssel. Teams, die Coverage Attributes automatisch prüfen, haben 40% bessere Test-Qualität.
Fazit: Eure nächsten Schritte zu besserer Test-Coverage
PHPUnit Coverage Attributes sind keine Spielerei – sie sind ein mächtiges Werkzeug für nachhaltige Softwarequalität. Die Migration lohnt sich, besonders wenn ihr:
✅ Von PHPUnit 9 auf 10+ upgraded
✅ PHP 8+ bereits nutzt
✅ Ehrliche Coverage-Metriken wollt
✅ IDE-Support voll ausnutzen möchtet
Unser Angebot für euch:
Ihr wollt Coverage Attributes in eurem Projekt einführen? Wir unterstützen euch gerne mit unserer Expertise:
- Remote Code Review eurer Test-Suite
- Migration Workshop mit praktischen Übungen
- CI/CD Setup für automatische Coverage-Checks
- Team Training für Best Practices
Meldet euch einfach bei uns: roland@nevercodealone.de
Keine Agentur mit 100 Projekten – sondern über 15 Jahre fokussiert auf Softwarequalität, Open Source und Remote Consulting. Wir sprechen eure Sprache und kennen eure Herausforderungen.
Was sind eure Erfahrungen mit PHPUnit Coverage Attributes? Schreibt uns oder hinterlasst einen Kommentar. Gemeinsam machen wir die PHP-Welt ein bisschen besser getestet.