如何对 Symfony2 控制器进行单元测试?

Posted

技术标签:

【中文标题】如何对 Symfony2 控制器进行单元测试?【英文标题】:How can I unit test a Symfony2 controller? 【发布时间】:2012-04-24 23:47:00 【问题描述】:

我想尽可能多地使用测试驱动开发——这是一种很棒的工作方式。

我对 Symfony2 控制器创建并返回一个新的 Response 对象这一事实感到困扰。

我希望能够单独对控制器进行单元测试。

你是怎么做到的?

答案是将控制器创建为普通旧 php 对象,将其注册为服务并使用依赖注入将新的 Response 对象(或 Response 工厂)传递给它吗?

【问题讨论】:

返回 Response 对象到底有什么问题? 没什么。我只是不喜欢在控制器中创建 Response 对象的事实。我是依赖注入的坚定信徒,我讨厌在除 DI 容器之外的任何东西中看到“new”关键字。也许这种信念是错误的。 【参考方案1】:

使用 mock 将模型和其他对象与主控制器方法的逻辑隔离开来,请参阅 http://www.phpunit.de/manual/3.7/en/test-doubles.html#test-doubles.mock-objects

我认为在旧版本中你可以模拟整个班级,但我拥有的最新 phpunit 3.6.10 似乎不起作用。所以我猜你留下了依赖注入模式

class objss
    function ss()
        $x = new zz();
        var_dump($x->z());
    




class MoTest extends PHPUnit_Framework_TestCase
    public function setUp()

    

    public function testA()
        $class = $this->getMock('zzMock', array('z'), array(), 'zz');
        $class->expects($this->any())->method('z')->will($this->returnValue('2'));

        $obj = new objss();
        $this->assertEquals('2', $obj->ss());
    

【讨论】:

Totaly - 但这些对象(包括响应对象)将在控制器中实例化。除非我使用 DI 容器为控制器提供模型和其他东西。也许我可以创建一个响应工厂服务,并从 DI 容器中获取它 - 这样它(DI 容器和工厂)可以被模拟以单独测试 Controller 类。【参考方案2】:

通常,您的控制器将不同的对象插入在一起,并以正确的顺序连接它们。也许他调用了一个存储库,读取了一些对象并通过 render 方法返回它们。也许他会打电话给其他一些做事的 Handlers/Managers。

这意味着控制器是高级组件。这通常表明功能测试是有序的,而不是单元测试。你的目标不应该是通过单元测试获得 100% 的代码覆盖率。也许您可以这样想:如果您对控制器调用的所有内容(模型、验证、表单、存储库)进行单元测试,会出现什么问题?大多数情况下,您只有在使用生产中涉及的所有真实类时才会观察到这一点。

我还想指出,TDD 并不意味着一切都必须经过单元测试。可以对高级代码进行一些功能测试。如前所述,如果您使用单元测试测试低级组件,您应该只测试它们如何协同工作,而您无法使用模拟测试,因为您告诉模拟返回值是什么。

如果您的控制器不仅仅是将系统的各个部分连接在一起,您应该考虑将这些东西重构为更底层的类,您可以使用单元测试进行测试。

所以我的建议是使用功能测试来测试您的控制器并使用单元测试来测试您的模型和业务逻辑。

如果您在功能测试方面遇到困难,可以阅读以下内容:

http://symfony.com/doc/current/book/testing.html#functional-tests http://blog.sznapka.pl/fully-isolated-tests-in-symfony2/

【讨论】:

好的。我认为既然 Controller 是一个类,它也需要进行单元测试。我不喜欢在编写测试之前编写代码的想法,所以我想知道是否有办法重构控制器的使用方式以便能够对它们进行单元测试。我想我仍然可以通过在编写控制器之前编写功能测试来使用 TDD。【参考方案3】:

刘易斯 - 我想我会跳到这里。上面的方法让你在测试中复制你的动作逻辑的更好部分。这并没有错,许多框架(尤其是 Rails 中的 RSPEC)实际上建议您对 Controller 对象执行单元测试以及功能测试。但是,鉴于您的示例,我想我会跳过单元测试并采用功能方法。

我认为测试的重点是创建一个沙盒环境,运行测试并检查副作用和直接结果。如果您的大部分测试都在隔离该方法,那么可能是时候使用不同的测试方法或编写类的不同方法了。鉴于这是一个控制器,并且本质上它们将堆栈的不同部分粘合在一起,我会在堆栈的更远位置创建您的沙箱。具体来说,我会使用这样的方法:

https://github.com/PolishSymfonyCommunity/SymfonyMockerContainer

对我来说很棒:)

【讨论】:

【参考方案4】:

单元测试

将您的控制器重构为服务: http://symfony.com/doc/current/cookbook/controller/service.html

然后您可以轻松地对它们进行单元测试。

功能测试

当然(正如其他人已经提到的),您可以使用WebTestCase,如下所述:http://symfony.com/doc/current/book/testing.html#functional-tests

【讨论】:

将控制器定义为服务并不是 Symfony 官方推荐的。一些开发人员将它们用于非常特定的用例,例如 DDD(领域驱动设计)和六边形架构应用程序。 谁说的? symfony.com/doc/current/controller/service.html 专业人士在他们自己的文档中胜过骗子。我很确定在未来的 symfony 版本中将支持作为服务的控制器,所以我不会太担心它“不被官方推荐”。

以上是关于如何对 Symfony2 控制器进行单元测试?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 XUnit 对 Web API 控制器进行单元测试

如何在 Cordapp 中对服务和控制器(kotlin)进行单元测试?

如何对剃刀视图进行单元测试

如何对 NestJS 中的控制器应用警卫进行单元测试?

使用注入对 NestJS 控制器进行单元测试

如何对 ASP.NET Core 控制器或模型对象进行单元测试?