首页 > 解决方案 > 更快的 Laravel 迁移,用于测试具有大量表的项目

问题描述

我刚刚开始从事这个零测试的大项目。我们的想法是对每个新功能和/或错误进行 TDD,并且随着时间的推移,我们将增加测试覆盖率。

我不使用 SQLite 内存数据库进行测试。我更喜欢使用 MySql,因为它与我在生产中使用的数据库相同。通常,在小项目中,这没有问题,但对于大项目,它是!

我面临的问题与性能有关,一个在磁盘中运行的普通 MySql 实例(M.2 SSD)大约需要90 秒来运行这个大项目的所有迁移。有超过 200 个表要迁移,有很多关系。

这个问题的解决方案是在内存中设置 MySql,使用tmpfsdocker。这个技巧让我将迁移时间减少到只有10 秒,还不错,但如果你只想运行 1 次测试真的很烦人!10 秒迁移,几毫秒测试。

Laravel 8 刚刚带来了一个新功能,叫做Schema Dumphttps ://github.com/laravel/framework/pull/32275

我刚刚看到这个新功能,它真的让我很开心,非常好!它将帮助很多人并节省大量时间。如果您有很多迁移,您可以显着减少迁移它们的时间。否则,这并不能解决我的问题。该项目的迁移次数非常接近每个表 1 次迁移。这里不需要优化任何东西。

出于好奇,我拍摄了数据库的 Schema 快照并尝试使用 MySql 命令行恢复它。运行架构还原并设置所有内容大约需要3 秒:

 mysql -h 127.0.0.1 -u root -P 3331 -p default < database/migrations.sql

目前,测试数据库一直保持迁移状态,这样我的测试流程(一次测试一次运行)保持超快!

我喜欢认为单个测试应该像一个按钮,你按下它,它会立即亮起绿色或红色。

我的问题: - 是否可以减少具有大量表的项目的迁移时间?(仅用于测试)

我没有关于 MySql 的内幕知识,也许我遗漏了一些东西......

标签: mysqllaravelperformancemigrationphpunit

解决方案


只是一个提示

当我们有不同的功能要测试时,我也遇到过这种情况,并且在每次测试后重置数据库通常很有用,这样之前测试的数据就不会干扰后续测试。

我采用了以下方法,希望您能从中得到一些帮助,只是分享提示。

如果您的大型项目有 40 个表或更多表,那么从头开始重写和部署所有迁移可能并不理想。在这种情况下,解决方案是将新迁移的数据库导出为 SQL“快照”。与原始迁移相比,这个包含所有迁移作为原始 SQL 查询的文件将显着加快解析和执行速度。如果你想测试你网站的每一个特性,那么Laravel's RefreshDatabase trait它是有意义的,实际上它是用来用一个新迁移的数据库开始每一个测试的。在后台,Laravel 在每次测试之前运行以下代码:

protected function refreshTestDatabase()
{
    if (! RefreshDatabaseState::$migrated) {
        $this->artisan('migrate:fresh');
        
        RefreshDatabaseState::$migrated = true;
    }

    $this->beginDatabaseTransaction();
}

如您所见,迁移只会在第一次测试之前运行一次。第一次测试后,使用数据库事务快速恢复到初始迁移的数据库状态。这在运行多个测试时节省了大量时间。但是,运行这些初始迁移的时间可能会显着延迟您的测试结果。如果您只想运行一个“快速”测试,这尤其令人讨厌

执行

首先清除数据库并使用php artisan migrate:fresh. 然后打开您首选的数据库客户端并导出(或备份)空数据库。您应该只剩下一个 SQL 文件。让我们将该文件重命名为migrations_2019_01_10.sql并将其放在我们应用程序的数据库目录中。接下来,我们必须在RefreshDatabase trait. 您可以将整个特征复制到您自己的代码库中,也可以简单地覆盖您自己的TestCase.php. 最终看起来像这样:

abstract class TestCase extends \Illuminate\Foundation\Testing\TestCase
{
    use CreatesApplication;
    use RefreshDatabase;

    protected function refreshTestDatabase()
    {
        if (! RefreshDatabaseState::$migrated) {
            DB::unprepared(file_get_contents(database_path('migrations_2019_01_10.sql')));

            $this->artisan('migrate');

            $this->app[Kernel::class]->setArtisan(null);

            RefreshDatabaseState::$migrated = true;
        }

        $this->beginDatabaseTransaction();
    }
}

如您所见,我将命令保留在那里以运行在我们的快照migrate之后可能添加的任何新迁移。migrations.sql这样您就不必在每次添加迁移时都导出迁移的数据库。只要记住每隔一段时间准备一个新的快照。


推荐阅读