Анатомия падения: чтение логов и расшифровка Stack Trace
Анатомия падения: чтение логов и расшифровка Stack Trace
В отчете Allure горит красный статус, а в логах красуется пугающее: Cannot invoke "..." because the return value of "..." is null. Для начинающего автоматизатора это выглядит как тупик. Кажется, что тест просто «сломался». Но на самом деле, современная Java не просто говорит вам, что произошла ошибка — она дает точные GPS-координаты места, где код шагнул в пустоту.
Наша задача сегодня — научиться читать эти координаты. Мы разберем реальное падение теста AEQS-11 (получение списка талонов), поймем механику возникновения NullPointerException и разберемся, почему фреймворк пытался выполнить обреченное действие ровно пять раз.
Расшифровка приговора: что говорит лог?
Давайте посмотрим на ошибку из вашего отчета Allure под микроскопом. Начиная с 14-й версии, Java внедрила механизм Helpful NullPointerExceptions. Вместо безликого сообщения система выдает детальное предложение, состоящее из двух частей: Следствия и Причины.
Cannot invoke "ru.vtb.at.commons.api.services.fegs_backend_arm_aggregation.models.EventResponseData.getWaitCountLastTRM()" because the return value of "ru.vtb.at.commons.api.steps.BackendArmAggregationSteps.event(String, ru.vtb.at.commons.enums.EventType)" is null
Разрежем эту фразу пополам по слову because:
- Следствие (Cannot invoke...): Java говорит: «Я не могу вызвать метод
getWaitCountLastTRM()у объекта типаEventResponseData». - Причина (because...): «Потому что результат выполнения метода
event(...)оказался равенnull(пустоте)».
В Java нельзя попросить «ничто» сделать «что-то». Если переменная или результат метода указывает на null, попытка поставить точку и вызвать следующий метод неминуемо приведет к взрыву.
Проекция на код: где именно мы упали?
Теперь перенесем это понимание на ваш код. Ошибка произошла на шаге 2, внутри метода processTicket, который использует библиотеку Awaitility для ожидания нужного состояния.
await("Ожидаем процессинга талона")
.atMost(10, SECONDS)
.pollInterval(2, SECONDS)
.execute(() -> event(session, eventType).getWaitCountLastTRM(), equalTo(ticketsLeft));
Смотрим на лямбда-выражение внутри execute: event(session, eventType).getWaitCountLastTRM().
Это классическая цепочка вызовов (method chaining). Выполнение идет слева направо:
- Сначала вызывается
event(session, eventType). Этот метод делает GET-запрос к API/operator/eventи должен вернуть объектEventResponseData. - Затем у этого возвращенного объекта вызывается
.getWaitCountLastTRM().
Лог Allure четко сказал нам: шаг 1 вернул null. Следовательно, на шаге 2 код попытался сделать null.getWaitCountLastTRM().
Анатомия ожидания: почему ошибка повторилась 5 раз?
Вы заметили странную деталь: шаг «Выполнить операцию с талоном» в отчете дублируется до 5 раз, и каждый раз возвращается null. Это не баг отчета, это прямое следствие того, как работает Awaitility.
Awaitility — это инструмент для тестирования асинхронных систем. Когда мы переводим талон в статус «На обслуживании» (ожидаем статус 4), бэкенду (backend-director или backend-arm-aggregation) нужно время на обработку. Состояние в базе данных меняется не мгновенно.
Разберем настройки вашего ожидания:
atMost(10, SECONDS)— максимальное время, которое тест готов ждать.pollInterval(2, SECONDS)— интервал опроса (polling). Как часто тест будет «дергать» систему с вопросом «Уже готово?».
Как это выглядит в динамике:
- 0 сек: Первый вызов
event(). Возвращаетсяnull. Awaitility перехватывает ошибку, понимает, что условие не выполнено, и ждет. - 2 сек: Второй вызов. Снова
null. - 4 сек: Третий вызов. Снова
null. - 6 сек: Четвертый вызов. Снова
null. - 8 сек: Пятый вызов. Снова
null. - 10 сек: Время вышло (таймаут). Awaitility сдается и пробрасывает накопленную ошибку наружу. Тест падает.
Именно поэтому вы видите 5 попыток. Фреймворк честно пытался дождаться момента, когда API начнет отдавать корректные данные, но чуда не произошло.
Что делать дальше? Формируем гипотезы
Мы выяснили где и как упал тест. Вызов API /operator/event возвращает пустоту вместо ожидаемого объекта. Но мы пока не знаем почему.
Отладка — это процесс проверки гипотез. Вот основные подозреваемые:
- Проблема с авторизацией (session): Возможно,
sessionId, полученный при старте сервиса для пользователяAUTO_TEST_1, невалиден, истек или не имеет прав на отделение 9111. - Неверный контракт (eventType): Переданный
START_PROCESS_TICKETможет не поддерживаться текущей версией API, и сервер возвращает пустой ответ или ошибку 400/500, которую клиентский код (методevent) молча проглатывает и возвращаетnull. - Рассинхронизация сервисов: Талон создан в
backend-director, но информация о нем еще не доехала доbackend-arm-aggregation, к которому мы обращаемся.
Где искать ответы (работа с документацией): Если вы оказались в такой ситуации, ваш следующий шаг — выйти за пределы Java-кода.
- Swagger / OpenAPI: Откройте спецификацию сервиса
backend-arm-aggregation. Найдите эндпоинт/operator/event. Посмотрите, какие обязательные параметры он требует и в каких случаях может вернуть пустой ответ. - Система логирования (Kibana / Splunk): Возьмите номер талона (который успешно создался на первом шаге) или
sessionIdи найдите логи бэкенда за время падения теста. Сервер наверняка оставил след, почему он не смог обработать запрос. - Требования (Confluence / Jira): Проверьте задачу AEQS-11. Возможно, логика работы вкладки «На обслуживании» изменилась, и теперь для получения количества талонов (
waitCountLastTRM) нужно вызывать другой эндпоинт.
В следующей статье мы спустимся на уровень сети и научимся перехватывать запросы между вашим тестом и бэкендом, чтобы увидеть, что именно сервер отвечает нашему коду до того, как это превратится в null.