一連のphpunitの中で、1度だけmigrationを実行する
DatabaseMigrations.phpは使用すると処理時間が長くなってしまうので自前で処理を組む。
詳細な理由はこちら
キチンと処理時間を計測して比較したわけではないですが、だいぶ早くなった感じはします。
実装内容
tests直下にデフォルトで作成されているTestCase.phpを拡張する。
TestCase.php
public function setUp()
{
parent::setUp();
// phpunit実行のタイミングで一度だけテスト用DBに対してマイグレーションを実行する
if (! self::$migrated) {
foreach (\DB::select('SHOW TABLES') as $table) {
$column_name = 'Tables_in_'.\DB::connection('')->getDatabaseName();
\DB::statement('DROP TABLE IF EXISTS `' . $table->$column_name. '`');
}
\Artisan::call('migrate');
self::$migrated = true;
}
}
public function tearDown()
{
foreach (\DB::select('SHOW TABLES') as $table) {
$column_name = 'Tables_in_'.\DB::connection('')->getDatabaseName();
\DB::statement('TRUNCATE TABLE `' . $table->$column_name . '`');
}
parent::tearDown();
}
公式に載っている方法だと、各テストfunction毎にmigration:rollbackからのmigrationが実行され結構時間がかかってしまう
というのもテストデータベースのmigration実行はsetUp()で呼び出されているためである。
- phpunit実行時にmigrationを実行したい場合にインポートする必要のあるtrait
DatabaseMigrations.php
trait DatabaseMigrations
{
/**
* Define hooks to migrate the database before and after each test.
*
* @return void
*/
public function runDatabaseMigrations()
{
$this->artisan('migrate');
$this->app[Kernel::class]->setArtisan(null);
$this->beforeApplicationDestroyed(function () {
$this->artisan('migrate:rollback');
});
}
}
- 各テストクラスの規定クラス
TestCase.php
/**
* Setup the test environment.
*
* @return void
*/
protected function setUp()
{
if (! $this->app) {
$this->refreshApplication();
}
$this->setUpTraits();
foreach ($this->afterApplicationCreatedCallbacks as $callback) {
call_user_func($callback);
}
Facade::clearResolvedInstances();
Model::setEventDispatcher($this->app['events']);
$this->setUpHasRun = true;
}
/**
* Boot the testing helper traits.
*
* @return void
*/
protected function setUpTraits()
{
$uses = array_flip(class_uses_recursive(static::class));
if (isset($uses[DatabaseMigrations::class])) {
$this->runDatabaseMigrations();
}
if (isset($uses[DatabaseTransactions::class])) {
$this->beginDatabaseTransaction();
}
if (isset($uses[WithoutMiddleware::class])) {
$this->disableMiddlewareForAllTests();
}
if (isset($uses[WithoutEvents::class])) {
$this->disableEventsForAllTests();
}
}
よって
1. テストfunctionA()の実行
2. setUp()の実行(migration)
3. tearDown()の実行
4. テストfunctionB()の実行
5. setUp()の実行(migration)
6. tearDown()の実行
7. 以下繰り返し
といった形でmigrationが複数回実行されてしまう。アプリケーションに必要なテーブルを作成するだけであればmigration自体は1度だけ実行して、以降はtruncateでテーブルをきれいにするだけで十分であると考える。