PHPUnit Basisklassen für symfony2-Projekte
Kommentare

Funktionale Tests für Services
Als Services werden in symfony2 Klassen bezeichnet, die vom Dependency Injection Container (DIC) verwaltet werden. Die korrekte Funktionalität einer solchen Klasse kann

Funktionale Tests für Services

Als Services werden in symfony2 Klassen bezeichnet, die vom Dependency Injection Container (DIC) verwaltet werden. Die korrekte Funktionalität einer solchen Klasse kann mittels Unittests sichergestellt werden. Diese Tests können jedoch die korrekte Nutzung von Services nicht sicherstellen. Das kann erst beim Zusammenspiel des DIC mit der Applikation getestet werden. Ebensowenig können mittels Unittests keine Tests von Annotations abgedeckt werden.

Am Beispiel der Validation-Komponente soll das Testen eines Services dargestellt werden. Eigene Validatoren können als einzelne Unit getestet werden. Die korrekte Nutzung bei Einbindung mittels Annotations muss wie erwähnt mittels der Nutzung des echten Validator-Services erfolgen. Denkbar ist z.B., dass alle Validatoren korrekt funktionieren, bei den zu validierenden Objekten aber Annotations vergessen oder nicht korrekt gesetzt wurden. Solche Fälle können von Unittests nicht abgefangen werden, da keine isolierte Einheit, sondern das Zusammenspiel mehrerer Klassen getestet werden muss.

Auch für solche Tests kann der WebTestCase, den symfony2 mitbringt, genutzt werden. Wie zuvor wird als erstes der Kernel gebootet. Danach wird der Service mit dem Schlüssel „validator“ geladen:

namespace AcmeDemoBundleTests;

abstract class ValidatorTestCase extends SymfonyBundleFrameworkBundleTestWebTestCase {

    protected static $validator;

    public static function setUpBeforeClass() {
        static::$kernel = static::createKernel();
        static::$kernel->boot();

        static::$validator = static::$kernel->getContainer()->get('validator');
    }

} 

Auch diese Klasses bootet den Kernel, allerdings beschränkt sich die weitere Logik darauf, den entsprechenden Service vom Container auszulesen. In einer Testklasse, die die ValidatorTestCase Klasse nutzt, könnte nun die Validierung eines Objektes stattfinden. Da der echte Validator genutzt wird, fallen Fehler in den Annotations bei gut gewählten Tests auf.

Den Browser simulieren

Der schon öfters erwähnte WebTestCase wird von symfony2 zur Verfügung gestellt, um mittels der TestClient Klasse einen Browser zu simulieren. Es empfiehlt sich, die aus der EntityTestCase Klasse bekannte Methodik zur Initialisierung der Datenbank zu verwenden, damit immer die gleiche Datenbasis zur Verfügung steht.

Zusätzlich ist es sinnvoll, eine Methode zur Verfügung zu stellen, die den simulierten Browser einloggt. Da keinerlei Sessions zwischen einzelnen Testfällen übergeben werden können, muss hierfür der Browser auf die Login-Seite geleitet werden und die Login-Daten abschicken. Danach kann der Browser verwendet werden, um eine Seite aufzurufen und zu testen, die nur von eingeloggten Benutzern betreten werden darf.

Eine so ausgestattete WebTestCase könnte wie folgt aussehen:

namespace AcmeDemoBundleTests;

use SymfonyBundleFrameworkBundleConsoleApplication;
use SymfonyComponentConsoleInputArrayInput;

abstract class WebTestCase extends SymfonyBundleFrameworkBundleTestWebTestCase {

    protected static $application;
    protected $client;
    protected $entityManager;

    public static function setUpBeforeClass() {
        static::$kernel = static::createKernel();
        static::$kernel->boot();

        static::$application = new Application(static::$kernel);
        static::$application->setAutoExit(false);

        static::createDatabase();
    }

    public static function tearDownAfterClass() {
        static::executeCommand("doctrine:schema:drop", array("--force" => true));
    }

    private static function createDatabase() {
        static::executeCommand("doctrine:schema:create");
        static::executeCommand("doctrine:fixtures:load");
    }

    private static function executeCommand($command, Array $options = array()) {
        $options["-e"] = "test";
        $options["-q"] = null;
        $options = array_merge($options, array('command' => $command));
        return static::$application->run(new ArrayInput($options));
    }

    public function setUp() {
        $this->client = static::createClient();
        $this->entityManager = static::$kernel->getContainer()->get('doctrine')->getEntityManager();
    }

    protected function login() {
        $this->client->request('GET', '/login');

        $crawler = $this->client->getCrawler();
        $buttonCrawlerNode = $crawler->selectButton('submit');

        $form = $buttonCrawlerNode->form(array(
            'login[email]' => 'user@rptr.local',
            'login[password]' => 'test'
            ));
        $this->client->submit($form);
        $this->client->followRedirect();
    }

}
Testklassen für verschiedenste Anwendungsfälle

Wie in diesem Artikel gezeigt wurde, können Testklassen als Basis für unterschiedliche Anwendungsfälle genutzt werden. Dies vereinfacht das Schreiben der einzelnen Tests und gibt eine Art „Best Pratice“ vor, wie bestimmte Arten von Klassen getestet werden sollen.

Die hier gezeigten Klassen sollen Anregungen für eigene Testklassen darstellen. Es gibt viele weitere Möglichkeiten, sie zu implementieren und sie in verschiedene Richtungen zu erweitern. Des Weiteren kann eine komplexere Struktur erarbeitet werden, die Duplikation von Code reduziert (siehe EntityTestCase sowie WebTestCase).

Sebastian Göttschkes ist Mit-Gründer der PicturePlix GmbH, einem Fotobuch-Designservice, und dort mitverantwortlich für die Softwareentwicklung. Er bloggt unter sgoettschkes.blogger.com und ist via twitter erreichbar.
Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -