简体   繁体   English

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

[英]How to unit test Symfony controllers

I'm trying to get a Symfony controller in a test harness using Codeception. 我正在尝试使用Codeception在测试工具中获取Symfony控制器。 Every method starts as follows: 每个方法都从如下开始:

public function saveAction(Request $request, $id)
{
    // Entity management
    /** @var EntityManager $em */
    $em = $this->getDoctrine()->getManager();

    /* Actual code here
    ...
    */
}

public function submitAction(Request $request, $id)
{
    // Entity management
    /** @var EntityManager $em */
    $em = $this->getDoctrine()->getManager();

    /* 200+ lines of procedural code here
    ...
    */
}

I've tried: 我试过了:

$request = \Symfony\Component\HttpFoundation\Request::create(
    $uri, $method, $parameters, $cookies, $files, $server, $content);

$my_controller = new MyController();
$my_controller->submitAction($request, $id);

from my unit tests, but it seems there's a lot of other setup I don't know about that Symfony does in the background. 从我的单元测试,但似乎有很多其他设置,我不知道Symfony在后台做了什么。 Every time I find a missing object and initialise it, there's another one that fails at some point. 每当我找到一个丢失的对象并对其进行初始化时,就会有另一个在某个时刻失败。

I've also tried stepping through the test from PhpStorm, but PhpUnit has some output that causes Symfony to die before it gets anywhere near the code I'm trying to test because it can't start the $_SESSION after any output has occurred. 我也试过从PhpStorm中逐步完成测试,但是PhpUnit有一些输出会导致Symfony在我试图测试的代码附近死掉之前因为它在任何输出发生后都无法启动$_SESSION I don't think this happens from the command line, but I'm not really close enough to tell yet. 我不认为这是从命令行发生的,但我还不是很接近。

How can I simply and extensibly run this code in a Unit Test? 如何在单元测试中简单且可扩展地运行此代码?


A bit of background: 一点背景:

I inherited this code. 我继承了这段代码。 I know it's dirty and smells because it's doing model logic in the controller. 我知道它很脏并且闻起来因为它在控制器中进行模型逻辑。 I know that what I'm asking for is not a "pure" unit test because it touches virtually the whole application. 我知道我所要求的并不是一个“纯粹的”单元测试,因为它几乎涉及整个应用程序。

But I need to be able to run just this "small" (200+ lines) bit of code automatically. 但我需要能够自动运行这个“小”(200+行)的代码。 The code should run in no more than a couple of seconds. 代码应该运行不超过几秒钟。 I don't know how long because I've never been able to run it independently. 我不知道多久,因为我从来没有能够独立运行它。

Currently, the setup time to run this code through the website is huge, as well as being complex. 目前,通过网站运行此代码的设置时间很长,而且很复杂。 The code does not generate a web page, it's basically an API call that generates a file. 代码不生成网页,它基本上是一个生成文件的API调用。 I need to be able to generate as many of these test files as I like in a short amount of time as I'm making coding changes. 我需要能够在很短的时间内生成尽可能多的这些测试文件,因为我正在进行编码更改。

The code is what it is. 代码就是这样。 It's my job to be able to make changes to it and at the moment I can't even run it without a huge overhead each time. 我的工作是能够对其进行更改,而且目前我甚至无法在没有巨大开销的情况下运行它。 It would be irresponsible to make changes to it without knowing what it's doing. 在不知道它在做什么的情况下对其进行更改是不负责任的。

Part of your problem is, that when you write a Controller that extends from Symfony's base Controller or the new AbstractController it will load other dependencies from the container. 部分问题是,当您编写从Symfony的基本Controller或新的AbstractController扩展的Controller时,它将从容器中加载其他依赖项。 These service you either have to instantiate in your tests and pass them to a container, that you then set in your controller like this: 这些服务要么必须在测试中实例化并将它们传递给容器,然后在控制器中设置如下:

$loader = new Twig_Loader_Filesystem('/path/to/project/app/Resources/views');
$twig = new Twig_Environment($loader, array(
    'cache' => '/path/to/app/var/cache/templates',
));

# ... other services like routing, doctrine and token_storage

$container = new Container();
$container->set('twig', $twig);

$controller = new MyController();
$controller->setContainer($container);

or mock them, which makes your test pretty much unreadable and break on every change you do to the code. 或者嘲笑它们,这使得你的测试几乎不可读,并且在你对代码所做的每一次改变中都会中断。

As you can see, this is not really a unit test, because you will need all the services you pull from your container directly by calling $this->get()/$this->container->get() or indirectly, such via the helper methods in the controller, eg getDoctrine() . 正如您所看到的,这不是一个单元测试,因为您将需要通过调用$this->get()/$this->container->get()或间接调用您从容器中提取的所有服务。通过控制器中的辅助方法,例如getDoctrine()

Not only is this tedious if you don't configure the services in the same way as you use in production, your tests might not be very meaningful, as they could pass in your tests but fail in production. 如果您不以与在生产中使用相同的方式配置服务,这不仅繁琐,您的测试可能不是很有意义,因为它们可能会通过您的测试但在生产中失败。

The other part of your problem is the comment inside your snippet: 问题的另一部分是您的代码段内的评论:

200+ lines of procedural code here 这里有200多行程序代码

Without seeing the code I can tell you that properly unit testing this is near impossible and not worth it. 没有看到代码,我可以告诉你,正确的单元测试这几乎是不可能的,不值得。

The short answer is, you can't. 简短的回答是,你做不到。

What I recommend is either writing Functional Tests using WebTestCase or something like Selenium with CodeCeption and test your controller indirectly through the UI. 我建议使用WebTestCase编写功能测试使用CodeCeption编写Selenium,并通过UI间接测试控制器。

Once you have tests covering the (main) functionality of your action, you can start refactoring your controller splitting things into smaller chunks and services that are easier to test. 一旦您完成了涵盖操作(主要)功能的测试,您就可以开始重构您的控制器,将事物分成更小的块和更容易测试的服务。 For those new classes unit tests make sense. 对于那些新类,单元测试是有意义的。 You will know when the website works again as before your changes, when your Functional Tests are green (again). 当您的功能测试为绿色(再次)时,您将知道网站何时再次发生变化。 Ideally you would not need to change these first tests as they only look at your website through the browser and therefore are not affected by any code changes you do. 理想情况下,您不需要更改这些首次测试,因为它们只通过浏览器查看您的网站,因此不会受到您所做的任何代码更改的影响。 Just be careful to not introduce changes to the templates and the routing. 请注意不要对模板和路由进行更改。

I have discovered that a few short lines are all that's needed to get Symfony into a test harness: 我发现只需要一些简短的线就可以将Symfony变成测试工具:

// Load the autoloader class so that the controller can find everything it needs
//$loader = require 'app/vendor/autoload.php';
require 'app/vendor/autoload.php';

// Create a new Symfony kernel instance
$kernel = new \AppKernel('prod', false);
//$kernel = new \AppKernel('dev', true);
// Boot the kernel
$kernel->boot();
// Get the kernel container
$container = $kernel->getContainer();
// Services can be retrieved like so if you need to
//$service = $container->get('name.of.registered.service');

// Create a new instance of your controller
$controller = new \What\You\Call\Your\Bundle\Controller\FooBarController();
// You MUST set the container for it to work properly
$controller->setContainer($container);

After this code, you can test any public methods on your controller. 在此代码之后,您可以测试控制器上的任何公共方法。 Of course, if you are testing production code (as I have to; my development code works completely differently because the code base is written terribly) be aware that you might be touching databases, making web calls, etc. 当然,如果您正在测试生产代码(因为我必须这样做;我的开发代码完全不同,因为代码库写得非常糟糕),请注意您可能正在触摸数据库,进行Web调用等。

However, the upside is that you can start doing code coverage of your controllers to understand why they're not working properly. 但是,好处是您可以开始对控制器进行代码覆盖,以了解它们无法正常工作的原因。

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

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