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

时间:2018-04-10 04:47:53

标签: php unit-testing symfony phpunit codeception

我试图使用Codeception在测试工具中获取Symfony控制器。每个方法都按如下方式开始:

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
    ...
    */
}

我试过了:

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

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

来自我的单元测试,但似乎还有很多其他设置,我不知道Symfony在后台做了什么。每当我找到一个丢失的对象并初始化它时,另一个会在某个时刻失败。

我也试过从PhpStorm中逐步完成测试,但是PhpUnit有一些输出导致Symfony在它接近代码的任何地方死亡我试图测试因为它无法启动任何输出发生后$_SESSION。我不认为这是从命令行发生的,但我还没有真正足够接近它。

如何在单元测试中简单且可扩展地运行此代码?

一些背景知识:

我继承了这段代码。我知道它很脏并且闻起来因为它在控制器中进行模型逻辑。我知道我要求的不是纯粹的"单元测试,因为它几乎涉及整个应用程序。

但我需要能够运行这个"小" (200多行)自动代码。代码应该运行不超过几秒钟。我不知道多久,因为我从来没有能够独立运行它。

目前,通过网站运行此代码的设置时间很长,而且很复杂。代码不生成网页,它基本上是生成文件的API调用。我需要能够在很短的时间内生成尽可能多的这些测试文件,因为我正在进行编码更改。

代码就是这样。能够对其进行更改是我的工作,目前我甚至无法在没有巨大开销的情况下运行它。在不知道它做什么的情况下对其进行更改是不负责任的。

3 个答案:

答案 0 :(得分:2)

您的部分问题是,当您编写从Symfony的基本Controller或新的AbstractController扩展的Controller时,它将从容器中加载其他依赖项。这些服务你要么必须在测试中实例化并将它们传递给容器,然后在控制器中设置如下:

$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);

或模仿它们,这使得您的测试几乎无法读取,并且会对您对代码所做的每一项更改中断。

正如您所看到的,这不是一个真正的单元测试,因为您需要通过调用$this->get()/$this->container->get()或间接调用从容器中提取的所有服务,例如通过控制器中的辅助方法,例如: getDoctrine()

如果您不以与在生产中使用相同的方式配置服务,这不仅繁琐,您的测试可能没有太大意义,因为它们可能会通过您的测试但在生产中失败。

问题的另一部分是您的代码段中的评论:

  

这里有200多行程序代码

没有看到代码,我可以告诉你,正确的单元测试几乎是不可能的,不值得。

简短的回答是,你不能

我建议您编写Functional Tests using WebTestCase或类似Selenium with CodeCeption之类的内容,并通过用户界面间接测试您的控制器。

一旦测试涵盖了您的操作的(主要)功能,您就可以开始重构您的控制器,将事物分成更小的块和更容易测试的服务。对于那些新类,单元测试是有意义的。当您的功能测试为绿色(再次)时,您将知道网站何时再次发生变化。理想情况下,您不需要更改这些首次测试,因为它们只通过浏览器查看您的网站,因此不会受到您所做的任何代码更改的影响。请注意不要对模板和路由进行更改。

答案 1 :(得分:0)

我发现,将Symfony纳入测试工具只需要几行:

<html>
<head>
<title>Window Title or Email Subject</title>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<!--@SPF-JS-HEADER@-->
<style type="text/css">
table.tblin, td.tblin, th, td.alt 
{
     border-color:#cc9;
     border-collapse:collapse;
     border-style:solid;
     border-width:1px;
     border-spacing:4px;
} 
<body>
<p>Hello</p>
</body>
</html>

此代码之后,您可以在控制器上测试任何公共方法。当然,如果您正在测试生产代码(必须这样做;由于代码库编写得很糟糕,我的开发代码的工作方式将完全不同),请注意,您可能正在接触数据库,进行Web调用等。

但是,好处是您可以开始对控制器进行代码覆盖,以了解它们为何无法正常工作。

答案 2 :(得分:0)

对于仍然在这个问题上迷anyone的人,通过控制器功能参数引入依赖注入,为控制器编写单元测试变得更加容易。

例如如果您的代码写法不同:

public function save(Request $request, DocumentManagerInterface $em, int $id): RedirectResponse
{
    /* Actual code here
    ...
    */
}

您的UNIT测试可以简单地做到这一点:

public function testSave(): void
{
    $em = $this->createMock(EntityManagerInterface::class);
    // test calls on the mock

    $controller = new XXXController();
    $response = $controller->save($em);

    // response assertions
}

还请注意,如果您使用存储库,则可以从服务中扩展存储库,从而直接注入存储库。

提示:您可能需要查看Symfony最佳实践,并使用ParamConverter代替$idhttps://symfony.com/doc/current/best_practices/index.html