Je suis assez nouveau dans le monde des tests et je veux m'assurer que je suis sur la bonne voie.
J'essaie de configurer des tests unitaires dans un projet symfony2 à l'aide de phpunit.
PHPUnit fonctionne et les tests du contrôleur par défaut simples fonctionnent correctement. (Pourtant, il ne s'agit pas de tests fonctionnels mais de tests unitaires de mon application.)
Mon projet repose cependant beaucoup sur les interactions avec les bases de données, et d'après ce que je comprends de la documentation de phpunit , je devrais configurer une classe basée sur \PHPUnit_Extensions_Database_TestCase
, puis créer des fixtures pour ma base de données et travailler à partir de là.
Cependant, symfony2 n'offre qu'une classe WebTestCase
qui s'étend uniquement de \PHPUnit_Framework_TestCase
prêt à l'emploi.
Alors, ai-je raison de supposer que je devrais créer ma propre DataBaseTestCase
qui copie principalement WebTestCase
, la seule différence étant qu'elle s'étend de \PHPUnit_Extensions_Database_TestCase
et implémente toutes ses méthodes abstraites?
Ou existe-t-il un autre flux de travail "intégré" recommandé pour symfony2 concernant les tests centrés sur la base de données?
Comme je veux m'assurer que mes modèles stockent et récupèrent les bonnes données, je ne veux pas finir par tester les spécificités de doctrine par accident.
tl; dr:
Le test unitaire n'a de sens que pour les services et non pour les référentiels. Et ces services peuvent utiliser des simulacres du responsable de l'entité. (J'irais même jusqu'à dire: si possible, écrivez des services qui n'attendent que des entités. Vous devez ensuite créer des simulacres de ces entités et vos tests unitaires de votre logique métier deviennent très simples.)
Mon cas d'utilisation réel pour mon application était assez bien réfléchi sur la documentation symfony2 sur comment tester le code qui interagit avec la base de données .
Ils fournissent cet exemple pour un test de service:
Classe de service:
use Doctrine\Common\Persistence\ObjectManager;
class SalaryCalculator
{
private $entityManager;
public function __construct(ObjectManager $entityManager)
{
$this->entityManager = $entityManager;
}
public function calculateTotalSalary($id)
{
$employeeRepository = $this->entityManager
->getRepository('AppBundle:Employee');
$employee = $employeeRepository->find($id);
return $employee->getSalary() + $employee->getBonus();
}
}
Classe de test de service:
namespace Tests\AppBundle\Salary;
use AppBundle\Salary\SalaryCalculator;
use AppBundle\Entity\Employee;
use Doctrine\ORM\EntityRepository;
use Doctrine\Common\Persistence\ObjectManager;
class SalaryCalculatorTest extends \PHPUnit_Framework_TestCase
{
public function testCalculateTotalSalary()
{
// First, mock the object to be used in the test
$employee = $this->getMock(Employee::class);
$employee->expects($this->once())
->method('getSalary')
->will($this->returnValue(1000));
$employee->expects($this->once())
->method('getBonus')
->will($this->returnValue(1100));
// Now, mock the repository so it returns the mock of the employee
$employeeRepository = $this
->getMockBuilder(EntityRepository::class)
->disableOriginalConstructor()
->getMock();
$employeeRepository->expects($this->once())
->method('find')
->will($this->returnValue($employee));
// Last, mock the EntityManager to return the mock of the repository
$entityManager = $this
->getMockBuilder(ObjectManager::class)
->disableOriginalConstructor()
->getMock();
$entityManager->expects($this->once())
->method('getRepository')
->will($this->returnValue($employeeRepository));
$salaryCalculator = new SalaryCalculator($entityManager);
$this->assertEquals(2100, $salaryCalculator->calculateTotalSalary(1));
}
}
Aucune base de données de test requise pour ce type de test, seulement des moqueries.
Comme il est important de tester la logique métier, pas la couche de persistance.
Ce n'est que pour les tests fonctionnels qu'il est logique de disposer de sa propre base de données de tests, qu'il convient de créer et de démolir par la suite. La grande question devrait donc être:
Quand le test fonctionnel a-t-il un sens?
J'avais l'habitude de penser que tester toutes les choses est la bonne réponse; pourtant, après avoir travaillé avec de nombreux logiciels existants qui, à eux seuls, étaient à peine développés, je suis devenu un peu plus paresseuxpragmatique et considérez certaines fonctionnalités comme fonctionnant jusqu’à preuve du contraire d’un bogue.
Supposons que je possède une application qui analyse un fichier XML, en crée un objet et stocke ces objets dans une base de données. Si la logique qui stocke les objets dans la base de données est connue pour fonctionner (comme dans: la société a besoin des données et n’est pas encore fictive), et même si cette logique est une grosse pile de merde, il n’existe imminent doit tester cela. Comme tout ce dont j'ai besoin pour être sûr que mon analyseur XML extrait les bonnes données. Je peux déduire de l’expérience que les bonnes données seront stockées.
Il existe des scénarios dans lesquels le test fonctionnel est très important, c’est-à-dire s’il fallait écrire une boutique en ligne. Dans ce cas, il serait essentiel que les articles achetés soient stockés dans la base de données. Dans ce cas, les tests fonctionnels avec la base de données de tests complète ont un sens absolu.
Je n'ai jamais utilisé le PHPUnit_Extensions_Database_TestCase
, principalement pour ces deux raisons:
Mon chemin en théorie ...
J'utilise le (doctrine/doctrine-fixtures-bundle } pour les fixtures (peu importe le but) et je configure toute la base de données avec tous les fixtures. J'exécute ensuite tous les tests sur cette base de données et veille à bien la recréer si un test l'a modifiée.
Les avantages sont que je n'ai pas besoin de reconfigurer une base de données si un test ne lit que sans rien changer. Pour les modifications, je dois le supprimer et le créer à nouveau ou m'assurer de revenir en arrière.
J'utilise sqlite pour les tests car je peux configurer la base de données, puis copier le fichier sqlite et le remplacer par un fichier vierge pour ramener la base de données d'origine. De cette façon, je n'ai pas besoin de supprimer la base de données, de la créer et de charger à nouveau tous les fixtures pour une base de données vierge.
... et dans le code
J'ai écrit un article sur la façon dont je fais des tests de base de données avec symfony2 et phpunit }.
Bien qu’il utilise sqlite, je pense que l’on peut facilement apporter les modifications nécessaires pour utiliser MySQL, Postgres ou autre.
Penser plus loin
Voici d'autres idées qui pourraient fonctionner:
Vous pouvez utiliser cette classe:
<?php
namespace Project\Bundle\Tests;
require_once dirname(__DIR__).'/../../../app/AppKernel.php';
use Doctrine\ORM\Tools\SchemaTool;
abstract class TestCase extends \PHPUnit_Framework_TestCase
{
/**
* @var Symfony\Component\HttpKernel\AppKernel
*/
protected $kernel;
/**
* @var Doctrine\ORM\EntityManager
*/
protected $entityManager;
/**
* @var Symfony\Component\DependencyInjection\Container
*/
protected $container;
public function setUp()
{
// Boot the AppKernel in the test environment and with the debug.
$this->kernel = new \AppKernel('test', true);
$this->kernel->boot();
// Store the container and the entity manager in test case properties
$this->container = $this->kernel->getContainer();
$this->entityManager = $this->container->get('doctrine')->getEntityManager();
// Build the schema for sqlite
$this->generateSchema();
parent::setUp();
}
public function tearDown()
{
// Shutdown the kernel.
$this->kernel->shutdown();
parent::tearDown();
}
protected function generateSchema()
{
// Get the metadatas of the application to create the schema.
$metadatas = $this->getMetadatas();
if ( ! empty($metadatas)) {
// Create SchemaTool
$tool = new SchemaTool($this->entityManager);
$tool->createSchema($metadatas);
} else {
throw new Doctrine\DBAL\Schema\SchemaException('No Metadata Classes to process.');
}
}
/**
* Overwrite this method to get specific metadatas.
*
* @return Array
*/
protected function getMetadatas()
{
return $this->entityManager->getMetadataFactory()->getAllMetadata();
}
}
Et ensuite, vous pouvez tester votre entité. Quelque chose comme ça (en supposant que vous avez un utilisateur d'entité)
//Entity Test
class EntityTest extends TestCase {
protected $user;
public function setUp()
{
parent::setUp();
$this->user = new User();
$this->user->setUsername('username');
$this->user->setPassword('p4ssw0rd');
$this->entityManager->persist($this->user);
$this->entityManager->flush();
}
public function testUser(){
$this->assertEquals($this->user->getUserName(), "username");
...
}
}
J'espère que cette aide.
Source: theodo.fr/blog/2011/09/symfony2-unit-database-tests