Вы знаете, что когда вам говорят, что предоставление HTTP API для вашего приложения (обычно REST-подобного) полезно для повторного использования и доступа к нему непредвиденными способами? Это то, что случилось со мной на прошлой неделе, когда я пытался собрать несколько функциональных тестов.
Контекст: архитектура
Это приложение разделено между несколькими компонентами на стороне сервера, которые взаимодействуют друг с другом, иногда асинхронно. Однако только один из них стоит снаружи.
Пользовательский интерфейс rich-client, написанный на JavaScript, запускается в браузере и обращается к этому порту приложения. Он связывается со стороной сервера через REST-подобный API, который как таковой не имеет персистентности сеанса. Например, передаваемые данные содержатся в параметрах POST, но в основном в теле запросов PUT и GET в форме JSON.
Поведение может быть немного обусловлено самим HTTP, поскольку он обеспечивает семантику, которую должен соблюдать браузер (перенаправления с учетом заголовка Location, семантики POST и GET и т. Д.). В этой архитектуре нет ничего нового и революционного, но удивительно, что она реализована последовательно.
Цель
Моей целью было написание функциональных тестов для проверки правильности интеграции серверных компонентов. Пользовательский интерфейс имеет свои собственные тесты, и мы уже используем немного волшебства Selenium для запуска его в браузере и тестирования базовой интеграции с сервером.
Как
Что ж, после того, как API, похожий на REST, доступен, вы можете очень легко принять библиотеку HTTP для вашего языка и начать писать тесты, как если бы вы были браузерным ботом:
- сделать HTTP-вызовы : POST, GET, PUT или DELETE в соответствии с требуемым действием.
- Кодируйте и декодируйте JSON (или XML) в свою собственную среду тестирования, которая может даже не быть написана на том же языке, что и приложение (это будет крайний случай).
- Напишите утверждения о возвращаемых данных, а также о состоянии диалога: коды состояния, заголовки ответа HTTP и все, что можно указать в отношении API.
Пока это все розы, проворные и давайте обниматься с деревьями . Но есть и проблемы, которые нужно решить, чтобы написать быстрые и надежные сквозные тесты.
Решенные проблемы
Первая проблема, которую нужно решить для написания этих тестов, — это проблема асинхронности . Поскольку компоненты на стороне сервера взаимодействуют друг с другом несколько раз, пользовательский интерфейс может (долго) опрашивать или прослушивать обновления на сервере.
Поэтому выполнение запроса не приведет к его сокращению: мы можем проверить результат слишком рано. Синхронизация сквозных тестов в основном означает ожидание выполнения условия, чтобы исключить недетерминированное поведение тестов, которые могут пройти один раз, а затем случайно произойти в середине набора тестов:
while (!condition()) { sleep(1); seconds++; if (seconds > timeout) { fail("We tried waiting for $condition to be fulfilled, but after $timeout seconds there was still no result, so we declare the test failed."); } }
Следующая проблема — это API, который не предоставляет правильные команды, потому что некоторая логика встроена в пользовательский интерфейс . Например, я обнаружил неправильный редирект, выполненный с заголовком Location: он фактически работал в браузере, но только из-за определенной логики, встроенной в клиент JavaScript. Можно сказать, что наличие тестов в качестве второго клиента для вашего API позволяет вам улучшить его: действительно, один из самых мощных способов проверить надежность дизайна — это изменить части, связанные с ним.
Третья проблема заключалась в состоянии сервера : обычно это не проблема с REST или веб-службами, так как они естественным образом работают одновременно и могут быть доступны многим клиентам одновременно. Однако могут возникнуть некоторые конфликты между ресурсами, если вы повторно используете одни и те же данные для повторения теста.
Подумайте, например, о создании пользователя с заданным номером социального страхования: ваша система не должна позволять вам создавать ресурс, представляющий его несколько раз. Вместо того, чтобы каждый раз менять тестовые данные и терять повторяемость сборки, проще сбросить базу данных до известного состояния в начале каждого теста.
Также очень важно быть очень точным при сбросе базы данных, для скорости и изоляции тестов. Вы должны очистить только то, что строго связано с текущим тестом: подумайте о будущем, в котором ваш тест может выполняться параллельно.
Наконец, я советую вам очистить состояние сервера в начале теста, а не в фазе разрыва, как это делает модульный тест. Это позволяет вам проверить конечное состояние сервера после того, как тест не пройден, и в то же время функционально эквивалентен для выполнения теста.
Выводы
Когда вы слышите еще один доклад или статью о написании HTTP API, не спешите с выводами: это не обязательно раскрученная архитектура, но может быть способом разделения проблем сервера и клиента, чтобы протестировать последний, не встречая первое в путь.