この記事では、(TDD - テスト駆動開発) アプローチを使用して、Gradleプラグインのための堅牢な開発基盤を構築する方法について探ります。 この方法により、私たちのコードが堅牢で保守しやすく、最初から正確に要件を満たすことが保証されます。

プロジェクトの初期化

Gradleはコマンドを使うことで新しいプラグインの作成を大いに容易にします。gradle init. "Gradle Plugin"をKotlinで作成することを選択すると、すぐに使えるプロジェクト構造が得られ、その中には2種類の重要なテストが含まれます:

  • 単体テスト :位置している`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. 第2サイクル 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. 機能テスト:統合の検証

ユニットのテストが終わった今、実際のビルドですべてが正しく連携することを確認する必要があります。これは機能テストの役割です。[something]における`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構成ファイルのパースなど、より複雑な機能を追加する際に大きな自信を与えてくれます。

この堅牢なテスト基盤は、プラグインの保守および将来の進化において最も貴重な資産です。

関連記事