이 글에서는 테스트 주도 개발(TDD) 접근법을 사용해 Gradle 플러그인을 위한 견고한 개발 기반을 구축하는 방법을 탐구해 보겠습니다. 이 방법은 우리 코드가 처음부터 견고하고 유지 가능하며 요구 사항을 정확히 충족하도록 보장합니다.

1. 프로젝트 초기화

Gradle은 명령을 통해 새로운 플러그인 생성을 매우 쉽게 합니다`gradle init`. Kotlin을 사용하여 Gradle 플러그인을 만들기로 선택하면, 바로 사용할 수 있는 프로젝트 구조를 얻을 수 있으며, 여기에는 두 가지 중요한 유형의 테스트가 포함됩니다 :

  • 단위 테스트 :에 위치한`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. 빌드 출력이 올바른지 확인하세요.

이 구성 파일을 복사하거나 만들 때마다 실행함으로써, 우리는 테스트가 보장됨을 확신합니다.재현 가능한그리고 외부 상태에 의존하지 않습니다. 이는 파일을 읽는 것과 관련될 수 있는 회귀로부터 우리의 테스트를 보호합니다.

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

모든 테스트, 단위 및 기능 테스트, 이제는 통과합니다.

결론

TDD 방식을 따라 점진적이고 안전하게 플러그인을 구축했습니다. 작은 기능 하나하나를 테스트로 즉시 검증하며, 빠른 단위 테스트부터 완전한 통합을 검증하는 기능 테스트까지 수행합니다.

테스트가 정상적으로 작동하도록 주의하면서밀폐된— 필요에 따라 프로그래밍 방식으로 필요한 구성 파일을 생성함으로써 — 우리는 매우 견고한 안전망을 구축합니다. 이 엄격함은 회귀에 효과적으로 대처하고 향후 더 복잡한 기능을 추가할 수 있는 큰 자신감을 줍니다. 예를 들어 YAML 구성 파일 파싱과 같이.

이 강력한 테스트베이스는 플러그인의 유지보수와 미래 발전을 위한 가장 귀중한 자산입니다.

관련 기사