从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。当服务尚未标记为公共时,使用get()
方法从服务容器中获取服务时,即将发布的版本3.4将触发弃用警告。
这让我想知道是否有办法让这个冒烟测试在没有创建公共服务的情况下运行,该公共服务将所有服务作为构造函数参数,当然容器中的近1000个服务当然不是一个可行的选项。
答案 0 :(得分:1)
此方法with all its pros/cons is described in this post with code examples。
访问私有服务的最佳解决方案是添加编译器通行证,使所有服务公开以进行测试。
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());
}
}
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进行测试等)。
然后您的所有测试都会按预期继续工作!
答案 1 :(得分:0)
您可以为您的服务设置自定义配置,仅适用于您将所有内容设置为公开的测试环境。或者,您可以为要测试的服务(在测试环境中)添加别名。
问题在于您将改变每个环境编译容器的方式,因此根据检索服务所花费的时间长度可能不再有用。好消息是,从一开始就没那么特别有用,因为没有什么可以做到的,因为它很慢而且有opcache它不应该是一个问题。
进行烟雾测试以确保服务可用,使其在测试环境中公开是好的(对我而言)或者您可以使用WebTestCase通过UI进行烟雾测试。通过确保您的路由可访问,您间接确保由于不可访问/配置错误的服务而导致500错误。
当涉及到来自容器的服务的功能测试时,我认为没有办法将它们公开或使它们混淆(仅在必要时进行测试)。
答案 2 :(得分:0)
我有一个非常类似的烟雾测试(这使我能够在之前找到问题) - 但是没有时间元素。我的私人'服务已经变得越来越长,没有某种形式的SQL Error [2377] [0A000]: [Vertica][VJDBC](2377) ROLLBACK: Cannot convert column "colname" from "varchar(128)" to type "int"
[Vertica][VJDBC](2377) ROLLBACK: Cannot convert column "colname" from "varchar(128)" to type "int"
com.vertica.util.ServerException: [Vertica][VJDBC](2377) ROLLBACK: Cannot convert column "src_mainuser" from "varchar(128)" to type "int"
将继续这样做。
我仍然会创建一些公共服务,或者来自框架,所以我很乐意将它们添加到排除列表中。