Testabhängigkeiten für mehrere Projekte mit Gradle

152

Ich habe eine Multiprojektkonfiguration und möchte gradle verwenden.

Meine Projekte sind wie folgt:

  • Projekt A.

    • -> src/main/java
    • -> src/test/java
  • Projekt B.

    • -> src/main/java(abhängig src/main/javavon Projekt A )
    • -> src/test/java(abhängig src/test/javavon Projekt A )

Meine Projekt B- build.gradle Datei sieht folgendermaßen aus:

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

Die Aufgabe compileJavafunktioniert hervorragend, aber compileTestJavadie Testdatei wird nicht aus Projekt A kompiliert .

mathd
quelle
2
Mögliches Duplikat: stackoverflow.com/questions/5144325/gradle-test-dependency
Mike Rylander

Antworten:

122

Veraltet - Verwenden Sie für Gradle 5.6 und höher diese Antwort .

In Projekt B müssen Sie nur eine testCompileAbhängigkeit hinzufügen :

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

Getestet mit Gradle 1.7.

Fesler
quelle
7
Es stellt sich heraus, dass die Eigenschaft classes veraltet ist. Verwenden Sie stattdessen die Ausgabe.
Fesler
12
Dies funktioniert in Gradle 1.3 nicht, da sourceSets nicht mehr öffentliches Eigentum eines Projekts ist.
David Pärsson
3
Beachten Sie, dass für die obige Lösung mindestens a erforderlich ist, gradle testClassesbevor die Build-Struktur tatsächlich gültig ist. Mit dem Eclipse-Plugin können Sie das Projekt beispielsweise vorher nicht importieren. Es ist wirklich eine Schande, dass testCompile project(':A')es nicht funktioniert. @ DavidPärsson: "Gradle 1.3" widerspricht "nicht mehr", da Fesler mit Gradle 1.7 getestet hat.
Patrick Bergner
3
hat bei mir nicht funktioniert. Fehlgeschlagen mit zirkulärer Abhängigkeit: compileTestJava \ ---: testClasses \ ---: compileTestJava (*)
rahulmohan
8
Tun Sie dies nicht, Projekte sollen nicht in andere Projekte hineinreichen. Verwenden Sie stattdessen die Antwort von Nikita und modellieren Sie diese korrekt als Projektabhängigkeit.
Stefan Oehme
63

Ein einfacher Weg ist das Hinzufügen einer expliziten Aufgabenabhängigkeit in ProjectB:

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

Eine schwierige (aber klarere) Möglichkeit besteht darin, eine zusätzliche Artefaktkonfiguration für ProjectA zu erstellen:

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

configurations {
  testArtifacts
}

artifacts {
   testArtifacts myTestsJar
}

und fügen Sie die testCompileAbhängigkeit für ProjectB hinzu

apply plugin: 'java'
dependencies {
  compile project(':ProjectA')
  testCompile project(path: ':ProjectA', configuration: 'testArtifacts')
}
Nikita Skvortsov
quelle
3
Ich habe dies versucht (auf einfache Weise) und obwohl dies sicherstellt, dass die Testklassen erstellt werden, wird der Testpfad nicht zum CLASSPATH hinzugefügt, sodass meine ProjectB-Tests, die von ProjectA-Testklassen abhängen, immer noch nicht erstellt werden können.
pjz
1
@dmoebius Sie müssen testArtifactsKonfiguration wie folgt hinzufügen : configurations { testArtifacts } Weitere Details finden Sie in diesem Abschnitt der Gradle-Hilfe: gradle.org/docs/current/dsl/…
Nikita Skvortsov
7
In Gradle 1.8 möchten Sie vielleicht from sourceSets.test.outputund möglicherweise classifier = 'tests'anstelle // pack whatever you need...der Antwort
Peter Lamberg
1
Bestätigt, dass mit Gradle 1.12 unter Verwendung der vollständigen Lösung mit @PeterLamberg vorgeschlagene Ergänzungen wie erwartet funktionieren. Hat keinen Einfluss auf den Import von Projekten in Eclipse.
sfitts
3
Dies funktioniert bei mir in Gradle 4.7. Sie haben jetzt einige Dokumente über den Ansatz unter docs.gradle.org/current/dsl/…
Nathan Williams
19

Dies wird jetzt als erstklassiges Feature in Gradle unterstützt. Module mit javaoder java-libraryPlugins können auch ein java-test-fixturesPlugin enthalten, das Hilfsklassen und Ressourcen verfügbar macht, die mit testFixturesHelfer verbraucht werden sollen . Vorteile dieses Ansatzes gegenüber Artefakten und Klassifikatoren sind:

  • ordnungsgemäßes Abhängigkeitsmanagement (Implementierung / API)
  • schöne Trennung vom Testcode (separater Quellensatz)
  • Testklassen müssen nicht herausgefiltert werden, um nur Dienstprogramme verfügbar zu machen
  • gepflegt von Gradle

Beispiel

:modul:one

modul / one / build.gradle

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

modul / one / src / testFixtures / java / com / example / Helper.java

package com.example;
public class Helper {}

:modul:other

modul / other / build.gradle

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

modul / other / src / test / java / com / example / other / SomeTest.java

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

Weiterführende Literatur

Weitere Informationen finden Sie in der Dokumentation:
https://docs.gradle.org/current/userguide/java_testing.html#sec:java_test_fixtures

Es wurde in 5.6 hinzugefügt:
https://docs.gradle.org/5.6/release-notes.html#test-fixtures-for-java-projects

TWiStErRob
quelle
Sie arbeiten daran, dies auf Android zu unterstützen, siehe issuetracker.google.com/issues/139762443 und issuetracker.google.com/issues/139438142
Albert Vila Calvo
18

Ich bin in letzter Zeit selbst auf dieses Problem gestoßen, und Mann, es ist schwierig, Antworten darauf zu finden.

Der Fehler, den Sie machen, besteht darin, dass ein Projekt seine Testelemente genauso exportieren sollte wie seine primären Artefakte und Abhängigkeiten.

Ich persönlich hatte viel mehr Erfolg damit, ein neues Projekt in Gradle zu machen. In Ihrem Beispiel würde ich es nennen

Projekt A_Test -> src / main / java

Ich würde die Dateien, die Sie derzeit in Projekt A / src / test / java haben, in src / main / java einfügen. Machen Sie alle testCompile-Abhängigkeiten Ihres Projekts A kompilieren Sie Abhängigkeiten von Project A_Test.

Machen Sie dann Projekt A_Test zu einer testCompile-Abhängigkeit von Projekt B.

Es ist nicht logisch, wenn man es aus der Sicht des Autors beider Projekte betrachtet, aber ich denke, es ist sehr sinnvoll, wenn man an Projekte wie junit und scalatest (und andere) denkt. Auch wenn diese Frameworks testbezogen sind, sind sie es werden nicht als Teil der "Test" -Ziele innerhalb ihrer eigenen Frameworks betrachtet - sie erzeugen primäre Artefakte, die andere Projekte zufällig in ihrer Testkonfiguration verwenden. Sie möchten nur demselben Muster folgen.

Der Versuch, die anderen hier aufgeführten Antworten zu geben, hat für mich persönlich nicht funktioniert (mit Gradle 1.9), aber ich habe festgestellt, dass das hier beschriebene Muster ohnehin eine sauberere Lösung ist.

Martin Snyder
quelle
Ja, ich habe mich am Ende des Tages für diesen Ansatz entschieden.
Koma
Dies ist der beste Ansatz! Außer ich würde den Testcode in Projekt A behalten und nur Abhängigkeiten für A src / test / java und B src / test / java nach A_Test verschieben. Dann machen Sie Projekt A_Test zu einer Testimplementierungsabhängigkeit von A und B.
Erik Sillén
17

Ich weiß, dass es eine alte Frage ist, aber ich hatte nur das gleiche Problem und verbrachte einige Zeit damit, herauszufinden, was los ist. Ich benutze Gradle 1.9. Alle Änderungen sollten in ProjectBs erfolgenbuild.gradle

So verwenden Sie Testklassen von ProjectA in Tests von ProjectB:

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

So stellen Sie sicher, dass die sourceSetsEigenschaft für ProjectA verfügbar ist:

evaluationDependsOn(':ProjectA')

So stellen Sie sicher, dass Testklassen von ProjectA tatsächlich vorhanden sind, wenn Sie ProjectB kompilieren:

compileTestJava.dependsOn tasks.getByPath(':ProjectA:testClasses')
Dominik Pawlak
quelle
1
Dies funktionierte auch für mich, außer dass ich das weglassen musste .classesDir.
11

Neue testJar-basierte Lösung (Unterstützung für trnsitive Abhängigkeiten unterstützt) als Gradle-Plugin verfügbar:

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

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

Aus der Dokumentation

Wenn Sie einen Gradle-Build für mehrere Projekte haben, können Testabhängigkeiten zwischen Unterprojekten bestehen (was wahrscheinlich ein Hinweis darauf ist, dass Ihre Projekte nicht gut strukturiert sind).

Angenommen, ein Projekt, bei dem das Teilprojekt Projekt B von Projekt A abhängt und B nicht nur eine Kompilierungsabhängigkeit von A, sondern auch eine Testabhängigkeit aufweist. Um die Tests von B zu kompilieren und auszuführen, benötigen wir einige Testhelferklassen von A.

Standardmäßig erstellt gradle kein JAR-Artefakt aus der Testbuild-Ausgabe eines Projekts.

Dieses Plugin fügt eine testArchives-Konfiguration (basierend auf testCompile) und eine jarTest-Task hinzu, um ein jar aus dem Testquellensatz zu erstellen (wobei der Klassifizierertest dem Namen des jar hinzugefügt wird). Wir können uns dann in B auf die testArchives-Konfiguration von A verlassen (die auch die transitiven Abhängigkeiten von A enthält).

In A würden wir das Plugin zu build.gradle hinzufügen:

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

In B verweisen wir wie folgt auf die testArchives-Konfiguration:

dependencies {
    ...
    testCompile project (path: ':ProjectA', configuration: 'testArchives') 
}
Dämon101
quelle
1
Während dieser Link die Frage beantworten kann, ist es besser, die wesentlichen Teile der Antwort hier aufzunehmen und den Link als Referenz bereitzustellen. Nur-Link-Antworten können ungültig werden, wenn sich die verknüpfte Seite ändert. - Von der Überprüfung
Ian
Einige Textzeilen wurden hinzugefügt
Demon101
Auf jeden Fall wurden Informationen zum neuen Gradle-Plugin bereitgestellt.
Demon101
4
@ Demon101 funktioniert nicht in Gradle 4.6, Fehler wird angezeigtCould not get unknown property 'testClasses' for project ':core' of type org.gradle.api.Project.
Vignesh Sundar
11

Bitte lesen Sie das Update unten.

Ähnliche von JustACluelessNewbie beschriebene Probleme treten in IntelliJ IDEA auf. Das Problem ist, dass Abhängigkeit testCompile project(':core').sourceSets.test.outputtatsächlich bedeutet: "Abhängig von Klassen, die durch die Gradle-Build-Aufgabe generiert wurden". Wenn Sie also ein sauberes Projekt öffnen, in dem noch keine Klassen generiert wurden, erkennt IDEA sie nicht und meldet Fehler.

Um dieses Problem zu beheben, müssen Sie neben der Abhängigkeit von kompilierten Klassen eine Abhängigkeit von Testquelldateien hinzufügen.

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

Von IDEA erkannte Abhängigkeiten können Sie unter Moduleinstellungen -> Abhängigkeiten (Testumfang) beobachten .

Übrigens. Dies ist keine gute Lösung, daher ist Refactoring eine Überlegung wert. Gradle selbst hat ein spezielles Teilprojekt, das nur Testunterstützungsklassen enthält. Siehe https://docs.gradle.org/current/userguide/test_kit.html

Update 05.06.2016 Mehr Ich denke über Lösungsvorschläge nach, weniger gefällt es mir. Es gibt nur wenige Probleme damit:

  1. Es werden zwei Abhängigkeiten in IDEA erstellt. Einer zeigt auf Testquellen, ein anderer auf kompilierte Klassen. Und es ist entscheidend, in welcher Reihenfolge diese Abhängigkeiten von IDEA erkannt werden. Sie können damit spielen, indem Sie die Abhängigkeitsreihenfolge unter Moduleinstellungen -> Registerkarte Abhängigkeiten ändern.
  2. Durch das Deklarieren dieser Abhängigkeiten verschmutzen Sie die Abhängigkeitsstruktur unnötig.

Was ist die bessere Lösung? Meiner Meinung nach wird ein neuer benutzerdefinierter Quellensatz erstellt und freigegebene Klassen eingefügt. Tatsächlich haben die Autoren des Gradle-Projekts dazu den Quellensatz testFixtures erstellt.

Um es zu tun, müssen Sie nur:

  1. Erstellen Sie einen Quellensatz und fügen Sie die erforderlichen Konfigurationen hinzu. Überprüfen Sie dieses im Gradle-Projekt verwendete Skript-Plugin: https://github.com/gradle/gradle/blob/v4.0.0/gradle/testFixtures.gradle
  2. Deklarieren Sie die richtige Abhängigkeit im abhängigen Projekt:

    dependencies {
        testCompile project(path: ':module-with-shared-classes', configuration: 'testFixturesUsageCompile')
    }
    
  3. Importieren Sie das Gradle-Projekt in IDEA und verwenden Sie beim Importieren die Option "Separates Modul pro Quellensatz erstellen".
Václav Kužel
quelle
1
@ jannis behoben. Übrigens. Gradle hat sein Groovy-basiertes Test-Fixtures-Plugin auf das neue Kotlin-basierte Plugin verschoben: github.com/gradle/gradle/blob/v5.0.0/buildSrc/subprojects/…
Václav Kužel
@ VáclavKužel Ich finde deine interessante Lösung über deinen Blog-Beitrag heraus und sie hat mein Problem sehr gut gelöst. Danke;)
zaerymoghaddam
10

Die Lösung von Fesler hat bei mir nicht funktioniert, als ich versucht habe, ein Android-Projekt (Gradle 2.2.0) zu erstellen. Also musste ich die erforderlichen Klassen manuell referenzieren:

android {
    sourceSets {
        androidTest {
            java.srcDir project(':A').file("src/androidTest/java")
        }
        test {
            java.srcDir project(':A').file("src/test/java")
        }
    }
}
Beloo
quelle
1
leichter Tippfehler, das Endzitat nach dem Projekt fehlt (': A'). Dies funktionierte jedoch für mich, danke m8
Ryan Newsom
1
Für Android funktionierte diese Idee wunderbar für mich, ohne das hackige Gefühl stackoverflow.com/a/50869037/197141
arberg
@arberg Ja, scheint ein guter Ansatz zu sein. Die einzige Einschränkung, die ich sehe, sind die @VisibleForTestingFlusenregeln. Sie können solche Methoden nicht über das reguläre Modul im Ordner "Nicht testen" aufrufen.
Beloo
5

Ich bin so spät zur Party (es ist jetzt Gradle v4.4), aber für alle anderen, die dies finden:

Angenommen:

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

Gehen Sie zum build.gradle von Projekt B (das einige Testklassen von A benötigt) und fügen Sie Folgendes hinzu:

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

oder (vorausgesetzt, Ihr Projekt heißt "ProjectB")

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

Voila!

Tricknologie
quelle
3
Die Frage erwähnt Android nicht. Können Sie Ihre Antwort unabhängig davon machen, ob der Entwickler für Android entwickelt oder nicht, oder nur für Android-Entwickler?
Robin Green
4

Wenn Sie Scheinabhängigkeiten haben, die Sie zwischen Tests teilen müssen, können Sie ein neues Projekt erstellen projectA-mockund es dann als Testabhängigkeit zu ProjectAund hinzufügen ProjectB:

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

Dies ist klare Lösung zu teilen Mock Abhängigkeiten, aber wenn Sie benötigen Tests aus laufen ProjectAin ProjectBGebrauch andere Lösung.

Sylwano
quelle
Tolle Lösung für den gemeinsamen Mock-Fall!
Erik Sillén
4

Wenn Sie Artefaktabhängigkeiten verwenden möchten, um Folgendes zu haben:

  • Die Quellklassen von ProjectB hängen von den Quellklassen von Project A ab
  • Die Testklassen von ProjectB hängen von den Testklassen von Project A ab

Dann sollte der Abschnitt mit den Abhängigkeiten von ProjectB in build.gradle ungefähr so aussehen:

dependencies {

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

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

}

Damit dies funktioniert, muss ProjectA ein Test- JAR erstellen und es in die von ihm erzeugten Artefakte aufnehmen.

Das build.gradle von ProjectA sollte folgende Konfiguration enthalten:

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

configurations {
    tests
}

artifacts {
    tests testsJar
    archives testsJar
}

jar.finalizedBy(testsJar)

Wenn die Artefakte von ProjectA in Ihrem Artefakt veröffentlicht werden, enthalten sie ein Testglas .

Die testCompile im Abschnitt Abhängigkeiten von ProjectB bringt die Klassen in das -tests- JAR.


Wenn Sie die Quell- und Testklassen von Flat ProjectA zu Entwicklungszwecken in ProjectB aufnehmen möchten, sieht der Abschnitt mit den Abhängigkeiten in ProjectBs build.gradle folgendermaßen aus:

dependencies {

  compile project(':projecta')

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

}
Joman68
quelle
1
Leider (in Gradle 6) funktioniert das Flat Include, das genau das war, was ich wollte, nicht mehr, da es keine Konfigurationstests mehr gibt. Mit println(configurations.joinToString("\n") { it.name + " - " + it.allDependencies.joinToString() })(in einem Kotlin-Buildscript) habe ich festgestellt, welche Konfigurationen noch existieren und Abhängigkeiten haben, aber für all diese hat sich Gradle beschwert: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

Einige der anderen Antworten verursachten auf die eine oder andere Weise Fehler - Gradle erkannte keine Testklassen aus anderen Projekten oder das Eclipse-Projekt hatte beim Import ungültige Abhängigkeiten. Wenn jemand das gleiche Problem hat, schlage ich vor:

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

Die erste Zeile zwingt die Eclipse, das andere Projekt als Abhängigkeit zu verknüpfen, sodass alle Quellen enthalten und auf dem neuesten Stand sind. Mit der zweiten Option kann Gradle die Quellen tatsächlich anzeigen, ohne wie bisher ungültige Abhängigkeitsfehler zu verursachen testCompile project(':core').sourceSets.test.output.

Czyzby
quelle
2

Wenn Sie Kotlin DSL verwenden , sollten Sie Ihre Aufgabe gemäß der Gradle- Dokumentation so erstellen .

Wie bei einigen früheren Antworten müssen Sie innerhalb des Projekts eine spezielle Konfiguration erstellen, die die Testklasse gemeinsam nutzt, damit Sie Test- und Hauptklassen nicht mischen.

Einfache Schritte

  1. In Projekt A müssten Sie Folgendes hinzufügen 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. Dann müssen Sie in Ihrem Projekt B in den Abhängigkeiten Folgendes hinzufügen build.gradle.kts:
dependencies {
    implementation(project(":ProjectA"))
    testImplementation(project(":ProjectA", "test"))
}
Sylhare
quelle
-1

in Projekt B:

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

Scheint in 1.7-rc-2 zu funktionieren

John Caron
quelle
2
Dies führt auch zu unnötigen Komplikationen bei der Abwicklung des Projekts durch Eclipse. Die von @NikitaSkvortsov vorgeschlagene Lösung ist vorzuziehen.
sfitts