Модульное тестирование с элементами, которым необходимо отправлять заголовки

В настоящее время я работаю с PHPUnit, чтобы попытаться разработать тесты вместе с тем, что я пишу, однако в настоящее время я работаю над написанием диспетчера сеансов, и у меня возникают проблемы с этим...

Конструктор класса обработки сеанса:

private function __construct()
{
    if (!headers_sent())
    {
        session_start();
        self::$session_id = session_id();
    }
}

Однако, поскольку PHPUnit отправляет текст перед началом тестирования, любое тестирование этого объекта возвращает неудачный тест, поскольку HTTP-заголовки были отправлены...


person Mez    schedule 10.10.2008    source источник
comment
Я только что столкнулся с той же проблемой, но ни один из приведенных ниже ответов не работает. Что ты сделал в итоге? Пожалуйста, напишите мне на электронную почту.   -  person Nicholas Shanks    schedule 17.07.2013


Ответы (11)


Ну, ваш менеджер сеансов в основном сломан по дизайну. Чтобы иметь возможность что-то протестировать, необходимо изолировать это от побочных эффектов. К сожалению, PHP устроен таким образом, что поощряет либеральное использование глобального состояния (echo, header, exit, session_start и т. д. и т. д.).

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

Если вы не можете этого сделать, у вас всегда есть возможность написать интеграционные тесты. Например. используйте эквивалент PHPUnit WebTestCase.

person troelskn    schedule 10.10.2008
comment
Спасибо, мне нужна была эта жесткая любовь! Мой дизайн, несомненно, стал лучше, поскольку я изолировал побочные эффекты окружающей среды от остальных участников моего класса. - person DaveGauer; 05.03.2014
comment
Документация для WebTestCase теперь находится по адресу simpletest.org/en/web_tester_documentation.html. - person untill; 17.03.2015

Создайте загрузочный файл для phpunit, который вызывает:

session_start();

Затем запустите phpunit следующим образом:

phpunit --bootstrap pathToBootstrap.php --anotherSwitch /your/test/path/

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

person Dominik    schedule 09.11.2009
comment
Если вы используете файл конфигурации XML, вы можете настроить начальную загрузку, добавив атрибут начальной загрузки к корневому элементу <phpunit/> следующим образом: <phpunit bootstrap="/path/to/bootstrap.php">...</phpunit> - person Joe Lencioni; 10.02.2012

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;
    }
}
person Michael    schedule 30.10.2010
comment
Лучшее решение для тестирования PHP-кода черного ящика, который вы не можете изменить. - person aercolino; 14.02.2011
comment
Обратите внимание, что PHP 3.6 теперь делает это автоматически. - person ; 14.12.2011
comment
Это решение сработало для меня: D Предполагается, что PHPUnit 3.6 реализует это, но у меня не сработало правильно. Я добавил ob_start(); в мой файл bootstrap.php, и все мои тесты с файлами cookie и заголовками снова начали работать правильно. - person Tim; 10.06.2012

У меня была такая же проблема, и я решил ее, вызвав phpunit с флагом --stderr следующим образом:

phpunit --stderr /path/to/your/test

Надеюсь, это поможет кому-то!

person Juanjo Lainez Reche    schedule 16.07.2014
comment
После часа или двух поисков это было самое простое и лучшее решение, позволяющее мне протестировать код, внутри которого была скрыта функция setcookie(). Использование @runInSeparateProcess вызывает другие проблемы, тогда как это идеально (на данный момент). - person Frug; 19.09.2014

Я думаю, что «правильным» решением является создание очень простого класса (настолько простого, что его не нужно тестировать), который является оболочкой для функций PHP, связанных с сеансом, и использовать его вместо прямого вызова session_start() и т. д.

В тесте передайте фиктивный объект вместо реального, непроверяемого класса сеанса с отслеживанием состояния.

private function __construct(SessionWrapper $wrapper)
{
   if (!$wrapper->headers_sent())
   {
      $wrapper->session_start();
      $this->session_id = $wrapper->session_id();
   }
}
person Kornel    schedule 12.11.2008

Мне интересно, почему никто не указал опцию 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);
}
person user487772    schedule 30.10.2015

Разве вы не можете использовать буферизацию вывода перед запуском теста? Если вы буферизуете все, что выводится, у вас не должно возникнуть проблем с настройкой каких-либо заголовков, поскольку в этот момент вывод еще не был отправлен клиенту.

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

person pilsetnieks    schedule 10.10.2008
comment
кажется, что ob_start() на самом деле не сбрасывает, думает ли PHP, что заголовки отправлены. - person Mez; 10.10.2008
comment
Да, ob_start не работает с заголовками, поэтому, даже если вы его используете, он все равно будет жаловаться, если заголовки больше не могут быть отправлены. Я мог бы отредактировать PHPUnit для этого, хотя... - person Mez; 10.10.2008

Насколько я знаю, Zend Framework использует ту же буферизацию вывода для своих тестов пакета Zend_Session. Вы можете взглянуть на их тестовые примеры, чтобы начать.

person Community    schedule 10.10.2008

Создание загрузочного файла, указанное 4 постами назад, кажется самым чистым способом обойти это.

Часто с PHP нам приходится поддерживать и пытаться добавить какую-то инженерную дисциплину к устаревшим проектам, которые ужасно собраны. У нас нет времени (или полномочий), чтобы выкинуть всю кучу мусора и начать заново, поэтому первый ответ troelskn не всегда возможен как путь вперед. (Если бы мы могли вернуться к исходному дизайну, тогда мы могли бы вообще отказаться от PHP и использовать что-то более современное, такое как ruby ​​или python, вместо того, чтобы помогать увековечивать этот COBOL в мире веб-разработки.)

Если вы пытаетесь написать модульные тесты для модулей, которые используют session_start или setcookie в них, то запуск сеанса в файле boostrap решит эти проблемы.

person Community    schedule 04.12.2009

Поскольку я сейчас тестирую свой бутстрап (да, я знаю, что большинство из вас этого не делает), я столкнулся с той же проблемой (и header(), и session_start()). Решение, которое я нашел, довольно простое: в вашем юниттест-бутстрапе определите константу и просто проверьте ее перед отправкой заголовка или запуском сеанса:

// 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!');
 }
person Flip Vernooij    schedule 19.10.2011

Кажется, вам нужно внедрить сеанс, чтобы вы могли протестировать свой код. Лучший вариант, который я использовал, — это Aura.Auth для процесса аутентификации и использование NullSession и NullSegment для тестирования.

Тестирование Aura с нулевыми сеансами

Фреймворк Aura прекрасно написан, и вы можете использовать Aura.Auth самостоятельно без каких-либо других зависимостей от фреймворка Aura.

person lost in binary    schedule 08.01.2016