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。

相關文章
相關標籤/搜索