Прежде всего, где мои знания:
Модульные тесты - это тесты, которые тестируют небольшой фрагмент кода (в основном, одиночные методы).
Интеграционные тесты - это тесты, которые проверяют взаимодействие между несколькими областями кода (которые, мы надеемся, уже имеют свои собственные модульные тесты).Иногда части тестируемого кода требуют, чтобы другой код действовал определенным образом.Вот тут-то и появляются Mocks & Stubs. Итак, мы макетируем / заглушаем часть кода, чтобы выполнить его очень специфично.Это позволяет нашему Интеграционному тесту работать предсказуемо без побочных эффектов.
Все тесты должны быть в состоянии выполняться автономно без совместного использования данных.Если обмен данными необходим, это признак того, что система недостаточно отделена.
Далее возникает ситуация, с которой я сталкиваюсь:
При взаимодействии с внешним API (в частности, RESTful)API, который будет изменять действительные данные с помощью запроса POST), я понимаю, что мы можем (должны?) Смоделировать взаимодействие с этим API (более красноречиво указано в этот ответ ) для интеграционного теста.Я также понимаю, что мы можем проводить модульное тестирование отдельных компонентов взаимодействия с этим API (создание запроса, анализ результата, выдача ошибок и т. Д.).Чего я не понимаю, так это как на самом деле поступить.
Итак, наконец: мой вопрос (ы).
Как проверить взаимодействие с внешним API, имеющим побочные эффекты?
Прекрасным примером является Google Content API для покупок .Чтобы иметь возможность выполнить поставленную задачу, требуется приличный объем подготовительной работы, затем выполнение фактического запроса, а затем анализ возвращаемого значения.Отчасти это без какой-либо среды «песочницы» .
Код для этого обычно содержит несколько уровней абстракции, что-то вроде:
<?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-запрос, имеющий побочный эффект изменения живых данных.
Является ли это приемлемым?Какие у меня есть альтернативы?Я не вижу способа макетировать объект запроса для теста.И даже если бы я это сделал, это означало бы настройку результатов / точек входа для каждого возможного пути кода, который принимает API Google (который в этом случае должен был бы быть найден методом проб и ошибок), но позволил бы мне использовать фиксаторы.
Дальнейшее расширение - это когда определенные запросы зависят от того, что определенные данные уже являются живыми.Используя API контента Google в качестве примера снова, чтобы добавить фид данных к дополнительной учетной записи, дополнительная учетная запись уже должна существовать.
Один из подходов, о которых я могу подумать, - это следующие шаги:
- В
testCreateAccount
- Создать субсчет
- Подтвердить, что субсчет был создан
- Удалить субсчет
- *
testCreateDataFeed
зависит от testCreateAccount
без ошибок - В
testCreateDataFeed
создайте новый аккаунт - Создайте фид данных
- Подтвердитеканал данных был создан
- Удалить канал данных
- Удалить субсчет
Затем возникает еще один вопрос;Как проверить удаление учетных записей / каналов данных?testCreateDataFeed
мне кажется грязным - Что, если создать фид данных не удастся?Тест не пройден, поэтому дополнительная учетная запись никогда не удаляется ... Я не могу проверить удаление без создания, поэтому я пишу другой тест (testDeleteAccount
), который опирается на testCreateAccount
перед созданием и удалением собственной учетной записи.(поскольку данные не должны совместно использоваться тестами).
Вкратце
- Как проверить взаимодействие с внешним API, который влияет на живые данные?
- Какможно ли макетировать / заглушки объекты в тесте интеграции, когда они скрыты за слоями абстракции?
- Что мне делать, если тест не пройден и текущие данные остаются в несогласованном состоянии?
- Как в коде действительно ли я все это делаю?
Связано: