使用 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

4月 14, 2013 在 2:46 下午

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

 回复

    Chris Yue

    4月 23, 2013 在 10:46 上午

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

     

    Chris Yue

    4月 23, 2013 在 10:53 上午

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

     

caoxin进行回复 取消回复

1 + 3 =

2021年4月
 1234
567891011
12131415161718
19202122232425
2627282930  
Composer CSS Firefox industrial metal JavaScript LeetCode Linux MySQL NGINX nu metal OAuth PHP PHP7 Shell Symfony Ubuntu Vim 全栈 到底系列 命令行 安全 教程 框架 算法 翻译