Имитация HTTP-запроса для модульного тестирования
Я пишу фреймворк MVC, который использует библиотеку, которая поможет управлять входными переменными (например, get, post и т.д.).
Прямо сейчас я тестирую POST и ПОЛУЧАЮ переменные, вводя их значения в суперглобальные значения $_POST и $_GET соответственно, но я столкнулся с препятствием при тестировании переменных запроса PUT и DELETE.
Пример:
public function testGET()
{
$_GET['test'] = 'test value';
$get_value = Input::get('test', '[default value]');
assertEquals('test value', $get_value);
}
Есть ли хороший способ имитировать/имитировать эти переменные или имитировать HTTP-запрос с использованием различных методов доступно (в частности, ПОЛУЧИТЬ, ОПУБЛИКОВАТЬ, ПОМЕСТИТЬ, УДАЛИТЬ)?
2 answers
Иногда мне кажется, что люди чрезмерно используют дизайн для тестируемости, в результате чего получается код, в котором 80 % сложности, по-видимому, просто облегчают тестирование. Я чувствую, что часто лучше переключаться на функциональное тестирование для вещей, которые становятся запутанными для модульного тестирования.
Но для суперглобалов PHP я сдаюсь. Поэтому я стараюсь избегать использования $_POST или $_GET в любом коде фреймворка, а вместо этого использую параметр или, если это делает код слишком уродливым, член класса или статический член.
Итак, ваша функция get() может быть переписана следующим образом:
public static function get($name,$default,$GET=null)
{
if($GET===null)$GET=$_GET; //For non-test code
if(!array_key_exists($name,$GET))return $default;
return $GET[$name];
}
Затем ваш тест становится:
public function testGET()
{
$GET=array('test' => 'test value');
$get_value = Input::get('test', '[default value]', $GET);
$this->assertEquals('test value', $get_value);
}
Это быстро становится неудобным, когда вызов Input::get() глубоко погружен в иерархию вызовов; тогда статический член класса лучше. Чтобы использовать члена класса, $GET может быть передан в функции init()
. Но если функция init()
неуместна, это можно сделать так:
public static $GET=null;
public static function get($name,$default)
{
if(self::$GET===null)self::$GET=$_GET; //Init on first call
if(!array_key_exists($name,self::$GET))return $default;
return $GET[$name];
}
И затем тест становится:
public function testGET()
{
Input::$GET=array('test' => 'test value');
$get_value = Input::get('test', '[default value]');
$this->assertEquals('test value', $get_value);
}
В сторону: другой (не тестируемый) причина, по которой мне нравится этот подход, заключается в том, что он позволяет клиентскому коду выбирать использование $_GET
,$_POST
, $_REQUEST
, и т.д. Поэтому я бы назвал статическую переменную self::$INPUT
и создал функцию init()
, в которой пользователь должен передать $_POST
или $_GET
или что они хотят.
Во второй половине вашего вопроса задается вопрос о тестировании ВВОДА и УДАЛЕНИЯ данных. Однако вы не сказали, как это обрабатывается в реальном коде. У вас есть какой-нибудь код, похожий на тот, что показан в этом ответе: https://stackoverflow.com/a/5374881/841830
Если это так, то один из подходов состоит в том, чтобы имитировать показанную там функцию getContent()
, чтобы возвращать жестко закодированный текст, вместо того, чтобы получать его из php://input
. Другой подход заключается в том, чтобы иметь другую функцию (в вашем реальном классе), главным образом для тестирования, которая входит и устанавливает $this->content
перед первым вызовом, который будет использовать эту переменную. ($this->content
в этом ответе в основном похожа на переменную self::$INPUT
, о которой я упоминал ранее.)
Заголовки запросов являются частью PHP super global $_SERVER. Вы можете установить соответствующий заголовок в $_SERVER. Например:
<?php
$_SERVER['REQUEST_METHOD'] = 'PUT';
// Now you should be able to pull data
?>
См. Руководство PHP $_SERVER. http://php.net/manual/en/reserved.variables.server.php