сеньорчикОткрыть в Telegram
← вся теориятеория к собесу · Тестирование API

Принципы REST

Принципы REST

Замер на десяти заказах, страницы по три, сначала новые. Первая страница отдала «заказ-10», «заказ-09», «заказ-08». Пока пользователь читал, приехал «заказ-11». Вторая страница по смещению отдала «заказ-08» (уже виденный), «заказ-07», «заказ-06». Итог: один заказ показан дважды, а «заказ-11» не показан вообще.

Стержень: ресурсы-существительные плюс методы-глаголы, каждый запрос самодостаточен, а поле в ответе можно добавить, но нельзя убрать.

// Формулировки: «что делает API RESTful?», «что такое stateless?», «какое изменение ломает клиентов?».

Ресурсы, методы, самодостаточность

REST - это набор соглашений о том, как строить HTTP-API. Первое: сущности адресуются существительными, а действия выражаются методами. GET /orders/42 читает заказ, DELETE /orders/42 удаляет. Вложенность отражает отношения: /users/7/orders - заказы седьмого пользователя. Если в адресах появляются глаголы (/getOrder, /createUser) и всё идёт через POST, это уже не REST, а вызов процедур в маскировке.

Второе соглашение - stateless, самодостаточность запроса: сервер не помнит, о чём вы говорили в прошлый раз, вся нужная информация едет в самом запросе, включая данные о том, кто вы. Благодаря этому запрос может обслужить любой из десятка одинаковых серверов, и их можно добавлять по мере роста нагрузки.

// Для тестировщика самодостаточность означает конкретную проверку: два одинаковых запроса подряд обязаны вести себя одинаково, независимо от того, какой сервер их обслужил и что происходило до этого. Если второй запрос вдруг отвечает иначе, значит где-то на сервере копится скрытое состояние - и это дефект, который на одном сервере не воспроизводится, а на десяти проявляется случайно.

ресурс
сущность со своим адресом; действия над ней - методами
stateless
запрос самодостаточен, сервер не помнит предыдущих

Списки: замер потерянной строки

Разберу замер из начала. Пагинация по смещению работает так: «пропусти первые N записей и дай следующие три». Пока данные неподвижны, всё честно. Но список заказов пополняется, и после вставки новой записи сверху все остальные сдвигаются на одну позицию вниз. Смещение при этом отсчитывается от начала, поэтому вторая страница показывает то, что уже было на первой, а одна запись проваливается между страницами насовсем.

Курсорная пагинация решает это иначе: клиент говорит не «пропусти три», а «дай то, что идёт после заказ-08». Проверка на том же наборе дала «заказ-07», «заказ-06», «заказ-05» - без дублей и без потерь, потому что точка отсчёта привязана к записи, а не к позиции.

// Что здесь тестируют, кроме самого списка: значения по умолчанию (сколько вернётся, если размер страницы не указан) и максимум (что будет, если попросить сто тысяч записей за раз - вернёт или ляжет). И то и другое - часть договора, а не внутреннее дело сервиса.

пагинация по смещению
«пропусти N, дай следующие»; ломается при вставках
курсорная пагинация
«дай то, что после этой записи»; устойчива к вставкам

Договор и его нарушение

API - это договор с чужими программами, и его нельзя менять как хочется. Добавить новое поле в ответ обычно безопасно: клиент, который его не ждёт, просто не заметит. А вот убрать поле, переименовать его или сменить тип - ломающее изменение: чужой клиент упадёт, причём уже в проде, у живых пользователей, и без предупреждения. Поле, которое кажется никому не нужным, почти всегда кем-то читается.

Отсюда версионирование: несовместимые изменения выпускают как новую версию (/v2/orders), а старую держат, пока клиенты не переедут. И отсюда же правило для клиентов: не падать от незнакомых полей в ответе, а игнорировать их - тогда добавление действительно останется безопасным.

// Договор описывают машиночитаемой спецификацией OpenAPI: какие адреса есть, какие поля, каких типов, что обязательно. Из неё делают проверки и заглушки - подставные ответы, чтобы разрабатывать клиент, пока сервер не готов. Но верить ей на слово нельзя: расхождение между спецификацией и фактическим поведением - это тоже дефект, и находят его, только сверяя одно с другим. Поэтому тестируют реальность, а спецификацию держат как эталон.

ломающее изменение
убрать поле, переименовать, сменить тип - чужие клиенты падают
OpenAPI
машиночитаемое описание договора: адреса, поля, типы

Как отвечать: «Что делает API RESTful и что здесь тестировать?»

RESTful API строится вокруг ресурсов: сущности адресуются существительными, а действия выражаются методами. GET на адрес заказа читает его, DELETE удаляет - а не POST на адрес вида slash deleteOrder. И каждый запрос самодостаточен: сервер не помнит предыдущих, всё нужное едет в самом запросе, поэтому любой из серверов обслужит любой запрос. Что я как тестировщик проверяю. Первое - что методы и коды соответствуют смыслу: создание отвечает двести один и заголовком с адресом созданного, несуществующее - четыреста четыре, конфликт - четыреста девять. Второе - списки: я специально проверял пагинацию по смещению на пополняемых данных, и одна запись показалась дважды, а другая пропала между страницами; поэтому смотрю на курсорную пагинацию и на значения по умолчанию с максимумами. Третье - единый формат ошибок по всему API, чтобы клиент не писал три разных разборщика. Четвёртое - идемпотентность записи: повторяю создание с тем же ключом и убеждаюсь, что дубля нет. И обязательно сверяю фактическое поведение со спецификацией, потому что расхождение доки и реальности - тоже дефект.

Почему это сильный ответ: признаки REST названы и объяснены, а проверки перечислены конкретно, причём про пагинацию - на основе наблюдения с дублем и потерей записи.

На чём валят

  • Глаголы в адресах и вся работа через POST - это вызов процедур, а не REST.
  • Пагинация по смещению на пополняемых данных: запись показана дважды, другая потеряна.
  • Удалить «никому не нужное» поле ответа - чужой клиент падает в проде молча.
  • Разные форматы ошибок в соседних адресах - клиент пишет три разных разборщика.
  • Верить спецификации без сверки с фактическим поведением: расхождение - тоже дефект.

Проверьте себя

Пять вопросов из банка по этой подтеме. Всего их 13, остальные разбираются в тренажёре.

  1. #rest_principles1 / 5
    Чем REST отличается от SOAP?
    A)REST работает по XML и WSDL, а SOAP — лёгкий стиль поверх JSON и HTTP-методов
    B)REST — лёгкий стиль над HTTP-методами и JSON; SOAP — строгий XML-протокол с WSDL
    C)REST и SOAP — два названия одного протокола, различие лишь в версии стандарта
    D)REST применяется на клиенте, а SOAP — на сервере; вместе они образуют один запрос
    показать ответ и разбор
    +B)REST — лёгкий стиль над HTTP-методами и JSON; SOAP — строгий XML-протокол с WSDL

    // разбор: REST — архитектурный стиль: ресурсы по URI, стандартные HTTP-методы, чаще JSON, гибко и легко. SOAP — полноценный протокол обмена сообщениями: строгий XML-конверт, формальный контракт WSDL, встроенные стандарты безопасности и транзакций, работа поверх разных транспортов. SOAP тяжелее и многословнее, но даёт жёсткий контракт и корпоративные гарантии — его берут в банках, интеграциях legacy. REST проще, быстрее в разработке и доминирует в веб/мобайл. Тестирование различается: REST — коды и JSON, SOAP — валидация XML против WSDL и SOAP-fault.

  2. #rest_principles2 / 5
    Какой URI в REST-стиле правильный для «получить заказы пользователя 42»?
    A)/getOrders?userId=42 — действие выносят в имя эндпоинта, а id передают параметром
    B)/users/42/orders/getAll — уточняющий глагол в конце пути делает намерение яснее
    C)/users/42/orders — существительные-ресурсы в пути, а действие задаёт метод GET, а не глагол в URL
    D)/orders — без привязки к пользователю, а нужного юзера сервер определит по cookie
    показать ответ и разбор
    +C)/users/42/orders — существительные-ресурсы в пути, а действие задаёт метод GET, а не глагол в URL

    // разбор: REST-URI строят из существительных-ресурсов, отражая иерархию: заказы принадлежат пользователю → /users/42/orders. Что с ними делать, определяет метод: GET — получить список, POST на этот же путь — создать заказ. Глаголы в URL (/getUserOrders, /users/42/getOrders) — анти-паттерн: они дублируют то, что и так выражает HTTP-метод, и ломают единообразие. Такой дизайн предсказуем: тестировщик по URI и методу сразу понимает намерение. Фильтры и сортировку добавляют query-параметрами: /users/42/orders?status=paid.

  3. #rest_principles3 / 5
    Зачем версионируют API (например, /v1 и /v2)?
    A)Чтобы ускорить API: новая версия обрабатывает запросы быстрее старой на том же сервере
    B)Чтобы разделить платных и бесплатных пользователей по разным адресам эндпоинтов
    C)Чтобы каждый клиент имел личную копию API под своим номером версии на сервере
    D)Чтобы вносить несовместимые изменения, не ломая существующих клиентов
    показать ответ и разбор
    +D)Чтобы вносить несовместимые изменения, не ломая существующих клиентов

    // разбор: Клиенты (мобильные приложения, интеграции) завязаны на конкретный формат ответа. Если изменить его несовместимо (переименовать/убрать поле, сменить структуру), все старые клиенты сломаются разом. Версионирование решает это: несовместимые изменения выкатывают в новой версии (/v2), а /v1 продолжает работать, пока клиенты не мигрируют. Так обновление API становится управляемым, а не разрушительным. Тестировщик проверяет, что старая версия не сломана изменениями, а новая совместима с задуманным контрактом; часто гоняют контрактные тесты по обеим.

  4. #rest_principles4 / 5
    Зачем в API применяют пагинацию списков?
    A)Отдавать данные страницами (limit/offset или курсор), а не весь набор разом
    B)Пагинация шифрует часть данных списка, отдавая клиенту лишь разрешённые записи
    C)Пагинация сортирует список по заданному полю перед отправкой его клиенту
    D)Пагинация дублирует данные на нескольких серверах для ускорения их отдачи клиенту
    показать ответ и разбор
    +A)Отдавать данные страницами (limit/offset или курсор), а не весь набор разом

    // разбор: Список из сотен тысяч записей нельзя отдать одним ответом: сервер перегрузит память формированием, сеть — передачей, а клиент — обработкой; ответ будет медленным или упадёт. Пагинация режет выдачу на страницы: клиент запрашивает порцию (limit=50&offset=100 или по курсору) и листает дальше. Это стандарт для любых коллекций. Тестировщик проверяет: корректность границ страниц (первая, последняя, за пределом), отсутствие пропусков/дублей на стыках, поведение при изменении данных между страницами, и что метаданные (total, next) верны.

  5. #rest_principles5 / 5
    Какой статус-код уместен в ответе на POST, создавший ресурс?
    A)204 No Content, поскольку при создании возвращать клиенту в теле уже нечего
    B)201 Created, часто с заголовком Location, указывающим адрес нового ресурса
    C)200 OK без дополнительных заголовков — стандартный код для всех успешных запросов
    D)400 Bad Request, так как POST c телом сервер трактует как потенциально ошибочный
    показать ответ и разбор
    +B)201 Created, часто с заголовком Location, указывающим адрес нового ресурса

    // разбор: Успешное создание ресурса по POST принято обозначать 201 Created, а не просто 200. Дополнительно в ответе часто ставят заголовок Location с URI нового ресурса (/orders/17), чтобы клиент знал, куда обращаться дальше, и в тело кладут созданную сущность с присвоенным id. Такой ответ несёт больше информации, чем голый 200, и точнее отражает семантику. Тестировщик проверяет именно 201 (а не 200), наличие id/Location и совпадение вернувшихся данных с отправленными.

дальше

Теорию прочитали. Навык ставится повторением

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