Имитация 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-запрос с использованием различных методов доступно (в частности, ПОЛУЧИТЬ, ОПУБЛИКОВАТЬ, ПОМЕСТИТЬ, УДАЛИТЬ)?

Author: Matt, 2014-01-13

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, о которой я упоминал ранее.)

 3
Author: Darren Cook, 2017-05-23 10:30:55

Заголовки запросов являются частью 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

 -3
Author: b01, 2014-08-15 19:14:01