在原生生态系统中进行测试是一个丰富的主题。存在许多不同的测试库和框架,以及许多不同类型的测试。所有这些都需要成为构建的一部分,无论它们是频繁执行还是不频繁执行。本章专门解释 Gradle 如何处理构建之间以及构建内部的不同要求,并重点介绍它如何在 macOS 和 Linux 上与 XCTest 集成。

本章解释: - 如何控制测试运行(测试执行) - 如何选择特定的测试运行(测试过滤) - 生成哪些测试报告以及如何影响该过程(测试报告) - Gradle 如何发现要运行的测试(测试检测)

但首先,我们来看一下 Gradle 中原生测试的基础知识。

基础知识

Gradle 支持与 Swift 语言的 XCTest 测试框架深度集成,并围绕 XCTest 任务类型展开。该任务类型使用 macOS 上的 Xcode XCTest 或 Linux 上的开源 Swift 核心库替代方案运行一系列测试用例,并整理结果。然后,您可以通过 TestReport 任务类型的实例将这些结果转换为报告。

为了运行,XCTest 任务类型需要三项信息: - 构建好的可测试 bundle(在 macOS 上)或可执行文件(在 Linux 上)的位置(属性:XCTest.getTestInstalledDirectory()) - 执行 bundle 或可执行文件的运行脚本(属性:XCTest.getRunScriptFile()) - 执行 bundle 或可执行文件的工作目录(属性:XCTest.getWorkingDirectory()

当您使用 XCTest 插件时,您将自动获得以下内容: - 一个专用的 xctest 扩展,类型为 SwiftXCTestSuite,用于配置测试组件及其变体 - 一个类型为 XCTestxcTest 任务,用于运行这些单元测试 - 一个与主组件对象文件链接的可测试 bundle 或可执行文件

测试插件会适当地配置所需的信息。此外,它们还会将 xcTestrun 任务关联到 check 生命周期任务。它还会创建 testImplementation 依赖项配置。仅用于测试编译、链接和运行时的依赖项可以添加到此配置中。xctest 脚本块的行为类似于 applicationlibrary 脚本块。

The XCTest 任务有许多配置选项。本章的其余部分将详细介绍其中的许多选项。

测试执行

Gradle 在一个单独的(“forked”)进程中执行测试。

您可以通过 XCTest 任务上的多个属性控制测试进程的启动方式,包括以下属性:

ignoreFailures - 默认值:false

如果此属性为 true,即使某些测试失败,Gradle 也会在测试完成后继续进行项目构建。请注意,默认情况下,无论此设置如何,这两种任务类型都会执行检测到的所有测试。

testLogging - 默认值:未设置

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

有关所有可用配置选项的详细信息,请参阅 XCTest

测试过滤

运行测试套件的子集是一个常见的需求,例如在修复 bug 或开发新的测试用例时。Gradle 提供了过滤功能来实现这一点。您可以根据以下条件选择要运行的测试:

  • 简单的类名或方法名,例如 SomeTest, SomeTest.someMethod

  • “*” 通配符匹配

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

build.gradle.kts
xctest {
    binaries.configureEach {
        runTask.get().filter.includeTestsMatching("SomeIntegTest.*") // or `"Testing.SomeIntegTest.*"` on macOS
    }
}
build.gradle
xctest {
    binaries.configureEach {
        runTask.get().configure {
            // include all tests from test class
            filter.includeTestsMatching "SomeIntegTest.*" // or `"Testing.SomeIntegTest.*"` on macOS
        }
    }
}

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

命令行选项对于执行单个测试方法特别有用。也可以提供多个 --tests 选项,所有这些选项的模式都将生效。以下部分包含几个使用命令行选项的示例。

目前的测试过滤仅支持 XCTest 兼容的过滤器。这意味着相同的过滤器在 macOS 和 Linux 之间会有所不同。在 macOS 上,需要在过滤器前加上 bundle 的基本名称,例如 TestBundle.SomeTest, TestBundle.SomeTest.someMethod。有关有效过滤模式的更多信息,请参阅下面的简单名称模式部分。

以下部分介绍了简单的类名/方法名的特定情况。

简单名称模式

Gradle 支持简单的类名过滤,或类名 + 方法名过滤。例如,以下命令行运行 SomeTestClass 测试用例中的所有测试或仅运行其中一个测试:

# Executes all tests in SomeTestClass
gradle xcTest --tests SomeTestClass
# or `gradle xcTest --tests TestBundle.SomeTestClass` on macOS

# Executes a single specified test in SomeTestClass
gradle xcTest --tests TestBundle.SomeTestClass.someSpecificMethod
# or `gradle xcTest --tests TestBundle.SomeTestClass.someSpecificMethod` on macOS

您还可以将命令行中定义的过滤器与持续构建结合使用,以便在生产或测试源文件发生任何更改后立即重新执行一部分测试。当更改触发测试运行时,以下命令将执行 'SomeTestClass' 测试类中的所有测试:

gradle test --continuous --tests SomeTestClass

测试报告

The XCTest 任务默认生成以下结果:

  • HTML 测试报告

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

  • 结果的高效二进制格式,由 XCTest 任务用于生成其他格式

在大多数情况下,您将使用标准的 HTML 报告,该报告自动包含您的 XCTest 任务的结果。

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

buildSrc/src/main/kotlin/myproject.xctest-conventions.gradle.kts
plugins {
    id("xctest")
}

extensions.configure<SwiftXCTestSuite>() {
    binaries.configureEach {
        // Disable the test report for the individual test task
        runTask.get().reports.html.required = false
    }
}

configurations.create("binaryTestResultsElements") {
    isCanBeResolved = false
    isCanBeConsumed = true
    attributes {
        attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
        attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named("test-report-data"))
    }
    tasks.withType<XCTest>() {
        outgoing.artifact(binaryResultsDirectory)
    }
}
build.gradle.kts
plugins {
    `reporting-base`
}

val testReportData by configurations.creating {
    isCanBeConsumed = false
    attributes {
        attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category.DOCUMENTATION))
        attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named("test-report-data"))
    }
}

dependencies {
    testReportData(project(":core"))
    testReportData(project(":util"))
}

tasks.register<TestReport>("testReport") {
    destinationDirectory = reporting.baseDirectory.dir("allTests")
    // Use test results from testReportData configuration
    testResults.from(testReportData)
}
buildSrc/src/main/groovy/myproject.xctest-conventions.gradle
plugins {
    id 'xctest'
}

xctest {
    binaries.configureEach {
        runTask.get().configure {
            // Disable the test report for the individual test task
            reports.html.required = false
        }
    }
}

// Share the test report data to be aggregated for the whole project
configurations {
    binaryTestResultsElements {
        canBeResolved = false
        attributes {
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION))
            attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'test-report-data'))
        }
        tasks.withType(XCTest).configureEach {
            outgoing.artifact(it.binaryResultsDirectory)
        }
    }
}
build.gradle
// A resolvable configuration to collect test reports data
plugins {
    id 'reporting-base'
}

configurations {
    testReportData {
        canBeConsumed = false
        attributes {
            attribute(Category.CATEGORY_ATTRIBUTE, objects.named(Category, Category.DOCUMENTATION))
            attribute(DocsType.DOCS_TYPE_ATTRIBUTE, objects.named(DocsType, 'test-report-data'))
        }
    }
}

dependencies {
    testReportData project(':core')
    testReportData project(':util')
}

tasks.register('testReport', TestReport) {
    destinationDirectory = reporting.baseDirectory.dir('allTests')
    // Use test results from testReportData configuration
    testResults.from(configurations.testReportData)
}

在此示例中,我们使用约定插件 myproject.xctest-conventions 将项目的测试结果暴露给 Gradle 的变体感知依赖项管理引擎

该插件声明了一个可消费的 binaryTestResultsElements 配置,该配置表示 test 任务的二进制测试结果。在聚合项目的构建文件中,我们声明 testReportData 配置并依赖于所有我们想要聚合结果的项目。Gradle 将自动从每个子项目中选择二进制测试结果变体,而不是项目的 jar 文件。最后,我们添加一个 testReport 任务,该任务聚合来自 testResultsDirs 属性的测试结果,该属性包含从 testReportData 配置解析的所有二进制测试结果。

您应该注意,TestReport 类型合并了来自多个测试任务的结果,并且需要聚合单个测试类的结果。这意味着如果一个给定的测试类由多个测试任务执行,那么测试报告将包含该类的执行记录,但很难区分该类的单独执行及其输出。