В этой статье мы рассмотрим, как создать надёжную основу разработки для плагина Gradle, используя подход разработки, управляемой тестами (TDD — Test-Driven Development). Этот метод гарантирует, что наш код надёжен, поддерживаемый и точно соответствует требованиям с самого начала.

Инициализация проекта

Gradle значительно упрощает создание нового плагина с помощью команды`gradle init`. Выбирая создать "Gradle Plugin" на Kotlin, мы получаем готовую структуру проекта, включающую два важных типа тестов:

  • Юнит-тесты:Расположены в`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. Второй цикл 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. Функциональное тестирование: Проверить интеграцию

Теперь, когда модули протестированы, нам нужно убедиться, что всё работает вместе в реальной сборке. Это роль функционального теста в`SiteBakerPluginFunctionalTest.kt`.

4.1. Тест интеграции : создать контролируемую среду

Этот тест будет моделировать настоящий проект, использующий наш плагин. Чтобы он был надежным, он должен бытьгерметичный, то есть он не должен зависеть от существующих файлов в системе. Он должен сам создать все необходимые условия для своего выполнения.

Таким образом, тест:

  1. Создать`build.gradle.kts`теста, который использует наш плагин и его DSL.

  2. Создать файл конфигурации(managed-jbake-context.yml) что плагин должен читать. Это важный этап для надежности теста.

  3. Выполнить задачу`printSiteConfig`через`GradleRunner`.

  4. Проверьте, что вывод сборки корректен.

Копируя или создавая этот файл конфигурации при каждом запуске, мы гарантируем, что тествоспроизводимыйet не зависит от внешнего состояния. Это защищает наши тесты от регрессий, которые могут быть связаны с чтением файла.

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

Все тесты, как unit, так и functional, теперь проходят.

Вывод

Следуя подходу TDD, мы построили плагин инкрементально и безопасно. Каждая небольшая функция сразу проверяется тестом, от быстрых юнит-тестов до функциональных тестов, подтверждающих полную интеграцию.

Старавшись сделать наши тесты функциональнымигерметичные— особенно программно создавая необходимые файлы конфигурации — мы строим чрезвычайно надежный защитный механизм. Эта строгость эффективно защищает нас от регрессий и дает нам большую уверенность для добавления более сложных функций позже, таких как анализ YAML-файла конфигурации.

Эта прочная база тестов является самым ценным активом для обслуживания и будущего развития плагина.

Похожие статьи