如何在多租户 laravel 应用程序中使用并行测试?

How to use parallel testing in multi tenant laravel application?

我正在使用 Stancl Tenancy for a multi tenant Laravel 8 app. I have this testing well in a single core, however I want to take advantage of the new feature to run tests in parallel

我认为要明智地执行此操作,我应该为并行化创建的每个数据库创建一个新租户。我一直在尝试使用 ParallelTesting::setUpTestDatabase 回调来执行此操作,但出现了一些奇怪的行为。

当我继续 运行 并行测试时,只有 7 个数据库中的第 5 个数据库(并且一直是这个数据库)为其创建了一个租户:

AppServiceProvider

ParallelTesting::setUpTestDatabase(function ($database, $token) {
            $tenant = Tenant::create(['id'=>"testTenant$token",'name'=>"PHPUnit Process $token"]);
});

没有调用创建模型,正在正确设置数据库:

ParallelTesting::setUpTestDatabase(function ($database, $token) {
            //$tenant = Tenant::create(['id'=>"testTenant$token",'name'=>"PHPUnit Process $token"]);
            Log::info("Setup called by $token My database connection is ".DB::connection()->getDatabaseName());
});
[2021-10-21 14:45:32] testing.INFO: Setup called by 4 My database connection is project_test_4
[2021-10-21 14:45:32] testing.INFO: Setup called by 3 My database connection is project_test_3
[2021-10-21 14:45:32] testing.INFO: Setup called by 7 My database connection is project_test_7
[2021-10-21 14:45:32] testing.INFO: Setup called by 6 My database connection is project_test_6
[2021-10-21 14:45:34] testing.INFO: Setup called by 5 My database connection is project_test_5

取消注释创建模型的行,不会记录任何内容,只有最后一个连接 5 会为其创建租户。随后的测试似乎使用了正确的连接,因为其中 4/5 因 TenantNotIdentified 而失败(因为它们没有租户或域)。

谁能找出问题所在?

setupTestDatabase 回调对此没有帮助,因为数据库尚未迁移。多租户可以与并行测试一起工作(经过大量工作!)如果:

    使用
  1. DatabaseTransactions 而不是 RefreshDatabbase
  2. 您为租户测试手动处理重启数据库事务
  3. 您手动进行一些清理工作

注册一个新的服务提供商

class ParallelTestingMultiTenantProvider extends ServiceProvider
{
    /**
     * Register services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap services.
     *
     * @return void
     */
    public function boot()
    {
        ParallelTesting::setUpTestCase(function ($token, $testCase) {
            $tenant = Tenant::firstOrCreate(['id' => "test_{$token}_tenant", 'name' => "testTenant$token"]);
            $tenant->domains()->firstOrCreate(['domain'=>"testtenant$token.site.test"]);
        });

        ParallelTesting::tearDownProcess(function ($token) {
            try {
                DB::disconnect();
                config(['database.connections.mysql.database'=>"site_test_{$token}"]);
                DB::reconnect();
                if($tenant = Tenant::find("test_{$token}_tenant")){
                    $tenant->delete();
                }
            }
            catch(\Exception $e){
                if($e->getCode() !== 1049){ //Intercepts database does not exist errors - where tests do not use Database traits
                    throw $e;
                }
            }
        });
    }
}

在您的基础测试中添加到 setup()

class TenantTestCase extends TestCase
{
    use DatabaseTransactions;

    public function setUp(): void
    {
        parent::setUp();
        if($this->isTestingInParallel()){
            $token = ParallelTesting::token();
            tenancy()->initialize(Tenant::find("test_{$token}_tenant"));
            DB::beginTransaction(); //Needed as otherwise database transactions don't work on the tenant database
            URL::forceRootUrl("https://testtenant$token.site.test");
        }
        else {
            //Test setup for when not running in parallel 
        }
    }

    protected function tearDown(): void
    {
        if($this->isTestingInParallel()){
            DB::rollBack();
        }
        else {
            //Tear down if not testing in parallel
        }
        parent::tearDown();
    }

    protected function isTestingInParallel() : bool
    {
        return (bool)ParallelTesting::token();
    }
}

这使我能够将整个测试套件 运行 的时间从 1 分 30 秒减少到 16 秒,同时保留了 运行 使用 if 语句对红绿进行非并行测试的能力。