Ketergantungan uji multi-proyek dengan gradle

153

Saya memiliki konfigurasi multi-proyek dan saya ingin menggunakan gradle.

Proyek saya seperti ini:

  • Proyek A

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

    • -> src/main/java(tergantung src/main/javapada Proyek A )
    • -> src/test/java(tergantung src/test/javapada Proyek A )

File Project B saya build.gradleseperti ini:

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

Tugas compileJavapekerjaan besar tetapi compileTestJavatidak mengkompilasi file tes dari Project A .

mathd
sumber
2
kemungkinan duplikat: stackoverflow.com/questions/5144325/gradle-test-dependency
Mike Rylander

Jawaban:

122

Tidak digunakan lagi - Untuk Gradle 5.6 ke atas gunakan jawaban ini .

Di Proyek B , Anda hanya perlu menambahkan testCompileketergantungan:

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

Diuji dengan Gradle 1.7.

Fesler
sumber
7
Ternyata properti kelas sudah usang - gunakan output sebagai gantinya.
Fesler
12
Ini tidak berfungsi di Gradle 1.3 karena sourceSets bukan lagi milik umum proyek.
David Pärsson
3
Perlu diingat bahwa solusi di atas memerlukan setidaknya gradle testClassessebelum struktur bangunan benar-benar valid. Misalnya plugin Eclipse tidak akan membiarkan Anda mengimpor proyek sebelumnya. Sayang testCompile project(':A')sekali tidak bekerja. @ DavidPärsson: "Gradle 1.3" bertentangan "tidak lagi" sejak Fesler diuji dengan Gradle 1.7.
Patrick Bergner
3
tidak bekerja untuk saya. Gagal dengan ketergantungan melingkar: compileTestJava \ ---: testClasses \ ---: compileTestJava (*)
rahulmohan
8
Jangan lakukan ini, proyek tidak seharusnya menjangkau ke proyek lain. Alih-alih menggunakan jawaban Nikita, memodelkan ini dengan benar sebagai ketergantungan proyek.
Stefan Oehme
63

Cara sederhana adalah menambahkan ketergantungan tugas eksplisit di ProjectB:

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

Cara sulit (tetapi lebih jelas) adalah membuat konfigurasi artefak tambahan untuk ProjectA:

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

configurations {
  testArtifacts
}

artifacts {
   testArtifacts myTestsJar
}

dan tambahkan testCompileketergantungan untuk ProjectB

apply plugin: 'java'
dependencies {
  compile project(':ProjectA')
  testCompile project(path: ':ProjectA', configuration: 'testArtifacts')
}
Nikita Skvortsov
sumber
3
Saya mencoba ini (cara sederhana) dan walaupun itu memastikan ia membangun testClasses, itu tidak menambahkan jalur tes ke CLASSPATH sehingga tes ProjectB saya yang bergantung pada kelas tes ProjectA masih gagal dibangun.
pjz
1
@dmoebius Anda harus menambahkan testArtifactskonfigurasi seperti ini: configurations { testArtifacts } untuk lebih jelasnya lihat bagian bantuan Gradle ini
Nikita Skvortsov
7
Dalam Gradle 1,8 Anda mungkin ingin from sourceSets.test.outputdan mungkin classifier = 'tests'di tempat // pack whatever you need...dalam jawaban
Peter Lamberg
1
Dikonfirmasi bahwa dengan Gradle 1.12 menggunakan solusi lengkap, dengan @PeterLamberg disarankan penambahan berfungsi seperti yang diharapkan. Tidak memengaruhi impor proyek ke Eclipse.
sfitts
3
Ini berfungsi untuk saya di Gradle 4.7. Mereka sekarang memiliki beberapa dokumen tentang pendekatan ini di docs.gradle.org/current/dsl/…
Nathan Williams
19

Ini sekarang didukung sebagai fitur kelas satu di Gradle. Modul dengan javaatau java-libraryplugin juga dapat menyertakan sebuah java-test-fixturesplugin yang memaparkan kelas pembantu dan sumber daya untuk dikonsumsi bersama testFixturespembantu. Manfaat dari pendekatan ini terhadap artefak dan pengklasifikasi adalah:

  • manajemen ketergantungan yang tepat (implementasi / api)
  • pemisahan yang bagus dari kode uji (set sumber terpisah)
  • tidak perlu menyaring kelas tes untuk mengekspos hanya utilitas
  • dikelola oleh Gradle

Contoh

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

Bacaan lebih lanjut

Untuk info lebih lanjut, lihat dokumentasi:
https://docs.gradle.org/current/userguide/java_testing.html#sec:java_test_fixtures

Itu ditambahkan dalam 5.6:
https://docs.gradle.org/5.6/release-notes.html#test-fixtures-for-java-projects

TWiStErRob
sumber
Mereka sedang bekerja untuk mendukung ini di Android, lihat issuetracker.google.com/issues/139762443 dan issuetracker.google.com/issues/139438142
Albert Vila Calvo
18

Saya sendiri telah menemukan masalah ini baru-baru ini, dan manusia adalah masalah sulit untuk menemukan jawaban.

Kesalahan yang Anda buat adalah berpikir bahwa suatu proyek harus mengekspor elemen pengujiannya dengan cara yang sama seperti ia mengekspor artefak dan dependensi utamanya.

Apa yang saya lebih sukses secara pribadi adalah membuat proyek baru di Gradle. Dalam contoh Anda, saya akan menyebutkannya

Project A_Test -> src / main / java

Saya akan memasukkan ke dalam src / main / java file yang saat ini Anda miliki di Project A / src / test / java. Buat setiap dependensi testCompile dari Proyek Anda, kompilasi dependensi dari Project A_Test.

Kemudian buat Project A_Test sebuah dependensi testCompile dari Project B.

Itu tidak logis ketika Anda datang dari sudut pandang penulis kedua proyek, tapi saya pikir itu masuk akal ketika Anda berpikir tentang proyek-proyek seperti junit dan scalatest (dan lain-lain. Meskipun kerangka kerja tersebut terkait pengujian, mereka tidak dianggap sebagai bagian dari target "test" dalam kerangka kerja mereka sendiri - mereka menghasilkan artefak utama yang kebetulan digunakan oleh proyek lain dalam konfigurasi pengujian mereka. Anda hanya ingin mengikuti pola yang sama.

Mencoba melakukan jawaban lain yang tercantum di sini tidak bekerja untuk saya secara pribadi (menggunakan Gradle 1.9), tetapi saya telah menemukan bahwa pola yang saya jelaskan di sini adalah solusi yang lebih bersih.

Martin Snyder
sumber
Ya, memilih pendekatan ini pada akhir hari.
koma
Ini adalah pendekatan terbaik! Kecuali saya akan menyimpan kode tes dalam proyek A dan hanya memindahkan dependensi untuk A src / test / java dan B src / test / java ke A_Test. Kemudian buat Project A_Test ketergantungan testImplementation dari A dan B.
Erik Sillén
17

Saya tahu ini adalah pertanyaan lama tetapi saya hanya memiliki masalah yang sama dan menghabiskan waktu mencari tahu apa yang sedang terjadi. Saya menggunakan Gradle 1.9. Semua perubahan harus dalam ProjectBbuild.gradle

Untuk menggunakan kelas uji dari ProjectA dalam tes ProjectB:

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

Untuk memastikan bahwa sourceSetsproperti tersedia untuk ProjectA:

evaluationDependsOn(':ProjectA')

Untuk memastikan kelas tes dari ProjectA benar-benar ada, ketika Anda mengkompilasi ProjectB:

compileTestJava.dependsOn tasks.getByPath(':ProjectA:testClasses')
Dominik Pawlak
sumber
1
Ini juga berfungsi untuk saya kecuali saya harus menghilangkan .classesDir.
11

Solusi testJar baru (didukung dependensi trnsitif) tersedia sebagai plugin gradle:

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

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

Dari dokumentasi

Jika Anda memiliki multi-proyek gradle build, Anda dapat menguji dependensi antar sub-proyek (yang mungkin merupakan petunjuk bahwa proyek Anda tidak terstruktur dengan baik).

Sebagai contoh, asumsikan sebuah proyek di mana sub proyek Proyek B bergantung pada Proyek A dan B tidak hanya memiliki ketergantungan kompilasi pada A tetapi juga ketergantungan uji. Untuk mengkompilasi dan menjalankan tes B kita membutuhkan beberapa kelas penolong tes dari A.

Secara default gradle tidak membuat artefak jar dari output build proyek.

Plugin ini menambahkan konfigurasi testArchives (berdasarkan testCompile) dan tugas jarTest untuk membuat jar dari set sumber tes (dengan tes classifier ditambahkan ke nama jar). Kita kemudian dapat bergantung pada B pada konfigurasi testArchives A (yang juga akan mencakup dependensi transitif A).

Dalam A kita akan menambahkan plugin ke build.gradle:

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

Di B kami mereferensikan konfigurasi testArchives seperti ini:

dependencies {
    ...
    testCompile project (path: ':ProjectA', configuration: 'testArchives') 
}
demon101
sumber
1
Meskipun tautan ini dapat menjawab pertanyaan, lebih baik untuk memasukkan bagian-bagian penting dari jawaban di sini dan memberikan tautan untuk referensi. Jawaban hanya tautan dapat menjadi tidak valid jika halaman tertaut berubah. - Dari Ulasan
Ian
beberapa baris teks telah ditambahkan
demon101
Bagaimanapun, informasi tentang plugin gradle baru telah disediakan.
demon101
4
@ demon101 Tidak bekerja di Gradle 4.6, mendapatkan kesalahanCould not get unknown property 'testClasses' for project ':core' of type org.gradle.api.Project.
Vignesh Sundar
11

Silakan baca pembaruan di bawah.

Masalah serupa yang dijelaskan oleh JustACluelessNewbie terjadi di IntelliJ IDEA. Masalahnya adalah bahwa ketergantungan testCompile project(':core').sourceSets.test.outputsebenarnya berarti: "bergantung pada kelas yang dihasilkan oleh tugas pembangunan gradle". Jadi, jika Anda membuka proyek bersih di mana kelas tidak dihasilkan namun IDEA tidak akan mengenalinya dan melaporkan kesalahan.

Untuk memperbaiki masalah ini, Anda harus menambahkan ketergantungan pada file sumber tes di samping ketergantungan pada kelas yang dikompilasi.

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

Anda dapat mengamati dependensi yang dikenali oleh IDEA dalam Pengaturan Modul -> Ketergantungan (lingkup pengujian) .

Btw. ini bukan solusi yang bagus sehingga refactoring patut dipertimbangkan. Gradle sendiri memiliki sub-proyek khusus yang hanya berisi kelas-kelas dukungan tes. Lihat https://docs.gradle.org/current/userguide/test_kit.html

Perbarui 2016-06-05 Lebih Banyak Saya berpikir tentang solusi yang diusulkan kurang saya suka. Ada beberapa masalah dengan itu:

  1. Itu menciptakan dua dependensi di IDEA. Satu poin untuk menguji sumber lain ke kelas yang dikompilasi. Dan sangat penting agar keteraturan ini diakui oleh IDEA. Anda dapat bermain dengannya dengan mengubah urutan ketergantungan pada pengaturan Modul -> tab Dependensi.
  2. Dengan mendeklarasikan dependensi ini Anda tidak perlu mencemari struktur dependensi.

Jadi apa solusi yang lebih baik? Menurut pendapat saya itu menciptakan set sumber kustom baru dan menempatkan kelas bersama ke dalamnya. Sebenarnya penulis proyek Gradle melakukannya dengan membuat set sumber testFixtures.

Untuk melakukannya Anda hanya perlu:

  1. Buat kumpulan sumber dan tambahkan konfigurasi yang diperlukan. Periksa plugin skrip ini yang digunakan dalam proyek Gradle: https://github.com/gradle/gradle/blob/v4.0.0/gradle/testFixtures.gradle
  2. Menyatakan ketergantungan yang tepat dalam proyek tergantung:

    dependencies {
        testCompile project(path: ':module-with-shared-classes', configuration: 'testFixturesUsageCompile')
    }
    
  3. Impor proyek Gradle ke IDEA dan gunakan opsi "buat modul terpisah per set sumber" saat mengimpor.
Václav Kužel
sumber
1
@jannis diperbaiki. Btw. Gradle memindahkan plugin perlengkapan tes berbasis Groovy ke berbasis Kotlin baru: github.com/gradle/gradle/blob/v5.0.0/buildSrc/subprojects/…
Václav Kužel
@ VáclavKužel Saya menemukan solusi menarik Anda melalui posting blog Anda dan itu memecahkan masalah saya dengan sangat baik. Terima kasih;)
zaerymoghaddam
10

Solusi Fesler tidak berhasil untuk saya, ketika saya mencobanya untuk membangun proyek android (gradle 2.2.0). Jadi saya harus referensi kelas yang diperlukan secara manual:

android {
    sourceSets {
        androidTest {
            java.srcDir project(':A').file("src/androidTest/java")
        }
        test {
            java.srcDir project(':A').file("src/test/java")
        }
    }
}
Bual
sumber
1
sedikit kesalahan ketik, melewatkan kutipan akhir setelah proyek (': A'). Ini berhasil bagi saya, terima kasih m8
Ryan Newsom
1
Untuk Android, ide ini bekerja dengan baik untuk saya, tanpa perasaan
simpanan
@arberg Ya, sepertinya pendekatan yang bagus. Satu-satunya batasan yang saya lihat adalah dengan @VisibleForTestingaturan serat. Anda tidak akan dapat memanggil metode seperti itu dari modul reguler di bawah folder tidak pengujian.
Beloo
5

Saya sangat terlambat ke pesta (sekarang Gradle v4.4) tetapi untuk siapa pun yang menemukan ini:

Asumsi:

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

Pergi ke build.gradle proyek B (yang membutuhkan beberapa kelas uji dari A) dan tambahkan yang berikut ini:

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

atau (dengan asumsi proyek Anda bernama "ProjectB")

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

Voila!

trik teknologi
sumber
3
Pertanyaannya tidak menyebutkan Android. Dapatkah Anda membuat jawaban Anda agnostik terhadap apakah pengembang mengembangkan untuk Android atau tidak, atau hanya untuk pengembang Android?
Robin Green
4

Jika Anda memiliki dependensi tiruan yang perlu Anda bagikan di antara tes, Anda dapat membuat proyek baru projectA-mockdan kemudian menambahkannya sebagai dependensi tes ke ProjectAdan ProjectB:

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

Ini adalah solusi yang jelas untuk dependensi berbagi pura-pura, tapi jika Anda perlu untuk menjalankan tes dari ProjectAdalam ProjectBpenggunaan solusi lain.

sylwano
sumber
Solusi hebat untuk kasus tiruan bersama!
Erik Sillén
4

Jika ingin digunakan dependensi artifak untuk memiliki:

  • Kelas sumber ProyekB bergantung pada kelas sumber Proyek A.
  • Kelas uji ProjectB bergantung pada kelas uji Project A.

maka bagian dependensi ProjectB di build.gradle akan terlihat seperti ini:

dependencies {

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

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

}

Agar ini berfungsi, ProjectA perlu membangun a toples- uji dan memasukkannya ke dalam artefak yang dihasilkannya.

Build.gradle ProjectA harus berisi konfigurasi seperti ini:

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

configurations {
    tests
}

artifacts {
    tests testsJar
    archives testsJar
}

jar.finalizedBy(testsJar)

Ketika artefak ProjectA dipublikasikan ke artifactory Anda, mereka akan menyertakan- pengujian toples- .

The testCompile pada bagian dependensi ProjectB akan membawa kelas di -tests jar.


Jika Anda ingin menyertakan sumber dan kelas uji ProjectAFlat di ProjectB untuk tujuan pengembangan maka bagian dependensi di build.gradle ProjectB akan terlihat seperti ini:

dependencies {

  compile project(':projecta')

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

}
Joman68
sumber
1
Sayangnya (dalam Gradle 6) flat termasuk, yang persis apa yang saya inginkan, tidak berfungsi lagi karena tidak ada konfigurasi 'tes' lagi. Menggunakan println(configurations.joinToString("\n") { it.name + " - " + it.allDependencies.joinToString() })(dalam kotlin buildscript), saya menentukan konfigurasi mana yang masih ada dan memiliki dependensi, tetapi untuk semua Gradle ini dikeluhkan: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

Beberapa jawaban lain menyebabkan kesalahan satu atau lain cara - Gradle tidak mendeteksi kelas uji dari proyek lain atau proyek Eclipse memiliki dependensi yang tidak valid ketika diimpor. Jika ada yang memiliki masalah yang sama, saya sarankan pergi dengan:

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

Baris pertama memaksa Eclipse untuk menghubungkan proyek lain sebagai ketergantungan, sehingga semua sumber disertakan dan terkini. Yang kedua memungkinkan Gradle untuk benar-benar melihat sumber, sementara tidak menyebabkan kesalahan ketergantungan yang tidak valid seperti yang testCompile project(':core').sourceSets.test.outputdilakukan.

Czyzby
sumber
2

Di sini jika Anda menggunakan Kotlin DSL , Anda harus membuat tugas Anda seperti itu menurut dokumentasi Gradle .

Seperti beberapa jawaban sebelumnya, Anda perlu membuat konfigurasi khusus di dalam proyek yang akan membagikan kelas tesnya, sehingga Anda tidak mencampur tes dan kelas utama.

Langkah sederhana

  1. Dalam proyek A Anda perlu menambahkan 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. Kemudian dalam proyek B Anda di dependensi, Anda perlu menambahkan build.gradle.kts:
dependencies {
    implementation(project(":ProjectA"))
    testImplementation(project(":ProjectA", "test"))
}
Sylhare
sumber
-1

dalam proyek B:

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

Tampaknya bekerja di 1.7-rc-2

John Caron
sumber
2
Ini juga menciptakan komplikasi yang tidak perlu dalam penanganan proyek oleh Eclipse. Solusi yang disarankan oleh @NikitaSkvortsov lebih disukai.
sfitts