Zależności testowe wielu projektów z gradle


153

Mam konfigurację z wieloma projektami i chcę używać gradle.

Moje projekty wyglądają tak:

  • Projekt A

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

    • -> src/main/java(zależy src/main/javaod projektu A )
    • -> src/test/java(zależy src/test/javaod projektu A )

Mój plik projektu B build.gradle jest taki:

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

Zadaniem compileJavawielkie dzieło, ale compileTestJavanie kompiluje plik testowy z Project A .


Odpowiedzi:


122

Przestarzałe - w przypadku Gradle 5.6 i nowszych użyj tej odpowiedzi .

W projekcie B wystarczy dodać testCompilezależność:

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

Testowane za pomocą Gradle 1.7.


7
Okazuje się, że właściwość classes jest przestarzała - zamiast tego użyj danych wyjściowych.
Fesler

12
To nie działa w Gradle 1.3, ponieważ sourceSets nie jest już publiczną właściwością projektu.
David Pärsson,

3
Pamiętaj, że powyższe rozwiązanie wymaga co najmniej czasu, gradle testClasseszanim struktura kompilacji będzie faktycznie prawidłowa. Np. Wtyczka Eclipse nie pozwoli ci zaimportować projektu wcześniej. Naprawdę szkoda, testCompile project(':A')że nie działa. @ DavidPärsson: „Gradle 1.3” zaprzecza „już nie”, ponieważ Fesler testował z Gradle 1.7.
Patrick Bergner,

3
nie działa dla mnie. Niepowodzenie w przypadku zależności cyklicznej: compileTestJava \ ---: testClasses \ ---: compileTestJava (*)
rahulmohan Stycznia

8
Nie rób tego, projekty nie powinny sięgać do innych projektów. Zamiast tego użyj odpowiedzi Nikity, poprawnie modelując to jako zależność projektu.
Stefan Oehme,

63

Prostym sposobem jest dodanie jawnej zależności zadań w ProjectB:

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

Trudnym (ale bardziej przejrzystym) sposobem jest utworzenie dodatkowej konfiguracji artefaktów dla ProjectA:

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

configurations {
  testArtifacts
}

artifacts {
   testArtifacts myTestsJar
}

i dodaj testCompilezależność dla ProjectB

apply plugin: 'java'
dependencies {
  compile project(':ProjectA')
  testCompile project(path: ':ProjectA', configuration: 'testArtifacts')
}

3
Wypróbowałem to (w prosty sposób) i chociaż zapewnia to budowanie testClasses, nie dodaje ścieżki testowej do CLASSPATH, więc moje testy ProjectB, które zależą od klas testowych ProjectA, nadal nie mogą się kompilować.
pjz

1
@dmoebius musisz dodać testArtifactskonfigurację w ten sposób: configurations { testArtifacts } aby uzyskać więcej informacji, zobacz tę sekcję pomocy Gradle: gradle.org/docs/current/dsl/…
Nikita Skvortsov

7
W Gradle 1.8 możesz chcieć from sourceSets.test.outputi być może classifier = 'tests'zamiast // pack whatever you need...w odpowiedzi
Peter Lamberg

1
Potwierdzono, że z Gradle 1.12 przy użyciu pełnego rozwiązania, z sugerowanymi dodatkami @PeterLamberg, działa zgodnie z oczekiwaniami. Nie ma wpływu na import projektu do Eclipse.
sfitts

3
To działa dla mnie w Gradle 4.7. Mają teraz kilka dokumentów na temat podejścia pod adresem docs.gradle.org/current/dsl/ ...
Nathan Williams

19

Jest to teraz obsługiwane jako funkcja pierwszej klasy w Gradle.Moduły z wtyczkami javalub java-librarymogą również zawierać java-test-fixtureswtyczkę, która udostępnia klasy pomocnicze i zasoby do wykorzystania z testFixturespomocnikiem. Zalety tego podejścia w przypadku artefaktów i klasyfikatorów to:

  • prawidłowe zarządzanie zależnościami (implementacja / API)
  • ładne oddzielenie od kodu testowego (oddzielny zestaw źródeł)
  • nie ma potrzeby odfiltrowywania klas testowych, aby udostępniać tylko narzędzia
  • utrzymywany przez Gradle

Przykład

: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
  }
}

Dalsza lektura

Więcej informacji można znaleźć w dokumentacji:
https://docs.gradle.org/current/userguide/java_testing.html#sec:java_test_fixtures

Został dodany w 5.6:
https://docs.gradle.org/5.6/release-notes.html#test-fixtures-for-java-projects



18

Sam ostatnio natknąłem się na ten problem i człowieku jest to trudne zagadnienie do znalezienia odpowiedzi.

Błędem, który popełniasz, jest myślenie, że projekt powinien eksportować swoje elementy testowe w taki sam sposób, w jaki eksportuje swoje podstawowe artefakty i zależności.

Osobiście odniosłem dużo większy sukces, że zrobiłem nowy projekt w Gradle. W twoim przykładzie nazwałbym to

Projekt A_Test -> src / main / java

Umieściłbym w katalogu src / main / java pliki, które obecnie masz w projekcie A / src / test / java. Utwórz wszelkie zależności testCompile projektu Kompiluj zależności projektu A_Test.

Następnie ustaw projekt A_Test jako zależność testCompile projektu B.

Nie jest to logiczne, jeśli spojrzysz na to z perspektywy autora obu projektów, ale myślę, że ma to dużo sensu, gdy myślisz o projektach takich jak junit i scalatest (i innych. Mimo że te frameworki są związane z testowaniem, nie są uważane za część celów „testowych” w ich własnych strukturach - wytwarzają podstawowe artefakty, których inne projekty po prostu używają w swojej konfiguracji testowej. Po prostu chcesz podążać za tym samym wzorcem.

Próba wykonania innych wymienionych tutaj odpowiedzi nie zadziałała dla mnie osobiście (używając Gradle 1.9), ale stwierdziłem, że wzór, który tu opisuję, i tak jest czystszym rozwiązaniem.


Tak, zdecydowałem się na takie podejście pod koniec dnia.
koma

To najlepsze podejście! Tyle że chciałbym zachować kod testowy w projekcie A i przenieść tylko zależności zarówno dla A src / test / java, jak i B src / test / java do A_Test. Następnie zrób z Projektu A_Test testową zależność implementacji zarówno A, jak i B.
Erik Sillén

17

Wiem, że to stare pytanie, ale miałem ten sam problem i spędziłem trochę czasu zastanawiając się, co się dzieje. Używam Gradle 1.9. Wszystkie zmiany powinny być w ProjectBbuild.gradle

Aby użyć klas testowych z ProjectA w testach ProjectB:

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

Aby upewnić się, że ta sourceSetswłaściwość jest dostępna dla ProjectA:

evaluationDependsOn(':ProjectA')

Aby upewnić się, że klasy testowe z ProjectA faktycznie istnieją, podczas kompilowania ProjectB:

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

1
To również działało dla mnie, z wyjątkiem tego, że musiałem pominąć .classesDir.

11

Nowe rozwiązanie oparte na testJar (obsługiwane trnsitive dependancies) dostępne jako wtyczka gradle:

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

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

Z dokumentacji

Jeśli masz kompilację z wieloma projektami, możesz mieć zależności testowe między podprojektami (co prawdopodobnie oznacza, że ​​twoje projekty nie są dobrze zorganizowane).

Na przykład załóżmy, że projekt, w którym projekt podrzędny B zależy od projektu A, a B ma nie tylko zależność kompilacji od A, ale także zależność testową. Aby skompilować i uruchomić testy B, potrzebujemy kilku testowych klas pomocniczych z A.

Domyślnie gradle nie tworzy artefaktu jar z wyników kompilacji testowej projektu.

Ta wtyczka dodaje konfigurację testArchives (opartą na testCompile) i zadanie jarTest w celu utworzenia jar z zestawu źródeł testowych (z testem klasyfikatora dodanym do nazwy jar). Możemy wtedy polegać w B na konfiguracji testArchives A (która będzie również zawierać zależności przechodnie A).

W A dodalibyśmy wtyczkę do build.gradle:

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

W B odwołujemy się do konfiguracji testArchives w następujący sposób:

dependencies {
    ...
    testCompile project (path: ':ProjectA', configuration: 'testArchives') 
}

1
Chociaż ten link może odpowiedzieć na pytanie, lepiej jest zawrzeć tutaj zasadnicze części odpowiedzi i podać link do odniesienia. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli połączona strona ulegnie zmianie. - Z recenzji
Ian

dodano kilka linijek tekstu
demon101

W każdym razie podano informacje o nowej wtyczce gradle.
demon101

4
@ demon101 Nie działa w Gradle 4.6, Could not get unknown property 'testClasses' for project ':core' of type org.gradle.api.Project.
pojawia

11

Przeczytaj aktualizację poniżej.

Podobne problemy opisane przez JustACluelessNewbie występują w IntelliJ IDEA. Problem w tej zależnościtestCompile project(':core').sourceSets.test.output rzeczywistości oznacza: „zależą od klas wygenerowanych przez zadanie budowania gradle”. Więc jeśli otworzysz czysty projekt, w którym klasy nie są jeszcze wygenerowane, IDEA nie rozpozna ich i zgłosi błąd.

Aby rozwiązać ten problem, musisz dodać zależność od testowych plików źródłowych obok zależności od skompilowanych klas.

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

Możesz obserwować zależności rozpoznawane przez IDEA w Ustawienia modułu -> Zależności (zakres testowy) .

Przy okazji. nie jest to fajne rozwiązanie, więc warto rozważyć refaktoryzację. Sam Gradle ma specjalny podprojekt zawierający tylko klasy obsługi testów. Zobacz https://docs.gradle.org/current/userguide/test_kit.html

Aktualizacja 2016-06-05 Więcej Myślę o proponowanym rozwiązaniu mniej mi się podoba. Jest z tym kilka problemów:

  1. Tworzy dwie zależności w IDEA. Jeden wskazuje na testowanie źródeł innym na skompilowane klasy. Istotne jest, w jakiej kolejności te zależności są rozpoznawane przez IDEA. Możesz się nim bawić zmieniając kolejność zależności w module Ustawienia -> zakładka Zależności.
  2. Deklarując te zależności, niepotrzebnie zanieczyszczasz strukturę zależności.

Więc jakie jest lepsze rozwiązanie? Moim zdaniem jest to tworzenie nowego niestandardowego zestawu źródeł i umieszczanie w nim wspólnych klas. Właściwie autorzy projektu Gradle zrobili to, tworząc zestaw źródeł testFixtures.

Aby to zrobić, wystarczy:

  1. Utwórz zestaw źródłowy i dodaj niezbędne konfiguracje. Sprawdź tę wtyczkę skryptu używaną w projekcie Gradle: https://github.com/gradle/gradle/blob/v4.0.0/gradle/testFixtures.gradle
  2. Zadeklaruj odpowiednią zależność w projekcie zależnym:

    dependencies {
        testCompile project(path: ':module-with-shared-classes', configuration: 'testFixturesUsageCompile')
    }
    
  3. Zaimportuj projekt Gradle do IDEA i użyj opcji „utwórz oddzielny moduł na zestaw źródłowy” podczas importowania.


1
@jannis naprawiony. Przy okazji. Gradle przeniósł swoją wtyczkę testową opartą na Groovy do nowej platformy Kotlin: github.com/gradle/gradle/blob/v5.0.0/buildSrc/subprojects/ ...
Václav Kužel

@ VáclavKužel Dowiaduję się o twoim interesującym rozwiązaniu poprzez twój post na blogu i bardzo ładnie rozwiązało mój problem. Dzięki;)
zaerymoghaddam

10

Rozwiązanie Feslera nie działało dla mnie, kiedy próbowałem go zbudować dla projektu Android (gradle 2.2.0). Musiałem więc ręcznie odwołać się do wymaganych klas:

android {
    sourceSets {
        androidTest {
            java.srcDir project(':A').file("src/androidTest/java")
        }
        test {
            java.srcDir project(':A').file("src/test/java")
        }
    }
}

1
drobna literówka, brak końcowego cudzysłowu po projekcie („: A”). To jednak zadziałało, dzięki m8
Ryan Newsom

1
W przypadku Androida ten pomysł zadziałał pięknie dla mnie, bez
hakerskiego

@arberg Tak, wydaje się dobrym podejściem. Jedynym ograniczeniem, jakie widzę, są @VisibleForTestingzasady kłaczków. Nie będziesz mógł wywołać takich metod ze zwykłego modułu w folderze not test.
Beloo

5

Spóźniłem się na imprezę (teraz jest Gradle v4.4), ale dla każdego, kto to znajdzie:

Zarozumiały:

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

Przejdź do build.gradle projektu B (tego, który wymaga kilku klas testowych z A) i dodaj następujące elementy:

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

lub (zakładając, że Twój projekt nosi nazwę „ProjektB”)

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

Voila!


3
Pytanie nie wspomina o Androidzie. Czy możesz sprawić, że Twoja odpowiedź będzie agnostyczna, czy programista tworzy oprogramowanie na Androida, czy też nie, czy tylko dla programistów Androida?
Robin Green,

4

Jeśli masz pozorowane zależności, które musisz udostępniać między testami, możesz utworzyć nowy projekt, projectA-mocka następnie dodać go jako zależność testową do ProjectAi ProjectB:

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

Jest oczywiste rozwiązanie zależności mock akcji, ale jeśli trzeba uruchomić testy z ProjectAw ProjectBużyciu innego rozwiązania.


Świetne rozwiązanie dla wspólnej makiety obudowy!
Erik Sillén

4

Jeśli chcesz użyć zależności artefaktów, aby mieć:

  • Klasy źródłowe ProjectB zależą od klas źródłowych Projektu A.
  • Klasy testowe ProjectB zależą od klas testowych Projektu A.

wtedy sekcja zależności ProjectB w build.gradle powinna wyglądać mniej więcej tak:

dependencies {

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

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

}

Aby to zadziałało, ProjectA musi zbudować -testy słoik i dołączyć go przez siebie artefaktów.

Plik build.gradle ProjectA powinien zawierać następującą konfigurację:

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

configurations {
    tests
}

artifacts {
    tests testsJar
    archives testsJar
}

jar.finalizedBy(testsJar)

Kiedy artefakty ProjectA zostaną opublikowane w twojej wytwórni , będą zawierać słoik -tests .

Sekcja testCompile w zależnościach ProjectB wprowadzi klasy w -tests jar.


Jeśli chcesz uwzględnić klasy źródłowe i testowe w ProjectB w ProjectB do celów programistycznych, sekcja zależności w pliku build.gradle ProjectB wyglądałaby następująco:

dependencies {

  compile project(':projecta')

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

}

1
Niestety (w Gradle 6) dołączenie mieszkania, które było dokładnie tym, czego chciałem, już nie działa, ponieważ nie ma już „testów” konfiguracji. Używając println(configurations.joinToString("\n") { it.name + " - " + it.allDependencies.joinToString() })(w skrypcie kompilacji kotlin), ustaliłem, które konfiguracje nadal istnieją i mają zależności, ale dla wszystkich tych Gradle narzekał: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

Niektóre inne odpowiedzi powodowały błędy w ten czy inny sposób - Gradle nie wykrył klas testowych z innych projektów lub projekt Eclipse miał nieprawidłowe zależności po zaimportowaniu. Jeśli ktoś ma ten sam problem to proponuję wybrać:

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

Pierwsza linia wymusza na Eclipse połączenie innego projektu jako zależności, więc wszystkie źródła są uwzględnione i aktualne. Drugi pozwala Gradle faktycznie zobaczyć źródła, nie powodując żadnych nieprawidłowych błędów zależności, jak testCompile project(':core').sourceSets.test.outputrobi.


2

Tutaj, jeśli używasz Kotlin DSL , powinieneś stworzyć swoje zadanie zgodnie z Gradle dokumentacją .

Podobnie jak w przypadku niektórych poprzednich odpowiedzi, musisz utworzyć specjalną konfigurację wewnątrz projektu, która będzie współdzielić klasę testów, aby nie mieszać klas testowych i głównych.

Proste kroki

  1. W projekcie A musiałbyś dodać 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. Następnie w swoim projekcie B w zależnościach będziesz musiał dodać build.gradle.kts:
dependencies {
    implementation(project(":ProjectA"))
    testImplementation(project(":ProjectA", "test"))
}

-1

w projekcie B:

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

Wydaje się działać w 1.7-rc-2


2
Stwarza też niepotrzebne komplikacje w obsłudze projektu przez Eclipse. Preferowane jest rozwiązanie zaproponowane przez @NikitaSkvortsov.
sfitts
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.