Spec-Zone.ru › PHPUnit

Фикстуры

Тест обычно следует структуре «Подготовительный этап, Действие, Проверка»: подготовительный этап включает все необходимые предварительные условия и входные данные (так называемая фикстура теста), действие выполняется над объектом, который тестируется, а проверка подтверждает, что ожидаемые результаты произошли.

Подготовительный этап, Ожидание, Действие

Когда ожидается, что действие вызовет исключение, или когда необходимо проверить взаимодействие между сотрудничающими объектами с использованием эмулированных объектов, тогда тест обычно следует структуре «Подготовительный этап, Ожидание, Действие».

Иногда фикстура теста состоит из одного объекта, а иногда — из более сложной графы объектов. Соответственно, объем кода, необходимый для её настройки, будет увеличиваться. Фактическое содержание теста теряется в шуме настройки фикстуры. Эта проблема усугубляется, когда вы пишете несколько тестов с похожими фикстурами.

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

Пример 5.1 Пример класса теста, использующего setUp() и tearDown()
<?php declare(strict_types=1);
namespace example;

use PHPUnit\Framework\TestCase;

final class ExampleTest extends TestCase
{
    private ?Example $example;

    public function testSomething(): void
    {
        $this->assertSame(
            'the-result',
            $this->example->doSomething()
        );
    }

    protected function setUp(): void
    {
        $this->example = new Example(
            $this->createStub(Collaborator::class)
        );
    }

    protected function tearDown(): void
    {
        $this->example = null;
    }
}

Шаблонные методы setUp() и tearDown() выполняются один раз для каждого метода теста (и для новых экземпляров) класса тестового случая.

Одна из проблем с шаблонными методами setUp() и tearDown() заключается в том, что они вызываются даже для тестов, не использующих фикстуру теста, управляемую этими методами. В приведённом выше примере это свойство $this->example.

Другая проблема может возникнуть, когда речь идёт об наследовании:

Пример 5.2 Пример абстрактного класса тестового случая с методом setUp()
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;

abstract class MyTestCase extends TestCase
{
    protected function setUp(): void
    {
        // ...
    }
}
Пример 5.3 Пример конкретного класса тестового случая, расширяющего абстрактный класс тестового случая с методом setUp()
<?php declare(strict_types=1);
namespace example;

use PHPUnit\Framework\TestCase;

final class ExampleTest extends MyTestCase
{
    protected function setUp(): void
    {
        // ...
    }
}

Если мы забудем вызвать parent::setUp() при реализации ExampleTest::setUp(), функциональность, предоставляемая MyTestCase, не будет работать. Чтобы снизить этот риск, доступны атрибуты PHPUnit\Framework\Attributes\Before и PHPUnit\Framework\Attributes\After. С их помощью можно настроить вызов нескольких методов до и после теста соответственно.

Больше setUp(), чем tearDown()

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

Однако, если вы создаёте большие графы объектов в setUp() и сохраняете их в свойствах объекта теста, вам может потребоваться unset() переменные, содержащие эти объекты, в вашей tearDown(), чтобы они могли быть быстрее удалены сборщиком мусора.

Объекты, созданные в setUp() (или методах тестов), которые хранятся в свойствах объекта теста, автоматически удаляются сборщиком мусора только в конце процесса PHP, в котором выполняется PHPUnit.

Обмен фикстурой

Существует мало веских причин для обмена фикстурами между тестами, но в большинстве случаев необходимость в обмене фикстурой между тестами связана с нерешённой проблемой проектирования.

Хороший пример фикстуры, которую имеет смысл использовать в нескольких тестах, — это подключение к базе данных: вы подключаетесь к базе данных один раз и используете подключение к базе данных вместо создания нового подключения для каждого теста. Это ускоряет выполнение тестов.

Шаблонные методы setUpBeforeClass() и tearDownAfterClass() вызываются соответственно перед первым тестом класса тестового случая и после последнего теста класса тестового случая.

Не используйте проверки в setUpBeforeClass() или tearDownAfterClass()

Обратите внимание, что использование проверок в методах setUpBeforeClass() или tearDownAfterClass() приводит к неопределённому поведению. Единственная причина, по которой использование проверок в этих шаблонных методах не приводит к ошибке, выводимой исполнителем тестов, заключается в том, что мы хотим избежать временных затрат на определение того, вызывается ли метод проверки из этих шаблонных методов.

Пример 5.4 использует шаблонные методы setUpBeforeClass() и tearDownAfterClass() для подключения к базе данных перед первым тестом класса тестового случая и для отключения от базы данных после последнего теста, соответственно.

Пример 5.4 Обмен фикстурой между тестами набора тестов
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;

final class DatabaseTest extends TestCase
{
    private static $dbh;

    public static function setUpBeforeClass(): void
    {
        self::$dbh = new PDO('sqlite::memory:');
    }

    public static function tearDownAfterClass(): void
    {
        self::$dbh = null;
    }
}

Нельзя достаточно подчеркнуть, что обмен фикстурами между тестами снижает ценность тестов. Основная проблема в том, что объекты слабо связаны. Вы получите лучшие результаты, решив основную проблему проектирования, а затем написав тесты с использованием заменителей (см. Замещающие объекты), чем создавая зависимости между тестами во время выполнения и игнорируя возможность улучшить свой дизайн.

END_OF_DOCUMENT_MARKER

Глобальное состояние

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

В PHP глобальные переменные работают так:

  • Глобальная переменная $foo = 'bar'; хранится как $GLOBALS['foo'] = 'bar';.

  • Переменная $GLOBALS — это так называемая суперглобальная переменная.

  • Суперглобальные переменные — это встроенные переменные, которые всегда доступны во всех областях видимости.

  • В области видимости функции или метода вы можете получить доступ к глобальной переменной $foo, либо напрямую обратившись к $GLOBALS['foo'], либо используя global $foo; для создания локальной переменной со ссылкой на глобальную переменную.

Помимо глобальных переменных, статические свойства классов также являются частью глобального состояния.

PHPUnit может по желанию запускать ваши тесты таким образом, чтобы изменения глобальных и суперглобальных переменных ($GLOBALS, $_ENV, $_POST, $_GET, $_COOKIE, $_SERVER, $_FILES, $_REQUEST) не влияли на другие тесты. Вы можете активировать это поведение, используя опцию --globals-backup или задав значение backupGlobals="true" в файле конфигурации XML.

Используя опцию --static-backup или задав значение backupStaticProperties="true" в файле конфигурации XML, это изолирование может быть расширено до статических свойств классов.

Примечание

Операции резервного копирования и восстановления для глобальных переменных и статических свойств классов используют serialize() и unserialize().

Объекты некоторых классов (например, PDO) не могут быть сериализованы, и операция резервного копирования завершится ошибкой, когда такой объект хранится, например, в массиве $GLOBALS.

Атрибут PHPUnit\Framework\Attributes\BackupGlobals можно использовать для управления операциями резервного копирования и восстановления для глобальных переменных.

Атрибут PHPUnit\Framework\Attributes\ExcludeGlobalVariableFromBackup можно использовать для исключения определенных глобальных переменных из операций резервного копирования и восстановления для глобальных переменных.

Атрибут PHPUnit\Framework\Attributes\BackupStaticProperties можно использовать для управления операциями резервного копирования и восстановления для статических свойств классов. Это влияет на все статические свойства во всех объявленных классах перед каждым тестом и восстанавливает их после него. Обрабатываются все классы, объявленные на момент запуска теста, а не только сам класс теста. Это применяется только к статическим свойствам классов, а не к статическим переменным внутри функций.

Атрибут PHPUnit\Framework\Attributes\ExcludeStaticPropertyFromBackup можно использовать для исключения определенных статических свойств из операций резервного копирования и восстановления для статических свойств.

Примечание

Операция резервного копирования для статических свойств классов выполняется перед методом теста, но только если она включена. Если статическое значение было изменено ранее выполненным тестом, у которого не было BackupStaticProperties(true), то это значение будет скопировано и восстановлено — а не исходное значение по умолчанию.

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

Для юнит-тестов рекомендуется явно сбрасывать значения статических свойств, которые тестируются, в вашем коде setUp() (и желательно также tearDown()), чтобы не влиять на последующие выполняемые тесты.

© 2005–2025 Sebastian Bergmann
Licensed under the Creative Commons Attribution 3.0 Unported License.
https://docs.phpunit.de/en/12.0/fixtures.html

Spec-Zone.ru

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