如何使用内存数据库在 Laravel 中的完整测试套件之前进行迁移和播种?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/38573830/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to Migrate and seed before the full test suite in Laravel with in memory database?
提问by Franklin Rivero
I'm trying to set up the test environment in my Laravel project. I'm using http://packalyst.com/packages/package/mayconbordin/l5-fixtureswith json for the seeding with a sqlite in memory database and calling:
我正在尝试在我的 Laravel 项目中设置测试环境。我正在使用http://packalyst.com/packages/package/mayconbordin/l5-fixtures和 json 在内存数据库中使用 sqlite 进行播种并调用:
Artisan::call('migrate');
Artisan::call('db:seed');
in my setUpfunction but this is executed before every single test which it can grow to thousands in this project.
在我的setUp函数中,但这是在每次测试之前执行的,在这个项目中它可以增长到数千个。
I tried the setUpBeforeClassbut it didn't work. I think there because the createApplicationmethod is called in every test and that reset the whole application and also wasn't loading the fixtures from the json probably for the same reason.
我尝试了setUpBeforeClass但它没有用。我认为这是因为在每个测试中都会调用createApplication方法并且重置整个应用程序并且也没有从 json 加载装置可能出于同样的原因。
回答by Franklin Rivero
This is how I did it in case someone else is struggling with the same, I created a base testClase
class that inherits from Laravel's and did this:
如果其他人遇到同样的问题,我就是这样做的,我创建了一个testClase
继承自 Laravel 的基类并执行了以下操作:
/**
* 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 = [];
}
I overwrote the createApplication()
and tearDown()
methods. I changed the first one to use the same $app
configuration and remove the part of the teardown()
where it flush $this->app
.
我覆盖了createApplication()
和tearDown()
方法。我将第一个更改为使用相同的$app
配置并删除了teardown()
它 flush的部分$this->app
。
Every other of my test has to inherit from this TestClass and that's it.
我的所有其他测试都必须从这个 TestClass 继承,仅此而已。
Everything else didn't work. This works even with in memory database, it's 100s times faster.
其他一切都不起作用。这甚至适用于内存数据库,速度提高了 100 倍。
if you are dealing with user session, once you log the user in you will have to log him out in tear down, otherwise the user will be logged in because the app environment is never reconstructed or you can dd something like this to refresh the application every time you want:
如果您正在处理用户会话,一旦您登录用户,您必须在拆卸时将他注销,否则用户将登录,因为应用程序环境永远不会重建,或者您可以添加类似这样的东西来刷新应用程序每次你想要:
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();
}
And add this to the tearDown()
before the $this->setUphasRun = false;
:
并将其添加到tearDown()
之前$this->setUphasRun = false;
:
if (self::$applicationRefreshed) {
self::$applicationRefreshed = false;
$this->app->flush();
$this->app = null;
self::$configurationApp = null;
}
回答by Kamil Kie?czewski
create file in your project testrunner
with this content (also prepare .env.testing
file with testing environment variables) :
testrunner
使用此内容在您的项目中创建文件(还准备 .env.testing
带有测试环境变量的文件):
php artisan migrate:rollback --env=testing
php artisan migrate --env=testing --seed
vendor/bin/phpunit
And give permission to execute by command chmod +x testrunner
and execute it by ./testrunner
. Thats all :)
并授予通过命令chmod +x testrunner
执行的权限并通过./testrunner
. 就这样 :)
回答by markdwhite
The main approach in the above solutions is to run all migrations for all tests. I prefer an approach to specify which migrations and seeds should run for each test.
上述解决方案的主要方法是为所有测试运行所有迁移。我更喜欢一种方法来指定每个测试应该运行哪些迁移和种子。
It may be more worthwhile on big projects as this can reduce timings for tests by about 70% (using the sqlite in-memory DB as already explained above). For small projects, it's maybe a bit too much faffing about. But anyway...
对于大型项目,它可能更有价值,因为这可以将测试时间减少约 70%(使用上面已经解释过的 sqlite 内存数据库)。对于小项目来说,这可能有点过头了。但无论如何...
Use these in 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]);
}
}
Then call migrate() and seed as required in individual tests, eg:
然后根据各个测试的需要调用 migrate() 和 seed,例如:
$this->migrate(
[
'2013_10_11_081829_create_users_table',
]
);
$this->seed(UserTableSeeder::class);
回答by Aine
Option 1
选项1
How about setting up the database using a migration and seeds and then using database transcations? (https://laravel.com/docs/5.1/testing#resetting-the-database-after-each-test)
如何使用迁移和种子设置数据库,然后使用数据库事务?(https://laravel.com/docs/5.1/testing#resetting-the-database-after-each-test)
I wanted to be able to set up my test database via artisan like this:
我希望能够通过这样的工匠设置我的测试数据库:
$ php artisan migrate --database=mysql_testing
$ php artisan db:seed --database=mysql_testing
As you can guess, I'm using mysql, but I don't see why this shouldn't work for sqlite. This is how I do it.
您可以猜到,我正在使用 mysql,但我不明白为什么这不适用于 sqlite。我就是这样做的。
config/database.php
配置/数据库.php
First add the test database info to your config/database.php file, under your current database info.
首先将测试数据库信息添加到您的 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,
],
],
If you do it like this, don't forget to add DB_TEST_DATABASE to your .envfile:
如果您这样做,请不要忘记将 DB_TEST_DATABASE 添加到您的 .env文件中:
DB_DATABASE=abc
DB_TEST_DATABASE=abc_test
phpunit.xml
phpunit.xml
Any values set in the phpunit.xml file, under overwrite values given in the .env file. So we tell phpunit to use the "mysql_testing" database connection instead of the "mysql" database connection.
在 phpunit.xml 文件中设置的任何值,在覆盖 .env 文件中给出的值下。所以我们告诉 phpunit 使用“mysql_testing”数据库连接而不是“mysql”数据库连接。
<?xml version="1.0" encoding="UTF-8"?>
<phpunit>
...
<php>
...
<env name="DB_CONNECTION" value="mysql_testing"/>
</php>
Test class
测试班
My test classes look like this:
我的测试类如下所示:
class MyTest extends \TestCase
{
use \Illuminate\Foundation\Testing\DatabaseTransactions;
public function testSomething()
{
Option 2
选项 2
Here the database is reset before every test, which is why I prefer Option 1. But you might be able to get it to work the way you like.
在这里,每次测试之前都会重置数据库,这就是我更喜欢选项 1 的原因。但是您也许可以让它以您喜欢的方式工作。
I tried this once before, and it might work for you.
我以前试过一次,它可能对你有用。
tests/TestCase.phpExtend the test case, to load a new .env file, .env.testing
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.testing
Create this new .env file and add in the database details
创建这个新的 .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
In the test class:
在测试类中:
Use PDO to drop and recreate the database - easier than trying to truncate everything. Then use artisan to migrate and seed the database.
使用 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');
}