Как загрузить отчёт 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.
С чего начать
- Заведите проект и выпустите токен приёма: Настройки → Интеграции → CI / автотесты.
- Добавьте
usePhrasedTestCaseMethodNameв Surefire и Failsafe (для Gradle — ничего) и шагqadesk-cli uploadв пайплайн. - Возьмите кейсы регистрации и авторизации и проставьте их ключи в
@DisplayNameсоответствующих тестов. - Заведите тест-план «релиз с миграциями» — он объясняет заказчику, почему выкатка занимает не пятнадцать минут.
Попробуйте на своём проекте
Заведите проект, загрузите первый отчёт или заведите первый дефект — бесплатный тариф без карты. Нужно демо на ваших сценариях — напишите на 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 без настройкиJUnit XML
Своего формата у QA Деск нет: подойдёт JUnit XML, который ваш раннер уже пишет. Один запрос из пайплайна — и отчёт становится прогоном с историей, покрытием и дефектами.
JUnit XML — формат, который умеет писать всё