Отладка никогда не была простым делом. В первых компьютерах причины ошибок приходилось искать буквально руками — ненадёжность железа умножалась на человеческие ошибки, и Морис Уилкс (руководитель проекта EDSAC) сетовал, что львиную долю оставшейся жизни скорее всего придётся искать ошибки в собственных программах, а один современный исследователь задавался вопросом, как на таком железе вообще удавалось сделать что-либо полезное.
Сейчас мы уже не переживаем о перегорающих электронных лампах, у нас накоплен 80-летний опыт дебага, а вокруг инструментария для работы с ошибками возникла целая индустрия — и всё же человеческое время всё так же загружено отладкой: 40-60% времени современных QA-команд тратится на диагностику сбоев в тестах. Ничего удивительного: если система полезна и работает, её будут масштабировать до тех пор, пока она работать не перестанет.
Эта проблема настолько серьёзна, что сейчас активно разрабатываются практики сокращения размеров тестовых сюит, Майкрософт рекомендует при триаже не разбирать каждое падение по отдельности, а кое-кто советует «стохастический подход» к разбору ошибок, при котором команды не тратят время на разбор каждого падения.
Если всё больше голосов говорят нам о том, что разбирать падения вручную становится слишком дорого, стоит задуматься об автоматизации. В этой статье я возьму тестовую сюиту средних размеров, попробую вначале пройти по результатам руками, а потом применю простейшее средство автоматизации: группировку результатов.
1. Ручные способы
Знакомимся, наша сюита: тесты-пустышки, написанные для демонстраций и отладки инструментов. При запуске она даёт 639 тестовых результатов (в т.ч. от параметризованных тестов). Это не мало — но по нынешним временам и не слишком много. Общее время прогона — 10 минут, каждый запуск даёт стабильно 10-15% сбоев, то есть 60-90 ошибок, с которыми каждый раз нужно разобраться. Как это сделать быстрее всего?
Линейный просмотр списка.
Первый вариант — проработать все падения по порядку. Работы тут порядочно: вывод от запуска даже без подробных стектрейсов получился размером в 20 тысяч символов, длиннее этой статьи.
Хорошо, если прогон был ночной, и эту работу мне нужно проделывать в день только один раз. А если сюита запускается по триггеру много раз на дню? Три раза по 20 тысяч символов — тянет уже на небольшую книгу. Тут уж совсем не хочется вспоминать, что Майкрософт советует помимо упавших тестов разбирать ещё и 5-10% от успешно выполненных — от ложноотрицательных результатов никто не застрахован.
К тому же стоит учесть: если я просто прошёл по списку, результаты работы отложились только у меня в памяти. Если те же ошибки выскочат у другого человека (или у меня, когда я про них забуду) — работу придётся дублировать.
Случайная выборка.
Вместо того чтобы проверять все 90 падений, можно проверить случайно выбранные 10 — подход, который использовался в контроле качества ещё до изобретения компьютеров.
Проблема в том, что мои падения не распределены равномерно по тестируемому коду: за несколькими падениями обычно стоит одна причина, и такой кластер легко может не попасть в мою выборку. К тому же редкое падение вовсе не значит неважное падение.
Документация и группировка падений вручную.
Стоимость анализа пропорциональна числу падений, а не числу причин; соответственно, если как-то сгруппировать падения по причинам, то это уже поможет мне разобраться в этом потоке информации. А если причины ещё и документировать, усилия по поиску не будут дублироваться, когда кто-то другой займётся этой же работой. Падения можно связывать, например, с тикетами в Jira — функциональность для этого есть в большинстве современных TMS. Это шаг в правильном направлении — но пока что триаж не ускорился, а только замедлился.
Grep.
Как ускорить поиск частых и известных ошибок? Можно взять нужное сообщение об ошибке и сделать поиск по нему в результатах тестов с помощью grep (или findstr под Windows):
findstr /I /N /C:"SessionNotCreatedException: session not created This version of ChromeDriver only supports Chrome version 139" test\*.*
Я это сделал, и в действительно получил стопку результатов:
test\TEST-io.qameta.allure.SearchFunctionalityTest.xml:5: <failure message="org.opentest4j.AssertionFailedError: SessionNotCreatedException: session not created This version of ChromeDriver only supports Chrome version 139 Current browser version is 140.0.7390.65" type="org.opentest4j.AssertionFailedError">org.opentest4j.AssertionFailedError: SessionNotCreatedException: session not created test\TEST-io.qameta.allure.SearchFunctionalityTest.xml:135: <failure message="org.opentest4j.AssertionFailedError: SessionNotCreatedException: session not created This version of ChromeDriver only supports Chrome version 139 Current browser version is 140.0.7390.65" type="org.opentest4j.AssertionFailedError">org.opentest4j.AssertionFailedError: SessionNotCreatedException: session not created test\TEST-io.qameta.allure.SearchFunctionalityTest.xml:265: <failure message="org.opentest4j.AssertionFailedError: SessionNotCreatedException: session not created This version of ChromeDriver only supports Chrome version 139 Current browser version is 140.0.7390.65" type="org.opentest4j.AssertionFailedError">org.opentest4j.AssertionFailedError: SessionNotCreatedException: session not created
Оказалось, что все эти ошибки пришли из одного класса (SearchFunctionalityTest). Уже есть над чем подумать!
Но проблема в том, что такой поиск слишком хрупкий — он ловит только точные вхождения, и при каждом новом паттерне ошибки его нужно менять. К тому же старые паттерны нигде не хранятся и не документируются. В общем, что-то в этом подходе есть, но хочется иметь вокруг него более надёжную инфраструктуру.
2. Автоматическая классификация
Как раз такую инфраструктуру предоставляют инструменты классификации падений тестов. Их сейчас существует несколько:
Kubernetes для своих нужд написали открытый инструмент для кластеризации похожих падений по CI-джобам. Он просматривает результаты тестов из Kubernetes CI, классифицирует их по низко- и высокоуровневым кластерам, и выгружает в JSON-файл, на основе которого затем строится дашборд.
BuildSheriff при кластеризации падений учитывает не только сообщения об ошибках, но и изменения в коде. Это — академический прототип, а не готовое решение, но он показывает, что проблема реальна, и есть запрос на автоматизацию.
Это тоже академический прототип, для проверки его внедрили как внутренний инструмент. LogSage не только кластеризует падения, но и анализирует их причины, предлагает решения, подбирает инструменты и проверяет, перезапуская нужные тесты — всё это, очевидно, с помощью LLM. Точность всего рабочего процесса превышает 80%.
Allure Report автоматически разбивает результаты тестов по категориям: как крупным (ошибка теста / ошибка кода), так и более гранулярным (по причинам ошибок). При желании иерархию категорий можно настроить самому. Если включить историю тестов, то категории сопоставляются с прошлыми запусками.
Из всех этих вариантов проще всего поставить эксперимент с Allure Report — чем я и займусь.
3. Классификация в Allure Report
Чтобы классификация ошибок заработала в моём проекте, я сделал следующее:
1.
2.
Установил интеграцию Allure с JUnit5 (для каждого фреймворка интеграция своя).
3.
Создал файл настроек allurerc.js в корне проекта:
export default { // Общий заголовок name: "Сортировка сбоев в Allure 3", // Путь к папке с отчётом output: "./allure-report", // Путь к файлу с историей historyPath: "./test-history/history.jsonl", // Плагины. В Allure 3 можно добавить несколько плагинов, и для каждого // будет создан отдельный отчёт. Здесь мне нужен только один. plugins: { // Базовый вариант отчёта в Allure 3 - 'awesome' "awesome": { import: "@allurereport/plugin-awesome", options: { // Имя отчёта reportName: "Главный отчёт", // Язык отчёта reportLanguage: "ru", }, }, }, };
4.
Запустил тесты. Allure 3 подключается к запуску, когда обычная команда выполнения тестов обёрнута в команду Allure:
npx allure run --config=файл\конфигурации -- "команда выполнения тестов"
В моём случае полная команда выглядит так:
npx allure run --config=.\allurerc.js -- ".\gradlew.bat test"
5.
Наконец, открыл отчёт:
allure open путь\к\отчёту
Сразу видно, что система обнаружила много нестабильных тестов:

Это неудивительно: в моей сюите для тестов-пустышек написаны произвольные падения — все ассёрты вызывают случайно падающую функцию:
private boolean isTimeToThrowException() { return new Random().nextBoolean() && new Random().nextBoolean() && new Random().nextBoolean() && new Random().nextBoolean(); }
Но что же насчёт кластеризации? Она — во вкладке «категории»:

Вместо однородного полотна текста передо мной аккуратно классифицированные причины падений. Попробую разобраться с самой крупной категорией —SessionNotCreatedException. Раньше я уже обнаружил, что все падения, связанные с этим сообщением об ошибке, исходят из класса SearchFunctionalityTest. Сейчас же можно прямо в отчёте открыть отдельный тест и посмотреть, что в нём произошло:

Каждый раз проблема возникает в одном месте — при подготовке, в startDriver. Значит, дело в общей фикстуре класса? Действительно, в начале класса в демонстрационных целях была добавлена ошибка:
@BeforeEach public void startDriver() { steps.startBadDriver(); } @Step("Starting web driver") public void startBadDriver() { throwVersionMismatchException(); }
Чтобы запомнить ошибку на будущее, можно создать пользовательскую категорию. Для этого в файл настроек (в моём случае allurerc.js) нужно добавить описание категории:
categories: { rules: [ { // Отображаемое имя name: "Несовпадение версии WebDriver", // Постоянный идентификатор, благодаря которому категорию // можно отслеживать по нескольким запускам id: "webdriver-version-mismatch", // Правила, по которым тест попадает в категорию matchers: { statuses: ["failed", "broken"], message: /SessionNotCreatedException/, }, // Правила, по которым найденные тесты отображаются внутри категории groupBy: ["layer", "owner", "status"], groupByMessage: true, groupEnvironments: true, expand: true, hide: false, }, ], },
Теперь при запуске тестов категория будет отображаться в отчёте:

4. Заключение
Сейчас во всю идёт разработка нейросетевых инструментов, которые смогут сортировать падения, предлагать исправления и даже самостоятельно их внедрять. Возможно, до готовых коммерческих решений уже не годы, а месяцы. Но они, скорее всего, не заменят существующий стек, а лягут поверх него.
Любая полезная система будет масштабироваться, пока не встретится с ограничением нашего рабочего времени. Поэтому важна не только эффективность этого времени, но и удобство работы. А работать с аккуратно отсортированными категориями гораздо приятнее, чем перелопачивать метры стек-трейсов, львиная доля которых приводит к одним и тем же ошибкам.