【问题标题】:Best way to create a test database and load fixtures on Symfony 2 WebTestCase?在 Symfony 2 WebTestCase 上创建测试数据库和加载夹具的最佳方法?
【发布时间】:2013-01-23 01:25:51
【问题描述】:

我有一个在我的应用程序中执行一些基本路由的 WebTestCase。

我想在 PHPUnit 的 setUp 方法上,创建一个与我的主数据库相同的测试数据库,并将夹具加载到其中。

我目前正在做一些解决方法并执行一些控制台命令,如下所示:

class FixturesWebTestCase extends WebTestCase
{
    protected static $application;

    protected function setUp()
    {
        self::runCommand('doctrine:database:create');
        self::runCommand('doctrine:schema:update --force');
        self::runCommand('doctrine:fixtures:load --purge-with-truncate');
    }

    protected static function runCommand($command)
    {
        $command = sprintf('%s --quiet', $command);

        return self::getApplication()->run(new StringInput($command));
    }

    protected static function getApplication()
    {
        if (null === self::$application) {
            $client = static::createClient();

            self::$application = new Application($client->getKernel());
            self::$application->setAutoExit(false);
        }

        return self::$application;
    }
}

但我很确定这不是最好的方法,尤其是因为doctrine:fixtures:load 期望用户点击Y 字符来确认操作。

我该如何解决?

【问题讨论】:

标签: symfony doctrine tdd


【解决方案1】:

如果你想使用doctrine:fixtures:load,你可以使用--append选项来避免用户确认。由于您每次都在重新创建数据库,因此不需要清除。我曾经单独使用理论夹具进行测试,但后来改用夹具和LiipFunctionalTestBundle 来避免 DRY。此捆绑包使灯具更易于管理。

编辑:David Jacquel 的答案是加载 Doctrine Fixtures 的正确答案:

doctrine:fixtures:load --no-interaction 
or
doctrine:fixtures:load -n

【讨论】:

  • 感谢LiipFunctionalTestBundle 的精彩回答和参考!
  • LiipFunctionalTestBundle 的一个很酷的地方是你可以像 Rails 一样使用自定义测试数据库:github.com/liip/LiipFunctionalTestBundle#sqlite
  • 但也有关于“镇上的新捆绑”的讨论:github.com/instaclick/ICBaseTestBundle/issues/24
  • “镇上的新包”似乎有点言过其实。该问题已存在 2 年,并且捆绑包本身已在 10 个月内未更新。 Liip 最近也没有更新。
【解决方案2】:

为了绕过用户确认,您可以使用

doctrine:fixtures:load --no-interaction
or
doctrine:fixtures:load -n

【讨论】:

  • 实际上这是最好的答案,因为这样做是正确的。
【解决方案3】:

更新答案

您可以为您的测试用例创建一个基类,通过利用 Doctrine Data Fixtures 库中的一些类来轻松加载夹具。这个类看起来很像这样:

<?php

use Doctrine\Common\DataFixtures\Executor\ORMExecutor;
use Doctrine\Common\DataFixtures\FixtureInterface;
use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bridge\Doctrine\DataFixtures\ContainerAwareLoader;
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

abstract class FixtureAwareTestCase extends KernelTestCase
{
    /**
     * @var ORMExecutor
     */
    private $fixtureExecutor;

    /**
     * @var ContainerAwareLoader
     */
    private $fixtureLoader;

    public function setUp()
    {
        self::bootKernel();
    }

    /**
     * Adds a new fixture to be loaded.
     *
     * @param FixtureInterface $fixture
     */
    protected function addFixture(FixtureInterface $fixture)
    {
        $this->getFixtureLoader()->addFixture($fixture);
    }

    /**
     * Executes all the fixtures that have been loaded so far.
     */
    protected function executeFixtures()
    {
        $this->getFixtureExecutor()->execute($this->getFixtureLoader()->getFixtures());
    }

    /**
     * @return ORMExecutor
     */
    private function getFixtureExecutor()
    {
        if (!$this->fixtureExecutor) {
            /** @var \Doctrine\ORM\EntityManager $entityManager */
            $entityManager = self::$kernel->getContainer()->get('doctrine')->getManager();
            $this->fixtureExecutor = new ORMExecutor($entityManager, new ORMPurger($entityManager));
        }
        return $this->fixtureExecutor;
    }

    /**
     * @return ContainerAwareLoader
     */
    private function getFixtureLoader()
    {
        if (!$this->fixtureLoader) {
            $this->fixtureLoader = new ContainerAwareLoader(self::$kernel->getContainer());
        }
        return $this->fixtureLoader;
    }
}

然后,在您的测试用例中,只需扩展上述类并在您的测试之前添加所有需要的夹具并执行它们。这将在加载夹具之前自动清除您的数据库。示例如下:

class MyTestCase extends FixtureAwareTestCase
{
    public function setUp()
    {
        parent::setUp();

        // Base fixture for all tests
        $this->addFixture(new FirstFixture());
        $this->addFixture(new SecondFixture());
        $this->executeFixtures();

        // Fixtures are now loaded in a clean DB. Yay!
    }
}

旧答案

(我决定“弃用”这个答案,因为它只解释了如何清理数据库而不告诉之后如何加载固定装置)。

有一种更简洁的方法可以完成此操作,而无需运行命令。它基本上包括使用 SchemaTool 和 ORMPurger 的组合。您可以创建一个执行此类操作的抽象基类,以避免为每个专门的测试用例重复它们。这是一个测试用例类的代码示例,它为通用测试用例设置数据库:

use Doctrine\Common\DataFixtures\Purger\ORMPurger;
use Doctrine\ORM\Tools\SchemaTool;

abstract class DatabaseAwareWebTestCase extends WebTestCase {

    public static function setUpBeforeClass() {

        parent::setUpBeforeClass();

        $kernel = static::createKernel();
        $kernel->boot();
        $em = $kernel->getContainer()->get('doctrine')->getManager();
        $schemaTool = new SchemaTool($em);
        $metadata = $em->getMetadataFactory()->getAllMetadata();

        // Drop and recreate tables for all entities
        $schemaTool->dropSchema($metadata);
        $schemaTool->createSchema($metadata);
    }

    protected function tearDown() {

        parent::tearDown();

        $purger = new ORMPurger($this->getContainer()->get('doctrine')->getManager());
        $purger->setPurgeMode(ORMPurger::PURGE_MODE_TRUNCATE);
        $purger->purge();
    }
}

这样,在运行从上述类继承的每个测试用例之前,数据库架构将从头开始重建,然后在每次测试运行后清理。

希望这会有所帮助。

【讨论】:

  • 不错的答案!但是我建议使用特征而不是类:)
  • @lazel 听起来像 laravel ;)
  • 建议不要在 tearDown 方法中清除数据库,如果测试失败,您仍然可以查看数据库并检查问题所在
【解决方案4】:

我偶然发现了一个名为 Doctrine-Test-Bundle 的非常简洁的捆绑包 它不是在每个测试上创建和删除模式,而是简单地回滚。 我的测试从 1 分 40 秒到.. 2 秒。而且是孤立的。 您只需要一个清晰的测试数据库就可以了。

【讨论】:

    【解决方案5】:

    我使用了这个命令:

    yes | php app/console doctrine:fixtures:load --purge-with-truncate
    

    当然,LiipFunctionalTestBundle 看起来很有希望。

    【讨论】:

      【解决方案6】:

      我想像doctrine:fixtures:load 命令那样加载所有的装置。我不想在测试用例中运行exec,因为它看起来像是一种混乱的做事方式。我查看了教条命令本身是如何做到这一点的,然后只是复制了相关行。

      我从 Symfony WebTestCase 扩展而来,在创建内核后,我只是调用了我的方法,它的工作方式与 Doctrine load-fixtures 命令完全相同。

          /**
           * Load fixtures for all bundles
           *
           * @param Kernel $kernel
           */
          private static function loadFixtures(Kernel $kernel)
          {
              $loader = new DataFixturesLoader($kernel->getContainer());
              $em = $kernel->getContainer()->get('doctrine')->getManager();
      
              foreach ($kernel->getBundles() as $bundle) {
                  $path = $bundle->getPath().'/DataFixtures/ORM';
      
                  if (is_dir($path)) {
                      $loader->loadFromDirectory($path);
                  }
              }
      
              $fixtures = $loader->getFixtures();
              if (!$fixtures) {
                  throw new InvalidArgumentException('Could not find any fixtures to load in');
              }
              $purger = new ORMPurger($em);
              $executor = new ORMExecutor($em, $purger);
              $executor->execute($fixtures, true);
          }
      

      【讨论】:

        【解决方案7】:

        就在最近,捆绑包 hautelook/AliceBundle 公开了两个特征来帮助您解决在功能测试中加载固定装置的用例:RefreshDatabaseTraitReloadDatabaseTrait

        来自文档:

        namespace App\Tests;
        
        use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait;
        use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
        
        class NewsTest extends WebTestCase
        {
            use RefreshDatabaseTrait;
        
            public function postCommentTest()
            {
                $client = static::createClient(); // The transaction starts just after the boot of the Symfony kernel
                $crawler = $client->request('GET', '/my-news');
                $form = $crawler->filter('#post-comment')->form(['new-comment' => 'Symfony is so cool!']);
                $client->submit($form);
                // At the end of this test, the transaction will be rolled back (even if the test fails)
            }
        }
        

        你很好!

        【讨论】:

          猜你喜欢
          • 2018-05-16
          • 1970-01-01
          • 2016-03-09
          • 1970-01-01
          • 1970-01-01
          • 2015-07-27
          • 1970-01-01
          • 2022-06-17
          • 1970-01-01
          相关资源
          最近更新 更多