4

从 DI 容器获取服务是我的测试套件中的冒烟测试不可或缺的一部分。例如,以下测试确保容器中注册的服务的构建没有问题,并且这些服务不会花费太多时间来构建。

private const DEFAULT_TRESHOLD = 30;

public function testServicesLoadInTime()
{
    $client = static::createClient();

    /**
     * Add serviceid as key, possible values:
     * - false: Skip test for this service
     * - integer value: Custom responsetime
     */
    $customCriteria = [
        // See: https://github.com/symfony/monolog-bundle/issues/192
        'monolog.activation_strategy.not_found' => false,
        'monolog.handler.fingers_crossed.error_level_activation_strategy' => false,
        // Should not be used directly (Factories will inject other parameters)
        'liip_imagine.binary.loader.prototype.filesystem' => false,
        // Services that are allowed to load longer (Only for CLI tasks like workers)
        'assetic.asset_manager' => 1000,
    ];

    foreach ($client->getContainer()->getServiceIds() as $id) {
        if (isset($customCriteria[$id]) && $customCriteria[$id] === false) {
            continue;
        }
        try {
            $startedAt = microtime(true);
            $service = $client->getContainer()->get($id);
            $elapsed = (microtime(true) - $startedAt) * 1000;
            $this->assertNotNull($service);
            $treshold = $customCriteria[$id] ?? self::DEFAULT_TRESHOLD;
            $this->assertLessThan($treshold, $elapsed, sprintf(
                'Service %s loaded in %d ms which is more than the %d ms threshold',
                $id, $elapsed, $treshold
            ));
        } catch (InactiveScopeException $e) {
            // Noop
        } catch (\Throwable $ex) {
            $this->fail(sprintf("Fetching service %s failed: %s", $id, $ex->getMessage()));
        }
    }
}

然而。Symfony 第 4 版默认将服务私有化get()即将发布的 3.4 版将在服务未标记为公共时使用该方法从服务容器中获取服务时触发弃用警告。

这让我想知道是否有一种方法可以在不创建将所有服务作为构造函数参数的公共服务的情况下保持此冒烟测试运行,而容器中有近 1000 个服务当然不是一个可行的选择。

4

3 回答 3

1

此方法及其所有优点/缺点在这篇文章的代码示例中进行了描述


访问私有服务的最佳解决方案是添加一个编译器通行证,使所有服务都公开以供测试

1.更新内核

 use Symfony\Component\HttpKernel\Kernel;
+use Symplify\PackageBuilder\DependencyInjection\CompilerPass\PublicForTestsCompilerPass;

 final class AppKernel extends Kernel
 {
     protected function build(ContainerBuilder $containerBuilder): void
     {
         $containerBuilder->addCompilerPass('...');
+        $containerBuilder->addCompilerPass(new PublicForTestsCompilerPass());
     }
 }

2. 要求或创建自己的编译器通行证

哪里PublicForTestsCompilerPass看起来像:

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

final class PublicForTestsCompilerPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $containerBuilder): void
    {
        if (! $this->isPHPUnit()) {
            return;
        }

        foreach ($containerBuilder->getDefinitions() as $definition) {
            $definition->setPublic(true);
        }

        foreach ($containerBuilder->getAliases() as $definition) {
            $definition->setPublic(true);
        }
    }

    private function isPHPUnit(): bool
    {
        // defined by PHPUnit
        return defined('PHPUNIT_COMPOSER_INSTALL') || defined('__PHPUNIT_PHAR__');
    }
}

要使用此类,只需通过以下方式添加包:

composer require symplify/package-builder

但当然,更好的方法是使用满足您需求的自己的类(您可以使用 Behat 进行测试等)。

然后您的所有测试将继续按预期工作!

于 2018-05-18T13:28:31.380 回答
0

我有一个非常相似的冒烟测试(这让我现在可以发现问题) - 但没有耗时的元素。我的“私人”服务清单越来越长,如果没有某种形式的->getContainer()->isPrivate($id)意愿,我会继续这样做。

我仍然会创建一些公共服务,或者从框架中创建一些公共服务,所以我很高兴在它们出现时将它们添加到排除列表中。

于 2017-10-11T18:08:37.573 回答
0

您可以为您的服务进行自定义配置,仅针对您将所有内容设置为公开的测试环境。或者,您可以为要测试的服务设置别名(在您的测试环境中)。

问题是您将更改容器在每个环境中的编译方式,因此有关检索服务所需时间的指标可能不再有用。好消息是,一开始它并不是特别有用,因为你无法真正解决它变慢的问题,而且使用 opcache 无论如何都不应该成为问题。

对于确保服务可用的冒烟测试,让它们在测试环境中公开是可以的(至少对我而言),或者您可以使用 WebTestCase 通过 UI 进行冒烟测试。通过确保您的路线是可访问的,您可以间接确保没有由于无法访问/配置错误的服务而导致的 500 错误。

当涉及到来自容器的服务的功能测试时,我认为没有办法将它们公开或别名化(仅在必要时进行测试)。

于 2017-10-11T12:46:34.493 回答