لماذا اختبار E2E هو أفضل صديق لك (وكيف تتعامل معه في Kotlin)
\n\nتخيل: لقد كتبت خدمة رائعة بلغة Kotlin، كل شيء تم تجميعه، اختبارات الوحدة خضراء، مراجعة الكود تمت. تقوم بالنشر في الإنتاج، و... يشكو المستخدمون من أن زر "شراء" لا يعمل، على الرغم من أن اختبارات الوحدة اختبرته. مألوف؟ مرحبًا بك في عالم حيث اختبار E2E لتطبيقات Kotlin ليس رفاهية، بل ضرورة.
\n\nالاختبار الشامل (end-to-end) هو التحقق من مسار المستخدم بالكامل: من النقر على زر إلى التسجيل في قاعدة البيانات والعودة. في هذه المقالة، لن نتحدث فقط عن النظرية. سنكتب اختبارات حقيقية لتطبيق Kotlin، ونحلل الأدوات، ونوضح كيف لا تفقد صوابك عند إعداد البنية التحتية. استعد، سيكون هناك كود وقهوة.
\n\nما هو اختبار E2E وكيف يختلف عن الأنواع الأخرى
\n\nدعنا نضع النقاط على الحروف فورًا. يختبر اختبار E2E (أو الشامل) النظام بأكمله كما يراه المستخدم. على عكس:
\n\n- \n
- اختبارات الوحدة — تختبر طريقة/فئة واحدة بشكل منفصل. \n
- الاختبارات التكاملية — تختبر التفاعل بين مكونين أو ثلاثة (مثل وحدة التحكم + الخدمة). \n
- اختبارات واجهة المستخدم — غالبًا ما يتم الخلط بينها وبين E2E، لكن اختبار واجهة المستخدم قد لا يختبر الواجهة الخلفية، بل فقط عرض الواجهة. \n
اختبار E2E لتطبيقات Kotlin هو دورة كاملة: واجهة المستخدم (إن وجدت)، API، منطق الأعمال، قاعدة البيانات، الخدمات الخارجية. يجب أن يعمل كل شيء معًا كالساعة.
\n\nأدوات اختبار E2E في Kotlin
\n\nيعتمد اختيار الأداة على ما تختبره: تطبيق Android، خدمة ويب على Ktor، أو تطبيق سطح المكتب. إليك الأفضل لمطور Kotlin:
\n\n- \n
- Selenide + Kotlin — لواجهات الويب. غلاف فوق Selenium، لكن مع بناء جملة موجز، مثالي لـ Kotlin. \n
- Ktor Client + TestContainers — للواجهة الخلفية. قم بتشغيل البنية التحتية للاختبار (قاعدة بيانات، ذاكرة تخزين مؤقت) في Docker وأرسل طلبات HTTP. \n
- Espresso + Compose Test — لنظام Android. أدوات أصلية تأتي مع Android SDK. \n
- Cucumber + Kotlin — إذا كنت بحاجة إلى سيناريوهات BDD بلغة مفهومة للعميل. \n
سنركز على السيناريوهين الأولين، حيث أنهما الأكثر شمولاً.
\n\nكتابة اختبار E2E لتطبيق ويب بلغة Kotlin (Selenide)
\n\nلنفترض أن لدينا متجرًا بسيطًا عبر الإنترنت على Spring Boot + Kotlin. نحتاج إلى التحقق من السيناريو: يدخل المستخدم إلى الموقع، ويضيف منتجًا إلى سلة التسوق، ويقدم الطلب.
\n\nأولاً، أضف التبعيات في build.gradle.kts:
dependencies {\n testImplementation("com.codeborne:selenide:6.19.1")\n testImplementation("org.junit.jupiter:junit-jupiter:5.10.0")\n testImplementation("io.github.bonigarcia:webdrivermanager:5.6.2")\n}\n\nالآن لنكتب الاختبار نفسه. لاحظ: نستخدم دوال الامتداد (extension functions) في Kotlin لجعل الكود قابلاً للقراءة:
\n\nimport com.codeborne.selenide.Condition.visible\nimport com.codeborne.selenide.Selenide.`$`\nimport com.codeborne.selenide.Selenide.open\nimport org.junit.jupiter.api.BeforeEach\nimport org.junit.jupiter.api.Test\n\nclass ShopE2ETest {\n\n @BeforeEach\n fun setUp() {\n // فتح المتصفح قبل كل اختبار\n open("https://test-shop.example.com")\n }\n\n @Test\n fun `يمكن للمستخدم إضافة منتج إلى السلة وإتمام الشراء`() {\n // الخطوة 1: العثور على المنتج وإضافته إلى السلة\n `$`("[data-testid='product-1']").shouldBe(visible).click()\n `$`("[data-testid='add-to-cart']").click()\n\n // الخطوة 2: التحقق من أن السلة ليست فارغة\n `$`("[data-testid='cart-badge']").shouldHave(text("1"))\n\n // الخطوة 3: الانتقال إلى إتمام الشراء\n `$`("[data-testid='checkout-btn']").click()\n\n // الخطوة 4: ملء النموذج\n `$`("[