اختبار في Kotlin: الوحدة، التكامل، E2E من الألف إلى الياء
\n\nالتطوير الحديث في Kotlin يتطلب نهجًا منهجيًا للاختبار. بغض النظر عما إذا كنت تكتب الواجهة الخلفية على Ktor أو تطبيق جوال لنظام Android، فإن التغطية الجيدة للكود بالاختبارات هي ضمان لاستقرار المشروع وقابليته للتنبؤ. في هذه المقالة، سنتناول ثلاثة مستويات من الاختبار: اختبارات الوحدة (unit)، واختبارات التكامل (integration)، والاختبارات الشاملة (e2e). ستتعرف على الأدوات التي يجب استخدامها، وكيفية كتابة اختبارات فعالة في Kotlin وتجنب الأخطاء الشائعة.
\n\n1. اختبار الوحدة في Kotlin: الأساس
\n\nتتحقق اختبارات الوحدة من عمل الدوال أو الفئات أو الطرق الفردية بمعزل عن التبعيات الخارجية. في Kotlin، يُستخدم إطار العمل JUnit 5 غالبًا مع مكتبة المحاكاة MockK.
\n\nإعداد المشروع
\n\nأضف التبعيات في ملف build.gradle.kts:
dependencies {\n testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")\n testImplementation("io.mockk:mockk:1.13.8")\n}\n\n\nمثال على اختبار وحدة
\n\nلنفترض أن لدينا خدمة بسيطة لحساب الخصم:
\n\nclass DiscountService {\n fun applyDiscount(price: Double, discountPercent: Int): Double {\n require(discountPercent in 0..100) { "يجب أن تكون نسبة الخصم بين 0 و 100" }\n return price * (100 - discountPercent) / 100\n }\n}\n\n\nاختبار هذه الخدمة:
\n\nimport org.junit.jupiter.api.Assertions.*\nimport org.junit.jupiter.api.Test\nimport org.junit.jupiter.api.assertThrows\n\nclass DiscountServiceTest {\n\n private val service = DiscountService()\n\n @Test\n fun `applyDiscount يحسب خصم 50 بالمئة بشكل صحيح`() {\n val result = service.applyDiscount(1000.0, 50)\n assertEquals(500.0, result, 0.001)\n }\n\n @Test\n fun `applyDiscount يرمي استثناء عند نسبة خصم غير صحيحة`() {\n assertThrows<IllegalArgumentException> {\n service.applyDiscount(100.0, 150)\n }\n }\n}\n\n\nنصيحة: استخدم assertThrows للتحقق من الاستثناءات وقم بتسمية الاختبارات بسلاسل نصية واضحة - فهذا يحسن قابلية قراءة التقارير.
المحاكاة باستخدام MockK
\n\nعندما تعتمد فصلك على مكونات أخرى (مثل مستودع أو API)، استخدم المحاكاة:
\n\nclass OrderService(private val paymentGateway: PaymentGateway) {\n fun processOrder(amount: Double): String {\n if (paymentGateway.charge(amount)) {\n return "تم الدفع بنجاح"\n }\n return "خطأ في الدفع"\n }\n}\n\n// اختبار\n@Test\nfun `processOrder يعيد النجاح عند الدفع الناجح`() {\n val mockGateway = mockk<PaymentGateway>()\n every { mockGateway.charge(any()) } returns true\n\n val service = OrderService(mockGateway)\n val result = service.processOrder(500.0)\n\n assertEquals("تم الدفع بنجاح", result)\n verify { mockGateway.charge(500.0) }\n}\n\n\n2. اختبار التكامل: التحقق من تفاعل المكونات
\n\nتتحقق اختبارات التكامل من كيفية عمل أجزاء مختلفة من النظام معًا: قاعدة البيانات، واجهات API الخارجية، نظام الملفات. بالنسبة لـ Kotlin، تعتبر Testcontainers مناسبة تمامًا - وهي مكتبة لتشغيل حاويات Docker أثناء الاختبارات.
\n\nمثال مع قاعدة بيانات PostgreSQL
\n\nأضف التبعيات:
\n\ntestImplementation("org.testcontainers:testcontainers:1.19.3")\ntestImplementation("org.testcontainers:postgresql:1.19.3")\n\n\nلنكتب اختبارًا لطبقة DAO:
\n\nclass UserRepositoryIntegrationTest {\n\n companion object {\n private val postgres = PostgreSQLContainer<Nothing>("postgres:15").apply {\n withDatabaseName("testdb")\n withUsername("test")\n withPassword("test")\n start()\n