支付宝扫一扫付款
微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
PHPUnit Bridge提供的功能包括,报告遗产级测试(legacy tests)和不建议使用的代码之用法,以及一个“对时间敏感的”测试的助手(a helper for time-sensitive tests)。
它包括以下功能:
C
);class_exists
以加载Doctrine annotations (当使用时);ClockMock
helper类。你可以通过下述两种方式安装:
通过Composer安装(Packagist上的symfony/phpunit-bridge
)
通过官方Git宝库(https://github.com/symfony/phpunit-bridge)
然后,包容vendor/autoload.php
文件,以开启Composer提供的自动加载机制。否则,你的程序将无法找到这个Symfony组件的类。
组件一旦安装完毕,它就自动注册一个 PHPUnit event listener,用于后续注册一个名为 DeprecationErrorHandler
的 PHP error handler 。在你运行完PHPUnit测试后,你会看到类似下图这样一个“报告”:
报告总结了以下内容:
“不建议使用”的通知,可以通过使用以下例程中的代码来触发:
1 | @trigger_error('Your deprecation message', E_USER_DEPRECATED); |
若不加 @-silencing 操作符,用户就得从“不建议使用”的通知信息中选择退出。默认“静默”的话可以避免触发通知,并让用户在他们准备好去解决这些问题时(通过添加自定义的error handler,像是由这个bridge所提供的那个)选择进入。当不静默时,“不建议使用”的通知将显示在“Deprecation报告”的 Unsilenced 区块中。
有四种方式可以把一个测试标记为遗产(legacy):
@group legacy
annotation到测试类和测试方法中;Legacy
前缀;testLegacy
开头而不是(常规的) test
;provideLegacy
或 getLegacy
开头。如果你需要检查一个由“单元测试”所触发的“特定deprecation通知”的stack trace(栈追踪),你可以把 SYMFONY_DEPRECATIONS_HELPER
environment variable(环境变量)设置成一个“能够匹配这条deprecation信息”的正则表达式,它由 /
封闭。例程:
1 2 3 4 5 6 7 8 9 10 11 12 | <!-- http://phpunit.de/manual/4.1/en/appendixes.configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
>
<!-- ... -->
<php>
<server name="KERNEL_DIR" value="app/" />
<env name="SYMFONY_DEPRECATIONS_HELPER" value="/foobar/" />
</php>
</phpunit> |
PHPUnit 将在含有 "foobar"
字符串的deprecation通知被触发时,中止你的测试包。
默认时,任何 non-legacy-tagged(未标记为遗产) 或 non-@-silenced (非静默) 的deprecation通知,都将令测试失败。一种方案是,设置 SYMFONY_DEPRECATIONS_HELPER
为任意值 (如: 320
) 可以令测试仅在一个更大数字的deprecation通知到来时 (0
是默认值) 才失败。你也可以把这个值设为 "weak"
,这将令bridge忽略任何deprecation信息。这对那些因向下兼容的原因而必须使用“不建议使用”之接口的项目来说十分有用。
3.1 关闭Deprecation Helper的能力,从Symfony 3.1开始被引入。
设置 SYMFONY_DEPRECATIONS_HELPER
环境变量为 disabled
以彻底禁用 deprecation helper。这在使用本组件提供的其他功能时有用,不会再收到错误信息和deprecation通知。
如果你有和时间相关的测试:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | use Symfony\Component\Stopwatch\Stopwatch;
class MyTest extends \PHPUnit_Framework_TestCase
{
public function testSomething()
{
$stopwatch = new Stopwatch();
$stopwatch->start();
sleep(10);
$duration = $stopwatch->stop();
$this->assertEquals(10, $duration);
}
} |
你使用了 Symfony Stopwatch 组件 来计算进程持续时间,这里是10秒。但是,根据“运行在你本地机器上的进程”对Server带来的负载, $duration
值可能会是 10.000023s 而非 10s。
这类测试被统称为 transient tests(瞬态测试): 它们会受到伪造或外部环境的影响而失败。当使用公开的、持续的整合型服务比如 Travis CI 时,它们经常引起麻烦。
这个bridge提供的 ClockMock
类,允许你mock PHP内置的时间函数 time()
, microtime()
, sleep()
和 usleep()
。
要在测试中使用 ClockMock
类,你可以:
@group time-sensitive
annotation 测试类或测试方法;ClockMock::register(__CLASS__)
和 ClockMock::withClockMock(true)
,可在测试之前手动注册它,或是在测试之后,调用 ClockMock::withClockMock(false)
来注册。作为结果,下列代码确保正常运行,并且不是一个transient test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | use Symfony\Component\Stopwatch\Stopwatch;
/**
* @group time-sensitive
*/
class MyTest extends \PHPUnit_Framework_TestCase
{
public function testSomething()
{
$stopwatch = new Stopwatch();
$stopwatch->start();
sleep(10);
$duration = $stopwatch->stop();
$this->assertEquals(10, $duration);
}
} |
就是这些了!
使用 ClockMock
类的一个附带的好处是,时间迅即流过。使用PHP的 sleep(10)
会令你的测试真正等待10秒(或多或少)。对比来看,ClockMock
类将内置的时钟给提前到一个给定的秒数,毋须真正经过这个时间,因此你的测试在执行时要快过10秒。
3.1 DNS相关函数的mocks,从Symfony 3.1开始被引入。
包含网络连接的测试,比如要检查DNS纪录的有效性,会因为网络条件而执行的很慢,或是不稳定。因此,本组件也提供了对这一类PHP函数的模拟(mock):
checkdnsrr
dns_check_record
getmxrr
dns_get_mx
gethostbyaddr
gethostbyname
gethostbynamel
dns_get_record
思考以下“使用了 Email
约束中的 checkMX
选项,来测试email域名有效性”的例程:
1 2 3 4 5 6 7 8 9 10 11 12 13 | use Symfony\Component\Validator\Constraints\Email;
class MyTest extends \PHPUnit_Framework_TestCase
{
public function testEmail()
{
$validator = ...
$constraint = new Email(array('checkMX' => true));
$result = $validator->validate('foo@example.com', $constraint);
// ...
} |
为了避免产生真实的网络连接,添加 @dns-sensitive
annotation 到测试类,并使用 DnsMock::withMockedHosts()
来配置 “你预期得到并用于给定义机” 的数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | use Symfony\Component\Validator\Constraints\Email;
/**
* @group dns-sensitive
*/
class MyTest extends \PHPUnit_Framework_TestCase
{
public function testEmails()
{
DnsMock::withMockedHosts(array('example.com' => array(array('type' => 'MX'))));
$validator = ...
$constraint = new Email(array('checkMX' => true));
$result = $validator->validate('foo@example.com', $constraint);
// ...
} |
withMockedHosts()
配置方法,被定义为一个数组。它的键是模拟的主机,值是DNS记录的数组,其格式与 dns_get_record
方法返回的相同,因此你可以模拟各种不同的网络条件:
@group time-sensitive
和 @group dns-sensitive
annotations 都是 "根据约定" 来工作,它们假定“被测试类”的命名空间可以通过从测试类中移除 Tests\
部分来获得。也就是,如果你的test case的 fully-qualified class name (FQCN类名) 是 App\Tests\Watch\DummyWatchTest
,它会推定被测试类的命名空间是 App\Watch
。
如果这种约定在你的程序中无法运行,在 phpunit.xml
文件中配置被模拟的命名空间,如同在 HttpKernel组件 中所展示的那样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <!-- http://phpunit.de/manual/4.1/en/appendixes.configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
>
<!-- ... -->
<listeners>
<listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener">
<arguments>
<array>
<element><string>Symfony\Component\HttpFoundation</string></element>
</array>
</arguments>
</listener>
</listeners>
</phpunit> |
本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。