Модульное тестирование с элементами, которым необходимо отправлять заголовки
-
06-07-2019 - |
Вопрос
В настоящее время я работаю с PHPUnit, чтобы попытаться разработать тесты наряду с тем, что я пишу, однако в настоящее время я работаю над написанием менеджера сеансов, и у меня возникают проблемы при этом...
Конструктором для класса обработки сеанса является
private function __construct()
{
if (!headers_sent())
{
session_start();
self::$session_id = session_id();
}
}
Однако, поскольку PHPUnit отправляет текст перед началом тестирования, любое тестирование этого объекта возвращает неудачный тест, поскольку были отправлены HTTP-"Заголовки"...
Решение
Ну, ваш менеджер сессий в основном не работает. Чтобы иметь возможность что-то тестировать, должна быть возможность изолировать это от побочных эффектов. К сожалению, PHP разработан таким образом, что он поощряет свободное использование глобального состояния ( echo
, header
, exit
, session_start
и т. д.).
Лучшее, что вы можете сделать, - это выделить побочные эффекты в компоненте, которые можно поменять местами во время выполнения. Таким образом, ваши тесты могут использовать фиктивные объекты, в то время как живой код использует адаптеры, которые имеют реальные побочные эффекты. Вы обнаружите, что это плохо работает с синглетами, которые, я полагаю, вы используете. Поэтому вам придется использовать какой-то другой механизм для распределения общих объектов в вашем коде. Вы можете начать со статического реестра, но есть и лучшие решения, если вы не возражаете против изучения.
Если вы не можете этого сделать, у вас всегда есть возможность написать интеграционные тесты. Например. используйте PHPUnit-эквивалент WebTestCase
.
Другие советы
Создайте файл начальной загрузки для phpunit, который вызывает:
session_start();
Затем запустите phpunit следующим образом:
phpunit --bootstrap pathToBootstrap.php --anotherSwitch /your/test/path/
Файл начальной загрузки вызывается раньше всего, поэтому заголовок не был отправлен, и все должно работать нормально.
phpUnit печатает выходные данные во время выполнения тестов, поэтому headers_sent () возвращает true даже в первом тесте.
Чтобы преодолеть эту проблему для всего набора тестов, вам просто нужно использовать ob_start () в вашем скрипте установки.
Например, допустим, у вас есть файл с именем AllTests.php, который первым загружает phpUnit. Этот скрипт может выглядеть следующим образом:
<?php
ob_start();
require_once 'YourFramework/AllTests.php';
class AllTests {
public static function suite() {
$suite = new PHPUnit_Framework_TestSuite('YourFramework');
$suite->addTest(YourFramework_AllTests::suite());
return $suite;
}
}
У меня была та же проблема, и я решил ее, вызвав phpunit с флагом --stderr, вот так:
phpunit --stderr /path/to/your/test
Надеюсь, это кому-нибудь поможет!
Я думаю, что это "правильно" Решение состоит в том, чтобы создать очень простой класс (настолько простой, что его не нужно тестировать), который является оболочкой для функций, связанных с сессиями PHP, и использовать его вместо непосредственного вызова session_start ()
и т. д. . р>
В тестовом прохождении фиктивный объект вместо реального, не проверяемого на уровне класса сеансового класса.
private function __construct(SessionWrapper $wrapper)
{
if (!$wrapper->headers_sent())
{
$wrapper->session_start();
$this->session_id = $wrapper->session_id();
}
}
Мне интересно, почему никто не указал параметр XDebug:
/**
* @runInSeparateProcess
* @requires extension xdebug
*/
public function testGivenHeaderIsIncludedIntoResponse()
{
$customHeaderName = 'foo';
$customHeaderValue = 'bar';
// Here execute the code which is supposed to set headers
// ...
$expectedHeader = $customHeaderName . ': ' . $customHeaderValue;
$headers = xdebug_get_headers();
$this->assertContains($expectedHeader, $headers);
}
Не можете ли вы использовать буферизацию вывода перед началом теста? Если вы буферизируете все, что выводится, у вас не должно возникнуть проблем с установкой каких-либо заголовков, так как в этот момент клиенту не было бы отправлено никакого вывода.
Даже если OB используется где-то внутри ваших классов, он является наращиваемым, и OB не должен влиять на то, что происходит внутри.
Насколько я знаю, Zend Framework использует ту же буферизацию вывода для своих тестов пакетов Zend_Session. Вы можете ознакомиться с их тестовыми примерами, чтобы начать работу.
Создание файла начальной загрузки, о котором говорилось 4 сообщения назад, кажется самым чистым способом решения этой проблемы.
Часто с PHP нам приходится поддерживать и пытаться добавить какую-то инженерную дисциплину к устаревшим проектам, которые плохо собраны вместе.У нас нет времени (или полномочий) выбрасывать всю кучу мусора и начинать все сначала, поэтому первый ответ от troelskn не всегда возможен как путь вперед.(Если бы мы могли вернуться к первоначальному дизайну, тогда мы могли бы вообще отказаться от PHP и использовать что-то более современное, такое как ruby или python, вместо того, чтобы помогать увековечивать этот COBOL в мире веб-разработки.)
Если вы пытаетесь написать модульные тесты для модулей, которые используют session_start или setcookie, запуск сеанса в файле boostrap устраняет эти проблемы.
Поскольку я сейчас тестирую мой загрузчик (да, я знаю, что большинство из вас этого не делают), я столкнулся с той же проблемой (и header (), и session_start ()). Решение, которое я нашел, довольно простое: в вашем тестовом модуле unittest определите константу и просто проверьте ее перед отправкой заголовка или началом сеанса:
// phpunit_bootstrap.php
define('UNITTEST_RUNNING', true);
// bootstrap.php (application bootstrap)
defined('UNITTEST_RUNNING') || define('UNITTEST_RUNNING', false);
.....
if(UNITTEST_RUNNING===false){
session_start();
}
Я согласен, что это не идеально по дизайну, но я тестирую существующее приложение, переписывать большие части нежелательно. Я также использовал ту же логику для тестирования закрытых методов, используя магические методы __call () и __set ().
public function __set($name, $value){
if(UNITTEST_RUNNING===true){
$name='_' . $name;
$this->$name=$value;
}
throw new Exception('__set() can only be used when unittesting!');
}
Похоже, вам нужно внедрить сеанс, чтобы вы могли проверить свой код. Лучшим вариантом, который я использовал, является Aura.Auth для процесса аутентификации и использования NullSession и NullSegment для тестирования. Р>
Тестирование ауры с нулевыми сеансами
Фреймворк Aura прекрасно написан, и вы можете использовать Aura.Auth самостоятельно, без каких-либо других зависимостей фреймворка Aura.