更快 Laravel 迁移以测试包含大量表的项目
Faster Laravel Migration for testing projects with a lot of tables
我刚刚开始从事这个零测试的大项目。我们的想法是对每个新功能 and/or 错误进行 TDD,随着时间的推移,我们将增加测试覆盖率。
我不使用 SQLite 内存数据库进行测试。我更喜欢使用 MySql 因为它是我在生产中使用的同一个数据库。一般小项目没问题,大项目就成问题了!
我遇到的问题与性能有关,一个正常的 MySql 实例 运行ning in-disk (M.2 SSD) 需要大约 90 秒 到 运行 这个大项目的所有迁移。有 200 多个 table 需要迁移,有很多关系。
这个问题的解决方案是在内存中也设置MySql,使用tmpfs
和docker。这个技巧让我将迁移时间减少到只有 10 秒 ,不错,但如果你只想 运行 1 次测试,那真的很烦人! 10 秒迁移,几毫秒测试。
Laravel 8 刚刚带来了一个名为 Schema Dump
的新功能:https://github.com/laravel/framework/pull/32275
刚看到这个新功能,真的把我逗乐了,太好了!它将帮助很多人并节省大量时间。如果您有很多迁移,则可以显着减少全部迁移的时间。
否则,这不能解决我的问题。该项目的迁移次数非常接近每个 table 1 次迁移。此处无需优化任何内容。
出于好奇,我拍摄了数据库的 Schema 快照并尝试使用 MySql 命令行恢复它。 3 秒 到 运行 架构恢复并设置所有内容:
mysql -h 127.0.0.1 -u root -P 3331 -p default < database/migrations.sql
目前,测试数据库一直保持迁移状态,这样我的测试流程(一个测试一个运行)保持超快!
我认为单次测试应该像按下一个按钮,它会立即亮起绿色或红色。
我的问题:- 对于具有大量 tables 的项目,可以减少更多的迁移时间吗?(仅用于测试)
我对MySql一无所知,也许我遗漏了什么...
如果目标是在某个时间点加载数据库,并且您需要重复使用同一个快照,那么我建议尝试使用 LVM 快照,而不是“迁移”。
它涉及 磁盘 的 OS-level 快照。你会安排在磁盘上只有 MySQL 数据集,并像这样使用 LVM:
一次性设置:停止 mysqld,拍摄 LVM 快照
准备好重新加载该快照时,执行不同的 LVM 魔术以使用快照而不是磁盘的当前状态。
抱歉,我无法预测需要多少秒,但它根本不涉及 mysqldump。
感谢 saddam kamal and shock_gone_wild 引发了我对这个问题的思考。我不需要每次 运行 测试时都迁移数据库。在我当前的工作流程中,我每天手动迁移一次所有内容。这可以自动化!
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
use DatabaseTransactions;
protected function setUp(): void
{
parent::setUp();
// first run of the day,
// the database will be migrated to tmpfs
$result = DB::select(DB::raw("SHOW TABLES LIKE 'users';"));
if (!count($result))
{
$this->artisan('migrate:fresh');
}
}
}
我不知道我在想什么!这真的是一段简单的代码,它让这一切发生了。由于数据库在内存中 (tmpfs),因此只需要在当天的第一次测试时迁移一次。第一次 运行 大约需要 10 秒,下一次测试将 运行 以毫秒为单位。
我使用的方法在第一次设置测试时可能会觉得笨拙,但它对 运行 测试套件的速度有很大影响。它只是 运行 那些测试所需的迁移,而不是 运行 宁所有迁移或进行完整的模式导入。例如:
这在测试中:
$this->migrate([
'2016_04_29_132815_create_authors_table',
'2016_04_29_132815_create_categories_table'
]);
$this->seed(CategoriesTableSeeder::class);
这在测试用例中:
use Artisan;
/**
* Runs migrations for individual tests
*
* @params array $migrations
* @return void
*/
public function migrate(array $migrations = []): void
{
$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
*
* @params string $seeds
* @return void
*/
public function seed($seeds = ''): void
{
$command = "db:seed";
if (empty($seeds)) {
Artisan::call($command);
} else {
Artisan::call($command, ['--class' => $seeds]);
}
}
我刚刚开始从事这个零测试的大项目。我们的想法是对每个新功能 and/or 错误进行 TDD,随着时间的推移,我们将增加测试覆盖率。
我不使用 SQLite 内存数据库进行测试。我更喜欢使用 MySql 因为它是我在生产中使用的同一个数据库。一般小项目没问题,大项目就成问题了!
我遇到的问题与性能有关,一个正常的 MySql 实例 运行ning in-disk (M.2 SSD) 需要大约 90 秒 到 运行 这个大项目的所有迁移。有 200 多个 table 需要迁移,有很多关系。
这个问题的解决方案是在内存中也设置MySql,使用tmpfs
和docker。这个技巧让我将迁移时间减少到只有 10 秒 ,不错,但如果你只想 运行 1 次测试,那真的很烦人! 10 秒迁移,几毫秒测试。
Laravel 8 刚刚带来了一个名为 Schema Dump
的新功能:https://github.com/laravel/framework/pull/32275
刚看到这个新功能,真的把我逗乐了,太好了!它将帮助很多人并节省大量时间。如果您有很多迁移,则可以显着减少全部迁移的时间。 否则,这不能解决我的问题。该项目的迁移次数非常接近每个 table 1 次迁移。此处无需优化任何内容。
出于好奇,我拍摄了数据库的 Schema 快照并尝试使用 MySql 命令行恢复它。 3 秒 到 运行 架构恢复并设置所有内容:
mysql -h 127.0.0.1 -u root -P 3331 -p default < database/migrations.sql
目前,测试数据库一直保持迁移状态,这样我的测试流程(一个测试一个运行)保持超快!
我认为单次测试应该像按下一个按钮,它会立即亮起绿色或红色。
我的问题:- 对于具有大量 tables 的项目,可以减少更多的迁移时间吗?(仅用于测试)
我对MySql一无所知,也许我遗漏了什么...
如果目标是在某个时间点加载数据库,并且您需要重复使用同一个快照,那么我建议尝试使用 LVM 快照,而不是“迁移”。
它涉及 磁盘 的 OS-level 快照。你会安排在磁盘上只有 MySQL 数据集,并像这样使用 LVM:
一次性设置:停止 mysqld,拍摄 LVM 快照
准备好重新加载该快照时,执行不同的 LVM 魔术以使用快照而不是磁盘的当前状态。
抱歉,我无法预测需要多少秒,但它根本不涉及 mysqldump。
感谢 saddam kamal and shock_gone_wild 引发了我对这个问题的思考。我不需要每次 运行 测试时都迁移数据库。在我当前的工作流程中,我每天手动迁移一次所有内容。这可以自动化!
abstract class TestCase extends BaseTestCase
{
use CreatesApplication;
use DatabaseTransactions;
protected function setUp(): void
{
parent::setUp();
// first run of the day,
// the database will be migrated to tmpfs
$result = DB::select(DB::raw("SHOW TABLES LIKE 'users';"));
if (!count($result))
{
$this->artisan('migrate:fresh');
}
}
}
我不知道我在想什么!这真的是一段简单的代码,它让这一切发生了。由于数据库在内存中 (tmpfs),因此只需要在当天的第一次测试时迁移一次。第一次 运行 大约需要 10 秒,下一次测试将 运行 以毫秒为单位。
我使用的方法在第一次设置测试时可能会觉得笨拙,但它对 运行 测试套件的速度有很大影响。它只是 运行 那些测试所需的迁移,而不是 运行 宁所有迁移或进行完整的模式导入。例如:
这在测试中:
$this->migrate([
'2016_04_29_132815_create_authors_table',
'2016_04_29_132815_create_categories_table'
]);
$this->seed(CategoriesTableSeeder::class);
这在测试用例中:
use Artisan;
/**
* Runs migrations for individual tests
*
* @params array $migrations
* @return void
*/
public function migrate(array $migrations = []): void
{
$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
*
* @params string $seeds
* @return void
*/
public function seed($seeds = ''): void
{
$command = "db:seed";
if (empty($seeds)) {
Artisan::call($command);
} else {
Artisan::call($command, ['--class' => $seeds]);
}
}