30

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

ResponseSymfony2 控制器创建并返回一个新对象,这让我感到困扰。

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

你怎么做呢?

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

4

4 回答 4

56

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

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

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

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

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

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

于 2012-04-12T21:48:03.693 回答
2

使用模拟将模型和其他对象与主控制器方法的逻辑隔离开来,请参阅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());
    }
}
于 2012-04-12T15:36:40.877 回答
1

单元测试

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

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

功能测试

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

于 2015-01-30T10:29:49.003 回答
0

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

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

https://github.com/PolishSymfonyCommunity/SymfonyMockerContainer

对我来说很好:)

于 2013-07-03T17:51:10.293 回答