Artikel 2 : Entwickeln Sie ein Gradle-Plugin mit einem TDD-Ansatz
Publié le 24 September 2025
In diesem Artikel werden wir untersuchen, wie wir eine solide Entwicklungsbasis für ein Gradle-Plugin mit einem Testgetriebenen Entwicklungsansatz (TDD - Test-Driven Development) einrichten können. Diese Methode stellt sicher, dass unser Code robust, wartbar und genau den Anforderungen entspricht, von Anfang an.
Die Initialisierung des Projekts
Gradle erleichtert die Erstellung eines neuen Plugins dank des Befehls erheblich.gradle init. Indem wir ein "Gradle Plugin" mit Kotlin erstellen, erhalten wir eine fertig einsatzbereite Projektstruktur, die zwei entscheidende Testtypen enthält:
-
Unit-Tests :Befinden sich in`src/test`, sie ermöglichen es, isolierte Komponenten unseres Plugins zu validieren, wie die interne Logik einer Aufgabe oder die Konfiguration einer Erweiterung. Sie sind schnell und benötigen keine vollständige Ausführung von Gradle.
-
Funktionstests:In … gelegen`src/functionalTest`, sie verwenden`GradleRunner`um eine vollständige Gradle-Build in einem temporären Testprojekt auszuführen. Dadurch können wir das tatsächliche Verhalten des Plugins in einer kontrollierten Umgebung überprüfen.
2. Unser erster TDD-Zyklus: Eine Aufgabe aufzeichnen
Unsere erste Anforderung ist einfach: Das Plugin muss eine benannte Aufgabe registrieren.printSiteConfig.
2.1. Der Test zuerst (Der Test, der fehlschlägt)
Gemäß dem TDD schreiben wir zunächst einen Test, der die Existenz dieser Aufgabe überprüft. In`SiteBakerPluginTest.kt`(unsere Unit-Test-Datei), wir fügen hinzu:
@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"))
}
Dieser Test schlägt fehl, weil wir noch keinen Code in unserem Plugin geschrieben haben.
2.2. Der Code danach (Den Test bestehen)
Jetzt schreiben wir den notwendigen Minimalcode in`SiteBakerPlugin.kt`damit der Test bestanden werde:
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
}
}
}
Wir führen die Tests erneut aus, und sie bestehen. Unsere erste Funktionalität ist bestätigt.
3. Zweiter TDD-Zyklus: Eine DSL-Erweiterung hinzufügen
Die folgende Anforderung besteht darin, den Benutzern zu ermöglichen, unser Plugin über einen DSL-Block in ihrem zu konfigurieren`build.gradle.kts`. Wir wollen einen Block`site { … }`wo man einen Konfigurationspfad angeben kann
3.1. Der Test zuerst
Wir fügen einen Test hinzu, um zu überprüfen, dass die Erweiterung`site`ist korrekt gespeichert und man kann ihr einen Wert zuweisen.
@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()
)
}
Dieser Test schlägt fehl denn weder die Klasse`SiteExtension`weder die Registrierung der Erweiterung existiert.
3.2. Der Code anschließend
Wir erstellen die Klasse`SiteBakerExtension.kt`und aktualisieren`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
}
}
}
}
Die Tests bestehen erneut.
4. Der Funktionstest: Die Integration validieren
Jetzt, da die Einheiten getestet sind, müssen wir sicherstellen, dass alles zusammen in einer echten Build funktioniert. Das ist die Rolle des funktionalen Tests in`SiteBakerPluginFunctionalTest.kt`.
4.1. Integrations-Test: Erstellen einer kontrollierten Umgebung
Dieser Test wird ein echtes Projekt mit unserem Plugin simulieren. Damit er zuverlässig ist, muss erhermetisch, das heißt er darf nicht von vorhandenen Dateien auf dem System abhängen. Er muss selbst alle notwendigen Voraussetzungen für seine Ausführung schaffen.
Der Test wird also:
-
Erstellen eines`build.gradle.kts`ein Test, der unser Plugin und sein DSL verwendet.
-
Erstellen Sie die Konfigurationsdatei(
managed-jbake-context.yml) das Plugin soll lesen. Das ist ein entscheidender Schritt für die Robustheit des Tests. -
Die Aufgabe ausführen`printSiteConfig`über`GradleRunner`.
-
Überprüfen Sie, dass die Build-Ausgabe korrekt ist.
Durch Kopieren oder Erstellen dieser Konfigurationsdatei bei jeder Ausführung, stellen wir sicher, dass der Test istreproduzierbarUnd ist nicht von einem externen Zustand abhängig. Das sichert unsere Tests gegen Regressionen, die durch das Lesen der Datei entstehen könnten.
@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"))
}
Dieser Test wird so lange fehlschlagen, bis die Aufgabe`printSiteConfig`wird den Wert der Erweiterung nicht wirklich verwenden.
4.2. Die Logik abschließen
Wir aktualisieren die Aufgabe im`SiteBakerPlugin.kt`damit sie den konfigurierten Wert anzeigt :
project.tasks.register("printSiteConfig") { task ->
task.doLast {
println("Site config path: ${extension.configPath.get()}")
}
}
Alle Tests, sowohl unitäre als auch funktionelle, bestehen jetzt.
Fazit
Indem wir einen TDD-Ansatz verfolgten, haben wir ein Plugin inkrementell und sicher gebaut. Jede kleine Funktionalität wird sofort durch einen Test validiert, von schnellen Unit-Tests bis zu Funktionalitätstests, die die vollständige Integration bestätigen.
Indem wir darauf achten, unsere Tests funktional zu machen.hermetisch— insbesondere durch programmatisches Erstellen der erforderlichen Konfigurationsdateien — bauen wir ein äußerst robustes Sicherheitsnetz auf. Diese Strenge schützt uns wirksam vor Regressionsfehlern und gibt uns viel Vertrauen, später komplexere Funktionen hinzuzufügen, wie das Parsen der YAML-Konfigurationsdatei.
Diese solide Testbasis ist der wertvollste Vermögenswert für die Wartung und die zukünftige Entwicklung des Plugins.