【问题标题】:Multi-project test dependencies with gradle使用 gradle 的多项目测试依赖项
【发布时间】:2011-08-04 09:07:44
【问题描述】:

我有一个多项目配置,我想使用 gradle。

我的项目是这样的:

  • 项目A

    • ->src/main/java
    • ->src/test/java
  • 项目B

    • -> src/main/java(取决于项目A上的src/main/java
    • -> src/test/java(取决于 Project A 上的 src/test/java

我的Project Bbuild.gradle文件是这样的:

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

任务compileJava 运行良好,但compileTestJava 无法从项目A 编译测试文件。

【问题讨论】:

标签: java gradle build dependencies multi-project


【解决方案1】:

已弃用 - 对于 Gradle 5.6 及更高版本,请使用 this answer

Project B中,只需要添加一个testCompile依赖即可:

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

使用 Gradle 1.7 测试。

【讨论】:

  • 原来 classes 属性已被弃用——改用输出。
  • 这在 Gradle 1.3 中不起作用,因为 sourceSets 不再是项目的公共属性。
  • 请记住,上述解决方案至少需要一个gradle testClasses 才能使构建结构真正有效。例如。 Eclipse 插件不允许您在此之前导入项目。真的很遗憾testCompile project(':A') 不起作用。 @DavidPärsson:“Gradle 1.3”与“不再”相矛盾,因为 Fesler 使用 Gradle 1.7 进行了测试。
  • 对我不起作用。循环依赖失败:compileTestJava \--- :testClasses \--- :compileTestJava (*)
  • 不要这样做,项目不应该涉及到其他项目。而是使用 Nikita 的答案,正确地将其建模为项目依赖项。
【解决方案2】:

现在作为 Gradle 中的一流功能支持此功能。 带有 javajava-library 插件的模块还可以包含一个 java-test-fixtures 插件,该插件公开了要使用的辅助类和资源testFixtures 帮手。这种方法对工件和分类器的好处是:

  • 适当的依赖管理(实现/api)
  • 与测试代码很好的分离(单独的源集)
  • 无需过滤掉测试类以仅公开实用程序
  • 由 Gradle 维护

示例

: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

模块/其他/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
  }
}

进一步阅读

有关详细信息,请参阅文档:
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

【讨论】:

【解决方案3】:

简单的方法是在ProjectB中添加明确的任务依赖:

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

困难(但更清晰)的方法是为 ProjectA 创建额外的工件配置:

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

configurations {
  testArtifacts
}

artifacts {
   testArtifacts myTestsJar
}

并为 ProjectB 添加 testCompile 依赖项

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

【讨论】:

  • 我试过这个(简单的方法),虽然它确保它构建了 testClasses,但它不会将测试路径添加到 CLASSPATH 所以我依赖于 ProjectA 测试类的 ProjectB 测试仍然失败构建。
  • @dmoebius 你必须像这样添加testArtifacts 配置:configurations { testArtifacts } 有关更多详细信息,请参阅 Gradle 帮助的这一部分:gradle.org/docs/current/dsl/…
  • 在 Gradle 1.8 中,您可能希望 from sourceSets.test.output 并且可能需要 classifier = 'tests' 来代替答案中的 // pack whatever you need...
  • 确认 Gradle 1.12 使用完整解决方案,@PeterLamberg 建议添加按预期工作。不影响将项目导入 Eclipse。
  • 这适用于我在 Gradle 4.7 中。他们现在在docs.gradle.org/current/dsl/… 有一些关于该方法的文档
【解决方案4】:

我知道这是一个老问题,但我只是遇到了同样的问题,并花了一些时间弄清楚发生了什么。我正在使用 Gradle 1.9。所有更改都应该在 ProjectB 的build.gradle

在 ProjectB 的测试中使用来自 ProjectA 的测试类:

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

要确保 sourceSets 属性可用于 ProjectA:

evaluationDependsOn(':ProjectA')

当你编译 ProjectB 时,要确保 ProjectA 中的测试类确实存在:

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

【讨论】:

  • 这也对我有用,只是我不得不省略.classesDir
【解决方案5】:

我自己最近也遇到过这个问题,这是一个很难找到答案的问题。

您所犯的错误是认为项目应该以导出其主要工件和依赖项的相同方式导出其测试元素。

我个人取得更大成功的是在 Gradle 中创建了一个新项目。在你的例子中,我会命名它

项目 A_Test -> src/main/java

我会将您当前在项目 A/src/test/java 中的文件放入 src/main/java。使您的项目 A 的任何 testCompile 依赖项编译项目 A_Test 的依赖项。

然后让Project A_Test成为Project B的testCompile依赖。

从两个项目的作者的角度来看,这不合逻辑,但我认为当你考虑像 junit 和 scalatest(以及其他)这样的项目时,这很有意义。即使这些框架正在测试 -相关,它们不被视为自己框架中“测试”目标的一部分 - 它们产生其他项目恰好在其测试配置中使用的主要工件。您只想遵循相同的模式。

尝试执行此处列出的其他答案对我个人而言不起作用(使用 Gradle 1.9),但我发现我在此处描述的模式无论如何都是更清洁的解决方案。

【讨论】:

  • 是的,最终选择了这种方法。
  • 这是最好的方法!除了我会将测试代码保留在项目 A 中,并且仅将 A src/test/java 和 B src/test/java 的依赖项移动到 A_Test。然后让 Project A_Test 成为 A 和 B 的 testImplementation 依赖项。
【解决方案6】:

请阅读下面的更新。

JustACluelessNewbie 描述的类似问题出现在 IntelliJ IDEA 中。问题是依赖 testCompile project(':core').sourceSets.test.output 实际上意味着:“依赖于 gradle build task 生成的类”。因此,如果您打开尚未生成类的干净项目,IDEA 将无法识别它们并报告错误。

要解决此问题,您必须在对已编译类的依赖项旁边添加对测试源文件的依赖项。

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

您可以在Module Settings -> Dependencies(测试范围)中观察IDEA识别的依赖关系

顺便说一句。这不是很好的解决方案,因此值得考虑重构。 Gradle 本身确实有仅包含测试支持类的特殊子项目。见https://docs.gradle.org/current/userguide/test_kit.html

2016-06-05 更新 更多我正在考虑提出的解决方案,我不喜欢它。它的问题很少:

  1. 它在 IDEA 中创建了两个依赖项。一个指向测试源另一个指向已编译的类。 IDEA 以何种顺序识别这些依赖关系至关重要。您可以通过在 Module settings -> Dependencies 选项卡中更改依赖顺序来使用它。
  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,并在导入时使用“为每个源集创建单独的模块”选项。

【讨论】:

【解决方案7】:

新的基于 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依赖 在项目 A 和 B 上不仅对 A 有编译依赖,而且 也是一个测试依赖。要编译和运行 B 的测试,我们需要一些 来自 A 的测试助手类。

默认情况下,gradle 不会从测试构建中创建 jar 工件 项目的输出。

这个插件添加了一个 testArchives 配置(基于 testCompile) 和一个 jarTest 任务,用于从测试源集创建一个 jar(使用 分类器测试添加到 jar 的名称)。然后我们可以依赖于 B A 的 testArchives 配置(其中还将包括 A) 的传递依赖。

在 A 中,我们将插件添加到 build.gradle:

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

在 B 中,我们引用 testArchives 配置如下:

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

【讨论】:

  • 虽然此链接可能会回答问题,但最好在此处包含答案的基本部分并提供链接以供参考。如果链接页面发生更改,仅链接答案可能会失效。 - From Review
  • 添加了几行文字
  • 总之,关于新的gradle插件的信息已经提供了。
  • @demon101 在 Gradle 4.6 中不工作,出现错误 Could not get unknown property 'testClasses' for project ':core' of type org.gradle.api.Project.
【解决方案8】:

当我尝试构建一个 android 项目(gradle 2.2.0)时,Fesler 的解决方案对我不起作用。 所以我不得不手动引用所需的类:

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

【讨论】:

  • 轻微错字,在项目(':A')之后缺少结束引号。不过这对我有用,谢谢 m8
  • 对于 Android,这个想法对我来说效果很好,没有 stackoverflow.com/a/50869037/197141 的 hacky 感觉
  • @arberg 是的,似乎是一个好方法。我看到的唯一限制是@VisibleForTesting lint 规则。您将无法从非测试文件夹下的常规模块调用此类方法。
【解决方案9】:

我参加聚会太晚了(现在是 Gradle v4.4),但对于发现这个的其他人来说:

假设:

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

转到项目 B 的 build.gradle(需要 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
    }
}

瞧!

【讨论】:

  • 这个问题没有提到Android。您能否就开发者是否为 Android 开发,还是仅针对 Android 开发者做出不可知论的回答?
  • 我在一个有周期的项目上工作,所以这个答案是唯一一个或多或少干净的答案。作为参考,我把来自projectExposingTestssn-p(kotlin 版本)的需要测试的项目放在:sourceSets { test { java.srcDir(project(":projectExposingTests").file("src/test/java")) } }
【解决方案10】:

如果您使用的是 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"))
}

【讨论】:

  • 如上所述,另一个答案如下:只是认为是的,配置应该真正命名为 test,仅此而已(否则 IDE 可能难以获取对测试源)。
【解决方案11】:

为 Gradle 6.6.x 创建测试罐

我知道有很多消息来源告诉你,那不行,fe:

但这太他妈简单了,我只是不喜欢在testFixtures 文件夹中单独拥有通用测试类的想法。

所以在模块 A 中:

task jarTests(type: Jar, dependsOn: testClasses) {
    classifier = 'tests'
    from sourceSets.test.output
}
configurations {
    tests {
        extendsFrom testRuntime
    }
}
artifacts {
    tests jarTests
}

在模块 B 中:

testImplementation project(':moduleA')
testImplementation project(path: ':moduleA', configuration: 'tests')

而且效果很好!

【讨论】:

  • IntelliJ 用户注意事项:使用此方法时,请确保将配置命名为 test。 gradle 命令行使用任何名称都可以正常工作,但至少 IntelliJ 可以更好地使用“test”(因为它会理解依赖实际上是对测试源的依赖,所以您不希望 IDE“真正”依赖于输出 jar!)
【解决方案12】:

如果你想使用 artifact 依赖有:

  • ProjectB 的源类依赖于 Project A 的源类
  • ProjectB 的测试类依赖于 Project A 的测试类

那么 build.gradle 中 ProjectB 的依赖项部分应如下所示:

dependencies {

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

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

}

为此,ProjectA 需要构建一个 -tests jar 并将其包含在它生成的工件中。

ProjectA 的 build.gradle 应该包含如下配置:

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

configurations {
    tests
}

artifacts {
    tests testsJar
    archives testsJar
}

jar.finalizedBy(testsJar)

当 ProjectA 的工件发布到您的工件时,它们将包含一个 -tests jar。

ProjectB 的依赖项部分中的 testCompile 将引入 -tests jar 中的类。


如果您想在 ProjectB 中includeFlat ProjectA 的源代码和测试类以用于开发目的,那么 ProjectB 的 build.gradle 中的依赖项部分将如下所示:

dependencies {

  compile project(':projecta')

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

}

【讨论】:

  • 不幸的是(在 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.
  • 此解决方案适用于 Gradle 7.0
【解决方案13】:

如果您有需要在测试之间共享的模拟依赖项,您可以创建新项目projectA-mock,然后将其作为测试依赖项添加到ProjectAProjectB

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

这是共享模拟依赖项的明确解决方案,但如果您需要在 ProjectB 中从 ProjectA 运行测试,请使用其他解决方案。

【讨论】:

  • 共享模拟案例的绝佳解决方案!
【解决方案14】:

Nikita 提到的 Android + Kotlin 的解决方案如下所示:

task jarTests(type: Jar, dependsOn: "assembleDebugUnitTest") {
    getArchiveClassifier().set('tests')
    from "$buildDir/tmp/kotlin-classes/debugUnitTest"
}

configurations {
    unitTestArtifact
}

artifacts {
    unitTestArtifact jarTests
}

将使用依赖项的项目的 Gradle:

testImplementation project(path: ':shared', configuration: 'unitTestArtifact')

【讨论】:

  • 为我工作。谢谢
【解决方案15】:

其他一些答案以某种方式导致错误 - Gradle 未检测到来自其他项目的测试类或 Eclipse 项目在导入时具有无效的依赖项。如果有人遇到同样的问题,我建议使用:

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

第一行强制 Eclipse 将其他项目链接为依赖项,因此所有源都包含在内并且是最新的。第二个允许 Gradle 实际查看源代码,同时不会像 testCompile project(':core').sourceSets.test.output 那样导致任何无效的依赖错误。

【讨论】:

    【解决方案16】:

    如果您正在努力使解决方案适应 Gradle Kotlin DSL,这是等效的:

    configurations {
        register("testClasses") {
            extendsFrom(testImplementation.get())
        }
    }
    val testJar = tasks.register<Jar>("testJar") {
        archiveClassifier.set("test")
        from(sourceSets.test)
    }
    artifacts.add("testClasses", testJar) 
    

    【讨论】:

      【解决方案17】:

      在项目 B 中:

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

      似乎在 1.7-rc-2 中工作

      【讨论】:

      • 它还会在 Eclipse 处理项目时产生不必要的复杂性。 @NikitaSkvortsov 建议的解决方案更可取。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-11-10
      • 2015-08-22
      • 2012-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多