如何在多租户 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 回调对此没有帮助,因为数据库尚未迁移。多租户可以与并行测试一起工作(经过大量工作!)如果:
使用 DatabaseTransactions
而不是 RefreshDatabbase
- 您为租户测试手动处理重启数据库事务
- 您手动进行一些清理工作
注册一个新的服务提供商
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 语句对红绿进行非并行测试的能力。
我正在使用 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 回调对此没有帮助,因为数据库尚未迁移。多租户可以与并行测试一起工作(经过大量工作!)如果:
-
使用
DatabaseTransactions
而不是RefreshDatabbase
- 您为租户测试手动处理重启数据库事务
- 您手动进行一些清理工作
注册一个新的服务提供商
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 语句对红绿进行非并行测试的能力。