Многопроектные тестовые зависимости с Gradle

154

У меня есть многопроектная конфигурация, и я хочу использовать gradle.

Мои проекты такие:

  • Проект А

    • -> src/main/java
    • -> src/test/java
  • Проект Б

    • -> src/main/java(зависит src/main/javaот проекта A )
    • -> src/test/java(зависит src/test/javaот проекта A )

Файл моего проекта B build.gradle выглядит так:

apply plugin: 'java'
dependencies {
  compile project(':ProjectA')
}

Задача compileJavaработы большой , но compileTestJavaне компилировать тестовый файл из проекта A .

mathd
источник
2
возможный дубликат: stackoverflow.com/questions/5144325/gradle-test-dependency
Майк Райландер

Ответы:

122

Устаревший - для Gradle 5.6 и выше используйте этот ответ .

В проекте B вам просто нужно добавить testCompileзависимость:

dependencies {
  ...
  testCompile project(':A').sourceSets.test.output
}

Протестировано с Gradle 1.7.

Fesler
источник
7
Оказывается, свойство классов устарело - вместо него используйте выход.
Феслер
12
Это не работает в Gradle 1.3, поскольку sourceSets больше не является общедоступным свойством проекта.
Дэвид Пярссон
3
Имейте в виду, что вышеупомянутое решение требует, по крайней мере, gradle testClassesдо того, как структура сборки станет действительной. Например, плагин Eclipse не позволит вам импортировать проект до этого. Это действительно позор testCompile project(':A'), не работает. @ DavidPärsson: «Gradle 1.3» противоречит «больше не», поскольку Феслер тестировал с Gradle 1.7.
Патрик Бергнер
3
не работал для меня Ошибка с круговой зависимостью: compileTestJava \ ---: testClasses \ ---: compileTestJava (*)
rahulmohan
8
Не делайте этого, проекты не должны охватывать другие проекты. Вместо этого используйте ответ Никиты, правильно смоделировав это как зависимость проекта.
Stefan Oehme
63

Простой способ - добавить явную зависимость задачи в ProjectB:

compileTestJava.dependsOn tasks.getByPath(':ProjectA:testClasses')

Сложный (но более понятный) способ - создать дополнительную конфигурацию артефактов для ProjectA:

task myTestsJar(type: Jar) { 
  // pack whatever you need...
}

configurations {
  testArtifacts
}

artifacts {
   testArtifacts myTestsJar
}

и добавьте testCompileзависимость для ProjectB

apply plugin: 'java'
dependencies {
  compile project(':ProjectA')
  testCompile project(path: ':ProjectA', configuration: 'testArtifacts')
}
Никита Скворцов
источник
3
Я попробовал это (простой способ), и хотя он гарантирует, что он создает testClasses, он не добавляет путь к CLASSPATH, поэтому мои тесты ProjectB, которые зависят от классов теста ProjectA, все еще не могут быть построены.
pjz
1
@dmoebius, вы должны добавить testArtifactsконфигурацию, подобную этой: configurations { testArtifacts } более подробную информацию смотрите в этом разделе справки Gradle: gradle.org/docs/current/dsl/…
Никита Скворцов,
7
В Gradle 1.8 вы, возможно, захотите from sourceSets.test.outputи, возможно, classifier = 'tests'замените его // pack whatever you need...в ответе
Питер Ламберг
1
Подтвердили, что с Gradle 1.12, используя полное решение, с @PeterLamberg предложенные дополнения работают как положено. Не влияет на импорт проекта в Eclipse.
sfitts
3
Это работает для меня в Gradle 4.7. Теперь у них есть некоторые документы о подходе на docs.gradle.org/current/dsl/…
Натан Уильямс
19

Теперь это поддерживается как первоклассная функция в Gradle. Модули с плагинами javaили java-libraryмогут также включать в себя java-test-fixturesплагин, который предоставляет вспомогательные классы и ресурсы, которые будут использоваться testFixturesпомощником. Преимущество этого подхода против артефактов и классификаторов:

  • правильное управление зависимостями (реализация / API)
  • хорошее отделение от тестового кода (отдельный исходный набор)
  • не нужно отфильтровывать тестовые классы, чтобы выставлять только утилиты
  • поддерживается Gradle

пример

:modul:one

Modul / один / build.gradle

plugins {
  id "java-library" // or "java"
  id "java-test-fixtures"
}

Modul / один / SRC / testFixtures / Java / COM / пример / Helper.java

package com.example;
public class Helper {}

:modul:other

Modul / другие / build.gradle

plugins {
  id "java" // or "java-library"
}
dependencies {
  testImplementation(testFixtures(project(":modul:one")))
}

Modul / другой / SRC / тест / Java / COM / пример / другой / SomeTest.java

package com.example.other;
import com.example.Helper;
public class SomeTest {
  @Test void f() {
    new Helper(); // used from :modul:one's testFixtures
  }
}

дальнейшее чтение

Для получения дополнительной информации см. Документацию:
https://docs.gradle.org/current/userguide/java_testing.html#sec:java_test_fixtures

Он был добавлен в 5.6:
https://docs.gradle.org/5.6/release-notes.html#test-fixtures-for-java-projects

TWiStErRob
источник
Они работают над поддержкой этого на Android, см. Issetracker.google.com/issues/139762443 и issetracker.google.com/issues/139438142
Альберт Вила Кальво
18

Я недавно столкнулся с этой проблемой, и человек это трудные вопросы, чтобы найти ответы.

Вы делаете ошибку, думая, что проект должен экспортировать свои тестовые элементы так же, как он экспортирует свои основные артефакты и зависимости.

То, с чем я лично добился гораздо большего успеха, - это создание нового проекта в Gradle. В вашем примере я бы назвал это

Проект A_Test -> src / main / java

Я бы поместил в src / main / java файлы, которые у вас есть в Project A / src / test / java. Сделайте любые зависимости testCompile вашего Project A зависимости компиляции Project A_Test.

Затем сделайте Project A_Test зависимостью testCompile проекта B.

Это не логично, когда вы подходите к этому с точки зрения автора обоих проектов, но я думаю, что это имеет смысл, когда вы думаете о таких проектах, как junit и scalatest (и другие. Даже если эти фреймворки связаны с тестированием, они не считаются частью «тестовых» целей в их собственных средах - они создают основные артефакты, которые другие проекты используют в своей тестовой конфигурации. Вы просто хотите следовать той же схеме.

Попытка сделать другие ответы, перечисленные здесь, не сработала лично для меня (используя Gradle 1.9), но я обнаружил, что шаблон, который я здесь описываю, в любом случае является более чистым решением.

Мартин Снайдер
источник
Да, выбрал этот подход в конце дня.
Кома
Это лучший подход! За исключением того, что я оставил бы тестовый код в проекте A и переместил только зависимости для A src / test / java и B src / test / java в A_Test. Затем сделайте Project A_Test зависимостью testImplementation для A и B.
Эрик Силлен,
17

Я знаю, что это старый вопрос, но у меня была та же проблема, и я потратил некоторое время на выяснение того, что происходит. Я использую Gradle 1.9. Все изменения должны быть в ProjectB'sbuild.gradle

Чтобы использовать тестовые классы из ProjectA в тестах ProjectB:

testCompile files(project(':ProjectA').sourceSets.test.output.classesDir)

Чтобы убедиться, что sourceSetsсвойство доступно для ProjectA:

evaluationDependsOn(':ProjectA')

Чтобы убедиться, что тестовые классы из ProjectA действительно присутствуют при компиляции ProjectB:

compileTestJava.dependsOn tasks.getByPath(':ProjectA:testClasses')
Доминик Павляк
источник
1
Это также сработало для меня, за исключением того, что я должен был опустить .classesDir.
11

Новое решение на базе testJar (поддерживается trnsitive зависимость), доступное как плагин gradle:

https://github.com/hauner/gradle-plugins/tree/master/jartest

https://plugins.gradle.org/plugin/com.github.hauner.jarTest/1.0

Из документации

Если у вас многопроектная сборка Gradle, у вас могут быть тестовые зависимости между подпроектами (что, вероятно, является подсказкой о том, что ваши проекты плохо структурированы).

Например, предположим, что проект, в котором подпроект Project B зависит от Project A и B, имеет не только зависимость компиляции от A, но также и тестовую зависимость. Для компиляции и запуска тестов B нам понадобятся несколько вспомогательных классов для тестирования из A.

По умолчанию Gradle не создает JAR-артефакт из результатов тестовой сборки проекта.

Этот плагин добавляет конфигурацию testArchives (на основе testCompile) и задачу jarTest для создания jar из набора тестовых источников (с добавлением теста классификатора к имени jar). Затем мы можем полагаться в B на конфигурацию testArchives для A (которая также будет включать транзитивные зависимости A).

В A мы добавим плагин для build.gradle:

apply plugin: 'com.github.hauner.jarTest'

В B мы ссылаемся на конфигурацию testArchives следующим образом:

dependencies {
    ...
    testCompile project (path: ':ProjectA', configuration: 'testArchives') 
}
demon101
источник
1
Хотя эта ссылка может ответить на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными, если связанная страница изменится. - Из обзора
Ян
добавлено несколько строк текста
demon101
В любом случае, информация о новом плагине Gradle предоставлена.
demon101
4
@ demon101 Не работает в Gradle 4.6, появляется ошибкаCould not get unknown property 'testClasses' for project ':core' of type org.gradle.api.Project.
Vignesh Sundaramoorthy
11

Пожалуйста, прочтите обновление ниже.

Подобные проблемы, описанные JustACluelessNewbie, возникают в IntelliJ IDEA. Проблема в том, что зависимость на testCompile project(':core').sourceSets.test.outputсамом деле означает: «зависеть от классов, сгенерированных задачей сборки gradle». Поэтому, если вы откроете чистый проект, где классы еще не созданы, IDEA не распознает их и сообщит об ошибке.

Чтобы решить эту проблему, вы должны добавить зависимость от тестовых исходных файлов рядом с зависимостью от скомпилированных классов.

// First dependency is for IDEA
testCompileOnly files { project(':core').sourceSets.test.java.srcDirs }
// Second is for Gradle
testCompile project(':core').sourceSets.test.output

Вы можете наблюдать зависимости, распознаваемые IDEA, в Настройках модуля -> Зависимости (область тестирования) .

Btw. Это не очень хорошее решение, поэтому стоит подумать о рефакторинге. Сам Gradle имеет специальный подпроект, содержащий только классы поддержки тестирования. См. Https://docs.gradle.org/current/userguide/test_kit.html.

Обновление 2016-06-05 Подробнее Я думаю о предлагаемом решении меньше, мне нравится. Есть несколько проблем с этим:

  1. Это создает две зависимости в IDEA. Один указывает на тестирование источников, другой на скомпилированные классы. И очень важно, в каком порядке IDEA признает эти зависимости. Вы можете поиграть с ним, изменив порядок зависимостей в Настройках модуля -> вкладка Зависимости.
  2. Объявляя эти зависимости, вы излишне загрязняете структуру зависимостей.

Так какое же решение лучше? На мой взгляд, это создание нового пользовательского набора исходных кодов и добавление в него общих классов. На самом деле авторы проекта Gradle сделали это, создав исходный набор testFixtures.

Для этого вам просто нужно:

  1. Создайте исходный набор и добавьте необходимые конфигурации. Проверьте этот плагин сценария, используемый в проекте Gradle: https://github.com/gradle/gradle/blob/v4.0.0/gradle/testFixtures.gradle
  2. Объявите правильную зависимость в зависимом проекте:

    dependencies {
        testCompile project(path: ':module-with-shared-classes', configuration: 'testFixturesUsageCompile')
    }
    
  3. Импортируйте проект Gradle в IDEA и используйте опцию «создать отдельный модуль для исходного набора» при импорте.
Вацлав Кужель
источник
1
@jannis исправлено. Btw. Gradle переместил свой плагин для тестирования на основе Groovy на новый Kotlin: github.com/gradle/gradle/blob/v5.0.0/buildSrc/subprojects/…
Вацлав Кужель
@ VáclavKužel Я нашел твое интересное решение в твоей записи в блоге, и оно очень хорошо решило мою проблему. Спасибо;)
Заеримогаддам
10

Решение Феслера не сработало для меня, когда я попытался построить проект для Android (версия 2.2.0). Поэтому мне пришлось ссылаться на необходимые классы вручную:

android {
    sourceSets {
        androidTest {
            java.srcDir project(':A').file("src/androidTest/java")
        }
        test {
            java.srcDir project(':A').file("src/test/java")
        }
    }
}
Beloo
источник
1
небольшая опечатка, пропущенная конечная цитата после проекта (': A'). Это сработало для меня, хотя, спасибо m8
Райан Ньюсом
1
Для Android эта идея прекрасно сработала для меня, без хакерских ощущений stackoverflow.com/a/50869037/197141
Арберг
@arberg Да, похоже, хороший подход. Единственное ограничение, которое я вижу, это @VisibleForTestingправила Lint. Вы не сможете вызывать такие методы из обычного модуля в не тестовой папке.
Белу
5

Я так опоздал на вечеринку (теперь это Gradle v4.4), но для всех, кто найдет это:

Предполагая, что:

~/allProjects
|
|-/ProjectA/module-a/src/test/java
|
|-/ProjectB/module-b/src/test/java

Перейдите к build.gradle проекта B (который нуждается в нескольких тестовых классах из A) и добавьте следующее:

sourceSets {
    String sharedTestDir = "${projectDir}"+'/module-b/src/test/java'
    test {
        java.srcDir sharedTestDir
    }
}

или (при условии, что ваш проект называется «ProjectB»)

sourceSets {
    String sharedTestDir = project(':ProjectB').file("module-b/src/test/java")
    test {
        java.srcDir sharedTestDir
    }
}

Вуаля!

tricknology
источник
3
Вопрос не затрагивает Android. Можете ли вы сделать свой ответ независимым от того, разрабатывает ли разработчик для Android или нет, или это только для разработчиков Android?
Робин Грин
4

Если у вас есть фиктивные зависимости, которые вам нужно разделить между тестами, вы можете создать новый проект, projectA-mockа затем добавить его как тестовую зависимость ProjectAи ProjectB:

dependencies {
  testCompile project(':projectA-mock')
}

Это понятно решение зависимостей доли ложных, но если вам нужно запустить тесты из ProjectAвProjectB использовании другого решения.

sylwano
источник
Отличное решение для общего дела!
Эрик Силлен
4

Если вы хотите использовать зависимости артефактов, чтобы:

  • Исходные классы ProjectB зависят от исходных классов Project A
  • Тестовые классы ProjectB зависят от тестовых классов Project A

тогда раздел зависимостей ProjectB в build.gradle должен выглядеть примерно так:

dependencies {

  compile("com.example:projecta:1.0.0")

  testCompile("com.example:projecta:1.0.0:tests")

}

Для того, чтобы это работало, ProjectA необходимо создать банку -tests и включить ее в артефакты, которые он создает.

Файл build.gradle в ProjectA должен содержать следующую конфигурацию:

task testsJar(type: Jar, dependsOn: testClasses) {
    classifier = 'tests'
    from sourceSets.test.output
}

configurations {
    tests
}

artifacts {
    tests testsJar
    archives testsJar
}

jar.finalizedBy(testsJar)

Когда артефакты Projecta публикуются на ваш Artifactory они будут включать в себя - тесты банку .

TestCompile в зависимости разделе ProjectB принесет в классах в - тестах баночки.


Если вы хотите включить исходный и тестовый классы ProjectA в ProjectB для целей разработки, то раздел зависимостей в build.gradle в ProjectB будет выглядеть следующим образом:

dependencies {

  compile project(':projecta')

  testCompile project(path: ':projecta', configuration: 'tests')

}
Joman68
источник
1
К сожалению (в Gradle 6), плоское включение, которое было именно тем, что я хотел, больше не работает, потому что больше нет «тестов» конфигурации. Используя println(configurations.joinToString("\n") { it.name + " - " + it.allDependencies.joinToString() })(в сценарии сборки kotlin), я определил, какие конфигурации все еще существуют и имеют зависимости, но на все эти вопросы Gradle пожаловался:Selected configuration 'testCompileClasspath' on 'project :sdk' but it can't be used as a project dependency because it isn't intended for consumption by other components.
Xerus
2

Некоторые из других ответов так или иначе вызывали ошибки - Gradle не обнаружил тестовые классы из других проектов или проект Eclipse имел недопустимые зависимости при импорте. Если у кого-то есть такая же проблема, я предлагаю перейти с:

testCompile project(':core')
testCompile files(project(':core').sourceSets.test.output.classesDir)

Первая строка заставляет Eclipse связать другой проект как зависимость, поэтому все источники включены и обновлены. Второй позволяет Gradle фактически видеть источники, не вызывая при этом каких-либо недопустимых ошибок зависимости, как это testCompile project(':core').sourceSets.test.outputделает.

Czyzby
источник
2

Здесь, если вы используете Kotlin DSL , вы должны создать свою задачу в соответствии с документацией Gradle .

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

Простые шаги

  1. В проекте A вы должны добавить в свой build.gradle.kts:
configurations {
    create("test")
}

tasks.register<Jar>("testArchive") {
    archiveBaseName.set("ProjectA-test")
    from(project.the<SourceSetContainer>()["test"].output)
}

artifacts {
    add("test", tasks["testArchive"])
}
  1. Затем в вашем проекте B в зависимости, вам нужно будет добавить в build.gradle.kts:
dependencies {
    implementation(project(":ProjectA"))
    testImplementation(project(":ProjectA", "test"))
}
Sylhare
источник
-1

в проекте B:

dependencies {
  testCompile project(':projectA').sourceSets.test.output
}

Кажется, работает в 1.7-RC-2

Джон Карон
источник
2
Это также создает ненужные сложности при обработке проекта Eclipse. Решение, предложенное @NikitaSkvortsov, является предпочтительным.
sfitts