Как загрузить тесты Spring Boot в QA Деск
Одной командой после mvn verify: qadesk-cli upload target/surefire-reports target/failsafe-reports. Срезы @WebMvcTest и @DataJpaTest Surefire прогоняет на фазе test, классы *IT с @SpringBootTest Failsafe — на integration-test, и оба каталога уезжают одним прогоном с одним ключом идемпотентности.
# GitLab CI; QADESK_TOKEN — в CI/CD Variables, masked
verify:
stage: test
when: always
services: [docker:dind] # для Testcontainers
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 verify" --env test --platform api
target/surefire-reports target/failsafe-reports
--platform api ложится в поле прогона: по нему потом фильтруется история бэкенда отдельно от e2e фронта. Стенд, на котором это проверено 22.09.2026: Spring Boot 3.4.1, spring-boot-starter-test, OpenJDK 21, Maven 3.9.9.
Как пометить тест Spring Boot ключом кейса
В @DisplayName: @DisplayName("QAD-C40 /health отвечает 200"). Ключ кейса содержит дефис, а имя Java-метода дефис не допускает, поэтому другого места для него нет. Чтобы Surefire записал @DisplayName в отчёт, у обоих плагинов включается usePhrasedTestCaseMethodName — родитель Spring Boot версии плагинов задаёт сам, указывать их не нужно.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<statelessTestsetReporter implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5Xml30StatelessReporter">
<usePhrasedTestCaseMethodName>true</usePhrasedTestCaseMethodName>
</statelessTestsetReporter>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-failsafe-plugin</artifactId>
<configuration>
<statelessTestsetReporter implementation="org.apache.maven.plugin.surefire.extensions.junit5.JUnit5Xml30StatelessReporter">
<usePhrasedTestCaseMethodName>true</usePhrasedTestCaseMethodName>
</statelessTestsetReporter>
</configuration>
<executions><execution><goals><goal>integration-test</goal><goal>verify</goal></goals></execution></executions>
</plugin>
@WebMvcTest(HealthController.class)
class HealthWebTest {
@Autowired MockMvc mvc;
@Test @DisplayName("QAD-C40 /health отвечает 200 и ok")
void health() throws Exception {
mvc.perform(get("/health")).andExpect(status().isOk()).andExpect(content().string("ok"));
}
}
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class AppIT {
@Test @DisplayName("QAD-C42 контекст поднимается и /health живой")
void context() { … }
}
В отчёте появляются <testcase name="QAD-C40 /health отвечает 200 и ok" classname="demo.HealthWebTest"> и <testcase name="QAD-C42 …" classname="demo.AppIT">, приёмник кладёт их в строки кейсов QAD-C40 и QAD-C42. Без usePhrasedTestCaseMethodName в отчёт уходят имена методов health и context, и оба результата возвращаются как несматченные.
Что делать с упавшим контекстом Spring
Упавший контекст — одна красная строка в прогоне с текстом ошибки, а не сорок красных тестов. Когда @SpringBootTest не поднялся, Surefire пишет <error> в каждый тест класса, приёмник считает <error> падением, а повторяющийся ключ в одном отчёте берёт худший исход.
Сообщения при этом разные: первому тесту достаётся Failed to load ApplicationContext с причиной, остальным — ApplicationContext failure threshold (1) exceeded, потому что Spring 6.1+ не поднимает упавший контекст повторно. Проверено на стенде 22.09.2026.
Дефект заводится из этой строки: текст ошибки переезжает в наблюдение, сборка и ветка заполняются из полей прогона, а в карточке прогона остаётся ссылка на задание CI. Разработчик открывает дефект и видит стек, а не «интеграционные упали, посмотри пайплайн».
Как быть с Testcontainers и тяжёлыми тестами
Тяжёлые тесты живут в Failsafe и не мешают срезам. Стандартное разделение Spring Boot — @WebMvcTest, @DataJpaTest, @JsonTest в Surefire за секунды; @SpringBootTest с Testcontainers (PostgreSQL, Kafka, Redis) в Failsafe — минуты. QA Деск это разделение не ломает: два каталога, один прогон, и в покрытии видно, какой кейс закрыт срезом, а какой — интеграционным тестом с настоящей базой.
Если интеграционные тесты запускаются отдельным заданием и позже, они загружаются отдельно с собственным --ci-run-id или --attempt. Под общим ключом второй, другой отчёт получит отказ 409 — приёмник не сливает результаты молча и не перезаписывает первый прогон вторым.
Чего в продукте нет
- нет стартера или автоконфигурации
qadesk-spring-boot-starter: разметка — стандартный@DisplayName, выгрузка — бинарник без зависимостей вpom.xml; - ключ через
@Tag,@SqlилиTestReporterне читается — в отчёт Surefire эти данные не попадают; - QA Деск не запускает тесты и не собирает JaCoCo — покрытие кода остаётся в CI;
- нет сопоставления
application.yml-профилей с окружениями прогона:--envпередаётся явно.
Частые вопросы
Нужно ли указывать версии Surefire и Failsafe?
Нет, если проект наследует spring-boot-starter-parent: он задаёт версии обоих плагинов. Достаточно секции statelessTestsetReporter с usePhrasedTestCaseMethodName в конфигурации каждого. У проекта на spring-boot-dependencies без родителя версия указывается своя, стенд проверен с 3.5.2.
Можно ли передать ключ кейса через @Tag?
Нет. @Tag фильтрует запуск, но в JUnit XML Surefire не попадает, и приёмник его не видит. Ключ кейса QA Деск читается из имени теста, а в имя Surefire записывает @DisplayName — при включённом usePhrasedTestCaseMethodName.
Как загрузить, если интеграционные тесты идут отдельным заданием?
С собственным ключом: --ci-run-id "$CI_PIPELINE_ID-it" или своим --attempt. Два разных отчёта под одним ключом — отказ 409 на второй, это защита от потерянных шардов, а не ошибка настройки.
Что попадает в дефект из упавшего @SpringBootTest?
Сообщение из <error> или <failure> целиком — со стеком, каким его записал Surefire, — плюс окружение, сборка, ветка и ссылка на задание CI из полей прогона. Заводит дефект человек кнопкой из строки прогона; автоматически дефекты не создаются.
Работает ли это с Kotlin и Gradle?
Да. Gradle пишет @DisplayName в отчёт без настройки, а имя Kotlin-функции в обратных кавычках может содержать дефис и ключ напрямую. Подробности и пример с Kotest — на странице Kotlin.
С чего начать
- Заведите проект и выпустите токен приёма: Настройки → Интеграции → CI / автотесты.
- Добавьте
usePhrasedTestCaseMethodNameв Surefire и Failsafe и шагqadesk-cli uploadс двумя каталогами послеmvn verify. - Возьмите кейсы регистрации и авторизации и проставьте их ключи в
@DisplayNameтестов контроллеров и security-конфигурации. - Заведите тест-план на релиз с миграциями Flyway или Liquibase на копии боевых данных — это то, что
mvn verifyне проверяет.
Попробуйте на своём проекте
Заведите проект, загрузите первый отчёт или заведите первый дефект — бесплатный тариф без карты. Нужно демо на ваших сценариях — напишите на hello@qadesk.ru.
Создать проектРядом по теме
Java · Maven / Gradle
Maven и Gradle уже пишут JUnit XML — QA Деск принимает его без плагинов. Единственная тонкость: ключ кейса `QAD-C31` содержит дефис, а имя Java-метода дефис содержать не может, поэтому ключ ставится в `@DisplayName`, и Surefire надо попросить его записать.
Java — отчёты Surefire и Gradle в покрытие, ключ кейса в @DisplayNameKotlin
В Kotlin имя тестовой функции пишется в обратных кавычках и может содержать дефис — ключ кейса `QAD-C31` ставится прямо в имя, а Gradle записывает его в отчёт без единой настройки. Kotest даёт то же самое строкой в `StringSpec`.
Kotlin — ключ кейса прямо в имени теста, отчёт Gradle без настройкиSentry
Ошибка уровня error или fatal становится дефектом выбранного проекта — с типом исключения, местом и ссылкой на issue. Одна ошибка — один дефект, повторные срабатывания дублей не плодят.
Sentry — ошибка у пользователя приходит дефектом