Фикстуры
Одной из самых трудоёмких частей написания тестов является написание кода для подготовки окружения в известном состоянии и последующего возвращения его в исходное состояние по завершении теста. Это известное состояние называется фикстурой теста.
В Тестирование операций с массивами с помощью PHPUnit фикстурой был массив, хранящийся в переменной $stack. Однако в большинстве случаев фикстура будет более сложной, чем простой массив, и объём кода, необходимого для её настройки, соответственно увеличится. Фактическое содержимое теста теряется в шуме настройки фикстуры. Эта проблема усугубляется при написании нескольких тестов с похожими фикстурами. Без помощи фреймворка для тестирования нам пришлось бы дублировать код настройки фикстуры для каждого написанного теста.
PHPUnit поддерживает совместное использование кода настройки. Перед выполнением метода теста вызывается шаблонный метод setUp(). В setUp() создаются объекты, по отношению к которым будет проводиться тестирование. После завершения выполнения метода теста, независимо от его успеха или неудачи, вызывается другой шаблонный метод tearDown(). В tearDown() происходит очистка объектов, по отношению к которым проводилось тестирование.
В Использование аннотации @depends для выражения зависимостей мы использовали отношения «производитель-потребитель» между тестами для совместного использования фикстуры. Это не всегда желательно или возможно. Пример 4.1 демонстрирует, как мы можем написать тесты для StackTest таким образом, чтобы не сама фикстура повторно использовалась, а код, её создающий. Сначала мы объявляем переменную экземпляра $stack, которую будем использовать вместо переменной внутри метода. Затем мы помещаем создание фикстуры array в метод setUp(). Наконец, мы удаляем дублирующийся код из методов тестов и используем недавно введённую переменную экземпляра $this->stack, вместо переменной внутри метода $stack, с методом проверки assertSame().
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class StackTest extends TestCase
{
private $stack;
protected function setUp(): void
{
$this->stack = [];
}
public function testEmpty(): void
{
$this->assertTrue(empty($this->stack));
}
public function testPush(): void
{
array_push($this->stack, 'foo');
$this->assertSame('foo', $this->stack[count($this->stack)-1]);
$this->assertFalse(empty($this->stack));
}
public function testPop(): void
{
array_push($this->stack, 'foo');
$this->assertSame('foo', array_pop($this->stack));
$this->assertTrue(empty($this->stack));
}
}
Шаблонные методы setUp() и tearDown() выполняются один раз для каждого метода теста (и для новых экземпляров) класса тестирования.
Кроме того, шаблонные методы setUpBeforeClass() и tearDownAfterClass() вызываются перед выполнением первого теста класса тестирования и после выполнения последнего теста класса тестирования соответственно.
Пример ниже демонстрирует все шаблонные методы, доступные в классе теста.
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
final class TemplateMethodsTest extends TestCase
{
public static function setUpBeforeClass(): void
{
fwrite(STDOUT, __METHOD__ . "\n");
}
protected function setUp(): void
{
fwrite(STDOUT, __METHOD__ . "\n");
}
protected function assertPreConditions(): void
{
fwrite(STDOUT, __METHOD__ . "\n");
}
public function testOne(): void
{
fwrite(STDOUT, __METHOD__ . "\n");
$this->assertTrue(true);
}
public function testTwo(): void
{
fwrite(STDOUT, __METHOD__ . "\n");
$this->assertTrue(false);
}
protected function assertPostConditions(): void
{
fwrite(STDOUT, __METHOD__ . "\n");
}
protected function tearDown(): void
{
fwrite(STDOUT, __METHOD__ . "\n");
}
public static function tearDownAfterClass(): void
{
fwrite(STDOUT, __METHOD__ . "\n");
}
protected function onNotSuccessfulTest(Throwable $t): void
{
fwrite(STDOUT, __METHOD__ . "\n");
throw $t;
}
}
$ phpunit TemplateMethodsTest PHPUnit 8.5.0 by Sebastian Bergmann and contributors. TemplateMethodsTest::setUpBeforeClass TemplateMethodsTest::setUp TemplateMethodsTest::assertPreConditions TemplateMethodsTest::testOne TemplateMethodsTest::assertPostConditions TemplateMethodsTest::tearDown .TemplateMethodsTest::setUp TemplateMethodsTest::assertPreConditions TemplateMethodsTest::testTwo TemplateMethodsTest::tearDown TemplateMethodsTest::onNotSuccessfulTest FTemplateMethodsTest::tearDownAfterClass Time: 0 seconds, Memory: 5.25Mb There was 1 failure: 1) TemplateMethodsTest::testTwo Failed asserting that <boolean:false> is true. /home/sb/TemplateMethodsTest.php:30 FAILURES! Tests: 2, Assertions: 2, Failures: 1.
Более setUp() чем tearDown()
setUp() и tearDown() теоретически симметричны, но на практике нет. На практике вам нужно реализовать только tearDown(), если вы выделили внешние ресурсы, такие как файлы или сокеты, в setUp(). Если ваша setUp() просто создаёт обычные объекты PHP, вы, как правило, можете игнорировать tearDown(). Однако, если вы создаёте множество объектов в вашей setUp(), вы можете захотеть unset() переменные, указывающие на эти объекты, в вашей tearDown(), чтобы они могли быть удалены сборщиком мусора. Удаление сборщиком мусора объектов класса тестирования непредсказуемо.
Варианты
Что происходит, когда у вас есть два теста с немного отличающимися настройками? Существует два варианта:
- Если код
setUp()отличается незначительно, перенесите код, отличающийся от кодаsetUp()в метод теста. - Если у вас действительно другая
setUp(), вам нужен другой класс теста. Назовите класс в соответствии с отличием в настройке.
Разделяемая фикстура
Существует несколько веских причин для совместного использования фикстур между тестами, но в большинстве случаев необходимость совместного использования фикстуры между тестами происходит от нерешённой проблемы проектирования.
Хороший пример фикстуры, которую имеет смысл разделять между несколькими тестами, — это подключение к базе данных: вы подключаетесь к базе данных один раз и повторно используете соединение, вместо создания нового соединения для каждого теста. Это ускоряет выполнение ваших тестов.
Пример 4.3 использует шаблонные методы setUpBeforeClass() и tearDownAfterClass() для подключения к базе данных перед первым тестом класса тестирования и для отключения от базы данных после последнего теста класса тестирования соответственно.
<?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;
}
}
Нельзя достаточно подчеркнуть, что совместное использование фикстур между тестами снижает ценность тестов. Основная проблема заключается в том, что объекты не являются слабо связанными. Вы добьётесь лучших результатов, решив основную проблему проектирования и затем написав тесты с использованием заглушек (см. Заглушки), чем путём создания зависимостей между тестами во время выполнения и игнорирования возможности улучшить свой дизайн.
Глобальное состояние
Сложно тестировать код, использующий синглтоны. То же самое верно и для кода, использующего глобальные переменные. Обычно код, который вы хотите протестировать, сильно связан с глобальной переменной, и вы не можете контролировать ее создание. Дополнительная проблема заключается в том, что изменения одной проверки в глобальной переменной могут сломать другую проверку.
В PHP глобальные переменные работают так:
- Глобальная переменная
$foo = 'bar';хранится как$GLOBALS['foo'] = 'bar';. - Переменная
$GLOBALS— это так называемая суперглобальная переменная. - Суперглобальные переменные — это встроенные переменные, которые всегда доступны во всех областях видимости.
- В области видимости функции или метода вы можете получить доступ к глобальной переменной
$foo, либо напрямую обратившись к$GLOBALS['foo'], либо используяglobal $foo;, чтобы создать локальную переменную со ссылкой на глобальную переменную.
Помимо глобальных переменных, статические атрибуты классов также являются частью глобального состояния.
До версии 6 по умолчанию PHPUnit запускал ваши тесты таким образом, что изменения глобальных и суперглобальных переменных ($GLOBALS, $_ENV, $_POST, $_GET, $_COOKIE, $_SERVER, $_FILES, $_REQUEST ) не влияли на другие тесты.
Начиная с версии 6, PHPUnit больше не выполняет эту операцию резервного копирования и восстановления для глобальных и суперглобальных переменных по умолчанию. Она может быть активирована с помощью параметра --globals-backup или установки backupGlobals="true" в файле конфигурации XML.
Используя параметр --static-backup или устанавливая backupStaticAttributes="true" в файле конфигурации XML, это изолирование может быть расширено до статических атрибутов классов.
Примечание
Операции резервного копирования и восстановления для глобальных переменных и статических атрибутов классов используют serialize() и unserialize().
Объекты некоторых классов (например, PDO) не могут быть сериализованы, и операция резервного копирования будет прервана, когда такой объект хранится, например, в массиве $GLOBALS.
Аннотация @backupGlobals, о которой идет речь в @backupGlobals, может использоваться для управления операциями резервного копирования и восстановления для глобальных переменных. В качестве альтернативы, вы можете предоставить список глобальных переменных, которые следует исключить из операций резервного копирования и восстановления, например так
final class MyTest extends TestCase
{
protected $backupGlobalsBlacklist = ['globalVariable'];
// ...
}
Примечание
Установка свойства $backupGlobalsBlacklist внутри, например, метода setUp() не оказывает никакого эффекта.
Аннотация @backupStaticAttributes, обсуждаемая в @backupStaticAttributes, может использоваться для резервного копирования всех значений статических свойств во всех объявленных классах перед каждым тестом и восстановления их после.
Обрабатываются все классы, объявленные в момент запуска теста, а не только сам класс теста. Она применяется только к статическим свойствам класса, а не к статическим переменным внутри функций.
Примечание
Операция @backupStaticAttributes выполняется перед методом теста, но только если она включена. Если статическое значение было изменено ранее выполненным тестом, в котором @backupStaticAttributes не был включен, то это значение будет скопировано и восстановлено — а не изначально объявленное значение по умолчанию. PHP не записывает изначально объявленное значение по умолчанию для любой статической переменной.
То же самое относится к статическим свойствам классов, которые были недавно загружены/объявлены внутри теста. Они не могут быть сброшены до их первоначально объявленного значения по умолчанию после теста, так как это значение неизвестно. Любое значение, которое устанавливается, попадет в последующие тесты.
Для юнит-тестов рекомендуется явно сбрасывать значения тестируемых статических свойств в вашем коде setUp() (и желательно также tearDown(), чтобы не влиять на последующие выполняемые тесты).
Вы можете предоставить список статических атрибутов, которые нужно исключить из операций резервного копирования и восстановления:
final class MyTest extends TestCase
{
protected $backupStaticAttributesBlacklist = [
'className' => ['attributeName']
];
// ...
}
Примечание
Установка свойства $backupStaticAttributesBlacklist внутри, например, метода setUp() не оказывает никакого эффекта.
© 2005–2020 Sebastian Bergmann
Licensed under the Creative Commons Attribution 3.0 Unported License.
https://phpunit.readthedocs.io/en/8.5/fixtures.html