PHPUnit Bridge

3.3 版本
维护中的版本

PHPUnit Bridge提供的功能包括,报告遗产级测试(legacy tests)和不建议使用的代码之用法,以及一个“对时间敏感的”测试的助手(a helper for time-sensitive tests)。

它包括以下功能:

  • 强制tests使用一个一致的locale (C);
  • 自动注册 class_exists 以加载Doctrine annotations (当使用时);
  • 显示程序中所有的“不建议使用”的功能之完全列表;
  • 显示指定的deprecation的stack trace(错误轨迹/栈追踪);
  • 向“对时间敏感”的测试,提供一个 ClockMock helper类。

安装 

你可以通过下述两种方式安装:

然后,包容vendor/autoload.php文件,以开启Composer提供的自动加载机制。否则,你的程序将无法找到这个Symfony组件的类。

用法 

组件一旦安装完毕,它就自动注册一个 PHPUnit event listener,用于后续注册一个名为 DeprecationErrorHandlerPHP error handler 。在你运行完PHPUnit测试后,你会看到类似下图这样一个“报告”:

报告总结了以下内容:

Unsilenced
报告的是没有使用“推荐的 @-silencing 操作符”的deprecation notices(“不建议使用”通知)。
Legacy
这个deprecation notices针对的是那些“显式地测试了遗产级功能”的测试。
Remaining/Other
这是所有其他的(non-legacy/非遗产) 通知, 消息群组, 用于测试的类和方法等,所发生的deprecation notices。

触发“不建议使用”的通知 

“不建议使用”的通知,可以通过使用以下例程中的代码来触发:

1
@trigger_error('Your deprecation message', E_USER_DEPRECATED);

若不加 @-silencing 操作符,用户就得从“不建议使用”的通知信息中选择退出。默认“静默”的话可以避免触发通知,并让用户在他们准备好去解决这些问题时(通过添加自定义的error handler,像是由这个bridge所提供的那个)选择进入。当不静默时,“不建议使用”的通知将显示在“Deprecation报告”的 Unsilenced 区块中。

标记为遗产级测试 

有四种方式可以把一个测试标记为遗产(legacy):

  • (推荐使用) 添加 @group legacy annotation到测试类和测试方法中;
  • 令测试类的名称是 Legacy 前缀;
  • 令测试方法名以 testLegacy 开头而不是(常规的) test;
  • 令测试程序的data provider以 provideLegacygetLegacy 开头。

配置信息 

如果你需要检查一个由“单元测试”所触发的“特定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信息。这对那些因向下兼容的原因而必须使用“不建议使用”之接口的项目来说十分有用。

关闭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 时,它们经常引起麻烦。

Clock Mocking 

这个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秒。

对DNS敏感的测试 

3.1 DNS相关函数的mocks,从Symfony 3.1开始被引入。

包含网络连接的测试,比如要检查DNS纪录的有效性,会因为网络条件而执行的很慢,或是不稳定。因此,本组件也提供了对这一类PHP函数的模拟(mock):

使用场合 

思考以下“使用了 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 方法返回的相同,因此你可以模拟各种不同的网络条件:

1
2
3
4
5
6
7
8
9
10
11
12
DnsMock::withMockedHosts(array(
    'example.com' => array(
        array(
            'type' => 'A',
            'ip' => '1.2.3.4',
        ),
        array(
            'type' => 'AAAA',
            'ipv6' => '::12',
        ),
    ),
));

疑难解答 

@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 创作共用授权。

登录symfonychina 发表评论或留下问题(我们会尽量回复)