CommonContexts icon indicating copy to clipboard operation
CommonContexts copied to clipboard

[SymfonyDoctrineContext] Improve performance for sqlite

Open wodor opened this issue 13 years ago • 9 comments
trafficstars

Anyone who used behat and sqlite can notice that

   $tool->dropSchema($metadata);
   $tool->createSchema($metadata);

are very slow when it's run on sqlite guys from Liip fixed this by copying filled db file ond restoring from backup inspired by https://github.com/liip/LiipFunctionalTestBundle/blob/master/Test/WebTestCase.php

this is a rough (although working) example how it could be done



use Behat\CommonContexts;
/**
 * Created by JetBrains PhpStorm.
 * User: wodor
 * Date: 12.03.12
 * Time: 15:30
 */
class FastSqliteDoctrineContext extends  \Behat\CommonContexts\SymfonyDoctrineContext
{

    /**
     * @param \Behat\Behat\Event\ScenarioEvent|\Behat\Behat\Event\OutlineExampleEvent $event
     *
     * @BeforeScenario
     *
     * @return null
     */
    public function buildSchema($event)
    {
        $metadata = $this->getMetadata();
        $container = $this->getContainer();

        $connection = current($this->getConnections());
        if ($connection->getDriver() instanceOf \Doctrine\DBAL\Driver\PDOSqlite\Driver) {
            $params = $connection->getParams();
            $name = isset($params['path']) ? $params['path'] : $params['dbname'];
            $metadatas = $this->getEntityManager()->getMetadataFactory()->getAllMetadata();

            $backup = $container->getParameter('kernel.cache_dir') . '/test_' . md5(serialize($metadatas)) . '.db';
            if (file_exists($backup)) {
                copy($backup, $name);
                return;
            }

            if (!empty($metadata)) {
                $tool = new \Doctrine\ORM\Tools\SchemaTool($this->getEntityManager());
                $tool->dropSchema($metadata);
                $tool->createSchema($metadata);
            }

            if (isset($backup)) {
                copy($name, $backup);
            }
        }
    }
}

This , could be blended into SymfonyDoctrineContext and, of course, requires polishing and introducing an option enabling it.

I could transform this into a PR @jakzal , are you interested in this improvement ?

wodor avatar Mar 12 '12 15:03 wodor

I don't think it's up to me to decide. I contributed SymfonyDoctrineContext but I'm not an official maintainer.

My personal opinion is that it's better to use the database you're using on production (and there's a limited number of use cases for sqlite on production...).

Also, I don't like the idea of maintaining the dump with a database structure.

If you thought of storing data dump in your sqlite file... I prefer to define fixtures in scenarios. It's better to be explicit about the application state.

jakzal avatar Mar 22 '12 18:03 jakzal

@jakzal I'm surprised to see such opinion. If you've got a big system, with many related entities to test, like:

  • Users with 10 or more diffrent access levels
  • For each "Basic" user you must create many entities (for each module), say you have some internal company document flow system.. for each user you must create Invoices, Contracts, Contacts, and stuff like that
  • now let's assume each of these entities may have a "Status", depending on which, diffrent users may perform diffrent actions

The easiest way I can think of, is to generate "for each user" one entity per status (so that all possible permutations exist), then store this as SQLLite db file, and "copy" the database before each scenario (to reset any changes), just like @wodor suggests.

Creating seperate smaller chunks of fixtures and loading them before each scenario would make tests run very slow.

@jakzal do you still hold your opinion in the comment above? if so, how would you solve case described above?

ioleo avatar Sep 05 '14 11:09 ioleo

Using sqlite is unreliable.

If you care about performance of your tests you probably shouldn't use a real database in aceprtance tests anyway. Your test repositories could use flat files for example.

jakzal avatar Sep 05 '14 13:09 jakzal

Anyway, the proposed solution is so specific to sqlite that it should be probably implemented as a separate context.

jakzal avatar Sep 05 '14 13:09 jakzal

@jakzal if you want to perform end-to-end tests of your system, you will need to have your persistence layer involved somehow. Otherwise, this layer will not be covered.

stof avatar Sep 05 '14 13:09 stof

However I agree that it should be implemented separately

stof avatar Sep 05 '14 13:09 stof

So, it's better to pollute FeatureContext's with custom steps like:

<?php
/**
 * @Given /I have a category "([^"]*)"/
 */
public function iHaveACategory($name)
{
    $em = $this->getContainer()->get('doctrine')->getEntityManager();

    $entity = new \Acme\DemoBundle\Entity\Category();
    $entity->setName($name);

    $em->persist($entity);
    $em->flush();
}
?>

every time we want to create and test some object?

PS. I'm asking becouse I'm right now learning Behat, and I'd like to do this "the right way".. and you guys are one of active and respected symfony community members :)

ioleo avatar Sep 05 '14 18:09 ioleo

Why pollute ?

IMO, having a step in your scenario saying you have a category is much more readable than relying on an implicit initial state

stof avatar Sep 05 '14 22:09 stof

I see, I'll try it that way then :)

ioleo avatar Sep 06 '14 10:09 ioleo