简体   繁体   English

使用 PHPUnit 在 Symfony3 中测试服务/服务容器的优雅方式

[英]Elegant way to testing the Services/Service Containers in Symfony3 with PHPUnit

I'm learning recently about the Symfony 3 framework and Dependency Injection .我最近在学习Symfony 3框架和Dependency Injection

I would like you to help me solve my doubts about the method of testing Services in Symfony 3 using PHPUnit .我希望你能帮助我解决我对使用PHPUnit在 Symfony 3 中测试服务的方法的疑虑。 I have some concerns how to do it right way.我有些担心如何正确地做到这一点。

Lets make an example of Service class:让我们举一个Service类的例子:

// src/AppBundle/Services/MathService.php
namespace AppBundle\Services;

class MathService
{
    public function subtract($a, $b)
    {
        return $a - $b;
    }
}

I see that usually the UnitTest classes in Symfony tests the Controllers .我看到通常 Symfony 中的 UnitTest 类测试Controllers

But what can I test independent classes like Services (which have business logic included for example) instead of Controllers ?但是我可以测试什么独立的类,比如服务(例如包含业务逻辑)而不是控制器

I know there are at least 2 ways to do it:我知道至少有两种方法可以做到:


1. Create a Test Class which extends the PHPUnit_Framework_TestCase and create the object of Service inside some methods or constructor in this Test Class (exactly like in Symfony docs about testing ) 1.创建一个扩展PHPUnit_Framework_TestCase的测试类,并在这个测试类的一些方法构造函数创建服务的对象(就像在关于测试的Symfony 文档中一样)

// tests/AppBundle/Services/MathTest.php
namespace Tests\AppBundle\Services;

use AppBundle\Services\MathService;

class MathTest extends \PHPUnit_Framework_TestCase
{
    protected $math;

    public function __construct() {
        $this->math = new MathService();
    }

    public function testSubtract()
    {
        $result = $this->math->subtract(5, 3);
        $this->assertEquals(2, $result);
    }
}

2. Make our Service class as a Service Container using Dependency injection. 2.使用依赖注入将我们的​​服务类作为服务容器 Then create a Test Class which extends the KernelTestCase to get access to the Kernel.然后创建一个扩展KernelTestCase的测试类以访问内核。 It will give us ability to inject our Service using Container from Kernel (based on Symfony docs about testing Doctrine ).它将使我们能够使用内核中的容器注入我们的服务(基于 Symfony 文档关于测试 Doctrine )。

Configuration of Service Container:服务容器的配置:

# app/config/services.yml
services:
    app.math:
        class: AppBundle\Services\MathService

Now our Test Class will looks like:现在我们的测试类将如下所示:

// tests/AppBundle/Services/MathTest.php
namespace Tests\AppBundle\Services;

use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;

class MathTest extends KernelTestCase
{
    private $math;

    protected function setUp()
    {
        self::bootKernel();

        $this->math = static::$kernel
            ->getContainer()
            ->get('app.math');
    }

    public function testSubtract()
    {
        $result = $this->math->subtract(5, 3);
        $this->assertEquals(2, $result);
    }
}

There are benefits when we choose this way.我们选择这种方式是有好处的。

Firstly we have access to our Service Container in controllers and tests through Dependency Injection .首先,我们可以通过依赖注入访问控制器测试中的服务容器

Secondly - if in the future we want to change the location of Service class or change the name of class - compared with 1. case - we can avoid changes in many files, because we will change path/name at least in services.yml file.其次-如果将来我们要改变服务类位置或改变类的名称-与1相比-我们才能避免在许多文件的变化,因为我们将在至少改变路径/名称services.yml文件.


My questions:我的问题:

Are there another ways to test Service class in Symfony 3 ?还有其他方法可以在 Symfony 3 中测试 Service 类吗? Which way is better and should be used?哪种方式更好,应该使用哪种方式?

UPDATED 2018 with tricky Symfony 3.4/4.0 solution.使用棘手的 Symfony 3.4/4.0 解决方案在 2018 年更新。

This approach with all its pros/cons is described in this post with code examples .这种方法及其所有优点/缺点在这篇带有代码示例的帖子中进行了描述


The best solution to access private services is to add a Compiler Pass that makes all services public for tests .访问私有服务的最佳解决方案是添加一个 Compiler Pass,使所有服务公开进行测试

1. Update Kernel 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. Require or create own Compiler Pass 2. 需要或创建自己的 Compiler Pass

Where PublicForTestsCompilerPass looks like:其中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__');
    }
}

To use this class, just add the package by:要使用这个类,只需通过以下方式添加包:

composer require symplify/package-builder

But of course, the better way is to use own class, that meets your needs (you migt Behat for tests etc.).但是当然,更好的方法是使用满足您需求的自己的类(您迁移 Behat 进行测试等)。

Then all your tests will keep working as expected!那么您的所有测试将继续按预期工作!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM