Платформы

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

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

Как загрузить отчёт 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;
  • нет автоматического заведения дефектов по упавшим тестам — решение принимает человек из строки прогона;
  • ключ через @Tag JUnit не читается: в 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 на стенде не прогонялись; первый проект на этой связке проверяет их руками по несматченным тестам в выводе утилиты.

С чего начать

  1. Заведите проект и выпустите токен приёма: Настройки → Интеграции → CI / автотесты.
  2. Добавьте ignoreFailures = true в tasks.test и шаг qadesk-cli upload build/test-results/test в пайплайн.
  3. Возьмите кейсы регистрации и авторизации и вынесите их ключи в имена соответствующих тестов.
  4. Проверьте по покрытию, что ни один кейс не остался без результата — так ловятся тесты, которые 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 в покрытие, ключ кейса в @DisplayName
Платформы

Spring Boot

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

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