как тестировать вызов curl в php - PullRequest
37 голосов
/ 27 октября 2011

Как бы вы пошли о модульном тестировании реализации curl?

  public function get() {
    $ch = curl_init($this->request->getUrl());

    curl_setopt($ch, CURLOPT_HEADER, false);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);

    $result = curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    curl_close($ch);

    if (!strstr($type, 'application/json')) {
      throw new HttpResponseException('JSON response not found');
    }

    return new HttpResponse($code, $result);
  }

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

Ответы [ 6 ]

48 голосов
/ 27 октября 2011

Как предложил Томасруттер, создайте класс, чтобы абстрагироваться от использования функций cURL.

interface HttpRequest
{
    public function setOption($name, $value);
    public function execute();
    public function getInfo($name);
    public function close();
}

class CurlRequest implements HttpRequest
{
    private $handle = null;

    public function __construct($url) {
        $this->handle = curl_init($url);
    }

    public function setOption($name, $value) {
        curl_setopt($this->handle, $name, $value);
    }

    public function execute() {
        return curl_exec($this->handle);
    }

    public function getInfo($name) {
        return curl_getinfo($this->handle, $name);
    }

    public function close() {
        curl_close($this->handle);
    }
}

Теперь вы можете тестировать, используя макет интерфейса HttpRequest, не вызывая ни одной из функций cURL.

public function testGetThrowsWhenContentTypeIsNotJson() {
    $http = $this->getMock('HttpRequest');
    $http->expects($this->any())
         ->method('getInfo')
         ->will($this->returnValue('not JSON'));
    $this->setExpectedException('HttpResponseException');
    // create class under test using $http instead of a real CurlRequest
    $fixture = new ClassUnderTest($http);
    $fixture->get();
}

Редактировать Исправлена ​​простая ошибка синтаксического анализа PHP.

7 голосов
/ 27 октября 2011

Не используйте curl напрямую, но через оболочку, такую ​​как PEAR HTTP_Request2 . Благодаря этому у вас есть возможность заменить драйвер скручивания на макет драйвера - идеально подходит для юнит-тестов.

5 голосов
/ 14 апреля 2015

Вы можете использовать библиотеку макетов функций. Я сделал один для вас: php-mock-phpunit

namespace foo;

use phpmock\phpunit\PHPMock;

class BuiltinTest extends \PHPUnit_Framework_TestCase
{

    use PHPMock;

    public function testCurl()
    {
        $curl_exec = $this->getFunctionMock(__NAMESPACE__, "curl_exec");
        $curl_exec->expects($this->once())->willReturn("body");

        $ch = curl_init();
        $this->assertEquals("body", curl_exec($ch));
    }
}
3 голосов
/ 24 сентября 2012

Я наткнулся на этот вопрос, когда пытался самостоятельно протестировать класс с помощью cURL. Я принял совет Дэвида Харкнесса близко к сердцу и создал интерфейс для cURL. Тем не менее, функциональность заглушки / макета, предоставляемая PHPUnit, в моем случае была недостаточной, поэтому я добавил собственную реализацию заглушки интерфейса и поместил все это на GitHub. И поскольку этот вопрос довольно рано появляется в Google при поиске этой проблемы, я подумал, что опубликую его здесь, чтобы другие могли сэкономить усилия.

Вот оно.

Вики-репозиторий содержит довольно подробную документацию о возможностях заглушки, но здесь они вкратце.

Интерфейс представляет собой отображение 1: 1 функций cURL в PHP, что позволяет очень легко начать использовать интерфейс (просто вручите свой ClassUnderTest экземпляр, реализующий SAI_CurlInterface, а затем вызовите все функции cURL, как и раньше, но как методы в этом случае). Класс SAI_Curl реализует этот интерфейс путем простого делегирования в cURL. Теперь, если вы хотите проверить ClassUnderTest, вы можете дать ему экземпляр SAI_CurlStub.

Заглушка в основном устраняет проблему, заключающуюся в том, что макеты и заглушки PHPUnit не могут возвращать фиктивные данные в зависимости от предыдущих вызовов функций (но именно так на самом деле работает cURL - вы устанавливаете свои параметры, и ответ, код ошибки и информация cURL зависят от этих опции). Итак, вот короткий пример, показывающий эти возможности для ответов (коды ошибок и информацию о cURL см. В вики).

public function testGetData()
{
    $curl = new SAI_CurlStub();

    // Set up the CurlStub
    $defaultOptions = array(
        CURLOPT_URL => 'http://www.myserver.com'
    );
    $chromeOptions = array(
        CURLOPT_URL => 'http://www.myserver.com',
        CURLOPT_USERAGENT => 'Chrome/22.0.1207.1'
    );
    $safariOptions = array(
        CURLOPT_URL => 'http://www.myserver.com',
        CURLOPT_USERAGENT => 'Safari/537.1'
    );

    $curl->setResponse('fallback response');
    $curl->setResponse('default response from myserver.com'
                       $defaultOptions);
    $curl->setResponse('response for Chrome from myserver.com',
                       $chromeOptions);
    $curl->setResponse('response for Safari from myserver.com',
                       $safariOptions);

    $cut = new ClassUnderTest($curl);

    // Insert assertions to check whether $cut handles the
    // different responses correctly
    ...
}

Вы можете сделать свой ответ зависимым от любой комбинации любых опций cURL. Конечно, вы можете пойти еще дальше. Например, ваш ClassUnderTest берет некоторые XML-данные с сервера и анализирует их (ну, у вас должно быть два отдельных класса для этих задач, но давайте предположим, что это для нашего примера), и вы хотите протестировать это поведение. Вы можете загрузить XML-ответ вручную, и ваш тест прочитает данные из файла и вставит их в ответ. Затем вы точно знаете, какие данные есть, и можете проверить, правильно ли они проанализированы. В качестве альтернативы вы могли бы реализовать SAI_CurlInterface загрузку всех ответов из вашей файловой системы сразу, но существующая реализация, безусловно, является точкой для начала.

В то время, когда я пишу этот ответ, @ SAI_CurlStub @ еще не поддерживает мультибиблиотечные функции cURL, но я планирую реализовать это и в будущем.

Надеюсь, эта заглушка поможет всем, кто хочет провести модульное тестирование классов, зависящих от cURL. Не стесняйтесь проверять и использовать классы, или внести свой вклад, конечно же - это на GitHub в конце концов :). Кроме того, я открыт для любой конструктивной критики в отношении реализации и использования интерфейса и заглушки.

2 голосов
/ 27 октября 2011

Один из подходов заключается в замене используемого вами интерфейса (в данном случае, функций curl_) на фиктивные версии самих себя, которые возвращают определенные значения.Если бы вы использовали объектно-ориентированную библиотеку, это было бы проще, потому что вы могли бы просто заменить фиктивный объект с такими же именами методов (и действительно, фреймворки, такие как simpletest, могут легко устанавливать фиктивные методы объекта).В противном случае, возможно, есть какое-то другое волшебство, которое вы можете использовать для переопределения встроенных функций с помощью макетов. Это расширение включает override_function () , которая выглядит как то, что вам нужно, хотя это добавит другую зависимость.

Если вы хотите проверить это, не заменяя функции curl_ на фиктивные версии, оноПохоже, вам нужно будет установить фиктивный сервер, который будет возвращать определенный результат, чтобы вы могли проверить, как ваш PHP и его расширение curl обрабатывают этот результат.Чтобы полностью протестировать его, вам нужно получить доступ к нему через HTTP, а не, скажем, из локального файла, потому что ваш PHP зависит от наличия кода ответа HTTP и т. Д. Поэтому для ваших тестов потребуется работающий HTTP-сервер.

Кстати, PHP 5.4 будет включать собственный веб-сервер , который пригодится для этой цели.В противном случае вы можете поместить тестовый сценарий на известный сервер, которым вы управляете, или распространить простую конфигурацию сервера вместе с вашими тестами.

Если бы вы фактически использовали живой сервер для тестирования, это бы уменьшило unit test и более интеграционный тест, потому что вы будете тестировать как ваш PHP и сервер, так и интеграцию между ними.Вы также упустите возможность по требованию протестировать, как ваш код обрабатывает определенные сбои.

0 голосов
/ 27 октября 2011

В вашем модульном тесте попросите request->getUrl() вернуть URI локального файла, который, как вы знаете, выдаст исключение.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...