在JVM上进行测试是一个很丰富的主题. 有许多不同的测试库和框架,以及许多不同类型的测试. 无论它们是频繁执行还是不频繁执行,所有这些都需要成为构建的一部分. 本章致力于说明Gradle如何处理内部版本之间以及内部版本之间的不同需求,并广泛介绍了Gradle如何与两个最常见的测试框架JUnitTestNG集成.

它说明:

但是首先,我们看一下Gradle中JVM测试的基础.

The basics

所有JVM测试都围绕一种任务类型: Test . 这将使用任何受支持的测试库(JUnit,JUnit Platform或TestNG)运行一组测试用例,并整理结果. 然后,您可以通过TestReport任务类型的实例将这些结果转换为报告.

为了进行操作," Test任务类型仅需要两条信息:

当您使用JVM语言插件(例如Java插件)时 ,您将自动获得以下信息:

  • 用于单元测试的专用test源集

  • 运行这些单元测试的Test类型的test任务

JVM语言插件使用源集来配置具有适当执行类路径和包含已编译测试类的目录的任务. 另外,他们将test任务附加到check 生命周期任务 .

还应该记住, testtestImplementation自动创建相应的依赖项配置 (其中最有用的是testImplementationtestRuntimeOnly ),这些插件会绑定到test任务的类路径中.

在大多数情况下,您要做的就是配置适当的编译和运行时依赖项,并将所有必要的配置添加到test任务中. 以下示例显示了一个简单的设置,该设置使用JUnit 4.x并将测试JVM的最大堆大小更改为1 GB:

例子1."测试"任务的基本配置
build.gradle
dependencies {
    testImplementation 'junit:junit:4.12'
}

test {
    useJUnit()

    maxHeapSize = '1G'
}
build.gradle.kts
dependencies {
    testImplementation("junit:junit:4.12")
}

tasks.test {
    useJUnit()

    maxHeapSize = "1G"
}

Test任务具有许多通用配置选项,以及可以在JUnitOptionsJUnitPlatformOptionsTestNGOptions中找到的几个特定于框架的配置选项. 在本章的其余部分中,我们将讨论其中的许多内容.

如果要使用自己的测试类集来设置自己的Test任务,那么最简单的方法是创建自己的源集和Test任务实例,如配置集成测试中所示.

Test execution

Gradle在独立于主构建过程的单独("分叉")JVM中执行测试. 这样可以防止类路径污染和构建过程中过多的内存消耗. 它还允许您使用与构建使用的JVM参数不同的JVM参数运行测试.

您可以通过" Test任务上的多个属性来控制如何启动测试过程,包括:

maxParallelForks — default: 1

您可以通过将此属性设置为大于1的值来并行运行测试.这可能会使您的测试套件更快地完成,尤其是在多核CPU上运行它们时. 使用并行测试执行时,请确保您的测试正确地相互隔离. 与文件系统交互的测试特别容易发生冲突,从而导致间歇性的测试失败.

您的测试可以使用org.gradle.test.worker属性的值来区分并行测试过程,该属性对于每个过程都是唯一的. 您可以将其用于所需的任何内容,但是对于文件名和其他资源标识符特别有用,可以防止我们刚才提到的这种冲突.

forkEvery — default: 0 (no maximum)

此属性指定Gradle在处置之前应创建的新测试类的最大数目. 这主要用作管理泄漏测试或框架的静态状态,这些静态状态无法在测试之间清除或重置.

警告:较低的值(非0)会严重损害测试的性能

ignoreFailures — default: false

如果此属性为true ,则即使测试中的某些失败,Gradle也会在测试完成后继续进行项目的构建. 请注意,默认情况下,无论此设置如何," Test任务都会始终执行它检测到的每个测试.

failFast —  (since Gradle 4.6) default: false

如果您希望构建失败并在其中一项测试失败后立即完成,则将其设置为true . 当您拥有长期运行的测试套件时,这可以节省大量时间,并且在连续集成服务器上运行构建时特别有用. 如果在运行所有测试之前构建失败,则测试报告仅包括已成功完成或未成功完成的测试结果.

您也可以使用--fail-fast命令行选项启用此行为.

testLogging — default: not set

此属性表示一组选项,用于控制记录哪些测试事件以及在什么级别进行记录. 您还可以通过此属性配置其他日志记录行为. 有关更多详细信息,请参见TestLoggingContainer .

有关所有可用配置选项的详细信息,请参见测试 .

如果配置不正确,测试过程可能会意外退出. 例如,如果Java可执行文件不存在或提供了无效的JVM参数,则测试过程将无法启动. 同样,如果测试对测试过程进行程序化更改,这也会导致意外失败.

例如,如果在测试中修改了SecurityManager则可能会出现问题,因为Gradle的内部消息传递依赖于反射和套接字通信,如果安全管理器上的权限发生更改,则可能会中断通信. 在这种特殊情况下,您应该在测试后还原原始的SecurityManager ,以便gradle测试工作程序进程可以继续运行.

Test filtering

运行测试套件的子集是常见的要求,例如,当您修复错误或开发新的测试用例时. Gradle提供了两种机制来执行此操作:

  • 过滤(首选选项)

  • 测试包含/排除

过滤取代了包含/排除机制,但您仍然可能在野外遇到后者.

使用Gradle的测试过滤,您可以根据以下条件选择要运行的测试:

  • 完全限定的类名或完全限定的方法名,例如org.gradle.SomeTestorg.gradle.SomeTest.someMethod

  • 如果模式以大写字母开头, SomeTest简单的类名或方法名,例如SomeTestSomeTest.someMethod (自Gradle 4.7起)

  • '*'通配符匹配

您可以在构建脚本中或通过--tests命令行选项启用过滤. 这是每次构建运行时都会应用的一些过滤器的示例:

示例2.在构建脚本中过滤测试
build.gradle
test {
    filter {
        //include specific method in any of the tests
        includeTestsMatching "*UiCheck"

        //include all tests from package
        includeTestsMatching "org.gradle.internal.*"

        //include all integration tests
        includeTestsMatching "*IntegTest"
    }
}
build.gradle.kts
tasks.test {
    filter {
        //include specific method in any of the tests
        includeTestsMatching("*UiCheck")

        //include all tests from package
        includeTestsMatching("org.gradle.internal.*")

        //include all integration tests
        includeTestsMatching("*IntegTest")
    }
}

有关在构建脚本中声明过滤器的更多详细信息和示例,请参见TestFilter参考.

命令行选项对于执行单个测试方法特别有用. 使用--tests ,请注意仍会尊重构建脚本中声明的包含. 也可以提供多个--tests选项,所有选项都会生效. 以下各节提供了几个使用命令行选项的示例.

并非所有的测试框架都可以很好地与过滤一起使用. 某些高级的综合测试可能不完全兼容. 但是,绝大多数测试和用例都可以通过Gradle的过滤机制很好地工作.

以下两节介绍简单类/方法名称和完全限定名称的特殊情况.

Simple name pattern

从4.7开始,Gradle将以大写字母开头的模式作为简单的类名或类名+方法名. 例如,以下命令行运行SomeTestClass测试用例中的SomeTestClass测试,或仅运行其中一个测试,而不管其位于哪个程序包中:

# Executes all tests in SomeTestClass
gradle test --tests SomeTestClass

# Executes a single specified test in SomeTestClass
gradle test --tests SomeTestClass.someSpecificMethod

gradle test --tests SomeTestClass.*someMethod*

Fully-qualified name pattern

在4.7之前,或者如果模式不是以大写字母开头,则Gradle会将模式视为完全合格. 因此,如果要使用测试类名称而不考虑其包,请使用--tests *.SomeTestClass . 这里还有更多示例:

# specific class
gradle test --tests org.gradle.SomeTestClass

# specific class and method
gradle test --tests org.gradle.SomeTestClass.someSpecificMethod

# method name containing spaces
gradle test --tests "org.gradle.SomeTestClass.some method containing spaces"

# all classes at specific package (recursively)
gradle test --tests 'all.in.specific.package*'

# specific method at specific package (recursively)
gradle test --tests 'all.in.specific.package*.someSpecificMethod'

gradle test --tests '*IntegTest'

gradle test --tests '*IntegTest*ui*'

gradle test --tests '*ParameterizedTest.foo*'

# the second iteration of a parameterized test
gradle test --tests '*ParameterizedTest.*[2]'

请注意,通配符'*'对'.'没有特殊的理解. 包装分离器. 它是纯粹基于文本的. 因此--tests *.SomeTestClass将匹配任何软件包,无论其"深度"如何.

您还可以将在命令行中定义的过滤器与连续构建相结合,以在每次对生产或测试源文件进行更改后立即重新执行测试的子集. 每当更改触发测试运行时,以下命令将执行" com.mypackage.foo"包或子包中的所有测试:

gradle test --continuous --tests "com.mypackage.foo.*"

Test reporting

默认情况下," Test任务会生成以下结果:

  • HTML测试报告

  • XML测试结果的格式与Ant JUnit报告任务兼容-许多其他工具(例如CI服务器)都支持该格式

  • Test任务用于生成其他格式的结果的有效二进制格式

在大多数情况下,您将使用标准HTML报告,该报告自动包括所有 Test任务的结果,甚至包括您自己显式添加到构建中的结果. 例如,如果添加用于集成测试的" Test任务,则如果两个任务都运行,则报告将同时包含单元测试和集成测试的结果.

与许多测试配置选项不同,有几个项目级别的约定属性会影响测试报告 . 例如,您可以像这样更改测试结果和报告的目的地:

例子3.改变默认的测试报告和结果目录
build.gradle
reporting.baseDir = "my-reports"
testResultsDirName = "$buildDir/my-test-results"

task showDirs {
    doLast {
        logger.quiet(rootDir.toPath().relativize(project.reportsDir.toPath()).toString())
        logger.quiet(rootDir.toPath().relativize(project.testResultsDir.toPath()).toString())
    }
}
build.gradle.kts
reporting.baseDir = file("my-reports")
project.setProperty("testResultsDirName", "$buildDir/my-test-results")

tasks.register("showDirs") {
    doLast {
        logger.quiet(rootDir.toPath().relativize((project.properties["reportsDir"] as File).toPath()).toString())
        logger.quiet(rootDir.toPath().relativize((project.properties["testResultsDir"] as File).toPath()).toString())
    }
}
gradle -q showDirs输出
> gradle -q showDirs
my-reports
build/my-test-results

单击链接到约定属性以获取更多详细信息.

还有一个独立的TestReport任务类型,可用于生成自定义HTML测试报告. 它所需要的只是destinationDir的值以及要包含在报告中的测试结果. 这是一个示例,可为所有子项目的单元测试生成组合报告:

例子4.为子项目创建一个单元测试报告
build.gradle
subprojects {
    apply plugin: 'java'

    // Disable the test report for the individual test task
    test {
        reports.html.enabled = false
    }
}

task testReport(type: TestReport) {
    destinationDir = file("$buildDir/reports/allTests")
    // Include the results from the `test` task in all subprojects
    reportOn subprojects*.test
}
build.gradle.kts
subprojects {
    apply(plugin = "java")

    // Disable the test report for the individual test task
    tasks.named<Test>("test") {
        reports.html.isEnabled = false
    }
}

tasks.register<TestReport>("testReport") {
    destinationDir = file("$buildDir/reports/allTests")
    // Include the results from the `test` task in all subprojects
    reportOn(subprojects.map { it.tasks["test"] })
}

您应该注意, TestReport类型组合了多个测试任务的结果,并且需要汇总单个测试类的结果. 这意味着如果给定的测试类由多个测试任务执行,则测试报告将包括该类的执行,但是很难区分该类的各个执行及其输出.

Test detection

默认情况下,Gradle将运行它检测到的所有测试,这是通过检查编译的测试类来完成的. 根据所使用的测试框架,此检测使用不同的标准.

对于JUnit ,Gradle会扫描JUnit 3和4测试类. 如果某个类符合以下条件,则将其视为JUnit测试:

  • 最终继承自TestCaseGroovyTestCase

  • @RunWith注释

  • Contains a method annotated with @Test or a super class does

对于TestNG ,Gradle扫描使用@Test注释的方法.

请注意,不执行抽象类. 另外,请注意Gradle会将继承树扫描到测试类路径上的jar文件中. 因此,如果这些JAR包含测试类,它们也将运行.

如果您不想使用测试类检测,可以通过将Test上的scanForTestClasses属性设置为false来禁用它. 这样做时,测试任务仅使用includesexcludes属性来查找测试类.

如果scanForTestClasses为false并且未指定包含或排除模式,则Gradle默认运行任何与**/*Tests.class**/*Test.class模式匹配的类,但不包括与**/Abstract*.class匹配的**/Abstract*.class .

使用JUnit Platform时 ,仅includesexcludes用于过滤测试类scanForTestClasses不起作用.

Test grouping

JUnit,JUnit Platform和TestNG允许对测试方法进行复杂的分组.

JUnit 4.8引入了将JUnit 4测试类和方法分组的类别的概念. [ 1 ] Test.useJUnit(org.gradle.api.Action)允许您指定要包括和排除的JUnit类别. 例如,以下配置包括CategoryA测试,而排除CategoryB针对test任务的test

例子5. JUnit类别
build.gradle
test {
    useJUnit {
        includeCategories 'org.gradle.junit.CategoryA'
        excludeCategories 'org.gradle.junit.CategoryB'
    }
}
build.gradle.kts
tasks.test {
    useJUnit {
        includeCategories("org.gradle.junit.CategoryA")
        excludeCategories("org.gradle.junit.CategoryB")
    }
}

JUnit Platform引入了标记来替换类别. 您可以通过Test.useJUnitPlatform(org.gradle.api.Action)指定包含/排除的标签,如下所示:

例子6. JUnit平台标签
build.gradle
test {
    useJUnitPlatform {
        includeTags 'fast'
        excludeTags 'slow'
    }
}
build.gradle.kts
tasks.test {
    useJUnitPlatform {
        includeTags("fast")
        excludeTags("slow")
    }
}

TestNG框架使用测试组的概念来达到类似的效果. [ 2 ]您可以通过Test.useTestNG(org.gradle.api.Action)设置配置在测试执行过程中包括或排除哪些测试组,如下所示:

例子7.分组TestNG测试
build.gradle
test {
    useTestNG {
        excludeGroups 'integrationTests'
        includeGroups 'unitTests'
    }
}
build.gradle.kts
tasks.named<Test>("test") {
    useTestNG {
        val options = this as TestNGOptions
        options.excludeGroups("integrationTests")
        options.includeGroups("unitTests")
    }
}

Using JUnit 5

JUnit 5是著名的JUnit测试框架的最新版本. 与之前的版本不同,JUnit 5是模块化的,由几个模块组成:

JUnit 5 = JUnit Platform + JUnit Jupiter + JUnit Vintage

JUnit平台是在JVM上启动测试框架的基础. JUnit Jupiter是新编程模型扩展模型的组合,用于在JUnit 5中编写测试和扩展TestEngine Vintage提供了一个TestEngine用于在平台上运行基于JUnit 3和JUnit 4的测试.

以下代码在build.gradle启用了JUnit Platform支持:

例子8.使JUnit平台运行测试
build.gradle
test {
    useJUnitPlatform()
}
build.gradle.kts
tasks.named<Test>("test") {
    useJUnitPlatform()
}

有关更多详细信息,请参见Test.useJUnitPlatform() .

将JUnit 5与Gradle结合使用存在一些已知的局限性,例如,不会发现静态嵌套类中的测试,并且类仍按其类名而不是@DisplayName . 这些将在Gradle的未来版本中修复. 如果您发现更多信息,请在https://github.com/gradle/gradle/issues/new告诉我们

Compiling and executing JUnit Jupiter tests

要在Gradle中启用JUnit Jupiter支持,您需要做的就是添加以下依赖项:

例子9. JUnit Jupiter依赖
build.gradle
dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.1.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.1.0'
}
build.gradle.kts
dependencies {
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.1.0")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.1.0")
}

然后,您可以将测试用例正常放置到src / test / java中 ,并使用gradle test执行它们.

Executing legacy tests with JUnit Vintage

如果要在JUnit Platform上运行JUnit 3/4测试,或者甚至将它们与Jupiter测试混合使用,则应添加额外的JUnit Vintage Engine依赖项:

例子10. JUnit Vintage依赖
build.gradle
dependencies {
    testImplementation 'org.junit.jupiter:junit-jupiter-api:5.1.0'
    testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.1.0'
    testCompileOnly 'junit:junit:4.12'
    testRuntimeOnly 'org.junit.vintage:junit-vintage-engine:5.1.0'
}
build.gradle.kts
dependencies {
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.1.0")
    testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.1.0")
    testCompileOnly("junit:junit:4.12")
    testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.1.0")
}

这样,您可以使用gradle test在JUnit Platform上测试JUnit 3/4测试,而无需重写它们.

可以在Gradle的"-全部"分布中的samples/testing/junitplatform/mix中找到混合测试的samples/testing/junitplatform/mix .

Filtering test engine

JUnit Platform允许您使用不同的测试引擎. JUnit当前提供了两个TestEngine实现: junit-jupiter-enginejunit-vintage-engine . 您还可以按照此处所述编写和插入自己的TestEngine实现.

默认情况下,将使用测试运行时类路径上的所有测试引擎. 要显式控制特定的测试引擎实现,可以将以下设置添加到构建脚本中:

例子11.过滤特定引擎
build.gradle
test {
    useJUnitPlatform {
        includeEngines 'junit-vintage'
        // excludeEngines 'junit-jupiter'
    }
}
build.gradle.kts
tasks.test {
    useJUnitPlatform {
        includeEngines("junit-vintage")
        // excludeEngines("junit-jupiter")
    }
}

A test engine filtering sample can be found at samples/testing/junitplatform/engine in the '-all' distribution of Gradle.

Test execution order in TestNG

当您使用testng.xml文件时,TestNG允许显式控制测试的执行顺序. 没有这样的文件(或由TestNGOptions.getSuiteXmlBuilder()配置的等效文件 ,您将无法指定测试执行顺序. 但是,您可以做的是控制测试的所有方面,包括与之关联的@BeforeXXX@AfterXXX方法,例如用@Before/AfterClass@Before/AfterMethod注释的方法,是否都在下一个测试开始之前执行. 您可以通过将TestNGOptions.getPreserveOrder()属性设置为truetrue . 如果将其设置为false ,则可能会遇到执行顺序类似于以下TestA.doBeforeClass()TestA.doBeforeClass()TestB.doBeforeClass()TestA测试.

直接使用testng.xml文件时,保留测试顺序是默认行为,而Gradle的TestNG集成所使用的TestNG API默认情况下以不可预测的顺序执行测试. [ 3 ] TestNG版本5.14.5引入了保留测试执行顺序的功能. 设置preserveOrder属性为true为较旧版本的TestNG的将导致生成失败.

例子12.保留TestNG测试的顺序
build.gradle
test {
    useTestNG {
        preserveOrder true
    }
}
build.gradle.kts
tasks.test {
    useTestNG {
        preserveOrder = true
    }
}

groupByInstance属性控制是否应按实例而不是按类别对测试进行分组. TestNG文档更详细地解释了差异,但是从本质上讲,如果您有一个依赖于B()的测试方法A() B() ,则按实例分组可确保每个AB对(例如B(1) A(1)都是在下一次配对之前执行. 通过按组分组,将运行所有B()方法,然后运行所有A()方法.

请注意,如果您使用数据提供程序对其进行参数化,则通常只有一个以上的测试实例. 此外,TestNG版本6.1引入了按实例对测试进行分组的功能. 对于较旧的TestNG版本,将groupByInstances属性设置为true会导致构建失败.

Example 13. Grouping TestNG tests by instances
build.gradle
test {
    useTestNG {
        groupByInstances = true
    }
}
build.gradle.kts
tasks.test {
    useTestNG {
        groupByInstances = true
    }
}

TestNG parameterized methods and reporting

TestNG支持参数化测试方法 ,允许使用不同的输入多次执行特定的测试方法. Gradle在报告测试方法执行情况时将参数值包括在内.

给定名为aTestMethod的参数化测试方法,该方法aTestMethod两个参数,它将以名称aTestMethod(toStringValueOfParam1, toStringValueOfParam2)进行报告. 这样可以轻松识别特定迭代的参数值.

Configuring integration tests

项目的常见要求是以一种或另一种形式合并集成测试. 他们的目的是验证项目的各个部分是否正常工作. 与单元测试相比,这通常意味着它们需要特殊的执行设置和依赖性.

将集成测试添加到构建中的最简单方法是采取以下步骤:

  1. Create a new source set for them

  2. 将所需的依赖项添加到该源集的适当配置中

  3. 为该源集配置编译和运行时类路径

  4. 创建任务以运行集成测试

您可能还需要执行一些其他配置,具体取决于集成测试采用的形式. 我们将讨论这些内容.

让我们从一个实际的示例开始,该示例在一个构建脚本中实现前三个步骤,并以一个新的源集intTest为中心:

Example 14. Setting up working integration tests
build.gradle
sourceSets {
    intTest {
        compileClasspath += sourceSets.main.output
        runtimeClasspath += sourceSets.main.output
    }
}

configurations {
    intTestImplementation.extendsFrom implementation
    intTestRuntimeOnly.extendsFrom runtimeOnly
}

dependencies {
    intTestImplementation 'junit:junit:4.12'
}
build.gradle.kts
sourceSets {
    create("intTest") {
        compileClasspath += sourceSets.main.get().output
        runtimeClasspath += sourceSets.main.get().output
    }
}

val intTestImplementation by configurations.getting {
    extendsFrom(configurations.implementation.get())
}

configurations["intTestRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get())

dependencies {
    intTestImplementation("junit:junit:4.12")
}

这将设置一个名为intTest的新源集,该源集将自动创建:

  • intTestImplementationintTestCompileOnlyintTestRuntimeOnly配置(以及其他一些较不常用的配置)

  • 一个compileIntTestJava任务,它将编译src / intTest / java下的所有源文件

该示例还执行以下操作,并非特定集成测试可能需要所有这些操作:

  • 将生产类从main源集中添加到集成测试的编译和运行时类路径中— sourceSets.main.output是包含已编译生产类和资源的所有目录的文件集合

  • 使intTestImplementation配置从implementation扩展,这意味着所有声明的生产代码依赖项也将成为集成测试的依赖项

  • intTestRuntimeOnly配置执行相同的intTestRuntimeOnly

在大多数情况下,您希望集成测试可以访问被测类,这就是为什么我们确保在此示例中将它们包括在编译和运行时类路径中的原因. 但是某些类型的测试以不同的方式与生产代码交互. 例如,您可能具有将应用程序作为可执行文件运行并测试输出的测试. 对于Web应用程序,测试可能会通过HTTP与您的应用程序进行交互. 由于在这种情况下测试不需要直接访问被测类,因此您无需将生产类添加到测试类路径中.

另一个常见的步骤是通过intTestImplementation.extendsFrom testImplementation将所有单元测试依赖项也附加到集成测试中,但这仅在集成测试要求单元测试具有全部或几乎所有相同的依赖项时才有意义.

您还应注意该示例的其他两个方面:

  • +=允许您将路径和路径集合追加到compileClasspathruntimeClasspath而不是覆盖它们

  • 如果要使用基于约定的配置,如intTestImplementation ,则必须在新的源集之后声明依赖项

创建和配置源集会自动设置编译阶段,但是对于运行集成测试没有任何作用. 因此,最后一个难题是自定义测试任务,该任务使用来自新源集的信息来配置其运行时类路径和测试类:

例子15.定义一个有效的集成测试任务
build.gradle
task integrationTest(type: Test) {
    description = 'Runs integration tests.'
    group = 'verification'

    testClassesDirs = sourceSets.intTest.output.classesDirs
    classpath = sourceSets.intTest.runtimeClasspath
    shouldRunAfter test
}

check.dependsOn integrationTest
build.gradle.kts
val integrationTest = task<Test>("integrationTest") {
    description = "Runs integration tests."
    group = "verification"

    testClassesDirs = sourceSets["intTest"].output.classesDirs
    classpath = sourceSets["intTest"].runtimeClasspath
    shouldRunAfter("test")
}

tasks.check { dependsOn(integrationTest) }

再次,我们正在访问源集以获取相关信息,即,已编译的测试类的位置testClassesDirs属性-运行它们时需要在类路径上classpath .

用户通常希望在单元测试之后运行集成测试,因为它们通常运行速度较慢,并且您希望构建在单元测试之前失败而不是在集成测试之后失败. 这就是为什么上面的示例添加了shouldRunAfter()声明的原因. 这比mustRunAfter()更可取,因此Gradle在并行执行构建时具有更大的灵活性.

Skipping the tests

如果要在运行构建时跳过测试,则有几种选择. 您可以通过命令行参数在构建脚本中执行此操作 . 要在命令行上执行此操作,可以使用-x--exclude-task选项,如下所示:

gradle build -x test

这排除了test任务及其专有依赖的任何其他任务,即没有其他任务依赖于同一任务. 这些任务不会被Gradle标记为"跳过",而只会出现在已执行任务的列表中.

通过构建脚本跳过测试可以通过几种方法完成. 一种常见的方法是通过Task.onlyIf(org.gradle.api.specs.Spec)方法使测试执行有条件. 如果项目具有名为mySkipTests的属性,则以下示例跳过test任务:

例子16.跳过基于项目属性的单元测试
build.gradle
test.onlyIf { !project.hasProperty('mySkipTests') }
build.gradle.kts
tasks.test { onlyIf { !project.hasProperty("mySkipTests") } }

在这种情况下,Gradle会将跳过的测试标记为"跳过",而不是将其从构建中排除.

Forcing tests to run

在定义良好的版本中,您可以依靠Gradle仅在测试本身或生产代码更改时运行测试. 但是,您可能会遇到测试依赖第三方服务或其他可能会更改但无法在构建中建模的情况.

您可以通过清除相关Test任务(例如test的输出并再次运行测试来强制在这种情况下运行测试,如下所示:

gradle cleanTest test

cleanTest基于基础插件提供的任务规则 . 您可以将其用于任何任务.

Debugging when running tests

在少数情况下,您想在测试运行时调试代码,如果可以在此时附加调试器,则可能会有所帮助. 您可以将Test.getDebug()属性设置为true或使用--debug-jvm命令行选项.

启用测试调试后,Gradle将暂停测试过程并监听端口5005.

您还可以在DSL中启用调试,还可以在其中配置其他属性:

test {
    debugOptions {
        enabled = true
        port = 4455
        server = true
        suspend = true
    }
}

使用此配置,测试JVM的行为就像传递--debug-jvm参数时一样,但是它将侦听端口4455.

Using test fixtures

Producing and using test fixtures within a single project

测试装置通常用于设置被测代码,或提供旨在促进组件测试的实用程序. 除了javajava-library插件外,Java项目还可以通过应用java-test-fixtures插件来启用测试装置支持:

例子17.应用Java测试装置插件
lib/build.gradle
plugins {
    // A Java Library
    id 'java-library'
    // which produces test fixtures
    id 'java-test-fixtures'
    // and is published
    id 'maven-publish'
}
lib/build.gradle.kts
plugins {
    // A Java Library
    `java-library`
    // which produces test fixtures
    `java-test-fixtures`
    // and is published
    `maven-publish`
}

这将自动创建一个testFixtures源集,您可以在其中编写测试装置. 测试夹具的配置如下:

  • 他们可以看到主要的源集类

  • 测试源可以看到测试夹具

For example for this main class:

src/main/java/com/acme/Person.java
public class Person {
    private final String firstName;
    private final String lastName;

    public Person(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    // ...

测试夹具可以写在src/testFixtures/java

src/testFixtures/java/com/acme/Simpsons.java
public class Simpsons {
    private static final Person HOMER = new Person("Homer", "Simpson");
    private static final Person MARGE = new Person("Majorie", "Simpson");
    private static final Person BART = new Person("Bartholomew", "Simpson");
    private static final Person LISA = new Person("Elisabeth Marie", "Simpson");
    private static final Person MAGGIE = new Person("Margaret Eve", "Simpson");
    private static final List<Person> FAMILY = new ArrayList<Person>() {{
        add(HOMER);
        add(MARGE);
        add(BART);
        add(LISA);
        add(MAGGIE);
    }};

    public static Person homer() { return HOMER; }

    public static Person marge() { return MARGE; }

    public static Person bart() { return BART; }

    public static Person lisa() { return LISA; }

    public static Person maggie() { return MAGGIE; }

    // ...

Declaring dependencies of test fixtures

Java库插件类似,测试装置公开了API和实现配置:

例子18.声明测试夹具的依赖关系
lib/build.gradle
dependencies {
    testImplementation 'junit:junit:4.12'

    // API dependencies are visible to consumers when building
    testFixturesApi 'org.apache.commons:commons-lang3:3.9'

    // Implementation dependencies are not leaked to consumers when building
    testFixturesImplementation 'org.apache.commons:commons-text:1.6'
}
lib/build.gradle.kts
dependencies {
    testImplementation("junit:junit:4.12")

    // API dependencies are visible to consumers when building
    testFixturesApi("org.apache.commons:commons-lang3:3.9")

    // Implementation dependencies are not leaked to consumers when building
    testFixturesImplementation("org.apache.commons:commons-text:1.6")
}

值得注意的是,如果依赖性是测试夹具的实现依赖性,那么在编译依赖于这些测试夹具测试时 ,实现依赖性将不会泄漏到编译类路径中. 这样可以改善关注点分离并更好地避免编译.

Consuming test fixtures of another project

测试装置不限于单个项目. 通常,从属项目测试也需要依赖项的测试装置. 使用testFixtures关键字可以很容易地实现这testFixtures

例子19.添加对另一个项目的测试装置的依赖
build.gradle
dependencies {
    implementation(project(":lib"))

    testImplementation 'junit:junit:4.12'
    testImplementation(testFixtures(project(":lib")))
}
build.gradle.kts
dependencies {
    implementation(project(":lib"))

    testImplementation("junit:junit:4.12")
    testImplementation(testFixtures(project(":lib")))
}

Publishing test fixtures

使用java-test-fixtures插件的优点之一是发布了测试装置. 按照惯例,测试夹具将与具有test-fixtures分类器的工件一起发布. 对于Maven和Ivy,带有该分类器的工件都与常规工件一起简单发布. 但是,如果您使用maven-publishivy-publish插件,则测试夹具将作为其他变体发布在Gradle模块元数据中,并且您可以直接依赖另一个Gradle项目中外部库的测试夹具:

例子20.添加一个对外部库测试装置的依赖
build.gradle
dependencies {
    // Adds a dependency on the test fixtures of Gson, however this
    // project doesn't publish such a thing
    functionalTest testFixtures("com.google.code.gson:gson:2.8.5")
}
build.gradle.kts
dependencies {
    // Adds a dependency on the test fixtures of Gson, however this
    // project doesn't publish such a thing
    functionalTest(testFixtures("com.google.code.gson:gson:2.8.5"))
}

值得注意的是,如果外部项目发布Gradle模块元数据,则解析将失败,并显示一条错误消息,指出找不到这样的变体:

gradle dependencyInsight --configuration functionalTestClasspath --dependency gson输出gradle dependencyInsight --configuration functionalTestClasspath --dependency gson
> gradle dependencyInsight --configuration functionalTestClasspath --dependency gson

> Task :dependencyInsight
com.google.code.gson:gson:2.8.5 FAILED
   Failures:
      - Could not resolve com.google.code.gson:gson:2.8.5.
          - Unable to find a variant of com.google.code.gson:gson:2.8.5 providing the requested capability com.google.code.gson:gson-test-fixtures:
               - Variant compile provides com.google.code.gson:gson:2.8.5
               - Variant runtime provides com.google.code.gson:gson:2.8.5
               - Variant platform-compile provides com.google.code.gson:gson-derived-platform:2.8.5
               - Variant platform-runtime provides com.google.code.gson:gson-derived-platform:2.8.5
               - Variant enforced-platform-compile provides com.google.code.gson:gson-derived-enforced-platform:2.8.5
               - Variant enforced-platform-runtime provides com.google.code.gson:gson-derived-enforced-platform:2.8.5

com.google.code.gson:gson:2.8.5 FAILED
\--- functionalTestClasspath

A web-based, searchable dependency report is available by adding the --scan option.

BUILD SUCCESSFUL in 0s
1 actionable task: 1 executed

错误消息中提到缺少的com.google.code.gson:gson-test-fixtures功能,该功能实际上并未为此库定义. 这是因为按照惯例,对于使用java-test-fixtures插件的项目,Gradle会自动创建具有其名称为主要组件名称的功能的测试夹具变体,并带有附录-test-fixtures .

如果您发布库并使用测试夹具,但是不想发布夹具,则可以停用测试夹具变体的发布,如下所示.

例子21.禁用发布测试夹具变体
build.gradle
components.java.withVariantsFromConfiguration(configurations.testFixturesApiElements) { skip() }
components.java.withVariantsFromConfiguration(configurations.testFixturesRuntimeElements) { skip() }
build.gradle.kts
val javaComponent = components["java"] as AdhocComponentWithVariants
javaComponent.withVariantsFromConfiguration(configurations["testFixturesApiElements"]) { skip() }
javaComponent.withVariantsFromConfiguration(configurations["testFixturesRuntimeElements"]) { skip() }

1 . JUnit Wiki包含有关如何使用JUnit类别的详细说明: https : //github.com/junit-team/junit/wiki/Categories .
2 . TestNG文档包含有关测试组的更多详细信息: http : //testng.org/doc/documentation-main.html#test-groups .
3 . 当使用testng.xml文件时,TestNG文档包含有关测试排序的更多详细信息: http : testng.xml .