Symfony2Book08:测试

不管什么时候,你只要编写一行新的代码,你就有可能引入新的Bug。你应该使用自动测试,该教程将向你显示如何为你的应用程序编写单元测试和功能测试。php

测试框架

Symfony2测试很大程序上依赖PHPUnit,它的最佳实践,和一些约定。这部分并非PHPUnit自己的文档,但若是你仍是不能理解的话,你能够阅读它优秀的文档html

Symfony2使用PHPUnit 3.5.11或以上版本。node

缺省的PHPUnit配置将在你Bundle的Tests/子目录中查找测试:web

<!-- app/phpunit.xml.dist -->

<phpunit bootstrap="../src/autoload.php">
    <testsuites>
        <testsuite name="Project Test Suite">
            <directory>../src/*/*Bundle/Tests</directory>
        </testsuite>
    </testsuites>

    ...
</phpunit>

对指定应用程序运行测试套件是简单的:正则表达式

# 在命令行指定配置目录
$ phpunit -c app/

# 或者在应用程序目录中运行phpunit
$ cd app/
$ phpunit

代码的覆盖范围能够经过 --coverate-html 来生成。bootstrap

 

单元测试

编写Symfony2单元测试与标准PHPUnit的单元测试没什么不一样。一般推荐将Bundle目录结构复制到Tests/子目录中。所以为Acme\HelloBundle\Model\Article类所写的测试会放置在Acme/HelloBundle/Tests/Model/ArticleTest.php文件中。数组

在单元测试中,自动加载经过src/autoload.php文件是自动启用的(这在phpunit.xml.dist文件中是被缺省配置的)。浏览器

为指定文件或目录运行测试也十分容易:cookie

# 为控制器运行全部测试
$ phpunit -c app src/Acme/HelloBundle/Tests/Controller/

# 为模型运行全部测试
$ phpunit -c app src/Acme/HelloBundle/Tests/Model/

# 为Article类运行测试
$ phpunit -c app src/Acme/HelloBundle/Tests/Model/ArticleTest.php

# 为整个Bundle运行全部测试
$ phpunit -c app src/Acme/HelloBundle/

功能测试

功能测试检查应用程序不一样层的集成(从路由到视图)。就PHPUnit关注度而言,它们与单元测试没什么不一样,除了它们有一个很是特殊的工做流:app

*制做一个请求
*测试响应
*点击连接或提交表单
*测试响应
*修正和重复

请求、点击和提交经过一个知道如何与应用程序通讯客户端来实现。要访问该客户端,你的测试必须继承Symfony2的WebTestCase类。沙箱提供了一个HelloControoler控制器简单的功能测试,以下所示:

// src/Acme/HelloBundle/Tests/Controller/HelloControllerTest.php
namespace Acme\HelloBundle\Tests\Controller;

use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;

class HelloControllerTest extends WebTestCase
{
    public function testIndex()
    {
        $client = $this->createClient();

        $crawler = $client->request('GET', '/hello/Fabien');

        $this->assertTrue($crawler->filter('html:contains("Hello Fabien")')->count() > 0);
    }
}

createClient()方法返回一个与当前应用程序绑定的客户端

$crawler = $client->request('GET', 'hello/Fabien');

request()方法返回一个Crawler对象,该对象能够用于在Response中选择元素。能够用来点击连接,也能够用来提交表单。

当Response的内容是XML或HTML文档,能够只使用Crawler对象。对于内容的其它类型,能够经过$client->getResponse()->getContent()来获得内容。

点击连接:首先选择Crawler使用XPath表达式或CSS选择器的连接,而后用Client去点击它:

$link = $crawler->filter('a:contains("Greet")')->eq(1)->link();

$crawler = $client->click($link);

提交表单也很是简单;选择一个表单按钮,你能够覆写一些表单的值,而后提交相应的表单:

$form = $crawler->selectButton('submit')->form();

// 设置一些值
$form['name'] = 'Lucas';

// 提交表单
$crawler = $client->submit($form);

每一个表单项根据它的类型都有相对应的方法:

// 填充一个input项
$form['name'] = 'Lucas';

// 选择一个option或radio
$form['country']->select('France');

// 勾掉一个检查框
$form['like_symfony']->tick();

// 上传一个文件
$form['photo']->upload('/path/to/lucas.jpg');

若是不想一次改变一个表单项,你也能够发送一个数组给submit()方法:

$crawler = $client->submit($form, array(
    'name'         => 'Lucas',
    'country'      => 'France',
    'like_symfony' => true,
    'photo'        => '/path/to/lucas.jpg',
));

如今你能够很轻易浏览应用程序,使用声明去测试看看程序其实是否按你所预期的执行。使用Crawler在DOM上执行中断:

// 声明响应匹配指定的CSS选择器
$this->assertTrue($crawler->filter('h1')->count() > 0);

或者,若是你只是想声明内容包含一些文本,test能够直接针对Response内容。若是Response不是一个XML/HTML文档,则没法实现。(这段翻得不顺畅,留下英文原文吧)

Or, test against the Response content directly if you just want to assert that the content contains some text, or if the Response is not an XML/HTML document:

$this->assertRegExp('/Hello Fabien/', $client->getResponse()->getContent());

有用的声明

在一段时间以后,你会注意到你老是写同一类型的声明。为了你更快地开始,这里有一个经常使用的声明列表:

// 声明响应匹配指定的CSS选择器。
$this->assertTrue($crawler->filter($selector)->count() > 0);

// 声明响应匹配指定的CSS选择器N次
$this->assertEquals($count, $crawler->filter($selector)->count());

// 声明响应头有给定的值
$this->assertTrue($client->getResponse()->headers->contains($key, $value));

// 声明响应内容匹配正则表达式
$this->assertRegExp($regexp, $client->getResponse()->getContent());

// 声明响应状态码
$this->assertTrue($client->getResponse()->isSuccessful());
$this->assertTrue($client->getResponse()->isNotFound());
$this->assertEquals(200, $client->getResponse()->getStatusCode());

// 声明响应状态码是重定向
$this->assertTrue($client->getResponse()->isRedirected('google.com'));

测试客户端

测试客户端模拟相似浏览器的HTTP客户端。

测试客户端基于BrowserKit和Crawler组件。

制造请求

客户端知道如何制做一个发往Symfony2应用的请求:

$crawler = $client->request('GET', '/hello/Fabien');

request()方法将HTTP方法和URL做为参数,而后返回一个Crawler实体。

使用Crawler去发现Response中的DOM元素。这些元素随后能够用于点击连接和提交表单:

$link = $crawler->selectLink('Go elsewhere...')->link();
$crawler = $client->click($link);

$form = $crawler->selectButton('validate')->form();
$crawler = $client->submit($form, array('name' => 'Fabien'));

click()和submit()方法都返回一个Crawler对象。这些方法浏览应用程序并隐藏大量细节的最好方式。例如,当你提交一个表单时,它自动匹配HTTP方法和表单URL、它给你一个设计良好的API去上传文件、它合并表单缺省值和提交值,等等储如此类。

在接下来的Crawler章节中,你将学到更多关于Link和Form对象。

但你也可使用request()方法的附加参数来模拟表单提交和复杂请求:

// 表单提交
$client->request('POST', '/submit', array('name' => 'Fabien'));

// 带文件上传的表单提交
$client->request('POST', '/submit', array('name' => 'Fabien'), array('photo' => '/path/to/photo'));

// 指定HTTP头
$client->request('DELETE', '/post/12', array(), array(), array('PHP_AUTH_USER' => 'username', 'PHP_AUTH_PW' => 'pa$$word'));

当一个请求返回一个重定向响应,客户端会自动遵循它。这个行为能够被followRedirects()方法改变:

$client->followRedirects(false);

当客户端遵循响应进行重定向时,你可使用followRedirect()迫强使它进行重定向:

$crawler = $client->followRedirect();

最后但并不是不重要,当在同一脚本使用多个客户端工做时,你能够迫使每一个请求都在它本身的PHP进程中执行以免产生反作用。

$client->insulate();

浏览

客户端支持许多实际浏览器的操做

$client->back();
$client->forward();
$client->reload();

// 清除全部cookies和浏览历史
$client->restart();

访问内部对象

若是你使用客户端去测试你的应用程序,你也许想去访问客户端的内部对象:

$history   = $client->getHistory();
$cookieJar = $client->getCookieJar();

你也能够获得最后请求相应的对象:

$request  = $client->getRequest();
$response = $client->getResponse();
$crawler  = $client->getCrawler();

若是你的请求没有被隔离,你也能够访问Container和Kernel:

$container = $client->getContainer();
$kernel    = $client->getKernel();

访问Container

强烈建议功能测试只测试Response。但在几种很是罕见的状况下,你也许想要访问一些内部对象对编写声明。在这种状况下,你能够访问依赖注入容器:

$container = $client->getContainer();

警告:若是你隔离了客户端或使用HTTP层,它将不能工做。

若是你所需信息被分析器检出是可用的话,那么用它们代替。

访问分析器数据

要让声明数据被分析器收集,你能够所下所示获得分析器:

use Symfony\Component\HttpKernel\Profiler\Profiler;

$profiler = new Profiler();
$profiler = $profiler->loadFromResponse($client->getResponse());

重定向

缺省状态下,客户端遵循HTTP重定向。但若是你想在重定向以前获得Response并将其重定向给本身,那么调用followRedirects()方法:

$client->followRedirects(false);

$crawler = $client->request('GET', '/');

// 用重定向响应作一些事

// 手工重定向
$crawler = $client->followRedirect();

$client->followRedirects(true);

Crawler

每次你用Client生成请求时都会返回一个Crawler实例。它容许你遍历HTML文档、选择节点、找到连接和表单。

建立一个Crawler实例

当你用Client生成请求时,一个Crawler实例将会自动为你建立。但你也能够很容易地自行建立:

use Symfony\Component\DomCrawler\Crawler;

$crawler = new Crawler($html, $url);

构造函数有两个参数:第2个参数是为连接和表单生成绝对URL的URL;第1个参数可使用如下内容:

* HTML文档
* XML文档
* DOMDocument实例
* DOMNodeList实例
* 上述元素的数组

建立以后,你能够添加更多的节点:

方法 描述
addHTMLDocument() HTML文档
addXMLDocument() XML文档
addDOMDocument() DOMDocument实例
addDOMNodeList() DOMNodeList实例
addDOMNode() DOMNode实例
addNodes() 上述元素的数组
add() 接受上述任一元素

遍历

象jQuery同样,Crawler有方法去遍历HTML/XML文档的DOM:

方法 描述
filter('h1') 匹配CSS选择器的节点
filterXpath('h1') 匹配XPath表达式的节点
eq(1) 指定索引的节点
first() 第1个节点
last() 最后1个节点
siblings() 兄弟节点
nextAll() 全部后面的兄弟节点
previousAll() 全部前面的兄弟节点
parents() 父节点
children() 子节点
reduce($lambda) 全部被调用后不返回false的节点

你能够经过链式方法调用来迭代缩小你选择的节点,注意你每一个匹配节点用的方法都须要返回一个新的Crawler实例。

$crawler
    ->filter('h1')
    ->reduce(function ($node, $i)
    {
        if (!$node->getAttribute('class')) {
            return false;
        }
    })
    ->first();

使用count()函数获得保存在Crawler:count($crawler)中的节点数。

提取信息

Crawler能够从节点提取信息:

// 返回第1个节点的属性值
$crawler->attr('class');

// 返回第1个节点的节点值
$crawler->text();

// 提取全部节点的属性数组(_text返回节点值)
$crawler->extract(array('_text', 'href'));

// 为每一个节点运行lambda,并返回结果数组
$data = $crawler->each(function ($node, $i)
{
    return $node->getAttribute('href');
});

连接

你能够选择带有遍历方法的连接,但selectLink()快捷方法更为方便:

$crawler->selectLink('Click here');

它选择包含指定文本的连接,或者alt属性包含指定文本的可点击图片。

Client对象的click()方法驱动一个被link()方法返回的Link实例:

$link = $crawler->link();

$client->click($link);

links()方法为全部节点返回一个Link对象的数组。

表单

你选择有着selectButton()方法的表单:

$crawler->selectButton('submit');

注意咱们选择了表单按钮而不是表单,由于表单能够有几个按钮;若是你使用遍历API,那么注意你必须发现按钮。

selectButton()方法能够选择按钮标签并提交input标签;这儿有一些发现它们的技巧:

* 值,属性的值
* 图片的id或alt属性
* 按钮标签的id或name属性

当你有一个表明按钮的节点,调用form()方法去获得一个Form实例,由于表单包含按钮节点:

$form = $crawler->form();

当调用form()方法时,你也能够发送一个覆写缺省值的那些表单项值的数组:

$form = $crawler->form(array(
    'name'         => 'Fabien',
    'like_symfony' => true,
));

若是你想为表单模拟一个特定的HTTP方法,将其做为第2个参数:

$form = $crawler->form(array(), 'DELETE');

Client能够提交一个Form实例:

$client->submit($form);

表单项的值也能够做为submit()方法的第2个参数发送:

$client->submit($form, array(
    'name'         => 'Fabien',
    'like_symfony' => true,
));

更复杂的状况,使用Form实例,并用一个数组来设置每一个单独表单项的值:

// 改变表单项的值
$form['name'] = 'Fabien';

也有设计良好的API按照表单项的类型去操做它的值:

// 选择一个option或radio
$form['country']->select('France');

// 勾选一个检查框
$form['like_symfony']->tick();

// 上传一个文件
$form['photo']->upload('/path/to/lucas.jpg');

你能够经过调用getValues()方法获得将提交的值。被上传的文件也能够经过getFiles()返回的数组中获得。getPhpValues()和getPhpFiles()也返回被提交的值,可是以PHP格式返回的(它将方括号中的关键词转换成PHP数组)。

 

测试配置


 

PHPUnit配置

每一个应用程序都有它本身的PHPUnit配置,它们被保存在phpunit.xml.dist文件中。你能够编辑这个文件以改变缺省值或者建立phpnit.xml文件去为你的本地机调整配置。

在你的代码库中保存phpunit.xml.dist文件,并忽略phpunit.xml文件。

缺省状况下,测试只被保存在那些经过运行phpunit命令的“标准”Bundle中,(标准是指测试位于Vendor\*Bundle\Tests名称空间)。但你能够很方便地添加更多的名称空间。例如,下面的配置将测试添加在安装的第三方Bundle中。

<!-- hello/phpunit.xml.dist -->
<testsuites>
    <testsuite name="Project Test Suite">
        <directory>../src/*/*Bundle/Tests</directory>
        <directory>../src/Acme/Bundle/*Bundle/Tests</directory>
    </testsuite>
</testsuites>

为了包含代码范围中的其它名称空间,也能够编辑<filter>段:

<filter>
    <whitelist>
        <directory>../src</directory>
        <exclude>
            <directory>../src/*/*Bundle/Resources</directory>
            <directory>../src/*/*Bundle/Tests</directory>
            <directory>../src/Acme/Bundle/*Bundle/Resources</directory>
            <directory>../src/Acme/Bundle/*Bundle/Tests</directory>
        </exclude>
    </whitelist>
</filter>

Client配置

经过功能测试使用的Client建立一个在特定测试环境下运行的Kernel,所以你能够如你所愿地调整它:

# app/config/config_test.yml
imports:
    - { resource: config_dev.yml }

framework:
    error_handler: false
    test: ~

web_profiler:
    toolbar: false
    intercept_redirects: false

monolog:
    handlers:
        main:
            type:  stream
            path:  %kernel.logs_dir%/%kernel.environment%.log
            level: debug

你也能够改变缺省环境(test),经过覆写调试模式(true),并将其作为选项发送给createClient()方法:

$client = $this->createClient(array(
    'environment' => 'my_test_env',
    'debug'       => false,
));

若是你的应用程序是根据一些HTTP头来运行的话,那么将它们做为第2个参数发送给createClient():

$client = $this->createClient(array(), array(
    'HTTP_HOST'       => 'en.example.com',
    'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
));

你也能够覆写每一个请求的HTTP头。

$client->request('GET', '/', array(), array(
    'HTTP_HOST'       => 'en.example.com',
    'HTTP_USER_AGENT' => 'MySuperBrowser/1.0',
));

覆写test.client.class参数或定义一个test.client服务来提供你本身的Client。

相关文章
相关标签/搜索