Как загрузить отчёт Gradle из Kotlin-проекта в QA Деск
qadesk-cli upload build/test-results/test после gradle test — отчёт JUnit XML Gradle пишет сам, конвертировать нечего. В build.gradle.kts нужны две строки: useJUnitPlatform() и ignoreFailures = true, чтобы красный тест не остановил сборку до шага загрузки.
tasks.test {
useJUnitPlatform()
ignoreFailures = true
}
# GitLab CI; QADESK_TOKEN — в CI/CD Variables, masked
test:
stage: test
when: always
script:
- ./gradlew test
- curl -fsSL https://qadesk.ru/cli/qadesk-cli-linux-amd64 -o qadesk-cli && chmod +x qadesk-cli
- ./qadesk-cli upload --run "$CI_PROJECT_NAME test" --env test build/test-results/test
Многомодульный проект — */build/test-results/test: утилита принимает несколько путей, обходит их рекурсивно и склеивает TEST-*.xml всех модулей в один прогон. Ключ идемпотентности берётся из CI_PIPELINE_ID, перезапуск задания второго прогона не создаёт.
Как пометить Kotlin-тест ключом кейса
Ключом в имени функции: ` fun QAD-C31 ссылка на сброс пароля одноразовая() . В Kotlin имя в обратных кавычках может содержать пробелы и дефис, Gradle записывает его в <testcase name> как есть, и приёмник находит QAD-C31. @DisplayName` тоже работает — Gradle пишет его в отчёт без настройки, в отличие от Maven Surefire.
class LoginTest {
@Test
fun `QAD-C31 ссылка на сброс пароля одноразовая`() { … }
@Test
@DisplayName("QAD-C32 неверный пароль даёт 401")
fun wrongPassword() { … }
}
Проверено на Gradle 8.10.2, Kotlin 2.0.21, JUnit Jupiter 5.11.4: в TEST-demo.LoginTest.xml лежат <testcase name="QAD-C31 ссылка на сброс пароля одноразовая()"> и <testcase name="QAD-C32 неверный пароль даёт 401">, оба результата легли в строки своих кейсов. Скобки, которые Gradle дописывает к имени функции, ключу не мешают.
Как это работает с Kotest
Строкой спецификации: в StringSpec имя теста — это строка, и ключ ставится в её начало. Kotest запускается через JUnit Platform, Gradle пишет строку в <testcase name> целиком, classname — класс спецификации.
class CheckoutSpec : StringSpec({
"QAD-C41 оформление заказа гостем" { … }
"QAD-C42 оплата картой" { … }
})
Проверено с kotest-runner-junit5 5.9.1: отчёт содержит <testcase name="QAD-C41 оформление заказа гостем" classname="demo.CheckoutSpec">, упавший shouldBe даёт <failure> с сообщением матчера, которое в QA Деск становится заметкой строки прогона. В FunSpec, ShouldSpec и BehaviorSpec имя теста — тоже строка, правило то же: ключ в начале строки листового теста.
Почему тест с телом-выражением пропадает из отчёта
Функция fun x() = fail("…") в отчёт Gradle не попадает вовсе: JUnit Jupiter требует, чтобы тестовый метод возвращал void, а тело-выражение с Nothing или Boolean компилируется в метод с другим типом возврата, и Jupiter молча его не запускает. На стенде 22.09.2026 такой тест исчез из TEST-*.xml, а сборка осталась зелёной — классический тихий зелёный.
Правило простое: у тестовой функции блочное тело { … }. Ловится это в QA Деск раньше, чем в CI: строка кейса, у которой пропал автотест, остаётся без результата в новом прогоне, и это видно в покрытии. Kotest этой ловушки не имеет — тест там объявляется строкой, а не функцией.
Как быть с Ktor и корутинами
Ключ и отчёт те же: testApplication { … } в Ktor 2 и 3 — обычный JUnit-тест, runTest из kotlinx-coroutines-test возвращает TestResult, который на JVM является Unit, и в отчёт тест попадает. Отдельная страница по Ktor не нужна.
Для Android-модулей на Kotlin отчёт лежит в app/build/test-results/ для JVM-тестов и в app/build/outputs/androidTest-results/ для инструментальных — рецепт с устройствами и матрицей конфигураций на странице Android. Multiplatform-проекты: у каждой цели свой каталог (build/test-results/jvmTest, …/jsTest), передаются списком в одну команду, ключ у теста один на все платформы.
Чего в продукте нет
- нет Gradle-плагина
qadesk: выгрузка — статический бинарник, ему не нужна JVM в раннере; - QA Деск не запускает тесты и не собирает покрытие Kover и JaCoCo;
- нет автоматического заведения дефектов по упавшим тестам — решение принимает человек из строки прогона;
- ключ через
@TagJUnit не читается: в JUnit XML он не попадает.
Частые вопросы
Нужна ли настройка Gradle для @DisplayName?
Нет. Gradle 8 пишет @DisplayName и имена в обратных кавычках в <testcase name> сам. Настройка usePhrasedTestCaseMethodName нужна только Maven Surefire — она описана на странице Java.
Мешают ли ключу скобки, которые Gradle дописывает к имени?
Нет. Gradle записывает QAD-C33 блокировка после 5 попыток() — ключ читается из начала строки, скобки в конце ему не мешают. Для @DisplayName скобок нет вовсе.
Как загрузить тесты всех модулей одним прогоном?
Списком путей: qadesk-cli upload */build/test-results/test. Утилита обходит каталоги рекурсивно и склеивает все TEST-*.xml в один отчёт под одним ключом идемпотентности. Отдельные загрузки под одним ключом дадут 409 на вторую.
Почему упавший тест не попал в отчёт?
Скорее всего, у него тело-выражение: fun x() = … с типом возврата не Unit. JUnit Jupiter такой метод не запускает и не сообщает об этом. Переписать на блочное тело { … } — и тест вернётся в отчёт вместе со своим падением.
Работает ли это с Maven и Kotlin?
Да, по правилу Java: @DisplayName плюс usePhrasedTestCaseMethodName у Surefire — рецепт со страницы Java. Имена в обратных кавычках под Maven на стенде не прогонялись; первый проект на этой связке проверяет их руками по несматченным тестам в выводе утилиты.
С чего начать
- Заведите проект и выпустите токен приёма: Настройки → Интеграции → CI / автотесты.
- Добавьте
ignoreFailures = trueвtasks.testи шагqadesk-cli upload build/test-results/testв пайплайн. - Возьмите кейсы регистрации и авторизации и вынесите их ключи в имена соответствующих тестов.
- Проверьте по покрытию, что ни один кейс не остался без результата — так ловятся тесты, которые Jupiter молча пропустил.
Попробуйте на своём проекте
Заведите проект, загрузите первый отчёт или заведите первый дефект — бесплатный тариф без карты. Нужно демо на ваших сценариях — напишите на hello@qadesk.ru.
Создать проектРядом по теме
Android
Gradle пишет JUnit XML для каждого инструментального теста — QA Деск принимает его как есть и кладёт в покрытие рядом с ручными кейсами; конфигурации прогона превращают план в матрицу «устройство × версия ОС».
Android — отчёт с эмулятора и устройства, прогон по матрицеJava · Maven / Gradle
Maven и Gradle уже пишут JUnit XML — QA Деск принимает его без плагинов. Единственная тонкость: ключ кейса `QAD-C31` содержит дефис, а имя Java-метода дефис содержать не может, поэтому ключ ставится в `@DisplayName`, и Surefire надо попросить его записать.
Java — отчёты Surefire и Gradle в покрытие, ключ кейса в @DisplayNameSpring Boot
Spring Boot наследует Surefire и Failsafe от `spring-boot-starter-parent`, и обе группы тестов — быстрые срезы и тяжёлые интеграционные — QA Деск принимает одним прогоном. Ключ кейса ставится в `@DisplayName`; настройка Surefire одна и та же, что для любого Java-проекта.
Spring Boot — @WebMvcTest и @SpringBootTest в одном прогоне, дефект из упавшего контекста