无论何时,你只要编写一行新的代码,你就有可能引入新的Bug。你应该使用自动测试,该教程将向你显示如何为你的应用程序编写单元测试和功能测试。
测试框架
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
|
单元测试
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/
|
功能测试
*测试响应
*点击链接或提交表单
*测试响应
*修正和重复
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'
);
|
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);
|
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'
);
|
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去上传文件、它合并表单缺省值和提交值,等等储如此类。
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
1
|
$container
=
$client
->getContainer();
|
警告:如果你隔离了客户端或使用HTTP层,它将不能工作。
访问分析器数据
1
2
3
|
use
Symfony\Component\HttpKernel\Profiler\Profiler;
$profiler
=
new
Profiler();
$profiler
=
$profiler
->loadFromResponse(
$client
->getResponse());
|
重定向
1
2
3
4
5
6
|
$client
->followRedirects(false);
$crawler
=
$client
->request(
'GET'
,
'/'
);
// 用重定向响应做一些事
// 手工重定向
$crawler
=
$client
->followRedirect();
$client
->followRedirects(true);
|
Crawler
创建一个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个参数可以使用以下内容:
* XML文档
* DOMDocument实例
* DOMNodeList实例
* 上述元素的数组
方法
|
描述
|
addHTMLDocument() | HTML文档 |
addXMLDocument() | XML文档 |
addDOMDocument() | DOMDocument实例 |
addDOMNode() | DOMNode实例 |
addNodes() | 上述元素的数组 |
add() | 上述元素的数组 |
遍历
方法 | 描述 |
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'
);
});
|
链接
1
|
$crawler
->selectLink(
'Click here'
);
|
它选择包含指定文本的链接,或者alt属性包含指定文本的可点击图片。
1
|
$link
=
$crawler
->link();[/color][/align]
$client
->click(
$link
);
|
links()方法为所有节点返回一个Link对象的数组。
表单
1
|
$crawler
->selectButton(
'submit'
);
|
注意我们选择了表单按钮而不是表单,因为表单可以有几个按钮;如果你使用遍历API,那么注意你必须发现按钮。
* 图片的id或alt属性
* 按钮标签的id或name属性
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'
);
|
测试配置
PHPUnit配置
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配置
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