在本文中,我们将探讨如何使用测试驱动开发(TDD - Test-Driven Development)的方法为 Gradle 插件建立一个坚实的开发基础。 这种方法确保我们的代码是健壮、可维护的,并且能够从一开始就精确满足需求。

项目初始化

Gradle 通过命令大大简化了创建新插件的过程。gradle init. 选择使用 Kotlin 创建一个 "Gradle Plugin",我们将得到一个即用型项目结构,其中包含两种关键的测试类型:

  • 单元测试 :位于`src/test`, 它们允许验证我们插件的隔离组件,例如任务的内部逻辑或扩展的配置。它们速度快,不需要完整的 Gradle 执行。

  • 功能测试:位于`src/functionalTest`, 他们使用`GradleRunner`为了在临时测试项目中执行完整的Gradle构建。这使我们能够在受控环境中验证插件的实际行为。

2. 我们的首个 TDD 周期:记录一项任务

我们的第一个要求很简单:插件必须记录一个已命名的任务。printSiteConfig。

2.1. 首先测试(失败的测试)

根据 TDD,我们首先编写一个测试来验证该任务的存在。在`SiteBakerPluginTest.kt`(我们的单元测试文件),我们添加:

@Test
fun `plugin registers task`() {
    // Créer un projet de test en mémoire
    val project = ProjectBuilder.builder().build()
    project.plugins.apply("com.cheroliv.site-baker")

    // Vérifier que la tâche a bien été enregistrée
    assertNotNull(project.tasks.findByName("printSiteConfig"))
}

此测试失败,因为我们尚未在插件中编写任何代码。

2.2. 代码随后(使测试通过)

现在,我们写所需的最少代码在`SiteBakerPlugin.kt`让测试通过:

class SiteBakerPlugin: Plugin<Project> {
    override fun apply(project: Project) {
        // Enregistrer une tâche simple
        project.tasks.register("printSiteConfig") { task ->
            // ... la logique de la tâche viendra plus tard
        }
    }
}

我们重新运行测试,它们通过了。我们的第一个功能已被验证。

3. TDD 第二阶段:添加 DSL 扩展

接下来的要求是允许用户通过一个 DSL 块在其配置我们的插件`build.gradle.kts`. 我们想要一个块`site { …​ }`可以指定配置路径的地方。

3.1. 首先测试

我们添加一个测试来验证扩展`site`它已正确记录,且可为其赋予一个值。

@Test
fun `plugin registers extension`() {
    val project = ProjectBuilder.builder().build()
    project.plugins.apply("com.cheroliv.site-baker")

    // Récupérer l'extension et lui affecter une valeur
    project.extensions
        .findByType(SiteExtension::class.java)!!
        .configPath
        .set("config.yml")

    // Vérifier que la valeur a bien été prise en compte
    assertEquals(
        "config.yml",
        project.extensions.findByType(SiteExtension::class.java)?.configPath?.get()
    )
}

此测试失败,因为既不该类`SiteExtension`也不存在扩展的注册

3.2. 接下来的代码

我们正在创建该类`SiteBakerExtension.kt`和我们更新`SiteBakerPlugin.kt`:

// SiteBakerExtension.kt
open class SiteExtension @Inject constructor(objects: ObjectFactory) {
    val configPath: Property<String> = objects.property(String::class.java)
}

// SiteBakerPlugin.kt
class SiteBakerPlugin: Plugin<Project> {
    override fun apply(project: Project) {
        // Enregistrer l'extension
        val extension = project.extensions.create("site", SiteExtension::class.java)

        project.tasks.register("printSiteConfig") { task ->
            task.doLast {
                // On utilisera l'extension plus tard
            }
        }
    }
}

�测试再次通过。

4. 功能测试:验证集成

既然单元已经测试完毕,我们必须确保它们在真实构建中能够协同工作。这就是功能测试的作用在`SiteBakerPluginFunctionalTest.kt`。

4.1. 集成测试:创建受控环境

这个测试将模拟使用我们插件的真实项目。为了让它可靠,它必须密封的, 换句话说,它不应依赖于系统上已存在的文件。它必须自己创建执行所需的所有条件。

�测试将因此 :

  1. 创建一个`build.gradle.kts`使用我们的插件及其DSL的测试

  2. 创建配置文件(managed-jbake-context.yml) 插件应该读取的内容。这是测试稳健性的一步关键步骤。

  3. 执行任务`printSiteConfig`经过`GradleRunner`.

  4. � 检查构建输出是否正确。

通过在每次执行时复制或创建此配置文件,我们确保测试是可重复并且不依赖于外部状态。这可以保护我们的测试免受与文件读取相关的回归问题的影响。

@Test fun `can run task with DSL`() {
    // 1. Créer un build.gradle.kts de test
    buildFile.writeText("""
        plugins { id("com.cheroliv.site-baker") }
        site { configPath = "managed-jbake-context.yml" }
    """.trimIndent())

    // 2. Créer le fichier de configuration pour un test contrôlé
    val configFile = File(projectDir, "managed-jbake-context.yml")
    configFile.writeText("site: { title: 'Mon Site de Test' }") // Contenu YAML simple

    // 3. Exécuter la build
    val runner = GradleRunner.create()
    runner.withPluginClasspath()
    runner.withArguments("printSiteConfig")
    runner.withProjectDir(projectDir) // Spécifier le répertoire du projet de test
    val result = runner.build()

    // 4. Vérifier la sortie
    assertTrue(result.output.contains("Site config path: managed-jbake-context.yml"))
}

此测试将在任务未完成时一直失败`printSiteConfig`它不会真的使用扩展的值。

4.2. 完成逻辑

我们正在更新任务在`SiteBakerPlugin.kt`为了使它显示配置的值 :

project.tasks.register("printSiteConfig") { task ->
    task.doLast {
        println("Site config path: ${extension.configPath.get()}")
    }
}

所有测试,单元测试和功能测试,现在都通过了。

结论

采用TDD方法,我们以增量且安全的方式构建了一个插件。每个小功能都会立即通过测试进行验证,从快速的单元测试到验证完整集成的功能测试。

通过确保我们的测试是功能性的密封的— 例如,通过以编程方式创建必要的配置文件 — — 我们构建了一个极其健壮的安全网。这种严谨性能够有效防止回归,并使我们有足够信心随后添加更复杂的功能,如解析 YAML 配置文件。

这个坚实的测试基础是维护和插件未来发展最宝贵的资产。

相关文章