Сначала, где мои знания:
Единичные тесты - это те, которые проверяют небольшой фрагмент кода (в основном, одиночные методы).
Интеграционные тесты - это те, которые проверяют взаимодействие между несколькими областями кода (которые, как мы надеемся, уже имеют свои собственные тесты 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, который влияет на данные в реальном времени?
- Как я могу обмануть/заглушить объекты в тесте интеграции, когда они скрыты за слоями абстракции?
- Что мне делать, когда тест терпит неудачу, и текущие данные остаются в несогласованном состоянии?
- Как в коде я действительно делаю все это?
по теме: