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

StackOverflow https://stackoverflow.com/questions/190292

  •  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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top