Подтвердить что ты не робот

Как записываются интеграционные тесты для взаимодействия с внешним API?

Сначала, где мои знания:

Единичные тесты - это те, которые проверяют небольшой фрагмент кода (в основном, одиночные методы).

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

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

Далее, ситуация, с которой я столкнулся:

При взаимодействии с внешним API (в частности, RESTful API, который будет модифицировать данные в реальном времени с помощью запроса POST), я понимаю, что мы можем (должны?) высмеять взаимодействие с этим API (более красноречиво указано в этот ответ) для теста интеграции. Я также понимаю, что мы можем Unit Test отдельные компоненты взаимодействия с этим API (построение запроса, разбор результатов, металирование ошибок и т.д.). Я не понимаю, как это сделать.

Итак, наконец: Мой вопрос (ы).

Как проверить мое взаимодействие с внешним API, который имеет побочные эффекты?

Прекрасным примером является API контента Google для покупок. Чтобы иметь возможность выполнить задачу под рукой, она требует достойного количества подготовительной работы, а затем выполняет фактический запрос, а затем анализирует возвращаемое значение. Некоторые из них без какой-либо среды "песочницы" .

Код для этого обычно имеет довольно много слоев абстракции, что-то вроде:

<?php
class Request
{
    public function setUrl(..){ /* ... */ }
    public function setData(..){ /* ... */ }
    public function setHeaders(..){ /* ... */ }
    public function execute(..){
        // Do some CURL request or some-such
    }   
    public function wasSuccessful(){
        // some test to see if the CURL request was successful
    }   
}

class GoogleAPIRequest
{
    private $request;
    abstract protected function getUrl();
    abstract protected function getData();

    public function __construct() {
        $this->request = new Request();
        $this->request->setUrl($this->getUrl());
        $this->request->setData($this->getData());
        $this->request->setHeaders($this->getHeaders());
    }   

    public function doRequest() {
        $this->request->execute();
    }   
    public function wasSuccessful() {
        return ($this->request->wasSuccessful() && $this->parseResult());
    }   
    private function parseResult() {
        // return false when result can't be parsed
    }   

    protected function getHeaders() {
        // return some GoogleAPI specific headers
    }   
}

class CreateSubAccountRequest extends GoogleAPIRequest
{
    private $dataObject;

    public function __construct($dataObject) {
        parent::__construct();
        $this->dataObject = $dataObject;
    }   
    protected function getUrl() {
        return "http://...";
    }
    protected function getData() {
        return $this->dataObject->getSomeValue();
    }
}

class aTest
{
    public function testTheRequest() {
        $dataObject = getSomeDataObject(..);
        $request = new CreateSubAccountRequest($dataObject);
        $request->doRequest();
        $this->assertTrue($request->wasSuccessful());
    }
}
?>

Примечание. Это пример PHP5/PHPUnit

Учитывая, что testTheRequest - это метод, вызванный набором тестов, пример выполнит живой запрос.

Теперь этот живой запрос будет (надеюсь, если все будет хорошо), выполнит запрос POST, который имеет побочный эффект изменения живых данных.

Это приемлемо? Какие у меня альтернативы? Я не вижу способа изрубить объект Request для теста. И даже если бы это было так, это означало бы создание точек результата/ввода для каждого возможного пути кода, который принимает API Google (который в этом случае должен быть найден методом проб и ошибок), но позволит мне использовать приборы.

Дальнейшее расширение - это когда определенные запросы полагаются на определенные данные, которые уже являются Live. Повторное использование API контента Google в качестве примера для добавления фида данных к дополнительной учетной записи, Суб-учетная запись должна уже существовать.

Один из подходов, о котором я могу думать, - это следующие шаги:

  • В testCreateAccount
    • Создание субсчета
    • Подтверждение создания субсчета
    • Удалить субсчет
  • У testCreateDataFeed зависит от testCreateAccount отсутствие ошибок
    • В testCreateDataFeed создать новую учетную запись
    • Создание фида данных
    • Подтверждение создания фида данных
    • Удалить фид данных
    • Удалить субсчет

Тогда возникает еще один вопрос; как проверить удаление учетных записей/фидов данных? testCreateDataFeed чувствует себя грязно для меня - Что делать, если создание фида данных не удается? Тест не выполняется, поэтому суб-аккаунт никогда не удаляется... Я не могу проверить удаление без создания, поэтому я пишу еще один тест (testDeleteAccount), который полагается на testCreateAccount, прежде чем создавать, а затем удалять собственную учетную запись (поскольку данные не должны делиться между тестами).

В резюме

  • Как проверить взаимодействие с внешним API, который влияет на данные в реальном времени?
  • Как я могу обмануть/заглушить объекты в тесте интеграции, когда они скрыты за слоями абстракции?
  • Что мне делать, когда тест терпит неудачу, и текущие данные остаются в несогласованном состоянии?
  • Как в коде я действительно делаю все это?

по теме:

4b9b3361

Ответ 1

Как проверить взаимодействие с внешним API, который влияет на данные в реальном времени?

Нет. Вы действительно должны верить, что фактический API действительно работает.

Вы можете - и должны - использовать API с живыми данными, чтобы убедиться, что вы это понимаете.

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

Как я могу обманывать/заглушать объекты в тесте интеграции, когда они скрыты за слоями абстракции?

Это точка. Проверьте абстракцию. Вы должны верить, что реализация выполнена. Вы тестируете свой код. Не их код.

Что мне делать, когда тест завершается с ошибкой, а живые данные остаются в несогласованном состоянии?

Что? Почему вы тестируете живой API, чтобы убедиться, что они работают? Вы им не доверяете? Если вы им не доверяете, не проверяйте. Найдите поставщика, которому вы можете доверять.

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


Как вы это делаете.

  • Играйте с API. Отправить запросы. Получите ответы.

  • Играйте со своим приложением. Выясните, какие запросы вы собираетесь отправить.

  • Вернитесь к API. Отправьте известный хороший запрос. Получите ответ. Сохранить этот ответ. Это ваш золотой стандарт ответа на хороший запрос. Канонизируйте это в тестовый пример.

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

  • После того, как вы проработали несколько вариантов использования (хороший запрос, плохой запрос), вы должны иметь возможность получить хороший ответ и некоторые типичные ответы об ошибках API. Сохраните сообщение и сообщения об ошибках. Они полезны для модульного тестирования, чтобы убедиться, что вы правильно обрабатываете некоторые из ответов.

Ответ 2

Это более подробный ответ на уже указанный:

Просматривая свой код, class GoogleAPIRequest имеет жестко закодированную зависимость class Request. Это не позволяет вам тестировать его независимо от класса запроса, поэтому вы не можете издеваться над запросом.

Вам нужно сделать запрос инъекционным, чтобы вы могли изменить его на макет во время тестирования. Это сделано, никакие реальные HTTP-запросы API не отправляются, живые данные не изменяются, и вы можете протестировать гораздо быстрее.

Ответ 3

Мне недавно пришлось обновлять библиотеку, потому что обновлен api, с которой она подключается.

Мое знание недостаточно для подробного объяснения, но я многому научился, глядя на код. https://github.com/gridiron-guru/FantasyDataAPI

Вы можете отправить запрос, как обычно, в api, а затем сохранить этот ответ в виде json файла, затем вы можете использовать его как макет.

Посмотрите тесты в этой библиотеке, которая соединяется с api с помощью Guzzle.

Он издевается над ответами из api, там много информации в документах о том, как работает тестирование, это может дать вам представление о том, как это сделать.

но в основном вы выполняете ручной вызов api вместе с любыми параметрами, которые вам нужны, и сохраняйте ответ в виде json файла.

Когда вы пишете тест для вызова api, отправляйте по тем же параметрам и загружайте его в макет, а не с помощью live api, вы можете затем проверить данные в маке, которую вы создали, содержит ожидаемые значения.

Мою обновленную версию api можно найти здесь. Обновлено Repo