Behat / CommonContexts

Common FeatureContext classes for Behat
116 stars 34 forks source link

[SymfonyDoctrineContext] Improve performance for sqlite #17

Closed wodor closed 1 month ago

wodor commented 12 years ago

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 ?

jakzal commented 12 years ago

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.

ioleo commented 10 years ago

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

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?

jakzal commented 10 years ago

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 commented 10 years ago

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

stof commented 10 years ago

@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 commented 10 years ago

However I agree that it should be implemented separately

ioleo commented 10 years ago

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 :)

stof commented 10 years ago

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

ioleo commented 10 years ago

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