في هذه المقالة، سنستكشف كيفية إعداد قاعدة تطوير قوية لإضافة 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. تحقق من أن مخرجات البناء صحيحة.

من خلال نسخ أو إنشاء ملف التكوين هذا في كل عملية تنفيذ، نتأكد من أن الاختبار هوقابل للتكرارولا يعتمد على حالة خارجية. هذا يؤمن اختباراتنا ضد الانحدارات التي قد تكون مرتبطة بقراءة الملف.

@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.

هذه القاعدة القوية للاختبارات هي الأصل الأكثر قيمة للصيانة والتطور المستقبلي للمكوّن الإضافي.

Articles connexes