Symfony2: Tester les dépôts des Doctrine2 avec Nelmio/Alice

Un petit sujet rapide pour vous montrer comment on peut facilement tester ses dépôts Doctrine2 pour nos projets Symfony2. Pour que vos tests fonctionnent, il vous faudra l'extension sqlite3 du PDO d'installée.

Les outils utilisés

Tout d'abord on utilise "nelmio/alice" que l'on peut installer avec:

composer require nelmio/alice # ajouter --dev pour l'include dans la section "require-dev"

Alice est indispensable si vous voulez mettre en place des jeux de données sans trop vous prendre la tête avec des fixtures. Il fonctionne avec des fichiers YAML. Alice dépend d'une librairie: "fzaninotto/faker" et composer devrait vous l'installer assez rapidement.

Je vous conseille de farie un tour sur les deux pages github des deux librairies pour comprendre un peu mieux leur foncitonnement car elles vous apportent beaucoup de choses.

L'idée est un peu la même que pour mettre en place des tests sur les formulaires avec le champs entité (voir note article à ce sujet).

La classe RepositoryTestCase

Tout démarre par la réalisation d'une classe de test réutilisable. La voici:

<?php
namespace Acme\Bundle\DemoBundle\Tests\PHPUnit;

/* Imports */
use Doctrine\ORM\Tools\SchemaTool;
use Symfony\Bridge\Doctrine\Test\DoctrineTestHelper;
use Nelmio\Alice\Fixtures;

/**
 * Test case for repositories
 *
 * @author Nicolas Macherey <nicolas.macherey@gmail.com>
 */
abstract class RepositoryTestCase extends \PHPUnit_Framework_TestCase
{
    /**
     * @var ObjectManager
     */
    protected $em;

    /**
     * Return in this method all fixtures you need to load
     * at each test setup. The fixtures must be file paths
     *
     * @return array
     */
    protected function getFixtures()
    {
        return array();
    }

    /**
     * Return the type estensions to register
     *
     * @return array
     */
    protected function getEntities()
    {
        return array();
    }

    /**
     * {@inheritdoc}
     */
    protected function setUp()
    {
        $this->em = DoctrineTestHelper::createTestEntityManager();

        $schemaTool = new SchemaTool($this->em);
        $classes = [];

        foreach ($this->getEntities() as $entityClass) {
            $classes[]= $this->em->getClassMetadata($entityClass);
        }

        try {
            $schemaTool->dropSchema($classes);
        } catch (\Exception $e) {
        }

        try {
            $schemaTool->createSchema($classes);
        } catch (\Exception $e) {
        }
    }

    /**
     * Load the fixtures
     *
     * @param array $fixtures additional fixtures to load
     */
    protected function loadFixtures(array $fixtures = array())
    {
        $fixtures = array_merge($fixtures, $this->getFixtures());
        Fixtures::load($fixtures, $this->em);
    }
}

Le principe est relativement simple, vous pouvez définir les différentes entités que vous voulez utiliser via la fonciton getEntities. Seules ces entités seront disponibles, vous n'avez donc pas à charger l'intégralité de votre base de données. Et les fichiers de fixtures que vous voulez charger sont fournis grace à la méthode getFixtures. Bien sur, il est indispensable de ne pas réutiliser des fixtures entre vos différentes classes de test car vous vous exposer à la maintenance de vos tests si pour les besoins d'un autre vous devez les changer.

Pour finir nous avons ajouté la méthode loadFixtures que vous devez appeler à chaque test pour charger les fixtures en base et tester vos requêtes. Cette méthode prend une liste de fichiers de fixtures supplémentaires au cas ou vous auriez besoin d'en charger des supplémentaires pour un test bien spécifique.

L'entité à tester

Nous allons tester une entité très simple:

<?php
namespace Acme\Bundle\DemoBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * MyEntity
 *
 * @ORM\Table
 * @ORM\Entity(repositoryClass="Acme\Bundle\Entity\Repository\MyEntityRepository")
 */
class MyEntity
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer",length=10)
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255)
     */
    private $name;

    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set name
     *
     * @param  string  $name
     * @return MyEntity
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

    /**
     * Get name
     *
     * @return string
     */
    public function getName()
    {
        return $this->name;
    }
}

Le dépôt

Nous allons prendre comme exemple le test d'une méthode count directement implémentée dans notre dépôt:

<?php
namespace Acme\Bundle\DemoBundle\Entity\Repository;

/* Imports */
use Doctrine\ORM\EntityRepository;
use Doctrine\ORM\QueryBuilder;

/**
 * MyEntityRepository
 *
 * @author Nicolas Macherey <nicolas.macherey@gmail.com>
 */
class MyEntityRepository extends EntityRepository
{
    /**
     * Count all MyEntity instances
     *
     * @param string $alias
     *
     * @return integer
     */
    public function count($alias = 'entity')
    {
        return $this->createQueryBuilder($alias)
          ->select("count($alias)")
          ->getQuery()
          ->getSingleScalarResult();
    }
}

Les fixtures

Pour réaliser nos tests, nous allons créer un simple fichier de fixtures qui va charger une série d'entités de notre base de données. Vous allez vois qu'avec Alice c'est d'un simplicité enfantine:

# src/Acme/Bundle/DemoBundle/Tests/Resources/fixtures/myentities.yml
Acme\Bundle\DemoBundle\Entity\MyEntity:
    instance{1..15}:
        name: <word()>

Cet exemple va nous permettre d'injecter 15 instances de MyEntity dans notre base de données. C'est quand même pas mal. La fonction word utilisée fait appel au Faker afin de générer un chaîne de caractère aléatoire.

La classe de test

Voilà on y est! On va pouvoir écrire notre test!

<?php
namespace Acme\Bundle\DemoBundle\Tests\Entity\Repository;

/* Imports */
use Acme\Bundle\DemoBundle\Tests\PHPUnit\RepositoryTestCase;

/**
 * MyEntityRepositoryTest
 *
 * @author Nicolas Macherey <nicolas.macherey@gmail.com>
 */
class MyEntityRepositoryTest extends RepositoryTestCase
{
    /**
     * {@inheritdoc}
     */
    protected function getFixtures()
    {
        return [
            __DIR__ . '/../../Resources/fixtures/myentities.yml',
        ];
    }

    /**
     * {@inheritdoc}
     */
    protected function getEntities()
    {
        return [
            'Acme\Bundle\DemoBundle\Entity\MyEntity',
        ];
    }

    /**
     * Test the count query is working properly
     */
    public function testCount()
    {
        $this->loadFixtures();
        $repository = $this->em->getRepository('Acme\Bundle\DemoBundle\Entity\MyEntity');

        $this->assertEquals(15, $repository->count());
    }
}

Voilà... C'est quand même pas trop mal on se retrouve avec du code lisible pour nos dépôts, on peut assez facilement tester nos requêtes et on ne polue pas une base MySQL pour faire ça ce qui fait que nos procédures de test sont quand même assez rapide en terme d'exécution.

Bien sur pour tester des requêtes basées sur des entités liées et un mapping plus complexe, il vous faudra travailler un fichier de fixtures plus complet et surtout charger plus d'entités via la méthode getEntities.

J'espère que ce petit post aura été utile aux développeurs consciencieux que vous êtes :-)

Comments

comments powered by Disqus