In dit artikel gaan we onderzoeken hoe je een solide ontwikkelingsbasis voor een Gradle-plugin kunt opzetten met behulp van een Test-Driven Development (TDD - Test-Driven Development) benadering. Deze methode garandeert dat onze code robuust, onderhoudbaar is en precies voldoet aan de eisen vanaf het begin.

1. Initialisatie van het Project

Gradle vergemakkelijkt het maken van een nieuwe plug‑in enorm dankzij de opdracht`gradle init`. Door te kiezen voor het maken van een "Gradle Plugin" met Kotlin, krijgen we een kant-en-klare projectstructuur, inclusief twee cruciale soorten tests :

  • Eenheidstests:Gelegen in`src/test`, ze stellen in staat om geïsoleerde componenten van onze plugin te valideren, zoals de interne logica van een taak of de configuratie van een extensie. Ze zijn snel en hebben geen volledige Gradle-uitvoering nodig.

  • Functionele tests:Gevestigd in`src/functionalTest`, zij gebruiken`GradleRunner`om een volledige Gradle-build uit te voeren in een tijdelijk testproject. Dit stelt ons in staat om het daadwerkelijke gedrag van de plug-in in een gecontroleerde omgeving te verifiëren.

2. Onze Eerste TDD-cyclus: Een taak registreren

Onze eerste vereiste is eenvoudig: de plugin moet een genoemde taak registreren`printSiteConfig`.

2.1. Test eerst (De test die mislukt)

Overeenkomstig TDD schrijven we eerst een test die controleert of deze taak bestaat. In`SiteBakerPluginTest.kt`(onze eenheidstestbestand), voegen we toe:

@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"))
}

Deze test faalt, omdat we nog geen code hebben geschreven in onze plugin.

2.2. De Code Daarna (De Test Uitvoeren)

Nu schrijven we het minimale noodzakelijke code in`SiteBakerPlugin.kt`zodat de test slaagt:

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
        }
    }
}

We herstarten de tests, en ze slagen. Onze eerste functie is gevalideerd.

3. Tweede TDD-cyclus: Een DSL-extensie toevoegen

De volgende vereiste is om gebruikers de mogelijkheid te geven onze plug-in te configureren via een DSL-blok in hun`build.gradle.kts`. Wij willen een blok`site { …​ }`waar u een configuratiepad kunt opgeven

3.1. De eerste test

We voegen een test toe om te controleren dat de extensie`site`is goed geregistreerd en er kan een waarde aan worden toegekend.

@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()
    )
}

Deze test faalt omdat noch de klasse`SiteExtension`noch bestaat de registratie van de extensie.

3.2. De volgende code

We maken de klasse`SiteBakerExtension.kt`en updaten`SiteBakerPlugin.kt`(outputs nothing)

// 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
            }
        }
    }
}

De tests slagen weer.

4. De functionele test: valideren van de integratie

Nu de eenheden zijn getest, moeten we ervoor zorgen dat alles samenwerkt in een echte build. Dit is de rol van functioneel testen in`SiteBakerPluginFunctionalTest.kt`.

4.1. De integratietest: Een gecontroleerde omgeving creëren

Deze test zal een echt project simuleren met onze plugin. Voor het betrouwbaar te zijn, moet het zijnhermetisch, dit betekent dat het niet afhankelijk mag zijn van bestaande bestanden op het systeem. Het moet zelf alle benodigde voorwaarden voor zijn uitvoering creëren.

De test zal dus :

  1. Een maken`build.gradle.kts`een test die onze plugin en zijn DSL gebruikt.

  2. Maak het configuratiebestand(managed-jbake-context.yml) dat de plugin zou moeten lezen. Dit is een cruciale stap voor de robuustheid van de test.

  3. Voer de taak uit`printSiteConfig`via`GradleRunner`.

  4. Controleer dat de uitvoer van de build correct is.

Door dit configuratiebestand bij elke uitvoering te kopiëren of aan te maken, zorgen we ervoor dat de test isreproduceerbaaren hangt niet af van een externe staat. Dit beveiligt onze tests tegen regressies die mogelijk gerelateerd zijn aan het lezen van het bestand.

@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"))
}

Dit test zal falen zolang de taak`printSiteConfig`Het zal de waarde van de extensie niet daadwerkelijk gebruiken.

4.2. De logica afronden

We zijn de taak aan het bijwerken in`SiteBakerPlugin.kt`zodaat het de geconfigureerde waarde weergeeft :

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

Alle tests, zowel eenheidstests als functionele tests, slagen nu.

Conclusie

Door een TDD-aanpak te volgen, hebben we een plugin op een incrementele en veilige manier gebouwd. Elke kleine functionaliteit wordt onmiddellijk gevalideerd door een test, van snelle unittests tot functionele tests die de volledige integratie valideren.

Door ervoor te zorgen dat onze tests functioneel zijnhermetisch— met name door programmeerbaar de benodigde configuratiebestanden te maken — bouwen we een extreem robuust veiligheidsnet op. Deze strengheid beschermt ons effectief tegen regressies en geeft ons veel vertrouwen om later meer complexe functies toe te voegen, zoals het parsen van het YAML-configuratiebestand.

Deze stevige testbasis is het meest waardevolle bezit voor het onderhoud en de toekomstige ontwikkeling van de plugin.

Gerelateerde artikelen