英文原文地址: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