在 SF3.4 及更高版本中运行 Symfony DIC 烟雾测试

Posted

技术标签:

【中文标题】在 SF3.4 及更高版本中运行 Symfony DIC 烟雾测试【英文标题】:Running Symfony DIC smoke tests in SF3.4 and above 【发布时间】:2018-03-22 11:57:16 【问题描述】:

从 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 版将成为services private by default。即将发布的 3.4 版本在服务未标记为 public 时使用 get() 方法从服务容器获取服务时将触发弃用警告。

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

【问题讨论】:

【参考方案1】:

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

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

进行冒烟测试以确保服务可用,将它们在测试环境中公开是可以的(至少对我而言),或者您也可以使用 WebTestCase 通过 UI 进行冒烟测试。通过确保您的路线可访问,您可以间接确保不会因无法访问/配置错误的服务而出现 500 错误。

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

【讨论】:

快速补充:当您想设置加载时间限制时,使用 WebTestCase 通过 UI 测试服务的可用性也更有用,因为页面加载速度作为指标可能更有用。有了它,您可以分析和检查导致性能问题的确切原因并修复具体问题,而不是在服务容器中的某处进行模糊和有问题的微优化。 ;) Offtopic:我同意烟雾测试路线也很重要。但是,检查服务的加载时间可以检测在构造函数或依赖项的构造函数中完成大量工作的位置。这可能是延迟加载服务的动机。例如,它让我发现了依赖关系树深处的一个不需要的阻塞 curl 调用,该调用在每次页面加载时触发,但几乎不需要。 现在我通过加载一个默认情况下所有服务都是公共的不同环境来修复它。我正在获取客户端:$client = static::createClient(['environment' => 'test']); 我的 test.yml 环境配置包含:services: _defaults: public: true 对上述内容的小补充:正如 Alister Bulman 指出的,明确标记为私有的服务确实需要添加到忽略列表中,因为无法检查服务是否为私有。 需要延迟加载的繁重服务实际上是我最初假设的一个很好的反例。我仍然认为这可以通过使用 XHprof 或 blackfire 等专用性能测试工具在更大的冒烟测试失败后通过应用程序分析来发现。将其作为测试套件的一部分在问题解决后可能没有什么好处,至少在不经常触及服务实例化的情况下,并且保持测试会导致套件膨胀且运行时间更长,这是我想避免的事情.【参考方案2】:

我有一个 非常 类似的冒烟测试(这让我能够在之前发现问题) - 但没有耗时元素。我的“私人”服务列表越来越长,如果没有某种形式的->getContainer()->isPrivate($id),我会继续这样做。

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

【讨论】:

【参考方案3】:

这种方法with all its pros/cons is described in this post with code examples。


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

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 进行测试等)。

那么您的所有测试将继续按预期工作!

【讨论】:

以上是关于在 SF3.4 及更高版本中运行 Symfony DIC 烟雾测试的主要内容,如果未能解决你的问题,请参考以下文章

在 Symfony 2.8、3.0 及更高版本中将数据传递给 buildForm()

android M 之前的运行时权限(api 14 及更高版本)

Android Edittext 不能专注于 API 22(代码在 API 23 及更高版本上运行良好)

希望我的应用程序只能在 iPhone 4 及更高版本上运行,而不能在其他设备上运行

如何让原生 WebSockets 在 Android 4.03 及更高版本中工作?

将 AfxGetMainWnd 上的 C 样式系列转换从 VC6/MFC6 转换为在现代 MFC 版本(VC++2008 及更高版本)中运行