如何使用内存数据库在 Laravel 中的完整测试套件之前迁移和播种?
How to Migrate and seed before the full test suite in Laravel with in memory database?
我正在尝试在我的 Laravel 项目中设置测试环境。
我正在使用 http://packalyst.com/packages/package/mayconbordin/l5-fixtures 和 json 在内存数据库中使用 sqlite 进行播种并调用:
Artisan::call('migrate');
Artisan::call('db:seed');
在我的 setUp 函数中,但这是在每次测试之前执行的,在这个项目中它可以增长到数千个。
我尝试了 setUpBeforeClass 但它没有用。
我认为那里是因为 createApplication 方法在每个测试中都被调用并且重置了整个应用程序并且也没有从 json 加载固定装置可能出于同样的原因。
在您的项目 testrunner
中使用此内容创建文件(同时准备 .env.testing
带有测试环境变量的文件):
php artisan migrate:rollback --env=testing
php artisan migrate --env=testing --seed
vendor/bin/phpunit
并通过命令chmod +x testrunner
授予执行权限,并通过./testrunner
执行。就这些:)
我就是这样做的,以防其他人遇到同样的问题,我创建了一个继承自 Laravel 的基础 testClase
class 并这样做了:
/**
* 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();
}
并将其添加到 $this->setUphasRun = false;
之前的 tearDown()
:
if (self::$applicationRefreshed) {
self::$applicationRefreshed = false;
$this->app->flush();
$this->app = null;
self::$configurationApp = null;
}
选项 1
如何使用迁移和种子设置数据库,然后使用数据库事务? (https://laravel.com/docs/5.1/testing#resetting-the-database-after-each-test)
我希望能够像这样通过 artisan 设置我的测试数据库:
$ php artisan migrate --database=mysql_testing
$ php artisan db:seed --database=mysql_testing
如您所料,我正在使用 mysql,但我不明白为什么这不适用于 sqlite。
我就是这样做的。
config/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 文件中给出的值。所以我们告诉 php 单元使用 "mysql_testing" 数据库连接而不是 "mysql" 数据库连接。
<?xml version="1.0" encoding="UTF-8"?>
<phpunit>
...
<php>
...
<env name="DB_CONNECTION" value="mysql_testing"/>
</php>
测试class
我的测试 class 是这样的:
class MyTest extends \TestCase
{
use \Illuminate\Foundation\Testing\DatabaseTransactions;
public function testSomething()
{
选项 2
此处数据库在每次测试前都会重置,这就是我更喜欢选项 1 的原因。但您也许可以按照自己喜欢的方式让它工作。
我以前试过一次,它可能对你有用。
tests/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
在测试中class:
使用 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');
}
上述解决方案中的主要方法是 运行 所有测试的所有迁移。我更喜欢一种方法来指定每个测试应该 运行 哪些迁移和种子。
在大项目上它可能更有价值,因为这可以将测试时间减少大约 70%(使用上面已经解释过的 sqlite in-memory 数据库)。对于小型项目,这可能有点太过分了。但无论如何...
在测试用例中使用这些:
/**
* 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);
在我的例子中,我创建了 .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
我正在尝试在我的 Laravel 项目中设置测试环境。 我正在使用 http://packalyst.com/packages/package/mayconbordin/l5-fixtures 和 json 在内存数据库中使用 sqlite 进行播种并调用:
Artisan::call('migrate');
Artisan::call('db:seed');
在我的 setUp 函数中,但这是在每次测试之前执行的,在这个项目中它可以增长到数千个。
我尝试了 setUpBeforeClass 但它没有用。 我认为那里是因为 createApplication 方法在每个测试中都被调用并且重置了整个应用程序并且也没有从 json 加载固定装置可能出于同样的原因。
在您的项目 testrunner
中使用此内容创建文件(同时准备 .env.testing
带有测试环境变量的文件):
php artisan migrate:rollback --env=testing
php artisan migrate --env=testing --seed
vendor/bin/phpunit
并通过命令chmod +x testrunner
授予执行权限,并通过./testrunner
执行。就这些:)
我就是这样做的,以防其他人遇到同样的问题,我创建了一个继承自 Laravel 的基础 testClase
class 并这样做了:
/**
* 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();
}
并将其添加到 $this->setUphasRun = false;
之前的 tearDown()
:
if (self::$applicationRefreshed) {
self::$applicationRefreshed = false;
$this->app->flush();
$this->app = null;
self::$configurationApp = null;
}
选项 1
如何使用迁移和种子设置数据库,然后使用数据库事务? (https://laravel.com/docs/5.1/testing#resetting-the-database-after-each-test)
我希望能够像这样通过 artisan 设置我的测试数据库:
$ php artisan migrate --database=mysql_testing
$ php artisan db:seed --database=mysql_testing
如您所料,我正在使用 mysql,但我不明白为什么这不适用于 sqlite。 我就是这样做的。
config/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 文件中给出的值。所以我们告诉 php 单元使用 "mysql_testing" 数据库连接而不是 "mysql" 数据库连接。
<?xml version="1.0" encoding="UTF-8"?>
<phpunit>
...
<php>
...
<env name="DB_CONNECTION" value="mysql_testing"/>
</php>
测试class
我的测试 class 是这样的:
class MyTest extends \TestCase
{
use \Illuminate\Foundation\Testing\DatabaseTransactions;
public function testSomething()
{
选项 2
此处数据库在每次测试前都会重置,这就是我更喜欢选项 1 的原因。但您也许可以按照自己喜欢的方式让它工作。
我以前试过一次,它可能对你有用。
tests/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
在测试中class:
使用 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');
}
上述解决方案中的主要方法是 运行 所有测试的所有迁移。我更喜欢一种方法来指定每个测试应该 运行 哪些迁移和种子。
在大项目上它可能更有价值,因为这可以将测试时间减少大约 70%(使用上面已经解释过的 sqlite in-memory 数据库)。对于小型项目,这可能有点太过分了。但无论如何...
在测试用例中使用这些:
/**
* 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);
在我的例子中,我创建了 .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