По-перше, там, де знання:
Юніт-тести - це ті, що перевіряють невеликий фрагмент коду (переважно одиночні методи).
Інтеграційні тести - це тести , які перевіряють взаємодію між різними областями коду (які, сподіваємось, уже мають власні модульні тести). Іноді частини тестованого коду вимагають, щоб інший код діяв певним чином. Тут з’являються Mocks & Stubs. Отже, ми висміюємо / заглушаємо частину коду, щоб виконати дуже конкретно. Це дозволяє нашому інтеграційному тесту працювати передбачувано без побічних ефектів.
Всі тести повинні мати можливість працювати самостійно без обміну даними. Якщо необхідний спільний доступ до даних, це ознака недостатньої розв’язки системи.
Далі, ситуація, з якою я стикаюся:
При взаємодії із зовнішнім 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 (який у цьому випадку потрібно було б знайти методом спроб і помилок), але дозволило б мені використовувати пристосування.
Подальше розширення - коли певні запити покладаються на певні дані, які вже є в реальному часі. Використовуючи Google Content API як приклад ще раз, щоб додати канал даних до субрахунку, він повинен уже існувати.
Одним із підходів, які я можу придумати, є наступні кроки;
- В
testCreateAccount
- Створіть субрахунок
- Стверджуємо, що субрахунок створено
- Видаліть субрахунок
- Have
testCreateDataFeed
залежать відtestCreateAccount
не мають жодних - або помилок- У
testCreateDataFeed
створіть новий обліковий запис - Створіть канал даних
- Переконайтеся, що канал даних створено
- Видаліть стрічку даних
- Видаліть субрахунок
- У
Потім це піднімає подальше питання; як протестувати видалення облікових записів / каналів даних? testCreateDataFeed
мені здається брудним - Що робити, якщо створення фіду даних не вдається? Тест не вдається, тому підрахунок ніколи не видаляється ... Я не можу протестувати видалення без створення, тому я пишу інший тест ( testDeleteAccount
), на який покладається testCreateAccount
перед створенням, а потім видалення власного облікового запису (оскільки дані не повинні бути спільним між тестами).
Коротко
- Як протестувати взаємодію із зовнішнім API, який впливає на дані в реальному часі?
- Як я можу знущатись / заглушати об’єкти в тесті інтеграції, коли вони заховані за шарами абстракції?
- Що робити, якщо тест невдалий і дані в режимі реального часу залишаються у несумісному стані?
- Як я в коді насправді займаюся цим?
Пов’язані: