如何在内存数据库中使用Laravel中的完整测试套件迁移和播种?

时间:2016-07-25 17:16:24

标签: php laravel phpunit laravel-5.2

我试图在我的Laravel项目中设置测试环境。 我使用http://packalyst.com/packages/package/mayconbordin/l5-fixtures和json一起在内存数据库中使用sqlite进行播种并调用:

Artisan::call('migrate');
Artisan::call('db:seed');

在我的 setUp 函数中,但这是在每个测试之前执行的,在此项目中它可以增长到数千个。

我尝试了 setUpBeforeClass ,但它没有用。 我认为是因为在每次测试中都会调用 createApplication 方法并重置整个应用程序,并且也可能出于同样的原因从json加载灯具。

5 个答案:

答案 0 :(得分:8)

我就是这样做的,以防万一其他人正在努力解决这个问题,我创建了一个继承自Laravel的基础testClase类并且做到了这一点:

/**
 * Creates the application.
 *
 * @return \Illuminate\Foundation\Application
 */
public function createApplication()
{
    return self::initialize();
}

private static $configurationApp = null;
public static function initialize(){

    if(is_null(self::$configurationApp)){
        $app = require __DIR__.'/../bootstrap/app.php';

        $app->loadEnvironmentFrom('.env.testing');

        $app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();

        if (config('database.default') == 'sqlite') {
            $db = app()->make('db');
            $db->connection()->getPdo()->exec("pragma foreign_keys=1");
        }

        Artisan::call('migrate');
        Artisan::call('db:seed');

        self::$configurationApp = $app;
        return $app;
    }

    return self::$configurationApp;
}

public function tearDown()
{
    if ($this->app) {
        foreach ($this->beforeApplicationDestroyedCallbacks as $callback) {
            call_user_func($callback);
        }

    }

    $this->setUpHasRun = false;

    if (property_exists($this, 'serverVariables')) {
        $this->serverVariables = [];
    }

    if (class_exists('Mockery')) {
        Mockery::close();
    }

    $this->afterApplicationCreatedCallbacks = [];
    $this->beforeApplicationDestroyedCallbacks = [];
}

我覆盖了createApplication()tearDown()方法。我更改了第一个配置以使用相同的$app配置,并删除了teardown()中已刷新$this->app的部分。

我的测试的其他所有内容都必须从这个TestClass继承并且是它。

其他一切都不起作用。即使在内存数据库中,这也可以快100倍。

如果您正在处理用户会话,一旦您登录该用户,您将不得不将其记录下来,否则用户将登录,因为应用程序环境永远不会重建或您可以这样做每次需要时刷新应用程序:

protected static $applicationRefreshed = false;

/**
 * Refresh the application instance.
 *
 * @return void
 */
protected function forceRefreshApplication() {
    if (!is_null($this->app)) {
        $this->app->flush();
    }
    $this->app = null;
    self::$configurationApp = null;
    self::$applicationRefreshed = true;
    parent::refreshApplication();
}

并将其添加到tearDown()之前的$this->setUphasRun = false;

if (self::$applicationRefreshed) {
        self::$applicationRefreshed = false;
        $this->app->flush();
        $this->app = null;
        self::$configurationApp = null;
}

答案 1 :(得分:1)

使用此内容在项目NioMultipartParser中创建文件(还准备带有测试环境变量的testrunner文件):

.env.testing

并允许命令php artisan migrate:rollback --env=testing php artisan migrate --env=testing --seed vendor/bin/phpunit 执行并由chmod +x testrunner执行。多数民众赞成:)

答案 2 :(得分:1)

上述解决方案的主要方法是为所有测试运行所有迁移。我更喜欢一种方法来指定每次测试应运行哪些迁移和种子。

在大型项目上可能更值得,因为这可以将测试时间减少大约70%(使用sqlite内存数据库,如上所述)。对于小型项目来说,可能有点太过分了。但无论如何......

在TestCase中使用这些:

/**
 * Runs migrations for individual tests
 *
 * @param array $migrations
 * @return void
 */
public function migrate(array $migrations = [])
{
    $path = database_path('migrations');
    $migrator = app()->make('migrator');
    $migrator->getRepository()->createRepository();
    $files = $migrator->getMigrationFiles($path);

    if (!empty($migrations)) {
        $files = collect($files)->filter(
            function ($value, $key) use ($migrations) {
                if (in_array($key, $migrations)) {
                    return [$key => $value];
                }
            }
        )->all();
    }

    $migrator->requireFiles($files);
    $migrator->runPending($files);
}

/**
 * Runs some or all seeds
 *
 * @param string $seed
 * @return void
 */
public function seed(string $seed = '')
{
    $command = "db:seed";

    if (empty($seed)) {
        Artisan::call($command);
    } else {
        Artisan::call($command, ['--class' => $seed]);
    }
}

然后在各个测试中根据需要调用migrate()和seed,例如:

    $this->migrate(
        [
            '2013_10_11_081829_create_users_table',
        ]
    );
    $this->seed(UserTableSeeder::class);

答案 3 :(得分:0)

选项1

如何使用迁移和种子设置数据库然后使用数据库转换? (https://laravel.com/docs/5.1/testing#resetting-the-database-after-each-test

我希望能够通过这样的工匠设置我的测试数据库:

$ php artisan migrate --database=mysql_testing
$ php artisan db:seed --database=mysql_testing

你可以猜到,我正在使用mysql,但我不明白为什么这不适用于sqlite。 我就是这样做的。

<强>配置/ database.php中

首先在您当前的数据库信息下将测试数据库信息添加到config / database.php文件中。

'connections' => [
        'mysql' => [
            'driver'    => 'mysql',
            'host'      => env('DB_HOST', 'localhost'),
            'database'  => env('DB_DATABASE', 'forge'),
            'username'  => env('DB_USERNAME', 'forge'),
            'password'  => env('DB_PASSWORD', ''),
            'charset'   => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix'    => '',
            'strict'    => false,
        ],
        'mysql_testing' => [
            'driver'    => 'mysql',
            'host'      => env('DB_HOST', 'localhost'),
            'database'  => env('DB_TEST_DATABASE'),
            'username'  => env('DB_USERNAME', 'forge'),
            'password'  => env('DB_PASSWORD', ''),
            'charset'   => 'utf8',
            'collation' => 'utf8_unicode_ci',
            'prefix'    => '',
            'strict'    => false,
        ],
    ],

如果您这样做,请不要忘记将DB_TEST_DATABASE添加到 .env 文件中:

DB_DATABASE=abc
DB_TEST_DATABASE=abc_test

<强> phpunit.xml

在phpunit.xml文件中设置的任何值,覆盖.env文件中给出的值。所以我们告诉phpunit使用“mysql_testing”数据库连接而不是“mysql”数据库连接。

<?xml version="1.0" encoding="UTF-8"?>
<phpunit>
    ...
    <php>
        ...
        <env name="DB_CONNECTION" value="mysql_testing"/>
</php>

测试类

我的测试类看起来像这样:

class MyTest extends \TestCase
{
    use \Illuminate\Foundation\Testing\DatabaseTransactions;

    public function testSomething()
    {

选项2

这里数据库在每次测试之前都会重置,这就是我更喜欢选项1的原因。但是你可以按照你喜欢的方式让它工作。

之前我试过这个,它可能适合你。

<强>测试/ TestCase.php 扩展测试用例,以加载新的.env文件,.env.testing

<?php

class TestCase extends Illuminate\Foundation\Testing\TestCase
{
    /**
     * The base URL to use while testing the application.
     *
     * @var string
     */
    protected $baseUrl = 'http://localhost';

    /**
     * Creates the application.
     *
     * @return \Illuminate\Foundation\Application
     */
    public function createApplication()
    {
        /** @var $app \Illuminate\Foundation\Application */
        $app = require __DIR__.'/../bootstrap/app.php';
        $app->loadEnvironmentFrom('.env.testing');

        $app->make(Illuminate\Contracts\Console\Kernel::class)->bootstrap();

        return $app;
    }
}

<强> .env.testing

创建这个新的.env文件并添加数据库详细信息

APP_ENV=testing
APP_DEBUG=true
APP_KEY=xxx

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=abc_testing
DB_USERNAME=xxx
DB_PASSWORD=xxx

测试类

使用PDO删除并重新创建数据库 - 比尝试截断所有内容更容易。 然后使用artisan来迁移和播种数据库。

class MyTest extends TestCase
{
    public static function setUpBeforeClass()
    {
        $config = parse_ini_file(".env.testing");
        $username = $config['DB_USERNAME'];
        $password = $config['DB_PASSWORD'];
        $database = $config['DB_DATABASE'];
        $host = $config['DB_HOST'];

        // Create test database
        $connection = new PDO("mysql:host={$host}", $username, $password);
        $connection->query("DROP DATABASE IF EXISTS " . $database);
        $connection->query("CREATE DATABASE " . $database);
    }

    public function testHomePage()
    {
        Artisan::call('migrate');
        Artisan::call('db:seed');

        $this->visit('/')
             ->see('Home')
             ->see('Please sign in')
             ->dontSee('Logout');
    }

答案 4 :(得分:0)

就我而言,我创建了.env.testing文件,该文件是从.env.example文件复制而来的。然后,我将数据库信息添加到此文件中。

APP_ENV=testing
APP_KEY=<generate your app key>
...
DB_CONNECTION=sqlite
DB_DATABASE=:memory:

在终端中,您可以像这样使用选项--env运行migration artisan命令。

php artisan migrate:fresh --env=testing