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

Во-первых, где я знаю:

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

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

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

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

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

Итак, напоследок: мой вопрос (ы).

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

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

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

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 (который в этом случае нужно было бы найти методом проб и ошибок), но позволил бы мне использовать фикстуры.

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

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

  1. В testCreateAccount
    1. Создать дополнительную учетную запись
    2. Подтвердить, что дополнительная учетная запись была создана
    3. Удалить дополнительную учетную запись
  2. У testCreateDataFeed в зависимости от testCreateAccount нет ошибок
    1. В testCreateDataFeed создайте новую учетную запись
    2. Создать канал данных
    3. Подтвердить, что канал данных был создан
    4. Удалить канал данных
    5. Удалить субсчет

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

Резюме

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

По теме:

70
задан Community 23 May 2017 в 12:26
поделиться