Программирование

Настройка Gradle зависимостей для исключения logback-android только в тестах

Решение конфликта SLF4J провайдеров в Gradle. Как исключить logback-android только для юнит-тестов, сохранив логирование в основном Android приложении.

7 ответов 1 просмотр

Как правильно настроить зависимости в Gradle, чтобы исключение logback-android применялось только для юнит-тестов, но сохранялось для основного Android приложения?

Мой проект использует Gradle для сборки, и в файле сборки есть следующие зависимости:

gradle
dependencies {
 implementation libs.android.room.runtime
 implementation libs.android.work.multiprocess
 implementation libs.android.work.runtime
 implementation libs.appcompat
 implementation libs.constraintlayout
 implementation libs.legacy.support.v4
 implementation libs.material
 implementation libs.okhttp.bom
 implementation libs.slf4j

 implementation project(':lib')

 testImplementation libs.android.room.testing
 testImplementation libs.json.org
 testImplementation libs.junit
 testImplementation libs.mockito

 runtimeOnly libs.logback.android
 testRuntimeOnly libs.logback.classic

 androidTestImplementation libs.android.work.testing
 androidTestImplementation libs.espresso.core
 androidTestImplementation libs.ext.junit
}

При запуске юнит-тестов возникает предупреждение, за которым следует ошибка, прерывающая выполнение тестов:

SLF4J(W): Class path contains multiple SLF4J providers.
SLF4J(W): Found provider [ch.qos.logback.classic.spi.LogbackServiceProvider@560348e6]
SLF4J(W): Found provider [org.slf4j.impl.LoggerServiceProvider@1df8b5b8]
SLF4J(W): See https://www.slf4j.org/codes.html#multiple_bindings for an explanation.

Если я закомментирую строку runtimeOnly libs.logback.android, то юнит-тесты запускаются с логированием, но Android приложение теряет логирование. Добавление исключения в конфигурацию:

gradle
configurations {
 all {
 exclude group: "com.github.tony19", module: "logback-android"
 }
}

работает аналогично закомментированию зависимости. Однако, если изменить all на test, то снова возникает ошибка дублирования провайдеров. Как настроить зависимости так, чтобы исключение применялось только для юнит-тестирования?

Проблема конфликта SLF4J провайдеров возникает из-за одновременного присутствия logback-android и logback.classic в classpath тестов. Вот правильная настройка зависимостей Gradle для исключения logback-android только для юнит-тестов:

gradle
dependencies {
 // Основные зависимости приложения
 implementation libs.android.room.runtime
 implementation libs.android.work.multiprocess
 implementation libs.android.work.runtime
 implementation libs.appcompat
 implementation libs.constraintlayout
 implementation libs.legacy.support.v4
 implementation libs.material
 implementation libs.okhttp.bom
 implementation libs.slf4j
 implementation project(':lib')
 
 // Тестовые зависимости
 testImplementation libs.android.room.testing
 testImplementation libs.json.org
 testImplementation libs.junit
 testImplementation libs.mockito
 
 // Ключевые настройки для логирования
 runtimeOnly libs.logback.android // Для основного приложения
 testRuntimeOnly libs.logback.classic // Только для тестов
 
 // Android тесты
 androidTestImplementation libs.android.work.testing
 androidTestImplementation libs.espresso.core
 androidTestImplementation libs.ext.junit
}

// Решение: исключаем logback-android только для тестовых конфигураций
configurations {
 testRuntimeOnly {
 exclude group: "com.github.tony19", module: "logback-android"
 }
}

Понимание конфигураций зависимостей Gradle для Android

Gradle использует разные конфигурации зависимостей для разных этапов сборки. Это ключевое для решения вашей проблемы. Как объясняется в документации Gradle, основные конфигурации:

Конфигурация Наследуется Компиляция Рантайм Описание
implementation нет да да Доступна только в текущем модуле
api нет да да Экспортируется для других модулей
runtimeOnly нет нет да Только для выполнения приложения
testImplementation нет да да Для компиляции тестов
testRuntimeOnly нет нет да Только для выполнения тестов

Как отмечено в статье о различиях конфигураций, правильный выбор конфигураций критически важен для оптимизации сборки и избежания конфликтов.

Проблема дублирования SLF4J провайдеров в юнит-тестах

Ошибка возникает потому, что в classpath тестов присутствуют оба провайдера SLF4J:

  1. logback-android из runtimeOnly (унаследован в тесты)
  2. logback.classic из testRuntimeOnly

SLF4J не поддерживает одновременное использование нескольких провайдеров, что вызывает сбой. Как показано в обсуждении на Stack Overflow, наследование зависимостей между конфигурациями - частая причина таких конфликтов.

Правильное исключение logback-android только для тестов

Решение заключается в целевом исключении зависимости только для тестовой конфигурации. Вместо глобального исключения через configurations.all, что влияет на все сборки, нужно использовать конкретную конфигурацию:

gradle
configurations {
 // Исключаем logback-android только для тестового рантайма
 testRuntimeOnly {
 exclude group: "com.github.tony19", module: "logback-android"
 }
}

Этот подход гарантирует:

  • logback-android остается в основном приложении (runtimeOnly)
  • Тесты используют только logback.classic из testRuntimeOnly
  • Android-тесты продолжают работать с logback-android

Конфигурации testImplementation и testRuntimeOnly в Gradle

Важное различие между тестовыми конфигурациями, как описано в документации Gradle:

  • testImplementation: Зависимости, необходимые для компиляции тестов (API, заглушки)
  • testRuntimeOnly: Зависимости, необходимые только для выполнения тестов (движки тестирования)

Для JUnit 5 это выглядит так:

gradle
// API для написания тестов
testImplementation "org.junit.jupiter:junit-jupiter-api:5.8.2" 

// Движок для выполнения
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.8.2"

Практическое решение конфликта зависимостей

Для вашего проекта оптимальная конфигурация:

gradle
dependencies {
 // Основные зависимости
 implementation libs.slf4j
 runtimeOnly libs.logback.android // Логирование в приложении
 
 // Тестовые зависимости
 testImplementation libs.junit
 testRuntimeOnly libs.logback.classic // Только для тестов
 
 // Android тесты
 androidTestImplementation libs.espresso.core
 androidTestImplementation libs.ext.junit
 // androidTestRuntimeOnly libs.logback.android // Если нужно для UI-тестов
}

// Ключевое решение
configurations {
 testRuntimeOnly {
 exclude group: "com.github.tony19", module: "logback-android"
 }
}

Если для Android UI-тестов (androidTestImplementation) тоже требуется логирование, добавьте:

gradle
androidTestRuntimeOnly libs.logback.android

Оптимизация сборки Android проекта с Gradle

Дополнительные рекомендации для улучшения производительности сборки:

  1. Изоляция зависимостей:
gradle
dependencies {
 api libs.slf4j // Если lib использует SLF4J в своем API
 implementation libs.logback.android
}
  1. Кэширование зависимостей:
gradle
gradle.properties
org.gradle.caching=true
org.gradle.parallel=true
  1. Версионирование зависимостей:
gradle
// В build.gradle
ext {
 slf4jVersion = "2.0.7"
 logbackAndroidVersion = "3.0.0"
}

dependencies {
 implementation "org.slf4j:slf4j-api:${slf4jVersion}"
 runtimeOnly "com.github.tony19:logback-android:${logbackAndroidVersion}"
}

Как отмечено в обсуждении Gradle Forums, правильная настройка конфигураций зависимостей критически важна для избежания конфликтов и оптимизации сборки.

Источники

  1. Stack Overflow на русском — Исключение пакета из зависимости в Gradle: https://ru.stackoverflow.com/questions/711622/gradle-исключить-пакет-из-зависимости
  2. Stack Overflow — Различие между implementation, api и compile: https://stackoverflow.com/questions/44493378/whats-the-difference-between-implementation-api-and-compile-in-gradle
  3. Medium — Детальное объяснение конфигураций Gradle: https://abhiappmobiledeveloper.medium.com/difference-between-implementation-api-compile-and-runtimeonly-in-gradle-dependency-55b70215d245
  4. Gradle Forums — Проблемы с testRuntimeOnly конфигурацией: https://discuss.gradle.org/t/resolving-dependency-configuration-testruntimeonly-is-not-allowed/40583
  5. Gradle Documentation — Конфигурации для тестирования: https://docs.gradle.org/current/userguide/java_testing.html

Заключение

Для решения конфликта SLF4J провайдов в Gradle:

  1. Используйте configurations.testRuntimeOnly { exclude ... } для целевого исключения
  2. Сохраняйте runtimeOnly libs.logback.android для основного приложения
  3. Добавляйте testRuntimeOnly libs.logback.classic только для тестов
  4. Для Android UI-тестов добавьте отдельную конфигурацию при необходимости

Этот подход гарантирует, что логирование будет работать и в приложении, и в тестах, но без конфликта SLF4J провайдеров. Правильная настройка зависимостей в Gradle — ключ к стабильной и быстрой сборке Android проектов.

T

Для исключения пакета из зависимости в Gradle используется синтаксис exclude. Вот несколько способов:

  1. Исключение в секции зависимостей:
gradle
dependencies {
 compile('rhino:js:1.7.7') {
 exclude group: 'com.github.tony19', module: 'logback-android'
 }
}
  1. Глобальное исключение через конфигурации:
gradle
configurations {
 all {
 exclude group: "com.github.tony19", module: "logback-android"
 }
}
  1. Исключение для определенной конфигурации:
gradle
configurations {
 compile {
 exclude group: "com.github.tony19", module: "logback-android"
 }
}

Важно понимать, что exclude удаляет только транзитивные зависимости. Если пакет является прямой зависимостью, его нужно исключать через transitive = false.

H

Различия между Gradle конфигурациями зависимостей:

Конфигурация Наследуется Компиляция Рантайм Описание
implementation нет да да Зависимость доступна только на уровне модуля, не экспортируется
api нет да да Зависимость доступна на уровне модуля и экспортируется для других модулей
compile наследуется да да Устаревшая конфигурация (заменена на implementation/api)
runtimeOnly нет нет да Зависимость доступна только на рантайме, не нужна для компиляции
testImplementation нет да да Зависимость только для тестов на уровне компиляции
testRuntimeOnly нет нет да Зависимость только для тестов на уровне рантайма

Особенности testRuntimeOnly:

  • Объявляет зависимости, которые требуются только во время выполнения тестов
  • Не требуются во время компиляции тестов
  • Используется для тестовых рантайм зависимостей, таких как движки тестирования
A

Разница между implementation, API, compile и runtimeOnly в Gradle:

  1. implementation:
  • Зависимость доступна только внутри текущего модуля
  • Не экспортируется для других модулей
  • Изменение API такой зависимости не вызовет перекомпиляцию зависимых модулей
  • Повышает скорость сборки за счет изоляции зависимостей
  1. api:
  • Зависимость доступна внутри текущего модуля и экспортируется
  • Изменение API такой зависимости вызовет перекомпиляцию всех зависимых модулей
  • Используется для библиотек, которые являются частью публичного API
  1. compile (устаревшая):
  • В Gradle 7.0 заменена на implementation и api
  • Имела те же возможности, что и api
  1. runtimeOnly:
  • Зависимость доступна только на рантайме
  • Не требуется для компиляции
  • Используется для зависимостей, которые нужны только при запуске приложения

Для тестовых конфигураций:

  • testImplementation: зависимости для компиляции тестов
  • testRuntimeOnly: зависимости для выполнения тестов

Важно правильно выбирать конфигурации для оптимизации сборки и избежания конфликтов зависимостей.

V

Проблема с конфигурацией testRuntimeOnly в Gradle 7:

После обновления до Gradle 7 и замены testRuntime на testRuntimeOnly в build.gradle возникает ошибка:

“Resolving dependency configuration ‘testRuntimeOnly’ is not allowed as it is defined as ‘canBeResolved=false’”

Причина:

  • В Gradle 7 testRuntimeOnly настроен как ‘canBeResolved=false’
  • Это означает, что конфигурация не может быть разрешена напрямую
  • Вместо этого нужно использовать резолвимую конфигурацию, которая наследует testRuntimeOnly

Решение:

gradle
configurations {
 testRuntimeClasspath {
 extendsFrom testRuntimeOnly
 }
}

Или использовать существующую конфигурацию testRuntimeClasspath вместо testRuntimeOnly.

V

Testing in Java & JVM projects:

Gradle автоматически создает конфигурации зависимостей для тестовых исходников:

  • testImplementation: зависимости, требуемые для компиляции тестов
  • testRuntimeOnly: зависимости, требуемые только во время выполнения тестов

Важно понимать, что тестовый исходный набор автоматически создает соответствующие конфигурации зависимостей, из которых наиболее полезными являются testImplementation и testRuntimeOnly, которые плагины интегрируют в путь к классам тестовой задачи.

Пример настройки тестовых зависимостей:

gradle
dependencies {
 testImplementation 'junit:junit:4.12'
 testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.2.0'
}

Эти конфигурации позволяют разделять зависимости, необходимые для компиляции тестов, и зависимости, необходимые только для их выполнения.

R

Пример использования testRuntimeOnly в контексте JUnit 5:

gradle
//(Required) Writing and executing Unit Tests on the JUnit Platform
testImplementation "org.junit.jupiter:junit-jupiter-api:5.2.0"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine:5.2.0"

// (Optional) If you need "Parameterized Tests"
testImplementation "org.junit.jupiter:junit-jupiter-params:5.2.0"

// (Optional) If you also have JUnit 4-based tests
testImplementation "junit:junit:4.12"
testRuntimeOnly "org.junit.vintage:junit-vintage-engine:5.2.0"

testImplementation "io.mockk:mockk:1.8.5"

Здесь:

  • testImplementation используется для API и параметризованных тестов
  • testRuntimeOnly используется для движков выполнения тестов
  • Это позволяет избежать включения движков в compile classpath
Авторы
T
Full stack-разработчик
E
Мобильный разработчик
H
Разработчик
A
Разработчик
A
Technical Writer
V
Community Contributors
V
Software Engineers
R
Разработчик
S
Разработчик
W
Разработчик
Проверено модерацией
НейроОтветы
Модерация
Настройка Gradle зависимостей для исключения logback-android только в тестах