Spec-Zone.ru › PHPUnit

Обработка ошибок

Средство запуска тестов PHPUnit регистрирует обработчик ошибок и обрабатывает ошибки E_DEPRECATED, E_USER_DEPRECATED, E_NOTICE, E_USER_NOTICE, E_STRICT, E_WARNING и E_USER_WARNING. В оставшейся части этой главы мы будем использовать термин «проблемы» для обозначения ошибок E_DEPRECATED, E_USER_DEPRECATED, E_NOTICE, E_USER_NOTICE, E_STRICT, E_WARNING и E_USER_WARNING.

Обработчик ошибок активен только во время выполнения теста и обрабатывает только проблемы, вызванные кодом теста или кодом, вызываемым из кода теста. Он игнорирует проблемы, вызванные собственным кодом PHPUnit, а также кодом зависимостей PHPUnit.

Другие обработчики ошибок

Когда средство запуска тестов PHPUnit обнаруживает (после вызова set_error_handler() для регистрации своего обработчика ошибок), что зарегистрирован другой обработчик ошибок, оно немедленно снимает свой обработчик, чтобы ранее зарегистрированный обработчик остался активным. Поэтому функции, описанные в этой главе, недоступны при использовании собственного обработчика ошибок.

Ваш собственный обработчик ошибок должен соответствовать рекомендациям

Ваш собственный обработчик ошибок должен игнорировать ошибки, выданные кодом, за который он не отвечает, например кодом PHPUnit.

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

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

.
├── phpunit.xml
├── src
│   └── FirstPartyClass.php
├── tests
│   └── FirstPartyClassTest.php
└── vendor
    ├── autoload.php
    └── ThirdPartyClass.php

4 directories, 5 files
Пример 10.1 tests/FirstPartyClassTest.php
<?php declare(strict_types=1);
namespace example;

use PHPUnit\Framework\TestCase;
use vendor\ThirdPartyClass;

final class FirstPartyClassTest extends TestCase
{
    public function testOne(): void
    {
        $this->assertTrue((new FirstPartyClass)->method());
    }

    public function testTwo(): void
    {
        $this->assertTrue((new ThirdPartyClass)->anotherMethod());
    }
}
Пример 10.2 src/FirstPartyClass.php
<?php declare(strict_types=1);
namespace example;

use function trigger_error;
use vendor\ThirdPartyClass;

final class FirstPartyClass
{
    public function method(): true
    {
        (new ThirdPartyClass)->method();

        trigger_error('deprecation in first-party code', E_USER_DEPRECATED);

        return true;
    }
}
Пример 10.3 vendor/ThirdPartyClass.php
<?php declare(strict_types=1);
namespace vendor;

use example\FirstPartyClass;

final class ThirdPartyClass
{
    public function method(): void
    {
        trigger_error('deprecation in third-party code', E_USER_DEPRECATED);
    }

    public function anotherMethod(): true
    {
        return (new FirstPartyClass)->method();
    }
}
Пример 10.4 phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunitxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.1/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory=".phpunit.cache"
>
<testsuites>
<testsuitename="default">
<directory>tests</directory>
</testsuite>
</testsuites>
</phpunit>

Средство запуска тестов PHPUnit выводит D, N и W соответственно для тестов, выполняющих код, который вызывает проблему (D для устаревших функций, N для уведомлений и W для предупреждений).

Ниже показан вывод средства запуска тестов PHPUnit по умолчанию для приведённого выше примера:

$ ./tools/phpunit
PHPUnit 11.1.0 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.3.4
Configuration: /path/to/example/phpunit.xml

DD                                                                  1 / 1 (100%)

Time: 00:00.002, Memory: 8.00 MB

OK, but there were issues!
Tests: 2, Assertions: 2, Deprecations: 2.

Подробные сведения, например о том, какая проблема и где была вызвана, выводятся только при использовании --display-deprecations, --display-phpunit-deprecations, --display-phpunit-notices, --display-errors, --display-notices, --display-warnings или --display-all-issues:

$ ./tools/phpunit --display-deprecations
PHPUnit 11.1.0 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.3.4
Configuration: /path/to/example/phpunit.xml

DD                                                                  1 / 1 (100%)

Time: 00:00.002, Memory: 8.00 MB

2 tests triggered 2 deprecations:

1) /path/to/vendor/ThirdPartyClass.php:10
deprecation in third-party code

Triggered by:

* exampleFirstPartyClassTest::testOne
  /path/to/tests/FirstPartyClassTest.php:17

* exampleFirstPartyClassTest::testTwo
  /path/to/tests/FirstPartyClassTest.php:22

2) /path/to/src/FirstPartyClass.php:13
deprecation in first-party code

Triggered by:

* exampleFirstPartyClassTest::testOne
  /path/to/tests/FirstPartyClassTest.php:17

* exampleFirstPartyClassTest::testTwo
  /path/to/tests/FirstPartyClassTest.php:22

OK, but there were issues!
Tests: 2, Assertions: 2, Deprecations: 2.

Ограничение проблем вашим кодом

Распространённая проблема заключается в том, что зависимости в vendor вызывают предупреждения об устаревших функциях, уведомления или предупреждения, засоряющие вывод тестов. Отчёт о проблемах можно ограничить «вашим кодом», чтобы отображались только проблемы, исходящие из кода, за который вы отвечаете.

Сначала нужно настроить, что вы считаете «своим кодом», с помощью элемента <source> в XML-файле конфигурации (см. Элемент <source>). Затем можно использовать следующие атрибуты элемента <source> для фильтрации проблем:

  • ignoreIndirectDeprecations="true" игнорирует E_DEPRECATED и E_USER_DEPRECATED, вызванные сторонним кодом (например, кодом в vendor)

  • restrictNotices="true" игнорирует E_NOTICE, E_USER_NOTICE и E_STRICT, вызванные сторонним кодом

  • restrictWarnings="true" игнорирует E_WARNING и E_USER_WARNING, вызванные сторонним кодом

Ниже приведена конфигурация, которая сообщает только о проблемах в вашем собственном коде:

Пример 10.5 phpunit.xml
<?xml version="1.0" encoding="UTF-8"?>
<phpunitxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/11.1/phpunit.xsd"
bootstrap="vendor/autoload.php"
cacheDirectory=".phpunit.cache"
>
<testsuites>
<testsuitename="default">
<directory>tests</directory>
</testsuite>
</testsuites>

<sourceignoreIndirectDeprecations="true"
restrictNotices="true"
restrictWarnings="true">
<include>
<directory>src</directory>
</include>
</source>
</phpunit>

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

$ ./tools/phpunit --display-deprecations
PHPUnit 11.1.0 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.3.4
Configuration: /path/to/example/phpunit.xml

DD                                                                  1 / 1 (100%)

Time: 00:00.002, Memory: 8.00 MB

2 tests triggered 2 deprecations:

1) /path/to/vendor/ThirdPartyClass.php:10
deprecation in third-party code

Triggered by:

* exampleFirstPartyClassTest::testOne
  /path/to/tests/FirstPartyClassTest.php:17

* exampleFirstPartyClassTest::testTwo
  /path/to/tests/FirstPartyClassTest.php:22

2) /path/to/src/FirstPartyClass.php:13
deprecation in first-party code

Triggered by:

* exampleFirstPartyClassTest::testOne
  /path/to/tests/FirstPartyClassTest.php:17

OK, but there were issues!
Tests: 2, Assertions: 2, Deprecations: 2.

Как видно из приведённого выше вывода, об устаревших функциях, вызванных сторонним кодом в каталоге vendor, больше не сообщается.

Для настройки того, как PHPUnit использует сведения о том, какой код является вашим, можно применять следующие атрибуты элемента <source>:

  • Параметр Атрибут <ignoreSelfDeprecations> позволяет игнорировать устаревшие функции, вызванные кодом первой стороны в коде первой стороны

  • Параметр Атрибут <ignoreDirectDeprecations> позволяет игнорировать устаревшие функции, вызванные кодом первой стороны в стороннем коде

  • Параметр Атрибут <ignoreIndirectDeprecations> позволяет игнорировать устаревшие функции, вызванные сторонним кодом

  • Параметр Атрибут <restrictNotices> позволяет игнорировать уведомления в стороннем коде

  • Параметр Атрибут <restrictWarnings> позволяет игнорировать предупреждения в стороннем коде

Игнорирование подавления проблем

По умолчанию обработчик ошибок, зарегистрированный средством запуска тестов PHPUnit, учитывает оператор подавления (@). Это означает, что о проблемах, вызванных, например, с помощью @trigger_error(), принтеры хода выполнения и результатов по умолчанию не сообщают.

Подавление проблем с помощью оператора подавления (@) можно отключить параметрами в XML-файле конфигурации PHPUnit:

  • Параметр Атрибут <ignoreSuppressionOfDeprecations> позволяет игнорировать подавление проблем E_USER_DEPRECATED

  • Параметр Атрибут <ignoreSuppressionOfPhpDeprecations> позволяет игнорировать подавление проблем E_DEPRECATED

  • Параметр Атрибут <ignoreSuppressionOfNotices> позволяет игнорировать подавление проблем E_USER_NOTICES

  • Параметр Атрибут <ignoreSuppressionOfPhpNotices> позволяет игнорировать подавление проблем E_NOTICE и E_STRICT

  • Параметр Атрибут <ignoreSuppressionOfWarnings> позволяет игнорировать подавление проблем E_USER_WARNING

  • Параметр Атрибут <ignoreSuppressionOfPhpWarnings> позволяет игнорировать подавление проблем E_WARNING

Игнорирование ранее зарегистрированных проблем

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

При запуске набора тестов с параметром CLI --generate-baseline средство запуска тестов PHPUnit запишет список всех вызванных проблем в XML-файл:

$ phpunit --generate-baseline baseline.xml
PHPUnit 11.1.0 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.2.10
Configuration: /path/to/example/phpunit.xml

D                                                                   1 / 1 (100%)

Time: 00:00.008, Memory: 4.00 MB

OK, but there were issues!
Tests: 1, Assertions: 1, Deprecations: 1.

Baseline written to /path/to/example/baseline.xml.

При запуске набора тестов с параметром CLI --use-baseline (или если базовая линия настроена в XML-файле конфигурации PHPUnit с помощью параметра Атрибут <baseline>) средство запуска тестов PHPUnit использует этот список уже известных проблем, чтобы игнорировать их при текущем запуске:

$ phpunit --use-baseline baseline.xml
PHPUnit 11.1.0 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.2.10
Configuration: /path/to/example/phpunit.xml

.                                                                   1 / 1 (100%)

Time: 00:00.007, Memory: 4.00 MB

OK (1 test, 1 assertion)

2 issues were ignored by baseline.

Ожидание устаревших функций (E_USER_DEPRECATED)

Метод expectUserDeprecationMessage() позволяет задать ожидание возникновения проблемы E_USER_DEPRECATED с указанным сообщением.

Пример 10.6 Использование expectUserDeprecationMessage()
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class DeprecationExpectationTest extends TestCase
{
    public function testFailure(): void
    {
        $this->expectUserDeprecationMessage('the-deprecation-message');
    }
}

Запуск приведённого выше теста даёт показанный ниже результат:

./tools/phpunit tests/DeprecationExpectationTest.php
PHPUnit 13.2.0 by Sebastian Bergmann and contributors.

Runtime:       PHP 8.5.7

F                                                                   1 / 1 (100%)

Time: 00:00, Memory: 28.00 MB

There was 1 failure:

1) DeprecationExpectationTest::testFailure
Expected deprecation with message "the-deprecation-message" was not triggered

FAILURES!
Tests: 1, Assertions: 1, Failures: 1.

Кроме того, можно использовать $this->expectUserDeprecationMessageMatches(), чтобы задать ожидание возникновения проблемы E_USER_DEPRECATED, сообщение об устаревшей функции которой соответствует указанному регулярному выражению.

Это можно использовать вместе с атрибутом #[IgnoreDeprecations], чтобы тест не завершился с ошибкой.

Тестирование устаревшей функциональности

Если вы пометили функциональность в своём коде как устаревшую, тесты для этого кода следует сохранить до его фактического удаления. Используйте атрибут #[IgnoreDeprecations] вместе с expectUserDeprecationMessage() в тестах, непосредственно проверяющих устаревшую функциональность. Это гарантирует, что устаревший код по-прежнему работает ожидаемым образом, ожидаемое сообщение об устаревшей функции проверяется, а тест не считается вызвавшим устаревшую функцию.

Пользовательские средства определения источника проблем

Элемент <deprecationTrigger> (см. Элемент <deprecationTrigger>) позволяет настроить функции и методы, выступающие обёртками для trigger_error(), однако некоторым фреймворкам требуется более сложная логика для определения правильного вызывающего и вызываемого кода при классификации проблем. Для таких случаев PHPUnit поддерживает пользовательские средства определения источника проблем.

Пользовательское средство определения источника проблем — это класс, реализующий интерфейс PHPUnit\Runner\IssueTriggerResolver\Resolver:

<?php declare(strict_types=1);
namespace PHPUnit\Runner\IssueTriggerResolver;

interface Resolver
{
    /**
     * Return null to defer to the next resolver in the chain.
     *
     * @param list<array{file?: string, line?: int, class?: class-string, function?: string, type?: string, args?: list<mixed>}> $trace
     */
    public function resolve(array $trace, string $message): ?Resolution;
}

Метод resolve() получает отфильтрованную трассировку стека и сообщение об ошибке. Он должен возвращать одно из следующего:

  • Объект PHPUnit\Runner\IssueTriggerResolver\Resolution, указывающий пути к файлам вызываемого и вызывающего кода

  • null, чтобы передать обработку следующему средству определения в цепочке

Класс Resolution создаётся с двумя аргументами типа string, допускающими значение null: путём к файлу вызываемого кода и путём к файлу вызывающего кода:

new Resolution(
    $trace[1]['file'] ?? null, // callee (where the issue originated)
    $trace[2]['file'] ?? null, // caller (what called the code that triggered the issue)
);

PHPUnit использует эти пути к файлам, чтобы классифицировать проблему как self, direct, indirect или test, проверяя, относится ли каждый файл к коду первой стороны, стороннему коду, тестовому коду или самому PHPUnit.

Реализация пользовательского средства определения

Рассмотрим фреймворк, который оборачивает trigger_error() собственным методом:

namespace Vendor;

final class Framework
{
    public function trigger(): void
    {
        @trigger_error('framework deprecation', E_USER_DEPRECATED);
    }
}

Без пользовательского средства определения стандартное средство определения PHPUnit использует $trace[0] в качестве вызываемого кода (файла, содержащего trigger_error()), а $trace[1] — в качестве вызывающего кода. Если метод фреймворка находится в стеке вызовов, это означает, что вызываемым кодом будет файл фреймворка, а вызывающим — код первой стороны, который его вызывает, что может не соответствовать предполагаемой классификации.

Пользовательское средство определения может анализировать трассировку стека и соответствующим образом скорректировать вызывающий и вызываемый код:

<?php declare(strict_types=1);
namespace Vendor;

use PHPUnit\Runner\IssueTriggerResolver\Resolution;
use PHPUnit\Runner\IssueTriggerResolver\Resolver;

final class FrameworkResolver implements Resolver
{
    public function resolve(array $trace, string $message): ?Resolution
    {
        if (isset($trace[0]['file']) && str_contains($trace[0]['file'], 'Framework.php')) {
            return new Resolution(
                $trace[1]['file'] ?? null,
                $trace[2]['file'] ?? null,
            );
        }

        return null;
    }
}

Это средство проверяет, относится ли первый кадр трассировки стека к фреймворку. Если да, оно смещает вызывающий и вызываемый код на один кадр, пропуская обёртку фреймворка. Если условие не выполняется, оно возвращает null, чтобы передать обработку проблемы следующему средству определения (или стандартному средству определения).

Регистрация пользовательского средства определения

Пользовательские средства определения регистрируются в XML-файле конфигурации PHPUnit с помощью элемента <issueTriggerResolvers> внутри <source> (см. Элемент <issueTriggerResolvers>):

<source>
<include>
<directory>src</directory>
</include>

<issueTriggerResolvers>
<issueTriggerResolverclassName="Vendor\FrameworkResolver"/>
</issueTriggerResolvers>
</source>

Можно зарегистрировать несколько средств определения; они вызываются в указанном порядке. Используется первое средство, вернувшее объект Resolution. Если все пользовательские средства определения возвращают null, PHPUnit использует стандартное средство определения.

Отключение обработчика ошибок PHPUnit

Если вы хотите протестировать собственный обработчик ошибок или проверить, что тестируемый модуль кода вызывает ожидаемую проблему, обработчик ошибок, зарегистрированный средством запуска тестов PHPUnit, будет мешать достижению этой цели.

В таких случаях атрибут #[WithoutErrorHandler] позволяет отключить обработчик ошибок PHPUnit для метода теста.

© 2005–2026 Sebastian Bergmann
Licensed under the Creative Commons Attribution 3.0 Unported License.
https://docs.phpunit.de/en/13.2/error-handling.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API