Компонент PHPUnit Bridge

PHPUnit Bridge предоставляет утилиты для отчётов о тестах наследия и использовании устаревшего кода, а также помощника для тестов, чувствительных ко времени.

Он имеет следующие функции:

  • Заставляет тесты использовать постоянную локаль (C);
  • Авторегистрирует class_exists, чтобы загружать аннотации Doctrine (когда они используются);
  • Отображает полный список устаревших функций, используемых в приложении;
  • Отображает отслеживание стека устаревшей функции по требованю;
  • Предоставляет классы помощника ClockMock и DnsMock для тестов, чувствительных ко времени и сети;
  • Предоставляет изменённую версию PHPUnit, которая не имеет встроенного symfony/yaml или prophecy, чтобы избежать конфликтов с этими зависимостями.

Установка

Вы можете установить компонент 2 разными способами:

Then, require the vendor/autoload.php file to enable the autoloading mechanism provided by Composer. Otherwise, your application won't be able to find the classes of this Symfony component.

Если вы планируете Напишите утверждения об устареваниях и использовать обычный скрипт PHPUnit (а не изменённый скрипт PHPUnit, предоставленный Symfony), то вам нужно зарегистрировать новый слушатель теста под названием SymfonyTestsListener:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<!-- http://phpunit.de/manual/6.0/en/appendixes.configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/6.0/phpunit.xsd"
>

    <!-- ... -->

    <listeners>
        <listener class="Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
    </listeners>
</phpunit>

Использование

Как только компонент установлен, он автоматически регистрирует слушатель событий PHPUnit, который, в свою очередь, регистрирует обработчик ошибок PHP под названием DeprecationErrorHandler. После выполнения ваших тестов PHPUnit, вы получите отчёт, похожий на этот:

../_images/report.png

Этот отчёт включает в себя:

Незаглушенные
Сообщает об уведомлениях об устаревании, которые были запущены без рекомендованного оператора @-silencing.
Унаследованные
Уведомления об устаревании помечают тесты, которые ясно тестируют какие-то функци наследования.
Оставшиеся/Другие
Все другие (не наследственные) уведомления об устаревании, сгруппированные по собщению, классу теста и методу.

Вызов уведомлений об устаревании

Уведомления об устаревании могут быть вызваны используя:

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

Без оператора @-silencing пользователям было бы нужно отказываться от уведомлений об устаревании. Заглушение этого поведения меняет его настройки по умолчанию и позволяет пользователям самим выбирать, когда они готовы с ними справляться (путём добавления пользовательского обработчика ошибок, вроде предоставленного этим мостом). Если они не заглушены, уведомления об устаревании будут появляться в разделе Незаглушенные отчёта об устаревании.

Помечание тестов в качестве наследования

Добавьте аннотацию @group legacy к классу или методу теста, чтобы отметить его, как наследование.

Конфигурация

В случае, если вам нужно ислледовать отслеживание стека определённого устаревания, вызванного нашими модульными тестами, вы можете установить `переменную окружения`_ SYMFONY_DEPRECATIONS_HELPER в регулярном выражении, которое соответствует сообщению этого устаревания, заканчивающийся на /. Например:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
<!-- http://phpunit.de/manual/6.0/en/appendixes.configuration.html -->
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/6.0/phpunit.xsd"
>

    <!-- ... -->

    <php>
        <server name="KERNEL_DIR" value="app/" />
        <env name="SYMFONY_DEPRECATIONS_HELPER" value="/foobar/" />
    </php>
</phpunit>

PHPUnit остановит ваш пакет тестов, как только будет вызвано уведомление об устаревании, сообщение которого содержит строку "foobar".

Как сделать, чтобы тест был неуспешным

По умолчанию, все сообщения об устаревании, тегированные не-наследственными или не-@-silenced заставят тесты терпеть неудачу. Как вариант, вы можете установить SYMFONY_DEPRECATIONS_HELPER в арбитражное значение (например, 320), что сделает ваши тесты неуспешными только, если будет достигнуто большее количество уведомлений об устаревании (0 - значение по умолчанию). Вы можете также установить значение "weak", которое заставит мост игнорировать все уведомления об устаревании. Это полезно для проктов. которые должны использовать устаревшие интерфейсы в связи с обратной совместимостью.

Когда вы содержите библиотеку, неудача пакета тестов при первом появлении устаревания в зависимости, нежелательно, так как это возлагает исправление этого устаревания на любого вкладчика, который отправляет запрос на включение вскоре после того, как с был сделан релиз поставщика с этим устареванием. Чтобы смягчить это, вы можете либо использвать более чёткие требования, надеясь, что зависимости не вызовут новых устареваний в версии патча, либо даже зафиксировать файл блокировки Composer, который будет создавать другой класс проблем. Поэтому библиотеки будут часто использовать SYMFONY_DEPRECATIONS_HELPER=weak. Это имеет недостаток в виде внесения вкладчиками своих устареваний, но:

  • забывает исправлять устаревшие вызовы, если они есть;
  • забывает отмечать соответствующие тесты аннотациями @group legacy.

При использовании значения "weak_vendors", устаревания, вызванные вне каталога vendors будут делать пакет тестов неуспешным, а устаревания, вызыванные в библиотеке внутри - не будут, что даст вам максимум преимуществ.

Отключение помощника устареваний

Установите переменную окружения SYMFONY_DEPRECATIONS_HELPER, как disabled, чтобы полностью отключить помощника устареваний. Это полезно для использования остальных функций, предоставляемых этим компонентом, не получая ошибок или сообщений, относящихся к устареваниям.

Напишите утверждения об устареваниях

При добавлении устареваний в ваш код, вам может захотеться писать тесты, проверяющие, чтобы они вызывались, как требуется. Чтобы сделать это, мост предоставляет аннотацию @expectedDeprecation, которую вы можете использовать в ваших методах тестов. Она требует, чтобы вы передали ожидаемое сообщение, данное в том же формате, что и для метода `PHPUnit assertStringMatchesFormat()`_. Если вы ожидаете более одного сообщения об устаревании для заданного метода теста, вы можете использовать аннотацию несколько раз (порядок имеет значение):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
/**
 * @group legacy
 * @expectedDeprecation This "%s" method is deprecated.
 * @expectedDeprecation The second argument of the "%s" method is deprecated.
 */
public function testDeprecatedCode()
{
    @trigger_error('This "Foo" method is deprecated.', E_USER_DEPRECATED);
    @trigger_error('The second argument of the "Bar" method is deprecated.', E_USER_DEPRECATED);
}

Тесты, чувствительные ко времени

Случаи применения

Если у вас есть такие тесты, чувствительные ко времени:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
use PHPUnit\Framework\TestCase;
use Symfony\Component\Stopwatch\Stopwatch;

class MyTest extends TestCase
{
    public function testSomething()
    {
        $stopwatch = new Stopwatch();

        $stopwatch->start('event_name');
        sleep(10);
        $duration = $stopwatch->stop('event_name')->getDuration();

        $this->assertEquals(10, $duration);
    }
}

Вы использвали Компонент Symfony Секундомер, чтобы подсчитать длительность вашего процесс, тут - 10 секунд. Однако, в зависимости от нагрузки на сервер или процессов, запущенных на вашей локальой машине, $duration может, к примеру, быть 10.000023s вместо`10s`.

Такие тесты называются переходными тестами: они терпят неудачи рандомно, в зависимости от побочных и внешних обстоятельств. Они часто вызывают проблемы при использовании постоянных публичных сервисов интеграции вроде Travis CI.

Имитация часов

Класс ClockMock, предоставленный этим мостом, позволяет вам имитировать встроенные PHP функции time(), microtime(), sleep() и usleep().

Чтобы использовать в вашем тесте класс ClockMock, добавьте аннотацию @group time-sensitive к его классу или методам. Эта аннотация работает только при выполнении PHPUnit, используя скрипт vendor/bin/simple-phpunit, или при регистрации следующего слушателя в вашей конфигурации PHPUnit:

1
2
3
4
5
<!-- phpunit.xml.dist -->
<!-- ... -->
<listeners>
    <listener class="\Symfony\Bridge\PhpUnit\SymfonyTestsListener" />
</listeners>

Note

Если вы не хотите использовать аннотацию @group time-sensitive, вы можете зарегистрировать класс ClockMock вручную, вызвав ClockMock::register(__CLASS__) и ClockMock::withClockMock(true) до теста, а ClockMock::withClockMock(false) - после.

В результате, следующее гарантировано будет работать, и больше не будет являться переходным тестом:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
use PHPUnit\Framework\TestCase;
use Symfony\Component\Stopwatch\Stopwatch;

/**
 * @group time-sensitive
 */
class MyTest extends TestCase
{
    public function testSomething()
    {
        $stopwatch = new Stopwatch();

        $stopwatch->start();
        sleep(10);
        $duration = $stopwatch->stop();

        $this->assertEquals(10, $duration);
    }
}

И это всё!

Tip

Дополнительный бонус использования класса ClockMock - время проходит моментально. Использование PHP sleep(10) заставит ваш тест ждать 10 настоящих секунд (плюс минус). В противовес этому, класс ClockMock переводит внуренние часы на заданное количество секунд, не ожидая это время на самом деле, поэтому ваш тест будет выполнен на 10 секунд быстрее.

Тесты, чувствительные к СДИ

Тесты, создающие соединения сети, например, проверка валидности записи СДИ (Системы доменных имён) может быть долгой в выполнении и ненадёжной вследствие условий сети. По этой причине, данный компонент также предоставляет имитации этих PHP функций:

Случаи применения

Рассмотрите следующий пример, который использует опцию checkMX ограничения Email, чтобы протестировать валидность домена электронной почты:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraints\Email;

class MyTest extends TestCase
{
    public function testEmail()
    {
        $validator = ...
        $constraint = new Email(array('checkMX' => true));

        $result = $validator->validate('[email protected]', $constraint);

        // ...
}

Чтобы избежать создания настоящего соединения сети, добавьте аннотацию @dns-sensitive к классу и используйте DnsMock::withMockedHosts(), чтобы сконфигурировать данные, которые вы ожидаете получить для заданных хостов:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
use PHPUnit\Framework\TestCase;
use Symfony\Component\Validator\Constraints\Email;

/**
 * @group dns-sensitive
 */
class MyTest extends TestCase
{
    public function testEmails()
    {
        DnsMock::withMockedHosts(array('example.com' => array(array('type' => 'MX'))));

        $validator = ...
        $constraint = new Email(array('checkMX' => true));

        $result = $validator->validate('[email protected]', $constraint);

        // ...
}

Конфигурация метода withMockedHosts() определяется в виде массива. Ключами являются сымитированные хосты, а значениями - массивы записей СДИ в том же формате, что возвращается 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 работают "по соглашению" и предполагают, что пространства имён тестируемого класса могут быть получены просто путём удаления части Tests\ из пространств имён тестов. Т.е., если полное имя класса вашего случая тестирования - App\Tests\Watch\DummyWatchTest, он предполагает, что пространство имён тестируемого класса - App\Watch.

Если это соглашение не работает для вашего приложение, сконфигурируйте имитацию пространств имён в файле phpunit.xml, как это делается, например, в HttpKernel Component:

 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 key="time-sensitive"><string>Symfony\Component\HttpFoundation</string></element>
                </array>
            </arguments>
        </listener>
    </listeners>
</phpunit>

Изменённый скрипт PHPUnit

New in version 3.2: Этот изменённый скрипт PHPUnit был представлен в версии 3.2 этого компонента.

Этот мост предоставляет изменённую версию PHPUnit, которую вы можете вызвать, используя его команду bin/simple-phpunit. Она имеет следующие функции:

  • Не встраивает symfony/yaml или prophecy, чтобы избежать конфликтов с этими зависимостями;
  • Использует PHPUnit 4.8 при запуске с PHP <=5.5,и PHPUnit 5.3 при запуске с PHP >=5.6;
  • Собирает и повторяет пропущенные тесты, когда определена переменная окружения SYMFONY_PHPUNIT_SKIPPED_TESTS: она должна указывать имя файла, который будет использован для хранения пропущенных тестов при первом запуске, и повторять их при повторном запуске;
  • Параллелит выполнение пакета тестов, когда каталог задан в качестве аргумента, сканируя этот каталог на предмет файлов phpunit.xml.dist до уровня SYMFONY_PHPUNIT_MAX_DEPTH (указан как переменная окружения, по умолчанию - 3);

Скрипт пишет изменённый PHPUnit, который он строит, в каталоге, который можно сконфигурировать с помощью SYMFONY_PHPUNIT_DIR, или в том же каталоге, что и simple-phpunit, если он не предоставлен.

Если вы установили мост через Composer, то вы можете запустить его, вызвав, к примеру:

1
$ vendor/bin/simple-phpunit

Tip

Установите переменную окружения SYMFONY_PHPUNIT_VERSION например, 5.5, чтобы изменить базовую версию PHPUnit на 5.5 вместо значения по умолчанию 5.3.

Tip

Если вам всё ещё надо использовать prophecy (но не symfony/yaml), то установите переменную окружения SYMFONY_PHPUNIT_REMOVE, как symfony/yaml.

Эта документация является переводом официальной документации Symfony и предоставляется по свободной лицензии CC BY-SA 3.0.