En este artículo, vamos a explorar cómo establecer una base de desarrollo sólida para un plugin Gradle utilizando un enfoque de Desarrollo Guiado por Pruebas (TDD - Test-Driven Development). Este método nos asegura que nuestro código es robusto, mantenable y responde precisamente a los requisitos desde el principio.

La Inicialización del Proyecto

Gradle facilita mucho la creación de un nuevo plugin gracias al comando`gradle init`. Al elegir crear un "Gradle Plugin" con Kotlin, obtenemos una estructura de proyecto lista para usar, incluyendo dos tipos de pruebas cruciales :

  • Pruebas unitarias :Situados en`src/test`, permiten validar componentes aislados de nuestro plugin, como la lógica interna de una tarea o la configuración de una extensión. Son rápidos y no necesitan una ejecución completa de Gradle.

  • Pruebas funcionales:Situados en`src/functionalTest`, ellos utilizan`GradleRunner`Para ejecutar una compilación completa de Gradle en un proyecto de prueba temporal. Esto nos permite comprobar el comportamiento real del plugin en un entorno controlado.

Nuestro Primer Ciclo TDD: Registrar una tarea

Nuestra primera exigencia es simple: el plugin debe registrar una tarea llamada`printSiteConfig`.

2.1. El Test Primero (El Test que Falla)

Según el TDD, primero escribimos una prueba que verifica la existencia de esta tarea. En`SiteBakerPluginTest.kt`(nuestro archivo de prueba unitaria), añadimos:

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

Este test falla porque todavía no hemos escrito ningún código en nuestro plugin.

2.2. El Código Luego (Hacer Pasar la Prueba)

Ahora escribimos el mínimo de código necesario en`SiteBakerPlugin.kt`para que la prueba pase:

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

Reiniciamos las pruebas, y pasan. Nuestra primera funcionalidad está validada.

3. Segundo Ciclo TDD : Agregar una Extensión DSL

La siguiente exigencia es permitir a los usuarios configurar nuestro plugin a través de un bloque DSL en su`build.gradle.kts`. Queremos un bloque`site { …​ }`donde se puede especificar una ruta de configuración.

3.1. Primero el test

Agregamos una prueba para verificar que la extensión`site`está bien registrada y se le puede asignar un valor.

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

Esta prueba falla porque ni la clase`SiteExtension`ni el registro de la extensión existen

3.2. El código luego

Creamos la clase`SiteBakerExtension.kt`y actualizamos`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
            }
        }
    }
}

Las pruebas pasan de nuevo.

4. La prueba funcional: validar la integración

Ahora que las unidades están probadas, debemos asegurarnos de que todo funcione junto en una compilación real. Éste es el papel de la prueba funcional en`SiteBakerPluginFunctionalTest.kt`.

4.1. La prueba de integración: crear un entorno controlado

Este test simulará un proyecto real utilizando nuestro plugin. Para que sea fiable, debe serhermético, es decir, no debe depender de archivos existentes en el sistema. Debe crear por sí mismo todas las condiciones necesarias para su ejecución.

El test, pues:

  1. Crear un`build.gradle.kts`de prueba que utiliza nuestro plugin y su DSL.

  2. Crear el archivo de configuración(managed-jbake-context.yml) que el plugin debe leer. Es un paso crucial para la robustez de la prueba.

  3. Ejecutar la tarea`printSiteConfig`via`GradleRunner`.

  4. Comprobar que la salida de la compilación es correcta.

Al copiar o crear este archivo de configuración en cada ejecución, nos aseguramos que la prueba estáreproducibley no depende de un estado externo. Esto asegura nuestras pruebas contra regresiones que podrían estar relacionadas con la lectura del archivo.

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

Este test fallará mientras la tarea`printSiteConfig`No utilizará realmente el valor de la extensión.

4.2. Finalizar la lógica

Estamos actualizando la tarea en`SiteBakerPlugin.kt`para que muestre el valor configurado :

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

Todas las pruebas, unitarias y funcionales, pasan ahora.

Conclusión

Siguiendo un enfoque TDD, hemos construido un plugin de forma incremental y segura. Cada pequeña funcionalidad se valida inmediatamente mediante una prueba, desde pruebas unitarias rápidas hasta pruebas funcionales que validan la integración completa.

Al asegurarnos de que nuestras pruebas funcionenherméticos— especialmente al crear mediante programación los archivos de configuración necesarios — estamos construyendo una red de seguridad extremadamente robusta. Esta rigurosidad nos protege eficazmente contra las regresiones y nos brinda una gran confianza para agregar funcionalidades más complejas más adelante, como el análisis del archivo de configuración YAML.

Esta base de pruebas sólidas es el activo más valioso para el mantenimiento y la evolución futura del plugin.

Articles connexes