Tests sind kein optionaler Luxus, sondern eine Investition in die Qualität und Wartbarkeit Ihres Codes. In Symfony-Projekten bietet PHPUnit dafür alle nötigen Werkzeuge.
Die Testpyramide
Eine gesunde Teststruktur folgt der Testpyramide:
- Unit-Tests (viele) – Testen einzelne Klassen isoliert
- Integration-Tests (einige) – Testen das Zusammenspiel mehrerer Komponenten
- Functional Tests (wenige) – Testen komplette HTTP-Requests
Unit-Tests: Services isoliert testen
Ein Service sollte unabhängig von Infrastruktur testbar sein:
<?php
declare(strict_types=1);
namespace App\Tests\Service;
use App\Service\PriceCalculator;
use PHPUnit\Framework\TestCase;
class PriceCalculatorTest extends TestCase
{
private PriceCalculator $calculator;
protected function setUp(): void
{
$this->calculator = new PriceCalculator();
}
public function testCalculateNetPrice(): void
{
$grossPrice = 119.0;
$taxRate = 19.0;
$netPrice = $this->calculator->calculateNet($grossPrice, $taxRate);
$this->assertEqualsWithDelta(100.0, $netPrice, 0.01);
}
public function testCalculateWithDiscount(): void
{
$grossPrice = 100.0;
$discount = 10.0;
$result = $this->calculator->applyDiscount($grossPrice, $discount);
$this->assertEquals(90.0, $result);
}
}
Integration-Tests: Datenbank einbeziehen
Für Tests mit Datenbankzugriff bietet Symfony den KernelTestCase:
<?php
declare(strict_types=1);
namespace App\Tests\Repository;
use App\Entity\Product;
use App\Repository\ProductRepository;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
class ProductRepositoryTest extends KernelTestCase
{
private ProductRepository $repository;
protected function setUp(): void
{
self::bootKernel();
$this->repository = static::getContainer()
->get(ProductRepository::class);
}
public function testFindActiveProducts(): void
{
$products = $this->repository->findActive();
$this->assertNotEmpty($products);
foreach ($products as $product) {
$this->assertTrue($product->isActive());
}
}
}
Functional Tests: Controller testen
Mit dem WebTestCase können Sie komplette HTTP-Requests simulieren:
<?php
declare(strict_types=1);
namespace App\Tests\Controller;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class ProductControllerTest extends WebTestCase
{
public function testProductListReturns200(): void
{
$client = static::createClient();
$client->request('GET', '/api/products');
$this->assertResponseIsSuccessful();
$this->assertResponseHeaderSame('content-type', 'application/json');
}
public function testCreateProductRequiresAuth(): void
{
$client = static::createClient();
$client->request('POST', '/api/products');
$this->assertResponseStatusCodeSame(401);
}
}
Data Providers für parametrisierte Tests
Data Providers reduzieren Duplikation bei ähnlichen Testfällen:
public static function invalidPriceProvider(): array
{
return [
'negative price' => [-10.0],
'zero price' => [0.0],
'extremely high' => [999999999.99],
];
}
#[\PHPUnit\Framework\Attributes\DataProvider('invalidPriceProvider')]
public function testRejectsInvalidPrices(float $price): void
{
$this->expectException(\InvalidArgumentException::class);
$this->calculator->validatePrice($price);
}
Shopware 6: Testing in der Plugin-Entwicklung
Shopware 6 folgt ebenfalls der Testpyramide, nutzt aber ein eigenes Test-Setup mit spezialisierten Base-Classes und Traits. Die Testlandschaft sieht so aus:
| Schicht | Framework | Einsatz |
|---|---|---|
| PHP Backend | PHPUnit | Unit-Tests, Integration-Tests, Migration-Tests |
| Administration (Vue) | Jest | Admin-Komponenten-Tests |
| Storefront JS | Jest | Storefront-JavaScript-Tests |
| E2E / Akzeptanz | Playwright | End-to-End-Tests (seit Ende 2023, ersetzt Cypress) |
PHPUnit-Setup für Plugins
Jedes Shopware-Plugin kann eigene PHPUnit-Tests mitbringen. Das Setup beginnt mit einer phpunit.xml im Plugin-Root und einer Bootstrap-Datei, die den Shopware-Kernel startet:
<?php
declare(strict_types=1);
use Shopware\Core\TestBootstrapper;
$loader = (new TestBootstrapper())
->addCallingPlugin()
->addActivePlugins('MeinPlugin')
->setForceInstallPlugins(true)
->bootstrap()
->getClassLoader();
$loader->addPsr4('MeinPlugin\\Tests\\', __DIR__);
setForceInstallPlugins(true) stellt sicher, dass das Plugin auch dann installiert und aktiv bleibt, wenn die Testdatenbank bereits existiert.
Integration-Tests mit IntegrationTestBehaviour
Das Trait IntegrationTestBehaviour ist das Arbeitspferd für Shopware-Plugin-Tests. Es bietet automatische Datenbank-Transaktionen (Rollback nach jedem Test), Cache-Clearing und Container-Zugriff:
<?php
declare(strict_types=1);
namespace MeinPlugin\Tests;
use PHPUnit\Framework\TestCase;
use Shopware\Core\Framework\Test\TestCaseBase\IntegrationTestBehaviour;
class MeinServiceTest extends TestCase
{
use IntegrationTestBehaviour;
public function testServiceIstRegistriert(): void
{
$service = $this->getContainer()->get(MeinService::class);
$this->assertInstanceOf(MeinService::class, $service);
}
}
Für Migration-Tests gibt es das schlankere KernelTestBehaviour, das nur Container-Zugriff ohne automatische Transaktions-Rollbacks bietet.
Tests ausführen
# Alle Plugin-Tests
vendor/bin/phpunit --configuration="custom/plugins/MeinPlugin"
# Nur Migrations-Tests
vendor/bin/phpunit --configuration="custom/plugins/MeinPlugin" --testsuite "migration"
# Jest-Tests für die Administration
composer run admin:unit
# Jest-Tests für die Storefront
composer run storefront:unit
E2E-Tests mit Playwright
Shopware hat Ende 2023 per ADR die Migration von Cypress auf Playwright beschlossen. Die Gründe: bessere Performance, stabilere Tests und integriertes Tracing für CI/CD. Das alte Cypress-Repository (@shopware-ag/e2e-testsuite-platform) ist archiviert.
Die neue Acceptance Test Suite basiert auf Playwright und bietet vorgefertigte Fixtures:
import { test, expect } from '@shopware-ag/acceptance-test-suite';
test('Produktanlage im Admin', async ({ ShopAdmin, AdminProductCreate }) => {
await ShopAdmin.goesTo(AdminProductCreate.url());
await ShopAdmin.expects(AdminProductCreate.nameInput).toBeVisible();
await ShopAdmin.expects(AdminProductCreate.saveButton).toBeVisible();
});
test('Testdaten per API erstellen', async ({ TestDataService }) => {
const product = await TestDataService.createProductWithImage({
description: 'Test-Beschreibung'
});
expect(product.description).toEqual('Test-Beschreibung');
expect(product.coverId).toBeDefined();
});
Setup:
npm init playwright@latest
npm install @shopware-ag/acceptance-test-suite
npx playwright install
Die Suite bietet ShopAdmin- und ShopCustomer-Akteure, einen AdminApiContext für API-Aufrufe und einen TestDataService für die programmatische Testdaten-Erstellung.
Fazit
Gute Tests geben Vertrauen bei Refactorings und sparen langfristig deutlich mehr Zeit als sie kosten. Starten Sie mit den kritischsten Pfaden und erweitern Sie die Abdeckung schrittweise. In Shopware-Plugins lohnt sich der Einstieg über Integration-Tests mit IntegrationTestBehaviour — das Trait nimmt Ihnen das meiste Setup-Boilerplate ab.
Kommentare
Kommentare werden von Remark42 bereitgestellt. Beim Laden werden Daten an unseren Kommentar-Server übertragen.