Trong bài viết này, chúng ta sẽ khám phá cách thiết lập một nền tảng phát triển vững chắc cho một plugin Gradle bằng cách sử dụng phương pháp Phát triển hướng Kiểm thử (TDD - Test-Driven Development). Phương pháp này đảm bảo rằng mã của chúng ta mạnh mẽ, dễ bảo trì và đáp ứng chính xác các yêu cầu ngay từ đầu.

1. Khởi tạo Dự án

Gradle tạo điều kiện thuận lợi đáng kể cho việc tạo một plugin mới nhờ vào lệnh`gradle init`. Bằng cách chọn tạo một "Gradle Plugin" với Kotlin, chúng ta có được một cấu trúc dự án sẵn sàng sử dụng, bao gồm hai loại kiểm thử quan trọng:

  • Kiểm thử Đơn vị:Nằm tại`src/test`, chúng cho phép xác thực các thành phần riêng biệt của plugin, chẳng hạn như logic nội bộ của một tác vụ hoặc cấu hình của một phần mở rộng. Chúng có tốc độ nhanh và không cần thực thi đầy đủ Gradle.

  • Kiểm thử Chức năng:Nằm tại`src/functionalTest`, họ sử dụng`GradleRunner`để thực thi một build Gradle đầy đủ trong một dự án kiểm thử tạm thời. Điều này cho phép chúng ta xác minh hành vi thực tế của plugin trong một môi trường được kiểm soát.

2. Chu trình TDD Đầu tiên của Chúng ta: Lưu một Tác vụ

Yêu cầu đầu tiên của chúng tôi rất đơn giản: plugin phải đăng ký một tác vụ có tên là`printSiteConfig`Vui lòng cung cấp văn bản cần dịch.

2.1. Kiểm thử Trước (Kiểm thử Thất bại)

Theo TDD, trước tiên chúng ta viết một bài kiểm tra để xác minh sự tồn tại của tác vụ này. Trong`SiteBakerPluginTest.kt`(tệp kiểm thử đơn vị của chúng ta), chúng ta thêm:

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

Bài kiểm tra này thất bại vì chúng ta chưa viết bất kỳ mã nào trong plugin của mình.

2.2. Mã hóa tiếp theo (Vượt qua bài kiểm tra)

Bây giờ, chúng ta viết lượng mã tối thiểu cần thiết trong`SiteBakerPlugin.kt`để bài kiểm tra vượt qua:

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

Chúng tôi chạy lại các bài kiểm tra và chúng đã vượt qua. Tính năng đầu tiên của chúng tôi đã được xác nhận.

3. Chu kỳ TDD thứ hai: Thêm một Extension DSL

Yêu cầu sau đây là cho phép người dùng cấu hình plugin của chúng tôi thông qua một khối DSL trong`build.gradle.kts`. Chúng tôi muốn một khối`site { …​ }`nơi có thể chỉ định một đường dẫn cấu hình.

3.1. Bài kiểm tra Đầu tiên

Chúng tôi thêm một bài kiểm tra để xác minh rằng tiện ích mở rộng`site`được đăng ký chính xác và có thể gán một giá trị cho nó.

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

Bài kiểm tra này thất bại vì cả lớp`SiteExtension`cũng như việc đăng ký tiện ích mở rộng không tồn tại.

3.2. Mã Code Tiếp Theo

Chúng ta tạo lớp`SiteBakerExtension.kt`và cập nhật`SiteBakerPlugin.kt`Vui lòng cung cấp văn bản cần dịch.

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

Các bài kiểm tra đã vượt qua trở lại.

4. Kiểm thử Chức năng: Xác thực Tích hợp

Bây giờ các unit đã được kiểm thử, chúng ta phải đảm bảo mọi thứ hoạt động cùng nhau trong một bản build thực tế. Đó là vai trò của kiểm thử chức năng trong`SiteBakerPluginFunctionalTest.kt`.

4.1. Kiểm thử Tích hợp: Tạo một Môi trường Kiểm soát

Bài kiểm tra này sẽ mô phỏng một dự án thực tế sử dụng plugin của chúng tôi. Để đảm bảo độ tin cậy, nó phải đượckín khít, nghĩa là nó không được phụ thuộc vào các tệp hiện có trên hệ thống. Nó phải tự tạo ra tất cả các điều kiện cần thiết để thực thi.

Vì vậy, bài kiểm tra sẽ:

  1. Tạo một`build.gradle.kts`kiểm thử sử dụng plugin và DSL của chúng tôi.

  2. Tạo tệp cấu hình(managed-jbake-context.yml) mà plugin được cho là sẽ đọc. Đây là một bước quan trọng để đảm bảo tính bền vững của bài kiểm tra.

  3. Thực hiện tác vụ`printSiteConfig`qua`GradleRunner`Vui lòng cung cấp văn bản cần dịch.

  4. Kiểm tra xem đầu ra của bản build có chính xác không.

Bằng cách sao chép hoặc tạo tệp cấu hình này mỗi khi chạy, chúng ta đảm bảo rằng bài kiểm tra làcó thể tái lậpvà không phụ thuộc vào một trạng thái bên ngoài. Điều này bảo vệ các bài kiểm tra của chúng ta khỏi những lỗi hồi quy có thể liên quan đến việc đọc tệp.

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

Bài kiểm tra này sẽ thất bại cho đến khi tác vụ`printSiteConfig`sẽ không thực sự sử dụng giá trị của phần mở rộng.

4.2. Hoàn tất Logic

Chúng tôi đang cập nhật tác vụ trong`SiteBakerPlugin.kt`để nó hiển thị giá trị đã được cấu hình:

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

Tất cả các bài kiểm tra, bao gồm cả kiểm tra đơn vị và kiểm tra chức năng, hiện đều đã vượt qua.

Kết luận

Bằng cách tuân theo phương pháp TDD, chúng tôi đã xây dựng một plugin một cách tăng dần và an toàn. Mỗi tính năng nhỏ đều được xác thực ngay lập tức bằng một bài kiểm tra, từ các bài kiểm tra đơn vị (unit test) nhanh chóng đến các bài kiểm tra chức năng nhằm xác thực việc tích hợp toàn diện.

Hãy chú ý làm cho các bài kiểm tra chức năng của chúng ta hoạt động chính xáckín khít— đặc biệt là bằng cách tạo lập trình các tệp cấu hình cần thiết — chúng tôi đang xây dựng một lưới an toàn cực kỳ vững chắc. Sự nghiêm ngặt này bảo vệ chúng tôi một cách hiệu quả khỏi các lỗi hồi quy và mang lại sự tự tin lớn để thêm các tính năng phức tạp hơn sau này, chẳng hạn như việc parsing tệp cấu hình YAML.

Nền tảng kiểm thử vững chắc này là tài sản quý giá nhất cho việc bảo trì và phát triển plugin trong tương lai.

Bài viết liên quan