Neste artigo, vamos explorar como montar uma base de desenvolvimento sólida para um plugin Gradle usando uma abordagem de Desenvolvimento Orientado por Testes (TDD - Test‑Driven Development). Este método garante que nosso código seja robusto, manutenível e atenda exatamente aos requisitos desde o início.

A Inicialização do Projeto

Gradle facilita muito a criação de um novo plugin graças ao comando`gradle init`. Ao escolher criar um 'Gradle Plugin' com Kotlin, obtemos uma estrutura de projeto pronta para uso, incluindo dois tipos de testes cruciais :

  • Testes Unitários :Localizados em`src/test`, eles permitem validar componentes isolados do nosso plugin, como a lógica interna de uma tarefa ou a configuração de uma extensão. Eles são rápidos e não precisam de uma execução completa do Gradle.

  • Testes Funcionais :Situados em`src/functionalTest`, eles usam`GradleRunner`para executar uma compilação Gradle completa em um projeto de teste temporário. Isso nos permite verificar o comportamento real do plugin em um ambiente controlado.

2. Nosso Primeiro Ciclo TDD : Registrar uma Tarefa

Nossa primeira exigência é simples: o plugin deve registrar uma tarefa nomeada`printSiteConfig`.

2.1. O teste primeiro (O teste que falha)

Conforme o TDD, escrevemos primeiro um teste que verifica a existência dessa tarefa. Dentro`SiteBakerPluginTest.kt`(nosso arquivo de teste unitário), adicionamos:

@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 teste falha porque ainda não escrevemos nenhum código em nosso plugin.

2.2. O Código Depois (Fazer Passar o Teste)

Agora, estamos escrevendo o mínimo de código necessário em`SiteBakerPlugin.kt`para que o teste passe :

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

Nós reiniciamos os testes, e eles passam. Nossa primeira funcionalidade está validada.

3. Segundo Ciclo TDD : Adicionar uma Extensão DSL

O requisito seguinte é permitir que os usuários configurem nosso plugin por meio de um bloco DSL em seu`build.gradle.kts`. Nós queremos um bloco`site { …​ }`onde se pode especificar um caminho de configuração.

3.1. O Teste Primeiro

Adicionamos um teste para verificar que a extensão`site`é bem registrada e pode-se atribuir um valor a ela.

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

Este teste falha porque nem a classe`SiteExtension`nem o registro da extensão existem

3.2. O Código Depois

Nós criamos a classe`SiteBakerExtension.kt`e atualizamos`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
            }
        }
    }
}

Os testes passam novamente.

4. O Teste Funcional: Validar a Integração

Agora que as unidades estão testadas, precisamos garantir que tudo funcione junto em uma build real. Essa é a função do teste funcional em`SiteBakerPluginFunctionalTest.kt`.

4.1. O Teste de Integração : Criar um Ambiente Controlado

Este teste vai simular um projeto real usando nosso plugin. Para que seja confiável, ele deve serhermético, ou seja, ele não deve depender de arquivos existentes no sistema. Ele deve criar por si mesmo todas as condições necessárias à sua execução.

O teste vai portanto :

  1. Criar um`build.gradle.kts`de teste que utiliza nosso plugin e seu DSL.

  2. Criar o arquivo de configuração(managed-jbake-context.yml) que o plugin deve ler. É uma etapa crucial para a robustez do teste.

  3. Executar a tarefa`printSiteConfig`via`GradleRunner`.

  4. Verificar que a saída da build está correta.

Ao copiar ou criar este arquivo de configuração em cada execução, garantimos que o teste estejareprodutívele não depende de um estado externo. Isso protege nossos testes contra regressões que poderiam estar relacionadas à leitura do arquivo.

@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 teste falhará enquanto a tarefa`printSiteConfig`Não usará realmente o valor da extensão.

4.2. Finalizar a Lógica

Estamos atualizando a tarefa em`SiteBakerPlugin.kt`para que ela exiba o valor configurado:

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

Todos os testes, unitários e funcionais, agora passam.

Conclusão

Seguindo uma abordagem TDD, construímos um plugin de forma incremental e segura. Cada pequena funcionalidade é imediatamente validada por um teste, desde testes unitários rápidos até testes funcionais que validam a integração completa.

Ao cuidar de tornar nossos testes funcionaisherméticos— notadamente ao criar programaticamente os arquivos de configuração necessários — construímos uma rede de segurança extremamente robusta. Essa rigidez nos protege eficazmente contra as regressões e nos dá muita confiança para adicionar funcionalidades mais complexas posteriormente, como a análise do arquivo de configuração YAML.

Esta base de testes sólidos é o ativo mais valioso para a manutenção e evolução futura do plugin.

Articles connexes