qadesk.ru / Платформы и стек / Java · Maven / Gradle
Платформы

Java — отчёты Surefire и Gradle в покрытие, ключ кейса в @DisplayName

Maven и Gradle уже пишут JUnit XML — QA Деск принимает его без плагинов. Единственная тонкость: ключ кейса QAD-C31 содержит дефис, а имя Java-метода дефис содержать не может, поэтому ключ ставится в @DisplayName, и Surefire надо попросить его записать.

Как загрузить отчёт Maven или Gradle в QA Деск

Отчёт не конвертируется: qadesk-cli upload target/surefire-reports отправляет JUnit XML, который Surefire уже написал, и печатает ссылку на прогон последней строкой. Для Gradle путь другой — build/test-results/test. Каталоги передаются списком, поэтому юнит-тесты Surefire и интеграционные Failsafe уезжают одним прогоном.

# GitLab CI; QADESK_TOKEN — в CI/CD Variables, masked
test:
  stage: test
  when: always
  script:
    - mvn -B verify -Dmaven.test.failure.ignore=true
    - curl -fsSL https://qadesk.ru/cli/qadesk-cli-linux-amd64 -o qadesk-cli && chmod +x qadesk-cli
    - ./qadesk-cli upload --run "$CI_PROJECT_NAME regress" --env test
        target/surefire-reports target/failsafe-reports

-Dmaven.test.failure.ignore=true нужен, чтобы упавший тест не оборвал сборку до шага загрузки: иначе красный прогон в деск не попадёт, и на дашборде останется прошлый зелёный. Ключ идемпотентности «прогон CI + попытка» утилита берёт из CI_PIPELINE_ID сама; перезапуск задания второго прогона не создаёт.

Как пометить Java-тест ключом кейса

Ключ ставится в @DisplayName: @DisplayName("QAD-C31 ссылка на сброс пароля одноразовая"). Имя метода не подходит — ключ кейса QA Деск содержит дефис (QAD-C31), а идентификатор Java дефис не допускает; testQAD_C31_reset() ключом не считается. Проверено на приёмнике 22.09.2026: QAD_C31 через подчёркивание не распознаётся.

@Test
@DisplayName("QAD-C31 ссылка на сброс пароля одноразовая")
void resetLinkSingleUse() { … }

@ParameterizedTest(name = "QAD-C36 вход под ролью {0}")
@ValueSource(strings = {"admin", "user"})
void login(String role) { … }

У параметризованного теста ключ пишется в шаблон name: каждая строка данных попадает в отчёт со своим именем и ключом, а повторяющийся ключ в одном отчёте берёт худший исход — если одна из ролей упала, кейс красный. @Tag("QAD-C31") в отчёт Surefire не попадает вовсе, и TestReporter.publishEntry тоже: разметка, которой нет в XML, не существует для приёмника.

Почему Surefire не пишет @DisplayName в отчёт

По умолчанию Surefire 3.x записывает в <testcase name> имя метода, а не @DisplayName. Чтобы ключ дошёл до отчёта, репортеру включается usePhrasedTestCaseMethodName — одна секция в pom.xml, одинаковая для Surefire и Failsafe:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>3.5.2</version>
  <configuration>
    <statelessTestsetReporter implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5Xml30StatelessReporter">
      <usePhrasedTestCaseMethodName>true</usePhrasedTestCaseMethodName>
    </statelessTestsetReporter>
  </configuration>
</plugin>

После этого в TEST-demo.LoginTest.xml появляется <testcase name="QAD-C31 ссылка на сброс пароля одноразовая" classname="demo.LoginTest">, и приёмник кладёт результат в строку кейса QAD-C31. Без этой секции отчёт принимается, но все результаты возвращаются как несматченные — утилита печатает их поимённо и при --strict завершается кодом 3. Стенд: Maven 3.9.9, Surefire и Failsafe 3.5.2, JUnit Jupiter 5.11.4, OpenJDK 21.

Как это работает в Gradle

Gradle пишет @DisplayName в отчёт сам, без настройки: после gradle test в build/test-results/test/TEST-*.xml лежит <testcase name="QAD-C31 …">. Проверено на Gradle 8.10.2 с JUnit Platform. Нужны только useJUnitPlatform() и ignoreFailures = true, чтобы шаг загрузки выполнялся и после красных тестов.

tasks.test {
    useJUnitPlatform()
    ignoreFailures = true
}

Многомодульный проект: у каждого модуля свой build/test-results, утилита принимает несколько путей и рекурсивно обходит каталоги — qadesk-cli upload */build/test-results/test собирает все модули в один прогон. Про Kotlin DSL, Kotest и имена в обратных кавычках — на странице Kotlin.

Как загрузить результаты TestNG

Через Allure: allure-testng пишет description теста в имя результата, и ключ из @Test(description = "QAD-C31 …") распознаётся. В JUnit XML, который Surefire собирает для TestNG, description не попадает, а отключённые тесты пропадают из отчёта совсем — этот путь для TestNG не годится.

@Test(description = "QAD-C31 ссылка на сброс пароля одноразовая")
public void resetLinkSingleUse() { … }

@Test
public void viaLabel() {
    Allure.label("qadesk_case", "QAD-C35");   // явная разметка, приоритет выше имени
    …
}

Зависимость — io.qameta.allure:allure-testng 2.29.0 в test-scope; каталог результатов задаётся системным свойством allure.results.directory, обычно target/allure-results. Загрузка: qadesk-cli upload target/allure-results — формат утилита определит по *-result.json. Отключённый через enabled = false тест приходит без статуса и записывается как skipped, а не исчезает. Каталог target/surefire-reports при TestNG в загрузку не передаётся: рядом с TEST-*.xml там лежат testng-results.xml и testng-failed.xml, которые JUnit-отчётом не являются.

Чем это отличается от вкладки Tests в CI

Вкладка CI показывает один прогон и забывает его через месяц вместе с артефактами; QA Деск хранит прогон в проекте рядом с ручными кейсами и дефектами. Кейс на проверку миграции руками и @DisplayName("QAD-C12 …") на ту же логику — одна строка покрытия, и видно, что из этого проверял человек, а что пайплайн, какой сборкой и когда.

Статусы считаются без поблажек: <failure> и <error> — падение, <skipped> — пропуск, broken и unknown у Allure — падение и пропуск соответственно, а не «почти зелёный». Пустой отчёт отклоняется, а не записывается как прогон без результатов. Дефект заводится из упавшей строки прогона: сообщение из <failure message> переезжает в наблюдение, окружение и сборка заполняются полями прогона.

Что стоит завести кейсами, а не тестами

В Java-проекте дороже всего то, что не покрывает mvn verify:

  • Flyway- и Liquibase-миграции на копии боевых данных — время, блокировки, откат;
  • поведение под нагрузкой: пул соединений, таймауты внешних сервисов, память после суток работы;
  • права и роли в интерфейсе, включая ту роль, что завели «на один раз»;
  • обновление JDK и major-версий зависимостей — регресс по чек-листу, а не «тесты зелёные».

Такие проверки живут тест-планом, а прогон по нему показывает, кто и когда их сделал. Автотесты в том же проекте дают вторую половину ответа на вопрос «релизить или нет».

Чего в продукте нет

  • нет плагина или расширения для JUnit и TestNG — разметка стандартными аннотациями, выгрузка одним бинарником без Java в раннере;
  • ключ через @Tag или TestReporter не читается: он не попадает в JUnit XML Surefire;
  • QA Деск не собирает покрытие кода JaCoCo — это другой инструмент и другая метрика;
  • нет автоматического заведения дефекта по упавшему тесту: решение принимает человек.

Частые вопросы

Нужен ли плагин Maven или Gradle для QA Деск?

Нет. Surefire, Failsafe и Gradle пишут JUnit XML сами; qadesk-cli — статический бинарник, которому не нужны Java и Node. Единственная настройка — usePhrasedTestCaseMethodName у Surefire, чтобы @DisplayName попал в отчёт.

Почему ключ нельзя написать в имени метода?

Ключ кейса QAD-C31 содержит дефис, а идентификатор Java его не допускает. Вариант с подчёркиванием QAD_C31 приёмник не распознаёт — проверено 22.09.2026. Ключ ставится в @DisplayName, для TestNG — в description или Allure.label.

Как загрузить Surefire и Failsafe одним прогоном?

Передать оба каталога одной командой: qadesk-cli upload target/surefire-reports target/failsafe-reports. Две отдельные загрузки под одним ключом CI дадут отказ 409 на вторую: тот же ключ с другим отчётом сервер не сливает молча.

Что будет с тестом без ключа?

Он не запишется. Утилита печатает несматченные тесты поимённо с причиной (no_key, case_archived), с флагом --strict завершается кодом 3. Принять отчёт целиком и записать восьмую часть — тот зелёный, который потом стоит дороже красного.

Работает ли это с JUnit 4?

Да: Surefire пишет для JUnit 4 тот же JUnit XML с именем метода. Аннотации @DisplayName в JUnit 4 нет, поэтому ключ передаётся через Allure (allure-junit4, @Description или Allure.label) либо через миграцию на JUnit Jupiter с junit-vintage-engine.

С чего начать

  1. Заведите проект и выпустите токен приёма: Настройки → Интеграции → CI / автотесты.
  2. Добавьте usePhrasedTestCaseMethodName в Surefire и Failsafe (для Gradle — ничего) и шаг qadesk-cli upload в пайплайн.
  3. Возьмите кейсы регистрации и авторизации и проставьте их ключи в @DisplayName соответствующих тестов.
  4. Заведите тест-план «релиз с миграциями» — он объясняет заказчику, почему выкатка занимает не пятнадцать минут.

Попробуйте на своём проекте

Заведите проект, загрузите первый отчёт или заведите первый дефект — бесплатный тариф без карты. Нужно демо на ваших сценариях — напишите на hello@qadesk.ru.

Создать проект

Рядом по теме

Платформы

Spring Boot

Spring Boot наследует Surefire и Failsafe от `spring-boot-starter-parent`, и обе группы тестов — быстрые срезы и тяжёлые интеграционные — QA Деск принимает одним прогоном. Ключ кейса ставится в `@DisplayName`; настройка Surefire одна и та же, что для любого Java-проекта.

Spring Boot — @WebMvcTest и @SpringBootTest в одном прогоне, дефект из упавшего контекста
Платформы

Kotlin

В Kotlin имя тестовой функции пишется в обратных кавычках и может содержать дефис — ключ кейса `QAD-C31` ставится прямо в имя, а Gradle записывает его в отчёт без единой настройки. Kotest даёт то же самое строкой в `StringSpec`.

Kotlin — ключ кейса прямо в имени теста, отчёт Gradle без настройки
Автотесты, CI и мониторинг

JUnit XML

Своего формата у QA Деск нет: подойдёт JUnit XML, который ваш раннер уже пишет. Один запрос из пайплайна — и отчёт становится прогоном с историей, покрытием и дефектами.

JUnit XML — формат, который умеет писать всё