В настоящее время я работаю с 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/
Файл bootstrap вызывается перед всем остальным, поэтому заголовок не отправляется, и все должно работать нормально.
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; } }
-<?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. Вы можете взглянуть на их тестовые примеры, чтобы начать работу.
Создание файла bootstrap, указывающего на 4 сообщения назад, кажется самым чистым способом.
Часто с PHP мы должны поддерживать и пытаться добавить какую-то инженерную дисциплину к старым проектам, которые ужасно сведены вместе. У нас нет времени (или авторитета), чтобы вырвать всю груду мусора и начать снова, поэтому первый ответ от troelskn не всегда возможен, как способ продвижения вперед. (Если бы мы могли вернуться к первоначальному дизайну, то мы могли бы полностью протолкнуть PHP и использовать что-то более современное, например ruby или python, а не помогать увековечить этот COBOL в мире веб-разработки.)
Если вы пытаетесь написать модульные тесты для модулей, которые используют session_start или setcookie на всем протяжении, чем запуск сеанса в файле boostrap, вы можете обойти эти проблемы.
Поскольку я сейчас не хочу запускать мой загрузочный блок (да, я знаю, что большинство из вас этого не делает), я столкнулся с той же проблемой (как header (), так и session_start ()). Решение, которое я нашел, довольно просто, в вашем unittest bootstrap определите константу и просто проверьте ее перед отправкой заголовка или началом сеанса:
// 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.