Отладка API-тестов методом белого ящика: от ошибки 503 до исправления кода в проекте aeqs

Курс обучает поиску причин падения автотестов через анализ исходного кода микросервисов. Вы научитесь сопоставлять элементы Java-кода с привычными инструментами (Postman, Swagger) для быстрой локализации изменений в API.

1. Анализ ошибки 503 и первичная диагностика причин падения теста в проекте aeqs

Анализ ошибки 503 и первичная диагностика причин падения теста в проекте aeqs

Вы запускаете автотесты в репозитории aeqs-qa-autotest, ожидая увидеть зеленую галочку успешного создания талона, но вместо этого консоль выдает красный текст: 503 Service Temporarily Unavailable. Первая мысль ручного тестировщика: «Стенд упал, пойду попью кофе». Но стенд работает, соседние тесты проходят, а Swagger открывается. Значит, проблема не в том, что сервер физически выключен. Проблема в том, что наш автотест стучится в дверь, которой больше не существует, или пытается войти не через тот вход.

Чтобы починить тест, нам предстоит залезть в код разработчиков сервиса aeqs-ticket. Но прежде чем открывать чужой репозиторий, нужно четко понять, что именно мы будем там искать.

О чем на самом деле говорит статус 503

Как QA-инженер, вы привыкли работать с вкладкой Network в DevTools. Давайте вспомним, как выглядят типичные ошибки:

* 404 Not Found: Запрос дошел до приложения, контроллер понял, чего вы хотите, но не нашел нужную запись в базе данных (например, талон с таким ID не существует). * 500 Internal Server Error: Запрос дошел до приложения, контроллер начал работу, но внутри кода произошел сбой (например, NullPointerException в Java). * 503 Service Unavailable: Запрос ушел из автотеста, но вообще не дошел до бизнес-логики приложения.

> Ошибка 503 в контексте микросервисов чаще всего означает, что балансировщик нагрузки или API-шлюз (Gateway) получил ваш запрос, посмотрел на него и не смог найти живой сервис, который готов этот запрос обработать. > > — Базовый принцип сетевой маршрутизации

Если стенд в целом жив, то 503 ошибка для конкретного эндпоинта — это классический симптом того, что контракт изменился, а автотест об этом не знает.

!Механизм возникновения ошибки 503 на шлюзе

Анатомия упавшего запроса

Давайте препарируем запрос, на котором упал наш тест: GET https://aeqs-ticket.dev.aeqs.corp.dev.vtb/v1/apt/ticket/create

Чтобы понять, где произошел разрыв связи, переведем этот URL на язык привычного вам Postman. Запрос состоит из трех критически важных частей, любая из которых могла измениться.

!Анатомия API-запроса в сравнении с Postman

1. Хост (Окружение)

https://aeqs-ticket.dev.aeqs.corp.dev.vtb

В Postman это обычно скрыто за переменной окружения, например {{base_url}}. Хост указывает, на какой конкретно сервер (или кластер) нужно отправить запрос. В микросервисной архитектуре имя сервиса часто зашито прямо в URL. Если инфраструктурная команда переименовала сервис или перенесла его на другой кластер, старый хост перестанет отвечать, и шлюз вернет 503.

2. Путь (Роутинг)

/v1/apt/ticket/create

В Postman это то, что вы пишете в строке запроса рядом с методом. Путь — это точный адрес метода внутри приложения. Он делится на базовый путь сервиса (/v1/apt/) и специфичный эндпоинт (/ticket/create). Если разработчики выпустили вторую версию API, путь мог измениться на /v2/apt/....

3. HTTP-Метод

GET

Это выпадающий список слева от URL в Postman. И здесь кроется главная зацепка нашего кейса.

Создание сущности (талона) через метод GET — это архитектурный антипаттерн (code smell). Метод GET предназначен для получения данных, он не должен изменять состояние системы. Для создания традиционно используется POST. Очень вероятно, что разработчики провели рефакторинг, привели API к стандартам REST и изменили метод на POST, убрав слово create из пути.

Формируем гипотезы для "Белого ящика"

В парадигме «черного ящика» (когда мы тестируем только через интерфейсы) при такой ошибке мы бы пошли дергать разработчика или искать обновленный Swagger. Но Swagger обновляется не всегда синхронно с кодом, а разработчик может быть занят.

В парадигме «белого ящика» мы берем инициативу в свои руки. У нас есть доступ к репозиторию aeqs-ticket. На основе анализа запроса мы формулируем три гипотезы, которые пойдем проверять прямо в исходном коде:

  • Гипотеза изменения контроллера: Разработчики изменили метод с GET на POST и/или поменяли сам путь (например, на POST /v1/apt/ticket).
  • Гипотеза изменения базового пути (Context Path): Изменилась глобальная настройка сервиса, и теперь все запросы должны начинаться с /v2/ или /api/v1/.
  • Гипотеза изменения конфигурации хоста: Сервис переехал, и нам нужно обновить базовый URL в конфигурации наших автотестов.
  • В следующей главе мы разберем, как именно запрос путешествует от нашего Java-теста до сервиса aeqs-ticket, чтобы понять, в каких именно файлах разработчиков искать ответы на эти гипотезы.

    2. Архитектура взаимодействия микросервисов: как запрос проходит путь от автотеста до сервиса aeqs-ticket

    Архитектура взаимодействия микросервисов: как запрос проходит путь от автотеста до сервиса aeqs-ticket

    Когда вы тестируете API вручную через Postman, процесс выглядит магически просто: нажал кнопку «Send» — получил JSON с ответом. Создается иллюзия, что ваш компьютер связывается напрямую с приложением, словно вы передаете письмо лично в руки адресату. Но в микросервисной архитектуре между вашим автотестом и бизнес-логикой лежит сложная инфраструктурная трасса. И именно на этой трассе наш тест из прошлой главы споткнулся, получив ошибку 503.

    Чтобы понять, как чинить автотесты «изнутри», нам нужно перестать смотреть на систему как на черный ящик и разобраться, как именно путешествует наш HTTP-запрос.

    От монолита к городу микросервисов

    Если монолитное приложение — это огромный супермаркет, где все отделы находятся под одной крышей и за одними дверями, то микросервисная архитектура — это целый город специализированных магазинов.

    В проекте aeqs сервис aeqs-ticket (регистрация талонов) — это лишь одно из множества зданий в этом городе. Рядом стоят сервисы уведомлений, управления пользователями, аналитики и так далее. Ваш автотест на Java (aeqs-qa-autotest) находится за пределами этого города. Ему нужно как-то попасть внутрь и найти правильное здание.

    API Gateway: Главные ворота города

    Первое, с чем сталкивается запрос из автотеста, — это не сам код разработчиков, а API Gateway (или Ingress-контроллер).

    > API Gateway (Шлюз) — это специализированный сервер, выступающий единой точкой входа во внутреннюю сеть микросервисов. Он принимает все внешние запросы и работает как регулировщик, перенаправляя их нужным внутренним сервисам.

    Вспомните вкладку Network в DevTools вашего браузера. Если вы откроете любой запрос и посмотрите на поле Remote Address, вы увидите там IP-адрес. В микросервисной архитектуре это почти никогда не адрес самого сервиса (например, aeqs-ticket). Это IP-адрес шлюза.

    Шлюз скрывает внутреннюю структуру системы от внешнего мира. Автотест знает только публичный адрес https://aeqs-ticket.dev.aeqs.corp.dev.vtb, стучится по нему, а дальше всю работу берет на себя шлюз.

    Маршрутизация: Как шлюз находит нужный сервис

    Получив запрос, API Gateway должен понять, кому именно в кластере его передать. Для этого он использует правила маршрутизации (Routing).

    Шлюз смотрит на URL запроса и сверяется со своей внутренней картой:

  • По хосту: Если запрос пришел на aeqs-ticket.dev.aeqs.corp.dev.vtb, шлюз понимает, что его нужно направить в группу контейнеров, отвечающих за работу с талонами.
  • По пути (Path): Иногда шлюз маршрутизирует запросы на основе пути. Например, всё, что начинается с /v1/apt/ticket/, уходит в сервис aeqs-ticket, а /v1/apt/users/ — в сервис aeqs-users.
  • | Этап | Инструмент ручного тестировщика | Аналогия в архитектуре | | :--- | :--- | :--- | | Формирование запроса | Коллекции и переменные окружения в Postman | Код автотеста (aeqs-qa-autotest), собирающий URL и тело запроса. | | Отправка и доставка | Вкладка Network в DevTools (видимый IP) | API Gateway, принимающий запрос и маршрутизирующий его по внутренним правилам. | | Обработка логики | Swagger (описание доступных эндпоинтов) | Внутренний код микросервиса (aeqs-ticket), который принимает запрос от шлюза. |

    После того как шлюз определил нужный сервис, он проксирует (пересылает) запрос внутрь кластера на конкретный работающий экземпляр приложения. И только в этот момент запрос достигает Java-кода, написанного разработчиками.

    Локализация проблемы: где упал наш тест?

    Теперь вернемся к нашему упавшему тесту. Мы отправляли запрос: GET https://aeqs-ticket.dev.aeqs.corp.dev.vtb/v1/apt/ticket/create

    Давайте проследим его путь и поймем, в какой момент возникла ошибка 503 (Service Unavailable):

  • Автотест сформировал HTTP-запрос.
  • Запрос ушел по сети и достиг API Gateway проекта aeqs.
  • Шлюз прочитал хост и путь, заглянул в свои правила маршрутизации.
  • Шлюз попытался передать запрос внутреннему сервису aeqs-ticket... и здесь произошел обрыв.
  • Ошибка 503 означает, что шлюз работает нормально, но он не смог доставить запрос до бизнес-логики. Почему это могло произойти?

    * Инфраструктурная причина: Внутренний контейнер сервиса aeqs-ticket упал (например, из-за нехватки памяти) или прямо сейчас идет деплой новой версии. В этом случае автотесты позеленеют сами через некоторое время. * Архитектурная причина (изменение контракта): Разработчики изменили конфигурацию сервиса. Например, они переименовали базовый путь, и теперь сервис ждет запросы не на /v1/apt/ticket/..., а на /api/v2/ticket/.... Шлюз пытается отправить запрос по старому маршруту, не находит там активного сервиса и возвращает нам 503.

    Если инфраструктурные проблемы решаются перезапуском тестов (или DevOps-инженерами), то изменения контрактов требуют нашего прямого вмешательства. Чтобы перестать гадать и точно узнать, что именно изменилось, нам нужно заглянуть внутрь самого здания — в исходный код разработчиков.

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

    3. Метод белого ящика в API-тестировании: переход от черного ящика Postman к чтению логики приложения

    Метод белого ящика в API-тестировании: переход от черного ящика Postman к чтению логики приложения

    Вы отправляете запрос в Postman, получаете ошибку 503, открываете Swagger, чтобы сверить контракт, а он либо не загружается, либо показывает старую версию. Знакомая ситуация? В этот момент тестировщик, привыкший работать с приложением снаружи, упирается в невидимую стену. Начинается перебор вариантов: а что если поменять метод на POST? А что если убрали /v1/ из пути? Этот процесс угадывания — главная проблема подхода «черного ящика» при отладке упавших автотестов.

    Чтобы перестать гадать и точно узнать, почему наш тест создания талона в проекте aeqs перестал работать, нам нужно сменить парадигму. Мы переходим от использования внешних инструментов к чтению исходного кода разработчиков — к методу «белого ящика».

    Границы черного ящика

    В ручном тестировании мы привыкли воспринимать микросервис как черный ящик. У него есть входы (URL, заголовки, тело запроса) и выходы (HTTP-статусы, JSON-ответы). Postman — идеальный инструмент для такого подхода. Вы отправляете данные в ящик и смотрите, что из него выпадет.

    Пока система работает штатно, черного ящика достаточно. Но когда мы получаем 503 ошибку, это означает, что наш запрос даже не смог попасть внутрь этого ящика — API Gateway отбросил его на подступах, потому что не нашел соответствия в маршрутах.

    В парадигме черного ящика у нас есть только один источник информации о том, как должен работать сервис — это документация, чаще всего Swagger (OpenAPI). Но здесь кроется ловушка.

    > Swagger — это не истина в последней инстанции. Это лишь визуальная проекция кода, удобный UI для чтения контрактов. Истина живет только в самом исходном коде.

    Если разработчик изменил логику в репозитории aeqs-ticket, но CI/CD пайплайн сборки документации сломался, или изменения еще не доехали до тестового стенда, ваш Swagger будет врать. Вы будете отправлять GET /v1/apt/ticket/create, как написано в документации, и получать 503.

    Белый ящик: код как единственный источник правды

    Метод белого ящика (White-box testing) в контексте автоматизации API — это не обязательно написание unit-тестов. Для нас это умение открыть репозиторий разработчиков (в нашем случае aeqs-ticket) и прочитать его как самую актуальную, никогда не врущую документацию.

    Давайте сравним, как выглядят одни и те же сущности с точки зрения двух подходов.

    | Сущность | В черном ящике (Postman / Swagger) | В белом ящике (Исходный код Java) | | :--- | :--- | :--- | | Группировка запросов | Папки и коллекции в Postman, теги в Swagger. | Контроллеры (Controllers) — классы, объединяющие родственные методы (например, TicketController). | | Эндпоинт и Метод | Строка URL и выпадающий список (GET/POST) в интерфейсе. | Аннотации над методами — специальные метки в коде (например, @PostMapping("/create")). | | Базовый URL (Хост) | Переменные окружения (Environment Variables), например {{base_url}}. | Конфигурационные файлы (например, application.yml), где прописаны правила маршрутизации. | | Тело запроса (Body) | JSON, который вы пишете руками, сверяясь с примером в Swagger. | DTO (Data Transfer Object) — классы, жестко описывающие, какие поля и типы данных ожидает сервис. |

    Открыв код, мы минуем посредников. Нам не нужно гадать, поменяли ли разработчики метод с GET на POST при рефакторинге технического долга. Мы просто найдем нужный метод в коде и увидим это своими глазами.

    Как это поможет исправить наш тест?

    Вспомним нашу проблему: автотест в репозитории aeqs-qa-autotest падает с ошибкой 503 при попытке выполнить GET https://aeqs-ticket.dev.aeqs.corp.dev.vtb/v1/apt/ticket/create.

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

  • Отказ от угадывания: Мы закрываем Postman и перестаем дергать неработающий эндпоинт.
  • Поиск точки входа в коде: Мы идем в репозиторий aeqs-ticket и ищем класс, который отвечает за обработку талонов (контроллер).
  • Анализ контракта: Внутри контроллера мы находим метод создания талона и смотрим, какой HTTP-метод и какой путь к нему сейчас привязаны разработчиками.
  • Проверка конфигураций: Если путь в контроллере совпадает с нашим, мы проверяем конфигурационные файлы — возможно, изменился глобальный префикс сервиса (например, /v1/apt/ превратился в /v2/apt/).
  • Переход к белому ящику делает вас независимым от актуальности документации. Вы начинаете видеть систему глазами разработчика, понимая, как именно входящий HTTP-запрос превращается в вызов конкретного куска кода.

    Следующим шагом мы применим этот подход на практике: откроем репозиторий aeqs-ticket и научимся читать Java-контроллеры, чтобы найти актуальный путь и метод для создания талона.

    4. Поиск актуальных эндпоинтов в коде разработчиков: работа с контроллерами как с живой документацией Swagger

    Поиск актуальных эндпоинтов в коде разработчиков: работа с контроллерами как с живой документацией Swagger

    Вы когда-нибудь задумывались, откуда берется красивая страница Swagger UI, в которой вы привыкли смотреть контракты API? Она не пишется вручную. Специальные библиотеки (например, Springfox или OpenAPI) сканируют исходный код приложения при его запуске и превращают его в удобный веб-интерфейс. Но если библиотека настроена криво, кэш браузера не обновился или сервис вообще не смог запуститься в тестовой среде, Swagger будет врать или будет недоступен. Исходный код — не врет никогда.

    В этой главе мы применим метод белого ящика на практике: откроем репозиторий aeqs-ticket и найдем реальный эндпоинт создания талона, чтобы понять, почему наш автотест падает.

    Где живут эндпоинты: ищем контроллер

    Опираясь на прошлую главу, мы помним, что контроллер в коде — это логический аналог папки (коллекции) в Postman, группирующий запросы по смыслу. В проекте aeqs-ticket (как и в большинстве Java-микросервисов) используется фреймворк Spring Boot.

    Чтобы найти нужный класс среди сотен файлов, нам нужно искать специфичную метку.

    > Аннотация (Annotation) — это специальный маркер в коде (начинается с символа @), который передает метаданные фреймворку, указывая, как именно нужно обрабатывать класс, метод или переменную.

    Классы, которые принимают HTTP-запросы извне, всегда помечаются аннотацией @RestController. Если мы откроем репозиторий разработчиков и введем в поиск по файлам слово Ticket, мы быстро обнаружим файл TicketController.java. Это и есть наша точка входа.

    Чтение аннотаций: как Java понимает HTTP-запросы

    Внутри контроллера лежат методы (функции), выполняющие бизнес-логику. Чтобы API Gateway и встроенный сервер микросервиса поняли, какой именно метод нужно вызвать при входящем запросе, разработчики используют аннотации маппинга (маршрутизации на уровне кода).

    Именно эти аннотации сканирует Swagger для построения своей документации. Давайте сопоставим их с привычным интерфейсом Postman:

    | Аннотация в Java коде | Аналог в Postman | Что означает для маршрутизации | |---|---|---| | @RequestMapping("/v1/apt/ticket") | Базовый URL папки | Задает общий префикс пути для всех запросов внутри этого контроллера. | | @GetMapping("/info") | Выбор метода GET | Метод сработает только при GET-запросе на добавленный путь. | | @PostMapping("/") | Выбор метода POST | Метод сработает при POST-запросе. | | @PathVariable("id") | Path Variables (например, :id) | Извлекает переменную прямо из URL (например, /ticket/123). |

    Разгадка нашей ошибки: смотрим в исходный код

    Давайте заглянем внутрь найденного TicketController.java. Наша задача — найти, куда делся метод, который раньше откликался на путь /create.

    Что мы видим? Разработчики исправили технический долг, о котором мы говорили в самом начале курса!

  • Изменился HTTP-метод: Вместо архитектурно неверного GET для создания сущности теперь используется правильный @PostMapping.
  • Изменился путь: У аннотации @PostMapping нет дополнительных параметров пути в скобках. Это значит, что путь к этому методу состоит только из базового пути контроллера. То есть эндпоинт теперь выглядит так: POST /v1/apt/ticket. Слово /create было удалено, так как сам метод POST уже подразумевает создание.
  • Изменилось тело запроса: Аннотация @RequestBody TicketDto request говорит о том, что теперь метод ждет данные для создания талона не в параметрах URL, а в теле запроса (JSON-формат), структура которого описана в классе TicketDto.
  • Стыковка с реальностью и новый вопрос

    Теперь картина проясняется. Наш автотест по-старинке отправляет запрос: GET https://aeqs-ticket.dev.aeqs.corp.dev.vtb/v1/apt/ticket/create

    А микросервис aeqs-ticket теперь ждет: POST https://aeqs-ticket.dev.aeqs.corp.dev.vtb/v1/apt/ticket

    Мы нашли причину рассинхронизации. Но здесь возникает серьезный архитектурный парадокс.

    Если мы отправляем запрос на несуществующий путь (/create), микросервис должен был вернуть нам стандартную ошибку 404 Not Found. Если бы мы угадали путь, но отправили GET вместо POST, микросервис вернул бы 405 Method Not Allowed.

    Но в самом начале мы зафиксировали, что автотест падает с ошибкой 503 Service Temporarily Unavailable. Как мы помним из схемы взаимодействия, эта ошибка означает, что API Gateway вообще не смог донести наш запрос до сервиса aeqs-ticket.

    Почему изменение пути внутри Java-контроллера привело к тому, что шлюз перестал «видеть» сервис? Чтобы ответить на этот вопрос, нам придется выйти за пределы Java-кода и заглянуть в конфигурационные файлы маршрутизации, что мы и сделаем на следующем шаге.

    5. Конфигурации и маршрутизация запросов: сопоставление переменных окружения и путей в исходном коде

    Конфигурации и маршрутизация запросов: сопоставление переменных окружения и путей в исходном коде

    В прошлой главе мы заглянули в исходный код aeqs-ticket и обнаружили, что разработчики удалили метод GET с окончанием /create, заменив его на POST. Но здесь возникает логическое противоречие. Если запрос дошел до микросервиса, а нужного метода там больше нет, приложение должно было вернуть ошибку 404 (Not Found) или 405 (Method Not Allowed). Почему же наш автотест столкнулся с ошибкой 503 (Service Unavailable), которая, как мы выяснили ранее, генерируется на уровне API Gateway?

    Чтобы разгадать этот парадокс, нам нужно выйти за пределы контроллеров и посмотреть на приложение глобально — так, как на него смотрит шлюз.

    Spring Profiles: переменные окружения в мире кода

    Когда вы тестируете API вручную через Postman, вы не хардкодите домены в каждом запросе. Вы используете переменные окружения (Environments): для тестового стенда у вас выбран Dev Environment со значением {{base_url}} = https://dev.api..., а для продакшена — Prod Environment с {{base_url}} = https://prod.api....

    Разработчики используют абсолютно тот же подход. В микросервисах на Java (в частности, на фреймворке Spring Boot) роль таких окружений играют профили (Profiles), а сами переменные хранятся в конфигурационных файлах.

    В репозитории aeqs-ticket в папке src/main/resources/ вы найдете несколько файлов:

  • application.yml — базовые настройки по умолчанию (аналог глобальных переменных).
  • application-dev.yml — настройки для тестового стенда (переопределяют базовые).
  • application-prod.yml — настройки для боевой среды.
  • > Конфигурационный файл (application.yml) — это текстовый файл в формате YAML, в котором задаются глобальные параметры микросервиса: порт для запуска, доступы к базам данных, настройки безопасности и правила формирования базовых путей.

    Давайте заглянем в файл application-dev.yml, который применяется на том самом стенде, где упал наш автотест:

    Здесь нас интересует параметр context-path.

    Анатомия пути: как собирается итоговый URL

    В Postman итоговый URL собирается из переменной окружения и пути конкретного запроса. В коде белого ящика происходит то же самое. Итоговый путь, по которому микросервис будет ждать запрос, складывается из двух слоев:

  • Глобальный префикс приложения — берется из server.servlet.context-path в конфигурационном файле.
  • Локальный путь контроллера — берется из аннотаций @RequestMapping и @PostMapping в Java-классе.
  • | Уровень | Инструмент ручного тестирования | Исходный код разработчиков | Значение в проекте aeqs | | :--- | :--- | :--- | :--- | | Глобальный | Переменная {{base_url}} | application.yml (context-path) | /v2/apt | | Локальный | Путь в сохраненном запросе | Аннотации в TicketController | /ticket |

    Если разработчик написал в контроллере @PostMapping("/ticket"), это не значит, что сервис доступен по адресу http://host/ticket. Фреймворк автоматически приклеит к нему глобальный префикс из конфигурации.

    Разгадка парадокса 503

    Теперь мы можем сложить все кусочки пазла воедино и понять, почему API Gateway оборвал соединение.

    Наш автотест отправлял запрос по старому адресу: https://aeqs-ticket.dev.aeqs.corp.dev.vtb/v1/apt/ticket/create

    Шлюз (API Gateway) получил этот запрос, извлек из него путь /v1/apt/... и обратился к внутреннему реестру микросервисов, чтобы узнать, какому контейнеру передать трафик.

    Однако, как мы увидели в application-dev.yml, разработчики в новом релизе изменили глобальный префикс приложения (версионирование API) с /v1/apt на /v2/apt. При запуске обновленный сервис aeqs-ticket сообщил шлюзу: "Я готов принимать запросы, но только те, которые начинаются с /v2/apt".

    Что произошло в момент запуска теста:

  • Шлюз ищет в кластере сервис, который зарегистрирован на обработку маршрута /v1/apt.
  • Такого сервиса больше нет (старая версия удалена, новая слушает /v2/apt).
  • Шлюз честно отвечает автотесту: 503 Service Unavailable (маршрут не найден, сервис недоступен).
  • Запрос даже не дошел до Java-кода контроллеров! Именно поэтому мы не получили 404 ошибку. Шлюз отбросил запрос на подлете, так как глобальные правила маршрутизации изменились.

    Сборка актуального контракта

    Используя метод белого ящика, мы полностью восстановили картину. Нам больше не нужно гадать или ждать, когда обновится Swagger. Мы прочитали код и конфигурации, и теперь можем собрать правильный запрос для исправления автотеста.

  • Хост: остается прежним (https://aeqs-ticket.dev.aeqs.corp.dev.vtb).
  • Глобальный путь: из application-dev.yml мы узнали новый префикс (/v2/apt).
  • Путь контроллера: из TicketController мы выяснили актуальный эндпоинт (/ticket).
  • Метод: из аннотации @PostMapping мы поняли, что теперь нужно использовать POST.
  • Итоговый актуальный запрос, который ожидает сервис: POST https://aeqs-ticket.dev.aeqs.corp.dev.vtb/v2/apt/ticket

    Мы локализовали проблему, поняли механику ее возникновения на всех архитектурных слоях и нашли актуальные данные. Остался последний шаг — перенести эти знания в репозиторий автотестов и заставить наш падающий тест снова стать зеленым.

    6. Практика исправления упавшего автотеста: синхронизация репозитория aeqs-qa-autotest с изменениями в коде сервиса

    Практика исправления упавшего автотеста: синхронизация репозитория aeqs-qa-autotest с изменениями в коде сервиса

    Диагноз поставлен, причина найдена. Благодаря нашему расследованию в репозитории разработчиков aeqs-ticket, мы точно знаем, почему тест получал ошибку 503. Старый маршрут GET /v1/apt/ticket/create больше не существует. Разработчики обновили глобальный префикс в конфигурациях и изменили метод в контроллере. Наша новая цель — POST /v2/apt/ticket. Теперь нам предстоит открыть репозиторий с автотестами aeqs-qa-autotest и привести код в соответствие с реальностью.

    Поиск ошибки — это аналитика, а исправление автотеста — это инженерия. В этой главе мы шаг за шагом трансформируем устаревший тестовый скрипт, опираясь на ваши знания ручного тестирования.

    Анатомия устаревшего теста

    Давайте посмотрим, как выглядел наш падающий тест в коде. Большинство современных фреймворков для API-тестирования на Java (например, RestAssured) используют синтаксис, который интуитивно понятен и читается почти как обычный английский текст.

    Этот код — точная копия того, что вы делали бы в Postman:

  • Выбрали окружение с переменной {{dev_host}}.
  • Ввели URL.
  • Выбрали метод GET из выпадающего списка.
  • Нажали «Send» и проверили, что статус ответа равен 200.
  • Чтобы исправить тест, нам нужно обновить три ключевых компонента: базовый путь, HTTP-метод с эндпоинтом и тело запроса.

    Шаг 1: Обновление маршрутизации

    В прошлой главе мы выяснили, что в файле application-dev.yml разработчики изменили context-path. Наш тест должен отразить это изменение.

    Вместо того чтобы хардкодить полный URL в каждой строке, грамотные автотесты разделяют его на части. Мы меняем базовый путь (префикс) с первой версии API на вторую:

    Далее мы обновляем сам эндпоинт и метод вызова, опираясь на аннотацию @PostMapping("/ticket"), которую мы нашли в контроллере разработчиков.

    Шаг 2: Формирование тела запроса (Payload)

    Здесь кроется самое важное концептуальное отличие. Метод GET передавал данные прямо в URL (или вообще их не требовал, что было архитектурной ошибкой). Метод POST подразумевает передачу полезной нагрузки — тела запроса (Request Body).

    В Postman для этого вы переходите на вкладку Body, выбираете raw и формат JSON, после чего руками пишете структуру:

    В Java-автотестах мы не пишем JSON руками в виде строк — это хрупко и неудобно поддерживать. Вместо этого мы используем объекты DTO (Data Transfer Object), о которых говорили в третьей главе.

    > Сериализация — это процесс автоматического преобразования Java-объекта (DTO) со всеми его полями в текстовый формат JSON перед отправкой по сети.

    В нашем репозитории aeqs-qa-autotest уже должен быть класс, описывающий создание талона. Мы создаем экземпляр этого класса и передаем его в метод .body(). Фреймворк сам выполнит сериализацию.

    Указание ContentType.JSON — это прямой аналог выбора формата "JSON" в выпадающем списке Postman. Без этого заголовка сервер (или API Gateway) может отклонить запрос с ошибкой 415 Unsupported Media Type.

    Итоговый рефакторинг: собираем всё вместе

    Теперь объединим все наши изменения в единый, рабочий тест. Сравним, как изменился подход.

    | Элемент запроса | Аналог в Postman | Как было в коде (GET) | Как стало в коде (POST) | | :--- | :--- | :--- | :--- | | Префикс API | Часть URL после хоста | .basePath("/v1/apt") | .basePath("/v2/apt") | | Метод и путь | Выпадающий список + URL | .get("/ticket/create") | .post("/ticket") | | Заголовки | Вкладка Headers | отсутствовали | .contentType(ContentType.JSON) | | Тело запроса | Вкладка Body (raw JSON) | отсутствовало | .body(payload) | | Ожидаемый статус | Вкладка Tests | .statusCode(200) | .statusCode(200) |

    А вот как выглядит финальный, исправленный код нашего автотеста:

    Мы запускаем тест. Ошибка 503 Service Temporarily Unavailable исчезает. API Gateway успешно находит маршрут /v2/apt, перенаправляет запрос в сервис aeqs-ticket, контроллер принимает метод POST на эндпоинт /ticket, десериализует наш JSON и успешно создает талон. В консоли загорается зеленый свет.

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

    7. Лучшие практики поддержки API-тестов и предотвращение деградации тестового покрытия

    Лучшие практики поддержки API-тестов и предотвращение деградации тестового покрытия

    По статистике, инженеры по автоматизации тратят до 70% своего рабочего времени не на написание новых тестов, а на починку старых. Когда автотестов в проекте aeqs становятся сотни, каждое изменение в коде разработчиков может вызывать эффект домино: красные дашборды, заблокированные релизы и долгие часы дебага. Вы уже научились находить причину поломки в исходном коде, но настоящий профессионализм заключается в том, чтобы не допускать этих поломок вовсе.

    От реактивности к проактивности: концепция Shift-Left

    Вся наша история с ошибкой 503 при создании талона была примером реактивного подхода. Разработчики изменили контракт (перешли с GET на POST и поменяли префикс v1 на v2), выкатили изменения на тестовый стенд, автотест упал, и только после этого мы начали расследование.

    Чтобы разорвать этот порочный круг, в современной инженерии применяется подход Shift-Left (сдвиг влево). Если представить процесс разработки как линию времени слева направо (от идеи до релиза), то сдвиг влево означает перенос тестирования на самые ранние этапы. Мы должны узнавать об изменении API до того, как код попадет на стенд.

    > Ошибка, найденная на этапе написания кода, стоит в 10 раз дешевле, чем ошибка, найденная при падении автотеста на сервере, и в 100 раз дешевле ошибки на проде. > > Национальный институт стандартов и технологий США (NIST)

    Единый источник истины: общие DTO вместо копирования

    В предыдущей главе мы исправили тест, создав класс TicketCreateRequest (DTO) в репозитории aeqs-qa-autotest. Но откуда мы взяли его структуру? Мы посмотрели в код разработчиков aeqs-ticket и воссоздали его у себя.

    Это классическая проблема дублирования. Если завтра разработчик добавит в талон обязательное поле priority, наш тест снова упадет, потому что наш QA-DTO ничего об этом не знает.

    Решение — использование общих библиотек (Shared Libraries).

    | Подход | Как это работает | Плюсы | Минусы | | :--- | :--- | :--- | :--- | | Изолированные репозитории (Черный ящик) | QA пишет свои DTO, опираясь на Swagger или JSON-ответы. | Полная независимость команд. | Рассинхронизация. Тесты падают при любом изменении контракта. | | Общая библиотека (Белый ящик) | DTO выносятся в отдельный Java-модуль (например, aeqs-api-models), который подключают и разработчики, и QA. | Единый источник истины. Изменился код — тесты сразу «видят» новые поля. | Требует настройки инфраструктуры и договоренностей с разработчиками. |

    При подходе с общей библиотекой, если разработчик меняет тип поля, ваш код автотеста просто не скомпилируется. Вы узнаете о проблеме еще в своей IDE (среде разработки), а не из отчета об упавшем тесте.

    Участие QA в Code Review разработчиков

    Теперь, когда вы умеете читать аннотации контроллеров и конфигурационные файлы, вам открыт доступ к самому мощному инструменту предотвращения багов — Pull Requests (PR) разработчиков.

    Pull Request — это запрос на слияние нового кода с основной веткой проекта. Раньше вы могли игнорировать PR разработчиков сервиса aeqs-ticket, считая, что там «сложная бизнес-логика». Но теперь вы знаете, куда смотреть.

    Открывая PR разработчика, обращайте внимание на маркеры изменения контрактов:

  • Появились ли новые аннотации @PostMapping или @GetMapping?
  • Изменились ли пути внутри @RequestMapping?
  • Были ли затронуты файлы application.yml (особенно параметры вроде context-path)?
  • Если вы видите, что разработчик удаляет старый эндпоинт и добавляет новый, вы можете прямо в комментариях к коду написать: «Это изменение сломает автотест создания талона. Давай сначала обновим общую библиотеку контрактов». Это и есть настоящий Shift-Left на практике.

    Контроль деградации тестового покрытия

    Когда проект активно развивается, возникает риск деградации тестов. Это явление, при котором старые тесты отключаются (например, помечаются аннотацией @Ignore до лучших времен), а новые эндпоинты не покрываются проверками. В результате зеленая галочка в CI/CD создает ложное чувство безопасности.

    Чтобы управлять этим процессом, необходимо внедрить метрику покрытия API.

    Где:

  • — процент покрытия API автотестами.
  • — количество уникальных эндпоинтов, на которые написан хотя бы один позитивный автотест.
  • — общее количество активных эндпоинтов в приложении (вычисляется путем сканирования всех @RestController в коде).
  • Если в сервисе регистрации талонов 20 эндпоинтов, а тесты дергают только 15, ваше покрытие — 75%. Если разработчики добавляют еще 5 новых методов, а вы не пишете новые тесты, знаменатель растет, и покрытие падает до 60%. Интеграция этой формулы в ваши отчеты позволит наглядно показывать бизнесу, когда команде автоматизации нужно выделить время на актуализацию тестов.

    Финал пути: от ошибки к пониманию системы

    Ваш путь начался с непонятной ошибки 503 и красного крестика в логах. Будучи мануальным тестировщиком, вы бы пошли к разработчику с вопросом «почему не работает Swagger?».

    Но применив метод белого ящика, вы:

  • Поняли, что запрос теряется на уровне API Gateway.
  • Открыли исходный код и нашли актуальный контроллер.
  • Разобрались, как конфигурации влияют на маршрутизацию.
  • Самостоятельно исправили автотест.
  • Выстроили процесс, который предотвратит подобные сбои в будущем.
  • Граница между тестировщиком черного ящика и инженером по автоматизации лежит не в знании языка программирования. Она лежит в готовности заглянуть под капот системы, понять архитектуру микросервисов и говорить с разработчиками на языке их собственного кода.