Практика тестирования API
Практика тестирования API, а не основы HTTP — проверка ответов по схеме, контракт ошибок, пагинация и фильтрация, лимиты запросов, GraphQL, загрузка файлов и обратная совместимость версий.
9 вопросов
JuniorТеорияОчень частоПомимо счастливого пути, что покрывает контракт ошибок API?
Помимо счастливого пути, что покрывает контракт ошибок API?
Счастливый путь — один валидный случай; контракт ошибок — как API ведёт себя вне него: плохой ввод, отсутствие авторизации, пропавшие ресурсы, конфликты. Он задаёт верный код на класс (400 некорректный, 401/403 авторизация, 404 отсутствие, 409 конфликт, 422 валидное но отклонённое) плюс стабильное разбираемое тело ошибки. Тестировать его — намеренно вызывать каждый класс и проверять код и сообщение, а не только что вернулся 200.
Типичные ошибки
- ✗Проверять только счастливый путь с 200 и не прогонять классы сбоев
- ✗Принимать любой 4xx как норму вместо конкретного кода, которого требует класс
- ✗Игнорировать форму тела ошибки, из-за чего клиенты не могут его разобрать
Уточняющие вопросы
- →Когда вы вернёте 422, а не 400 на запрос, который сервер отклоняет?
- →Почему тело ошибки должно быть стабильным между релизами, как и тело успеха?
JuniorТеорияЧастоЧто такое проверка ответа API по схеме и что она выявляет?
Что такое проверка ответа API по схеме и что она выявляет?
Проверка по схеме сверяет форму ответа — имена полей, типы данных, обязательные поля, enum, форматы — с машиночитаемым контрактом вроде JSON Schema или спецификации OpenAPI. Одна проверка покрывает весь payload и ловит смену типов и пропавшие или переименованные поля, мимо которых выборочные проверки отдельных полей проходят насквозь. Она доказывает форму, а не бизнес-корректность значений.
Типичные ошибки
- ✗Проверять руками пару полей вместо валидации всего payload
- ✗Считать, что пройденная схема доказывает и бизнес-корректность значений
- ✗Не задавать required и additionalProperties, из-за чего пропущенное или лишнее поле проходит
Уточняющие вопросы
- →Что ловит схема с additionalProperties, выставленным в false, чего не заметит нестрогая?
- →Как держать схему в тестах в синхроне со спецификацией OpenAPI сервиса?
MiddleТеорияЧастоЧем для тестировщика отличается тестирование стилей API GraphQL и REST?
Чем для тестировщика отличается тестирование стилей API GraphQL и REST?
GraphQL выставляет один эндпоинт, где клиент сам формирует запрос, поэтому тестирование смещается с кодов на эндпоинт к поведению на запрос: избыточная и недостаточная выборка, вложенное разрешение, частичные результаты. Ответ GraphQL обычно 200 даже при сбое и несёт ошибки в массиве errors — проверяете этот массив, а не HTTP-код. Типизированная схема GraphQL — это контракт, по которому валидируете. REST же опирается на разные URL, глаголы и статус-коды на ресурс.
Типичные ошибки
- ✗Проверять HTTP-статус вместо массива errors в ответе GraphQL
- ✗Считать, что REST-модель «эндпоинт на ресурс» переносится на единый эндпоинт GraphQL
- ✗Пропускать проверки избыточной и недостаточной выборки, раз клиент формирует запрос
Уточняющие вопросы
- →Как проверить, что запрос GraphQL вернул частичные данные вместе с ошибками?
- →Почему нельзя судить о вызове GraphQL только по HTTP-статусу?
MiddleДизайнЧастоЭндпоинт списка GET /orders принимает page и pageSize (по умолчанию 20, максимум 100), фильтр по status и параметр сортировки (createdAt или total, asc или desc). Он возвращает элементы плюс общий счётчик. Продукт хочет уверенности, что пагинация, фильтрация и сортировка работают на реальных данных. Спроектируйте подход к тестам: какие случаи покрываете для границ пагинации, как доказываете, что фильтр и сортировка действительно применяются, а не игнорируются, как пагинация и фильтрация взаимодействуют и что проверяете о счётчике и стабильности страниц, пока строки вставляются. Отметьте проверку формы ответа, на которую опираетесь в каждом случае.
Эндпоинт списка GET /orders принимает page и pageSize (по умолчанию 20, максимум 100), фильтр по status и параметр сортировки (createdAt или total, asc или desc). Он возвращает элементы плюс общий счётчик. Продукт хочет уверенности, что пагинация, фильтрация и сортировка работают на реальных данных. Спроектируйте подход к тестам: какие случаи покрываете для границ пагинации, как доказываете, что фильтр и сортировка действительно применяются, а не игнорируются, как пагинация и фильтрация взаимодействуют и что проверяете о счётчике и стабильности страниц, пока строки вставляются. Отметьте проверку формы ответа, на которую опираетесь в каждом случае.
Покройте границы пагинации — первая, последняя, пустая, за концом и pageSize равный 1, максимуму, выше максимума (обрезка или отказ). Докажите сортировку, проверяя порядок для каждого ключа и направления, включая совпадения. Докажите фильтрацию: каждая строка совпадает с фильтром, а известная несовпадающая отсутствует; совместите фильтр с пагинацией, чтобы счётчики сходились. Проверьте, что total отражает фильтр, а не страницу; сортируйте по стабильному ключу, чтобы страницы не плыли. Валидируйте схему каждого ответа.
Типичные ошибки
- ✗Тестировать каждый параметр отдельно и никогда взаимодействие фильтра с пагинацией
- ✗Считать total длиной страницы, а не размером всего отфильтрованного результата
- ✗Сортировать по нестабильному ключу, из-за чего страницы плывут при вставке строк
Уточняющие вопросы
- →Как обнаружить, что страница 2 потеряла или повторила строку после новых вставок?
- →Какой крайний случай ждёте, когда pageSize запрошен выше задокументированного максимума?
SeniorДизайнЧастоВаш автоматизированный набор тестов API должен обращаться к эндпоинтам под защитой фреймворка авторизации OAuth2, где bearer-токен берётся с token-эндпоинта и истекает через час. Спроектируйте автоматизацию авторизации так, чтобы набор оставался быстрым, стабильным и безопасным: как получаете и переиспользуете токен между многими тестами вместо авторизации на каждый запрос, как набор обнаруживает истёкший токен и восстанавливается на ходу, где живут учётные данные клиента, чтобы ничего секретного не попало в репозиторий, и какой scope должен быть у тестового клиента. Включите негативные случаи, которые проверяете — отсутствующий токен, истёкший токен и токен с недостаточным scope — и скажите, как держите защищённый ответ проверенным по схеме.
Ваш автоматизированный набор тестов API должен обращаться к эндпоинтам под защитой фреймворка авторизации OAuth2, где bearer-токен берётся с token-эндпоинта и истекает через час. Спроектируйте автоматизацию авторизации так, чтобы набор оставался быстрым, стабильным и безопасным: как получаете и переиспользуете токен между многими тестами вместо авторизации на каждый запрос, как набор обнаруживает истёкший токен и восстанавливается на ходу, где живут учётные данные клиента, чтобы ничего секретного не попало в репозиторий, и какой scope должен быть у тестового клиента. Включите негативные случаи, которые проверяете — отсутствующий токен, истёкший токен и токен с недостаточным scope — и скажите, как держите защищённый ответ проверенным по схеме.
Получите токен один раз через token-эндпоинт и кэшируйте между тестами вместо авторизации на каждый запрос. Определяйте истечение по claim exp или по 401, обновляйте упреждающе или реактивно на 401, затем повторяйте вызов. Держите client id и secret в защищённом конфиге или env, вне репозитория, и дайте клиенту минимально нужный scope. Проверяйте негатив — 401 на отсутствующий или истёкший токен, 403 на недостаточный scope — и валидируйте схему защищённых ответов.
Типичные ошибки
- ✗Гонять полный поток авторизации на каждый тест вместо кэша одного токена на прогон
- ✗Зашивать учётные данные клиента в файлы тестов вместо защищённого конфига или env
- ✗Пропускать негатив: отсутствующий, истёкший и недостаточный по scope токен
Уточняющие вопросы
- →Как набор восстанавливается, когда кэшированный токен истекает посреди прогона?
- →Почему дать тестовому клиенту минимальный scope, а не широкий административный?
MiddleДизайнИногдаЭндпоинт POST /documents принимает multipart-файл до 10 МБ, разрешая только PDF и PNG, и возвращает метаданные (id, size, contentType). GET /documents/{id} отдаёт файл обратно потоком. Спроектируйте подход к тестам этой пары: какие случаи загрузки покрываете вокруг лимита размера и разрешённых типов, как доказываете, что скачанный файл байт-в-байт тот, что вы загрузили, какие заголовки ответа проверяете при скачивании и какие вредоносные или некорректные входы намеренно пробуете. Отметьте, как обрабатываете большой файл, чтобы сам тест не исчерпал память, и проверку формы для результата загрузки.
Эндпоинт POST /documents принимает multipart-файл до 10 МБ, разрешая только PDF и PNG, и возвращает метаданные (id, size, contentType). GET /documents/{id} отдаёт файл обратно потоком. Спроектируйте подход к тестам этой пары: какие случаи загрузки покрываете вокруг лимита размера и разрешённых типов, как доказываете, что скачанный файл байт-в-байт тот, что вы загрузили, какие заголовки ответа проверяете при скачивании и какие вредоносные или некорректные входы намеренно пробуете. Отметьте, как обрабатываете большой файл, чтобы сам тест не исчерпал память, и проверку формы для результата загрузки.
Загрузка: граница размера (на 10 МБ проходит, выше отклоняется), разрешённые против запрещённых типов, пустой и обрезанный файл, расхождение расширения и MIME. Скачивание: докажите идентичный возврат байтов контрольной суммой и проверьте Content-Type, Content-Disposition и статус. Пробуйте вредоносные входы — исполняемый, переименованный в .pdf, обход пути в имени. Большие файлы читайте потоком, а не буферизуя целиком. Валидируйте схему метаданных загрузки.
Типичные ошибки
- ✗Тестировать только маленькую валидную загрузку, пропуская границы размера и типа
- ✗Не сравнивать скачанные байты с оригиналом, из-за чего тихая порча проходит
- ✗Считать замаскированные исполняемые и обход пути вне области функционального QA
Уточняющие вопросы
- →Как обнаружить, что скачанный файл был тихо обрезан или испорчен?
- →Почему проверять реальный MIME-тип надёжнее, чем доверять расширению файла?
MiddleДизайнИногдаAPI ограничивает каждый ключ 100 запросами в минуту и отвечает 429 с заголовком Retry-After по достижении лимита. Продукт также жалуется, что легитимные всплески трафика иногда режутся. Спроектируйте, как вы тестируете ограничитель: как подтверждаете, что лимит срабатывает на нужном счёте, что проверяете в throttled-ответе, как убеждаетесь, что окно сбрасывается, и как отделяете настоящий дефект лимитера от ложного срабатывания, наказывающего честный всплеск. Скажите, как удержать сам тест от нестабильности, учитывая тайминг и общий счётчик, и отметьте, что бы вы измерили, чтобы понять, не задан ли лимит слишком агрессивно для реального использования.
API ограничивает каждый ключ 100 запросами в минуту и отвечает 429 с заголовком Retry-After по достижении лимита. Продукт также жалуется, что легитимные всплески трафика иногда режутся. Спроектируйте, как вы тестируете ограничитель: как подтверждаете, что лимит срабатывает на нужном счёте, что проверяете в throttled-ответе, как убеждаетесь, что окно сбрасывается, и как отделяете настоящий дефект лимитера от ложного срабатывания, наказывающего честный всплеск. Скажите, как удержать сам тест от нестабильности, учитывая тайминг и общий счётчик, и отметьте, что бы вы измерили, чтобы понять, не задан ли лимит слишком агрессивно для реального использования.
Гоните запросы к границе: запрос 100 ещё проходит, а 101 возвращает 429 с Retry-After, который клиент может соблюсти. Подтвердите сброс окна — после Retry-After вызовы снова успешны. Отделите дефект от ложного срабатывания, моделируя задокументированный всплеск, который резаться не должен; режется только превышающий лимит трафик. Держите тест стабильным: выделенный ключ и контроль часов. Чтобы судить, не тесен ли лимит, измеряйте долю 429 против реального трафика.
Типичные ошибки
- ✗Принимать любой 429 за успех вместо фиксации точного счёта срабатывания
- ✗Не проверять Retry-After или что окно действительно сбрасывается после
- ✗Считать любое урезание верным, из-за чего ложные срабатывания на честных всплесках незаметны
Уточняющие вопросы
- →Как удержать зависящий от тайминга тест лимита от нестабильности в CI?
- →Какая метрика показывает, что лимит режет легитимных пользователей, а не только злоупотребляющих?
MiddleДизайнИногдаВаша команда выпускает /v2 API, пока /v1 остаётся живой для существующих мобильных клиентов, которые обновляются медленно. В /v2 поле переименовали, а ранее необязательное поле стало обязательным. Спроектируйте, как вы тестируете обратную совместимость: как подтверждаете, что живая /v1 ведёт себя ровно как раньше для старых клиентов, как решаете, аддитивно изменение или ломающее, как прогоняете обе версии параллельно без утечки одной в другую и как ловите ломающее изменение до продакшена. Скажите, что здесь даёт потребительский контрактный тест и как бы вы проверили, что версия выводится из эксплуатации аккуратно, а не выдёргивается из-под вызывающих.
Ваша команда выпускает /v2 API, пока /v1 остаётся живой для существующих мобильных клиентов, которые обновляются медленно. В /v2 поле переименовали, а ранее необязательное поле стало обязательным. Спроектируйте, как вы тестируете обратную совместимость: как подтверждаете, что живая /v1 ведёт себя ровно как раньше для старых клиентов, как решаете, аддитивно изменение или ломающее, как прогоняете обе версии параллельно без утечки одной в другую и как ловите ломающее изменение до продакшена. Скажите, что здесь даёт потребительский контрактный тест и как бы вы проверили, что версия выводится из эксплуатации аккуратно, а не выдёргивается из-под вызывающих.
Зафиксируйте v1: прогоните её контрактные тесты без изменений — старые клиенты видят те же поля, типы и статус-коды. Классифицируйте изменение: новое необязательное поле или эндпоинт аддитивно; переименованное или удалённое поле, ставший обязательным вход или сменившийся тип либо статус — ломающее и не должно попадать в живую версию. Тестируйте v1 и v2 бок о бок, чтобы вызов v1 не получал семантику v2. Потребительские контрактные тесты роняют ломающее изменение в CI до релиза.
Типичные ошибки
- ✗Не перезапускать контрактные тесты v1 после выхода v2, из-за чего регрессии v1 незаметны
- ✗Считать переименованное поле или ставший обязательным вход безопасным изменением на месте
- ✗Тестировать только новейшую версию и никогда две бок о бок
Уточняющие вопросы
- →Какие типы изменений отнесёте к ломающим, а какие к аддитивным, и почему?
- →Как потребительский контрактный тест падает до попадания ломающего изменения в прод?
SeniorДизайнИногдаПлатёжный API предоставляет PUT /accounts/{id} для обновления счёта и DELETE /accounts/{id} для его закрытия, и оба задокументированы как идемпотентные, чтобы клиент мог безопасно повторить запрос после сетевого таймаута. Спроектируйте, как вы проверяете это заявление об идемпотентности, а не только счастливый путь: что проверяете, когда один и тот же PUT отправлен несколько раз, что должно произойти на повторный DELETE уже удалённого счёта, как доказываете, что повтор не срабатывает дважды побочным эффектом (двойное списание или дубликат записи) и как воспроизводите реальный триггер — клиент, повторяющий запрос после таймаута. Скажите, какой статус-код ждёте на каждый повтор по задокументированному контракту и что проверяете о состоянии ресурса после того, как повторы улягутся.
Платёжный API предоставляет PUT /accounts/{id} для обновления счёта и DELETE /accounts/{id} для его закрытия, и оба задокументированы как идемпотентные, чтобы клиент мог безопасно повторить запрос после сетевого таймаута. Спроектируйте, как вы проверяете это заявление об идемпотентности, а не только счастливый путь: что проверяете, когда один и тот же PUT отправлен несколько раз, что должно произойти на повторный DELETE уже удалённого счёта, как доказываете, что повтор не срабатывает дважды побочным эффектом (двойное списание или дубликат записи) и как воспроизводите реальный триггер — клиент, повторяющий запрос после таймаута. Скажите, какой статус-код ждёте на каждый повтор по задокументированному контракту и что проверяете о состоянии ресурса после того, как повторы улягутся.
Повторяйте каждый вызов и сравнивайте итоговое состояние, а не только первый ответ. PUT, отправленный N раз, каждый раз оставляет ресурс в том же состоянии с теми же ответами. Первый DELETE удаляет счёт (200/204); повтор по отсутствующему счёту возвращает задокументированный статус (404 или 204) без лишнего эффекта. Докажите, что повторы не дублируют списание, запись или эффект — через состояние ниже по потоку. Воспроизведите повтор после таймаута и валидируйте схему результата.
Типичные ошибки
- ✗Проверять только равенство ответов, а не итоговое состояние ресурса после повторов
- ✗Ждать ошибку на второй DELETE вместо задокументированного идемпотентного статуса
- ✗Не воспроизводить реальный триггер — повтор клиента после таймаута
Уточняющие вопросы
- →Второй DELETE должен вернуть 404 или 204, и как решить, что верно?
- →Как обнаружить, что повторённый PUT тихо сработал побочным эффектом дважды?