使用 Symfony 的组件创建自己的 PHP 框架(第八部分:单元测试)

使用 Symfony 的组件创建自己的 PHP 框架(第八部分:单元测试)

Chris Yue 3 comments
Posts

英文原文地址:https://symfony.com/doc/current/create_framework/unit_testing.html

一些细心的读者已经发现,前一章完成的框架里还存在难以发现却很严重的bug。创建框架,你必须得保证它能像当初设计的那样工作,否则的话使用它做出来的程序都会出现它导致的问题。当然好消息是:如果你修改了一个bug,你就等于修改了一大堆应用程序。

今天我们的任务是,利用 PHPUnit 来为我们的框架写一些单元测试。首先创建一个 PHPUnit 的配置文件 example.com/phpunit.xml.dist:

<?xml version="1.0" encoding="UTF-8"?>

<phpunit backupGlobals="false"
     backupStaticAttributes="false"
     colors="true"
     convertErrorsToExceptions="true"
     convertNoticesToExceptions="true"
     convertWarningsToExceptions="true"
     processIsolation="false"
     stopOnFailure="false"
     syntaxCheck="false"
     bootstrap="vendor/.composer/autoload.php"
>
    <testsuites>
        <testsuite name="Test Suite">
            <directory>./tests</directory>
        </testsuite>
    </testsuites>
</phpunit>

此配置定义了 PHPUnit 大部分设置很好的默认选项;更有趣的是,自动加载器将会用来启动测试,而所有的测试都将放在 example.com/tests/ 目录。

现在,让我们写一个“资源无法找到”的响应的测试。为了防止要测试的对象的依赖组件对测试的影响,我们打算使用 test doubles(译者注:对于 test doubles 我自己的理解是,在测试某一个组件的时候,有可能这个组件需要其他组件才能工作,比如方向盘。当然为了测试方向盘能否正常工作,不见得非要把方向盘装车上,若有专门的仪器可以模拟真实的环境也可以测试)。如果我们的组件都依赖于接口而不是实际的类,我们将很容易使用模拟环境(test doubles)来做测试。还好 Symfony 的核心组件都提供了这样的接口,比如 url 匹配器以及控制器解析器。让我们修改一下框架来使用这些接口:

<?php

// example.com/src/Simplex/Framework.php

namespace Simplex;

// ...

use Symfony\Component\Routing\Matcher\UrlMatcherInterface;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;

class Framework
{
    protected $matcher;
    protected $resolver;

    public function __construct(
        UrlMatcherInterface $matcher, 
        ControllerResolverInterface $resolver
    ) {
        $this->matcher = $matcher;
        $this->resolver = $resolver;
    }

    // ...
}

我们现在便可以写我们的第一个测试了:

<?php

// example.com/tests/Simplex/Tests/FrameworkTest.php

namespace Simplex\Tests;

use Simplex\Framework;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Exception\ResourceNotFoundException;

class FrameworkTest extends \PHPUnit_Framework_TestCase
{
    public function testNotFoundHandling()
    {
        $framework = $this->getFrameworkForException(new ResourceNotFoundException());

        $response = $framework->handle(new Request());

        $this->assertEquals(404, $response->getStatusCode());
    }

    protected function getFrameworkForException($exception)
    {
        $matcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface');
        $matcher
            ->expects($this->once())
            ->method('match')
            ->will($this->throwException($exception))
        ;
        $resolver = $this->getMock(
            'Symfony\Component\HttpKernel\Controller\ControllerResolverInterface'
        );

        return new Framework($matcher, $resolver);
    }
}

此测试模拟了一个不匹配任何路由规则的请求,接着 match() 方法将产生 ResourceNotFoundException 的意外。我们将测试我们的框架类能否将此意外转变成一个 404 相应。

执行测试程序非常简单:

phpunit

我将不会详细解释代码执行的细节,因为这不是本章的重点,但如果你实在被这些代码搞的崩溃,我强烈推荐你们看看 PHPUnit 关于 test doubles 的文档

执行完上面的命令,你应该能看到一条绿色背景提示(在 windows 下面应该是看不到的,绿色背景的提示表示测试很 OK),如果没有,说明你的框架代码可能有问题哦。

添加测试控制器抛出的异常的代码也是 so easy 的事情:

public function testErrorHandling()
{
    $framework = $this->getFrameworkForException(new \RuntimeException());

    $response = $framework->handle(new Request());

    $this->assertEquals(500, $response->getStatusCode());
}

最后很重要的一步,添加一个返回正常相应的测试:

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;

public function testControllerResponse()
{
    $matcher = $this->getMock('Symfony\Component\Routing\Matcher\UrlMatcherInterface');
    $matcher
        ->expects($this->once())
        ->method('match')
        ->will($this->returnValue(array(
            '_route' => 'foo',
            'name' => 'Fabien',
            '_controller' => function ($name) {
                return new Response('Hello '.$name);
            }
        )))
    ;
    $resolver = new ControllerResolver();

    $framework = new Framework($matcher, $resolver);

    $response = $framework->handle(new Request());

    $this->assertEquals(200, $response->getStatusCode());
    $this->assertContains('Hello Fabien', $response->getContent());
}

在此测试中,我们模拟了一个能匹配的路由,并返回了一个简单的控制器。我们测试了框架返回的相应代码是否 200,并且相应内容是否是我们期待的那样工作。

要查看此测试代码是否测试了所有的运行事例(译者注:也就是代码测试率,即测试运行过的代码行数占整个代码行数的比率),请使用 PHPUnit 的代码测试率功能(你需要安装 PHP 的 XDebug 插件):

phpunit --coverage-html=cov/

在浏览器中打开 example.com/cov/src_Simplex_Framework.php.html,检查所有关于框架类的代码是不是都是绿色的(绿色的代码表示这些它们已经被测试过了)

得益于我们写的面向对象的代码,我们能够写出能测试全部使用事例的框架测试代码。test doubles 测试可以保证我们测试的是只自己的代码而不是 Symfony 的。

现在我们(又一次)对自己的代码充满信心,我们可以安心的考虑我们接下来要加什么功能了

返回阅读第七部分 | 继续阅读第九部分

使用 Symfony 的组件创建自己的 PHP 框架(第八部分:单元测试) by Chris Yue is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

微信赞赏码

写作累,服务器还越来越贵
求分担,祝愿好人一生平安
天使打赏人

3 Comments

caoxin

四月 14, 2013 在 2:46 下午

博主:你翻译的真好!期待您能将后续的几章翻译一下。给我们这些英语差又想更好的学好这个框架的人,一定希望。

    Chris Yue

    四月 23, 2013 在 10:46 上午

    谢谢!此系列只有12篇,已经全部翻译完了哦

     

    Chris Yue

    四月 23, 2013 在 10:53 上午

    噢!原来没有写下一篇的链接,感谢帮我发现这个bug

     

发表评论

1 + 2 =