【Symfony2官方文档翻译】 phpunit

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

测试框架

Symfony2测试很大程序上依赖PHPUnit,它的最佳实践,和一些约定。这部分并不是PHPUnit本身的文档,但如果你还是不能理解的话,你可以阅读它优秀的 文档 。
Symfony2使用PHPUnit 3.5.11或以上版本。
缺省的PHPUnit配置将在你Bundle的Tests/子目录中查找测试:
1
2
3
4
5
6
7
8
9
<!-- app/phpunit.xml.dist -->
< phpunit bootstrap = "../src/autoload.php" >
     < testsuites >
         < testsuite name = "Project Test Suite" >
             < directory >../src/*/*Bundle/Tests</ directory >
         </ testsuite >
     </ testsuites >
     ...
</ phpunit >

对指定应用程序运行测试套件是简单的:

1
2
3
4
5
# 在命令行指定配置目录
$ phpunit -c app/[ /color ]
[color=rgb(44,44,44)] # 或者在应用程序目录中运行phpunit
cd app/
$ phpunit

 

代码的覆盖范围可以通过 –coverate-html 来生成。

单元测试

编写Symfony2单元测试与标准PHPUnit的单元测试没什么不同。通常推荐将Bundle目录结构复制到Tests/子目录中。因此为Acme\HelloBundle\Model\Article类所写的测试会放置在Acme/HelloBundle/Tests/Model/ArticleTest.php文件中。
在单元测试中,自动加载通过src/autoload.php文件是自动启用的(这在phpunit.xml.dist文件中是被缺省配置的)。
为指定文件或目录运行测试也十分容易:

1
2
3
4
5
# 为控制器运行所有测试
$ phpunit -c app src /Acme/HelloBundle/Tests/Controller/ [ /align ][align=left] # 为模型运行所有测试
$ phpunit -c app src /Acme/HelloBundle/Tests/Model/ [ /align ][align=left] # 为Article类运行测试
$ phpunit -c app src /Acme/HelloBundle/Tests/Model/ArticleTest .php[ /align ][align=left] # 为整个Bundle运行所有测试
$ phpunit -c app src /Acme/HelloBundle/

功能测试

功能测试检查应用程序不同层的集成(从路由到视图)。就PHPUnit关注度而言,它们与单元测试没什么不同,除了它们有一个非常特殊的工作流:
*制作一个请求
*测试响应
*点击链接或提交表单
*测试响应
*修正和重复
请求、点击和提交通过一个知道如何与应用程序通信客户端来实现。要访问该客户端,你的测试必须继承Symfony2的WebTestCase类。沙箱提供了一个HelloControoler控制器简单的功能测试,如下所示:

1
2
3
4
5
6
7
8
// src/Acme/HelloBundle/Tests/Controller/HelloControllerTest.php
namespace Acme\HelloBundle\Tests\Controller;[/align][align=left] use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;[/align][align=left] class HelloControllerTest  extends WebTestCase
{
     public function testIndex()
     {
         $client $this ->createClient();[/align][align=left]         $crawler $client ->request( 'GET' '/hello/Fabien' );[/align][align=left]         $this ->assertTrue( $crawler ->filter( 'html:contains("Hello Fabien")' )-> count () > 0);
     }
}

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

1
$crawler $client ->request( 'GET' 'hello/Fabien' );
request()方法返回一个Crawler对象,该对象可以用于在Response中选择元素。可以用来点击链接,也可以用来提交表单。
当Response的内容是XML或HTML文档,可以只使用Crawler对象。对于内容的其它类型,可以通过$client->getResponse()->getContent()来得到内容。
点击链接:首先选择Crawler使用XPath表达式或CSS选择器的链接,然后用Client去点击它:
1
2
$link $crawler ->filter( 'a:contains("Greet")' )->eq(1)->link();
$crawler $client ->click( $link );

提交表单也非常简单;选择一个表单按钮,你可以覆写一些表单的值,然后提交相应的表单:

1
2
3
4
5
$form $crawler ->selectButton( 'submit' )->form();
// 设置一些值
$form [ 'name' ] =  'Lucas' ;
// 提交表单
$crawler $client ->submit( $form );

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

1
2
3
4
5
6
7
8
// 填充一个input项
$form [ 'name' ] =  'Lucas' ;
// 选择一个option或radio
$form [ 'country' ]->select( 'France' );
// 勾掉一个检查框
$form [ 'like_symfony' ]->tick();
// 上传一个文件
$form [ 'photo' ]->upload( '/path/to/lucas.jpg' );

如果不想一次改变一个表单项,你也可以发送一个数组给submit()方法:

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

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

1
2
// 声明响应匹配指定的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:
1
$this ->assertRegExp( '/Hello Fabien/' $client ->getResponse()->getContent());

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 声明响应匹配指定的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应用的请求:

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

 

request()方法将HTTP方法和URL作为参数,然后返回一个Crawler实体。
使用Crawler去发现Response中的DOM元素。这些元素随后可以用于点击链接和提交表单:

1
2
3
$link $crawler ->selectLink( 'Go elsewhere...' )->link();
$crawler $client ->click( $link );[/align][align=left] $form $crawler ->selectButton( 'validate' )->form();
$crawler $client ->submit( $form array ( 'name' =>  'Fabien' ));

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

在接下来的Crawler章节中,你将学到更多关于Link和Form对象。
但你也可以使用request()方法的附加参数来模拟表单提交和复杂请求:
1
2
3
4
5
6
// 表单提交
$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()方法改变:

1
$client ->followRedirects(false);

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

1
$crawler $client ->followRedirect();

最后但并非不重要,当在同一脚本使用多个客户端工作时,你可以迫使每个请求都在它自己的PHP进程中执行以避免产生副作用。

1
$client ->insulate();

浏览

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

1
2
3
4
$client ->back();
$client ->forward();
$client ->reload();[/align][align=left] // 清除所有cookies和浏览历史
$client ->restart();

访问内部对象

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

1
2
$history   $client ->getHistory();
$cookieJar $client ->getCookieJar();

你也可以得到最后请求相应的对象:

1
2
3
$request  $client ->getRequest();
$response $client ->getResponse();
$crawler  $client ->getCrawler();

如果你的请求没有被隔离,你也可以访问Container和Kernel:

1
2
$container $client ->getContainer();
$kernel    $client ->getKernel();

访问Container

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

1
$container $client ->getContainer();

警告:如果你隔离了客户端或使用HTTP层,它将不能工作。

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

访问分析器数据

要让声明数据被分析器收集,你可以所下所示得到分析器:
1
2
3
use Symfony\Component\HttpKernel\Profiler\Profiler;
$profiler new Profiler();
$profiler $profiler ->loadFromResponse( $client ->getResponse());

重定向

缺省状态下,客户端遵循HTTP重定向。但如果你想在重定向之前得到Response并将其重定向给自己,那么调用followRedirects()方法:
1
2
3
4
5
6
$client ->followRedirects(false);
$crawler $client ->request( 'GET' '/' );
// 用重定向响应做一些事
// 手工重定向
$crawler $client ->followRedirect();
$client ->followRedirects(true);

Crawler

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

创建一个Crawler实例

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

1
use Symfony\Component\DomCrawler\Crawler;[/color][/align][align=left][color=rgb(44,44,44)] $crawler new Crawler( $html $url );

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

* HTML文档
* XML文档
* DOMDocument实例
* DOMNodeList实例
* 上述元素的数组
创建之后,你可以添加更多的节点:
方法
描述
addHTMLDocument() HTML文档
addXMLDocument() XML文档
addDOMDocument() DOMDocument实例
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实例。

1
2
3
4
5
6
7
8
9
$crawler
     ->filter( 'h1' )
     ->reduce( function ( $node $i )
     {
         if (! $node ->getAttribute( 'class' )) {
             return false;
         }
     })
     ->first();

使用count()函数得到保存在Crawler:count($crawler)中的节点数。
提取信息
Crawler可以从节点提取信息:

1
2
3
4
5
6
7
8
9
10
// 返回第1个节点的属性值
$crawler ->attr( 'class' );[/color][/align] // 返回第1个节点的节点值
$crawler ->text();
// 提取所有节点的属性数组(_text返回节点值)
$crawler ->extract( array ( '_text' 'href' ));
// 为每个节点运行lambda,并返回结果数组
$data $crawler ->each( function ( $node $i )
{
     return $node ->getAttribute( 'href' );
});

链接

你可以选择带有遍历方法的链接,但selectLink()快捷方法更为方便:

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

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

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

1
$link $crawler ->link();[/color][/align] $client ->click( $link );

links()方法为所有节点返回一个Link对象的数组。
表单

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

1
$crawler ->selectButton( 'submit' );

注意我们选择了表单按钮而不是表单,因为表单可以有几个按钮;如果你使用遍历API,那么注意你必须发现按钮。

selectButton()方法可以选择按钮标签并提交input标签;这儿有一些发现它们的技巧:
* 值,属性的值
* 图片的id或alt属性
* 按钮标签的id或name属性
当你有一个代表按钮的节点,调用form()方法去得到一个Form实例,因为表单包含按钮节点:

1
$form $crawler ->form();

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

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

如果你想为表单模拟一个特定的HTTP方法,将其作为第2个参数:

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

Client可以提交一个Form实例:

1
$client ->submit( $form );

表单项的值也可以作为submit()方法的第2个参数发送:

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

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

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

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

1
2
3
4
5
// 选择一个option或radio
$form [ 'country' ]->select( 'France' );[/color][/align][color=rgb(44,44,44)] // 勾选一个检查框
$form [ 'like_symfony' ]->tick();[/color]
[color=rgb(44,44,44)] // 上传一个文件
$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中。

1
2
3
4
5
6
7
<!-- 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>段:

1
2
3
4
5
6
7
8
9
10
11
< 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,因此你可以如你所愿地调整它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# app/config/config_test.yml
imports:
     - { resource: config_dev.yml }[/align]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()方法:

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

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

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

你也可以覆写每个请求的HTTP头。

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

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值