Whatsapp Telegram Telegram Call Anrufen

Testen in Symfony


Das Testen ist ein wesentlicher Bestandteil der Softwareentwicklung, um die Qualität und Stabilität einer Anwendung sicherzustellen. Symfony bietet umfangreiche Werkzeuge und Best Practices für das Testen von Anwendungen auf verschiedenen Ebenen, von Unit-Tests bis hin zu funktionalen Tests.

In diesem Artikel werden wir die folgenden Themen ausführlich behandeln:

  1. Einrichtung von PHPUnit für Symfony
  2. Schreiben von Unit-Tests für Services
  3. Funktionale Tests von Controllern und Routen
  4. Testen von Formularen und Validierung
  5. Verwendung von Test-Doubles und Mocks

1. Einrichtung von PHPUnit für Symfony

1.1 Was ist PHPUnit?

PHPUnit ist das de-facto Standard-Framework für Unit-Tests in PHP. Es ermöglicht das Schreiben und Ausführen von Tests, um die Funktionalität einzelner Komponenten sicherzustellen.

1.2 Installation von PHPUnit in Symfony

Symfony-Projekte werden oft mit PHPUnit vorinstalliert ausgeliefert. Falls nicht, können Sie PHPUnit mit Composer installieren.

Schritt 1: Installation von PHPUnit

Fügen Sie PHPUnit zu Ihren Entwicklungsabhängigkeiten hinzu:

composer require --dev phpunit/phpunit

Schritt 2: Installieren des Symfony Test Packs

Das Symfony Test Pack enthält nützliche Pakete für das Testen.

composer require --dev symfony/test-pack

Dies installiert unter anderem:

  • symfony/phpunit-bridge: Brücke zwischen Symfony und PHPUnit.
  • symfony/browser-kit: Simuliert einen Browser für funktionale Tests.
  • symfony/css-selector: Erlaubt die Verwendung von CSS-Selektoren in Tests.

Schritt 3: Konfiguration von PHPUnit

Eine Standardkonfiguration befindet sich in der Datei phpunit.xml.dist im Stammverzeichnis Ihres Projekts. Falls nicht vorhanden, können Sie diese erstellen.

Beispiel phpunit.xml.dist:

<?xml version="1.0" encoding="UTF-8"?>

<phpunit bootstrap="config/bootstrap.php">
    <testsuites>
        <testsuite name="Project Test Suite">
            <directory>tests</directory>
        </testsuite>
    </testsuites>

    <php>
        <ini name="error_reporting" value="-1"/>
        <server name="KERNEL_CLASS" value="App\Kernel"/>
    </php>
</phpunit>

Schritt 4: Initialisierung des Testverzeichnisses

Erstellen Sie das Verzeichnis tests/ im Stammverzeichnis Ihres Projekts, falls es nicht existiert.

2. Schreiben von Unit-Tests für Services

2.1 Was sind Unit-Tests?

Unit-Tests prüfen einzelne Einheiten des Codes, wie Klassen oder Methoden, isoliert von anderen Teilen des Systems.

2.2 Beispiel-Service zum Testen

Angenommen, wir haben einen einfachen Service, der mathematische Operationen ausführt.

// src/Service/Calculator.php

namespace App\Service;

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 ist nicht erlaubt.');
        }

        return $a / $b;
    }
}

2.3 Schreiben eines Unit-Tests

Schritt 1: Erstellen der Testklasse

Erstellen Sie eine Testklasse im Verzeichnis tests/Service/.

// tests/Service/CalculatorTest.php

namespace App\Tests\Service;

use App\Service\Calculator;
use PHPUnit\Framework\TestCase;

class CalculatorTest extends TestCase
{
    public function testAdd()
    {
        $calculator = new Calculator();
        $result = $calculator->add(5, 3);
        $this->assertEquals(8, $result);
    }

    public function testDivide()
    {
        $calculator = new Calculator();
        $result = $calculator->divide(10, 2);
        $this->assertEquals(5, $result);
    }

    public function testDivideByZero()
    {
        $calculator = new Calculator();
        $this->expectException(\InvalidArgumentException::class);
        $calculator->divide(10, 0);
    }
}

Schritt 2: Ausführen der Tests

Führen Sie die Tests mit dem folgenden Befehl aus:

php bin/phpunit

Erwartete Ausgabe:

PHPUnit 9.6.21 by Sebastian Bergmann and contributors.

Testing
...                                                                 3 / 3 (100%)

Time: 00:00.012, Memory: 8.00 MB

OK (3 tests, 3 assertions)


2.4 Assertions in PHPUnit

PHPUnit bietet viele Assertions, um Testbedingungen zu prüfen:

  • assertEquals($expected, $actual): Prüft, ob zwei Werte gleich sind.
  • assertTrue($condition): Prüft, ob eine Bedingung true ist.
  • assertFalse($condition): Prüft, ob eine Bedingung false ist.
  • assertNull($value): Prüft, ob ein Wert null ist.
  • assertCount($expectedCount, $array): Prüft die Anzahl der Elemente in einem Array.

3. Funktionale Tests von Controllern und Routen

3.1 Was sind funktionale Tests?

Funktionale Tests prüfen das Zusammenspiel mehrerer Komponenten, z. B. die Reaktion eines Controllers auf eine HTTP-Anfrage.

3.2 Beispiel-Controller zum Testen

// src/Controller/CalculatorController.php

namespace App\Controller;

use App\Service\Calculator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class CalculatorController extends AbstractController
{
    #[Route('/add/{a}/{b}', name: 'add')]
    public function add(int $a, int $b, Calculator $calculator): Response
    {
        $result = $calculator->add($a, $b);
        return new Response("Ergebnis: $result");
    }
}

3.3 Schreiben eines funktionalen Tests

Schritt 1: Erstellen der Testklasse

Erstellen Sie eine Testklasse im Verzeichnis tests/Controller/.

// tests/Controller/CalculatorControllerTest.php

namespace App\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class CalculatorControllerTest extends WebTestCase
{
    public function testAdd()
    {
        $client = static::createClient();
        $client->request('GET', '/add/5/3');

        $this->assertResponseIsSuccessful();
        $this->assertSelectorTextContains('body', 'Ergebnis: 8');
    }
}

Schritt 2: Ausführen des Tests

php bin/phpunit

3.4 Verwendung des Test-Clients

Der Test-Client simuliert einen Browser und ermöglicht das Senden von HTTP-Anfragen.

  • createClient(): Erstellt einen neuen Client.
  • request($method, $uri, $parameters = [], $files = [], $server = [], $content = null): Sendet eine HTTP-Anfrage.
  • getResponse(): Ruft die Antwort ab.
  • getCrawler(): Ruft den Crawler ab, um das DOM zu durchsuchen.

3.5 Wichtige Assertions für funktionale Tests

  • assertResponseIsSuccessful(): Prüft, ob der HTTP-Statuscode 2xx ist.
  • assertResponseStatusCodeSame($statusCode): Prüft auf einen bestimmten Statuscode.
  • assertSelectorTextContains($selector, $text): Prüft, ob ein bestimmter Text in einem HTML-Element enthalten ist.
  • assertPageTitleContains($text): Prüft den Seitentitel.

4. Testen von Formularen und Validierung

4.1 Beispiel: Registrierungsformular

Angenommen, wir haben ein Registrierungsformular für Benutzer.

Formular-Typ

// src/Form/RegistrationFormType.php

namespace App\Form;

use App\Entity\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\FormBuilderInterface;

class RegistrationFormType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('email', EmailType::class)
            ->add('plainPassword', PasswordType::class);
    }
}

4.2 Schreiben eines Tests für das Formular

Schritt 1: Erstellen der Testklasse

// tests/Form/RegistrationFormTypeTest.php

namespace App\Tests\Form;

use App\Form\RegistrationFormType;
use App\Entity\User;
use Symfony\Component\Form\Test\TypeTestCase;

class RegistrationFormTypeTest extends TypeTestCase
{
    public function testSubmitValidData()
    {
        $formData = [
            'email' => 'user@example.com',
            'plainPassword' => 'password123',
        ];

        $model = new User();

        $form = $this->factory->create(RegistrationFormType::class, $model);

        $expected = new User();
        $expected->setEmail('user@example.com');
        $expected->setPlainPassword('password123');

        // Daten dem Formular übermitteln
        $form->submit($formData);

        $this->assertTrue($form->isSynchronized());

        // Modell mit erwarteten Daten vergleichen
        $this->assertEquals($expected->getEmail(), $model->getEmail());
        $this->assertEquals($expected->getPlainPassword(), $model->getPlainPassword());

        // Form View testen
        $view = $form->createView();
        $children = $view->children;

        foreach (array_keys($formData) as $key) {
            $this->assertArrayHasKey($key, $children);
        }
    }
}

4.3 Validierungsregeln testen

Anpassung der Entität mit Validierung

// src/Entity/User.php

namespace App\Entity;

use Symfony\Component\Validator\Constraints as Assert;

class User
{
    // ...

    #[Assert\NotBlank]
    #[Assert\Email]
    private ?string $email = null;

    #[Assert\NotBlank]
    #[Assert\Length(min: 6)]
    private ?string $plainPassword = null;

    // Getter und Setter ...
}

Testen der Validierung

// tests/Entity/UserTest.php

namespace App\Tests\Entity;

use App\Entity\User;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class UserTest extends KernelTestCase
{
    public function testValidUser()
    {
        self::bootKernel();
        $container = static::getContainer();

        $validator = $container->get('validator');

        $user = new User();
        $user->setEmail('user@example.com');
        $user->setPlainPassword('password123');

        $errors = $validator->validate($user);

        $this->assertCount(0, $errors);
    }

    public function testInvalidEmail()
    {
        self::bootKernel();
        $container = static::getContainer();

        $validator = $container->get('validator');

        $user = new User();
        $user->setEmail('invalid-email');
        $user->setPlainPassword('password123');

        $errors = $validator->validate($user);

        $this->assertCount(1, $errors);
    }
}

5. Verwendung von Test-Doubles und Mocks

5.1 Was sind Test-Doubles und Mocks?

  • Test-Doubles: Allgemeiner Begriff für Objekte, die reale Abhängigkeiten in Tests ersetzen.
  • Mocks: Spezielle Art von Test-Doubles, die das Verhalten einer Abhängigkeit simulieren und Erwartungen setzen.

5.2 Verwendung von Mocks mit PHPUnit

Beispiel: Mocking eines Services

Angenommen, unser CalculatorController verwendet einen externen Service, den wir mocken möchten.

// src/Service/ExternalApiService.php

namespace App\Service;

class ExternalApiService
{
    public function fetchData(): array
    {
        // Führt einen externen API-Aufruf aus
        // In Tests möchten wir dies vermeiden
    }
}

Anpassung des Controllers

// src/Controller/CalculatorController.php

namespace App\Controller;

use App\Service\Calculator;
use App\Service\ExternalApiService;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;

class CalculatorController extends AbstractController
{
    #[Route('/external', name: 'external')]
    public function externalData(ExternalApiService $apiService): Response
    {
        $data = $apiService->fetchData();
        return new Response('Externe Daten: ' . json_encode($data));
    }
}

Schreiben eines Tests mit einem Mock

// tests/Controller/CalculatorControllerTest.php

namespace App\Tests\Controller;

use App\Service\ExternalApiService;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class CalculatorControllerTest extends WebTestCase
{
    public function testExternalData()
    {
        $client = static::createClient();

        // Erstellen eines Mocks für ExternalApiService
        $mockApiService = $this->createMock(ExternalApiService::class);
        $mockApiService->method('fetchData')
            ->willReturn(['key' => 'value']);

        // Überschreiben des Services im Container
        $client->getContainer()->set(ExternalApiService::class, $mockApiService);

        $client->request('GET', '/external');

        $this->assertResponseIsSuccessful();
        $this->assertSelectorTextContains('body', 'Externe Daten: {"key":"value"}');
    }
}

5.3 Verwendung von Test-Doubles für Datenbankinteraktionen

Bei Unit-Tests möchten wir häufig echte Datenbankinteraktionen vermeiden. Hier können wir Mocks für Repositories erstellen.

Beispiel: Mocking eines Repositorys

// src/Repository/UserRepository.php

namespace App\Repository;

use App\Entity\User;
use Doctrine\ORM\EntityRepository;

class UserRepository extends EntityRepository
{
    public function findActiveUsers(): array
    {
        return $this->createQueryBuilder('u')
            ->where('u.active = :active')
            ->setParameter('active', true)
            ->getQuery()
            ->getResult();
    }
}

Test mit einem Mock-Repository

// tests/Service/UserServiceTest.php

namespace App\Tests\Service;

use App\Entity\User;
use App\Repository\UserRepository;
use PHPUnit\Framework\TestCase;

class UserServiceTest extends TestCase
{
    public function testGetActiveUsers()
    {
        // Erstellen von Mock-Daten
        $user1 = new User();
        $user1->setEmail('user1@example.com');

        $user2 = new User();
        $user2->setEmail('user2@example.com');

        // Erstellen eines Mocks für UserRepository
        $mockRepository = $this->createMock(UserRepository::class);
        $mockRepository->method('findActiveUsers')
            ->willReturn([$user1, $user2]);

        // Testen der Methode, die das Repository verwendet
        $users = $mockRepository->findActiveUsers();

        $this->assertCount(2, $users);
        $this->assertEquals('user1@example.com', $users[0]->getEmail());
    }
}

Zusammenfassung

  • Einrichtung von PHPUnit: Installieren und konfigurieren Sie PHPUnit und das Symfony Test Pack für Ihre Anwendung.
  • Unit-Tests: Schreiben Sie Tests für einzelne Klassen und Methoden, um deren korrekte Funktion sicherzustellen.
  • Funktionale Tests: Testen Sie das Verhalten Ihrer Anwendung auf höherer Ebene, z. B. Controller und Routen.
  • Formulare und Validierung: Stellen Sie sicher, dass Ihre Formulare korrekt funktionieren und die Validierungsregeln greifen.
  • Test-Doubles und Mocks: Verwenden Sie Mocks, um Abhängigkeiten zu isolieren und gezielt zu testen.

Weiterführende Ressourcen

Testing

SymfonyCasts Tutorial


CEO Image

Ali Ajjoub

info@ajjoub.com

Adresse 0049-15773651670

Adresse Jacob-winter-platz,1 01239 Dresden

Buchen Sie jetzt Ihren Termin für eine umfassende und individuelle Beratung.

Termin Buchen

Kontaktieren Sie uns

Lassen Sie uns K o n t a k t aufnehmen!