Анатомия ошибки: чтение стектрейса и поиск точки отказа
Анатомия ошибки: чтение стектрейса и поиск точки отказа
java.lang.AssertionError: Статус талона отличен от ожидаемого. Ожидалось: 7 (DROPPED), фактически: 3 (CALLING).
Если вы пишете или поддерживаете автотесты, вы наверняка регулярно видите подобные красные строки в логах. Тест упал, сборка остановилась. Для новичка первая реакция — паника или желание перезапустить тест в надежде, что «само пройдёт» (и иногда, к сожалению, проходит, что делает ситуацию только хуже).
Первый шаг к стабильным тестам — научиться хладнокровно препарировать ошибку. Давайте разберём этот лог на составляющие и найдём точное место в коде, где всё пошло не так.
Шаг 1. Расшифровка сообщения об ошибке
Сообщение об ошибке (Error Message) — это диагноз. В нашем случае это AssertionError. Эта ошибка означает, что логика теста отработала без технических сбоев (никто не поделил на ноль и не обратился к пустой ссылке), но бизнес-проверка не сошлась с реальностью.
Вчитаемся в детали:
Ожидалось: 7 (DROPPED), фактически: 3 (CALLING)
Откуда взялись эти цифры? Заглянем в словарь нашего проекта — перечисление TicketStatusEnum. В нём зафиксированы все возможные состояния талона в системе:
CALLING(3)— талон вызывается.DROPPED(7)— талон сброшен системой.
Тест ожидал, что после его действий талон получит статус 7 (сброшен). Но когда тест заглянул в базу данных, он увидел там статус 3 (вызывается). Система не перевела талон в нужное состояние.
Шаг 2. Чтение стектрейса (Stack Trace)
Поняв что пошло не так, нам нужно выяснить где это произошло. Для этого нужен стектрейс — простыня текста под сообщением об ошибке, которая пугает многих начинающих автоматизаторов.
Стектрейс — это просто история вызовов методов, записанная в обратном порядке. Самая верхняя строка — это место, где произошла авария. Строка ниже — метод, который вызвал аварийный метод, и так далее.
В логах нашего упавшего теста ключевыми будут эти три строки:
FeqsSuoDbSteps.assertLastTicketStatus(FeqsSuoDbSteps.java:62)FeqsUiMainSteps.resetTicket(FeqsUiMainSteps.java:107)FeqsUiMainSteps.resetTickets(FeqsUiMainSteps.java:116)
Как это прочитать?
Мы начали выполнять массовый сброс талонов в методе resetTickets (строка 116). Внутри него для конкретного талона мы вызвали метод resetTicket (строка 107). А уже внутри него мы попытались проверить статус в БД через assertLastTicketStatus (строка 62) — и именно здесь тест разбился.
Шаг 3. Анализ точки отказа
Мы нашли точное место. Теперь нужно посмотреть на код вокруг 107-й строки, чтобы понять контекст.
@Step("Сбросить талон, находящийся на обслуживании")
public void resetTicket(TechnicalDepartmentEnum technicalDepartment, String ticketNumber) {
Response response = feqsUiMainService.reset(getCookie(), technicalDepartment.getId());
checkResponse(response, 200);
checkSuccess(response);
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("Прервано ожидание перед сбросом талона", e);
}
// Строка 107:
feqsSuoDbSteps.assertLastTicketStatus(technicalDepartment.getId(), ticketNumber, DROPPED);
}
Давайте восстановим хронологию событий в этом методе:
- Мы отправляем API-запрос на сброс талона (
feqsUiMainService.reset). - Мы проверяем, что сервер ответил нам HTTP-статусом 200 (ОК).
- Мы замираем и ждём ровно 5 секунд с помощью
Thread.sleep(5000). - Мы идём в базу данных и проверяем, что статус стал
DROPPED.
Здесь кроется главная подсказка. API-запрос выполнился успешно (иначе тест упал бы раньше, на проверке ответа сервера). Мы честно подождали 5 секунд. Но база данных всё равно вернула нам старый статус CALLING.
Мы нашли точку отказа и поняли контекст. Наш тест столкнулся с классической проблемой асинхронных систем: мы попросили систему сделать работу, подождали фиксированное время, но система не успела. В следующей главе мы разберём, почему жёсткие паузы вроде Thread.sleep — это бомба замедленного действия, и познакомимся с понятием Race Condition.