@Rule must be public في Kotlin: حل مشكلة ActivityTestRule

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

مقدمة: الخطأ الذي يدفع المطورين للجنون

\n\n

هل سبق لك أن كتبت اختبارًا بلغة Kotlin لتطبيق Android، وأضفت ActivityTestRule، ثم شغّلت الاختبار وفجأة ظهرت لك هذه الرسالة المقلقة: "The @Rule must be public"؟ إذا كان الأمر كذلك، فأنت لست وحدك. هذا السؤال على Stack Overflow حصد آلاف المشاهدات، وكل يوم يقع العشرات من المطورين في نفس المشكلة.

\n\n

المشكلة تبدو بسيطة، لكنها في Kotlin تتحول إلى رحلة بحث حقيقية. في Java، كل شيء يعمل بسلاسة: @Rule، حقل عام (public) — والأمر منتهٍ. أما في Kotlin... حسنًا، أنت تعرف القصة. اليوم سنفهم لماذا يحدث هذا، والأهم من ذلك، كيف نصلحه نهائيًا.

\n\n

بالمناسبة، إذا كنت تفضل Python، فموقع PythonLib لديه الكثير من المحتوى المفيد عن الاختبارات. لكن اليوم، سنتحدث عن Kotlin الخالص وAndroid.

\n\n

لماذا يظهر خطأ "The @Rule must be public"؟

\n\n

لنتذكر كيف يعمل ActivityTestRule في JUnit. هذه القاعدة (Rule) تقوم بتشغيل Activity قبل كل اختبار وتنهيه بشكل صحيح بعده. في Java، تكتب:

\n\n
@Rule\npublic ActivityTestRule<MainActivity> activityTestRule =\n    new ActivityTestRule<>(MainActivity.class);\n
\n\n

الأمر بسيط: الحقل عام (public)، والقاعدة عامة. في Kotlin، كل شيء يكون public افتراضيًا، لكن هناك خدعة. عندما تكتب:

\n\n
@Rule\nval activityTestRule = ActivityTestRule(MainActivity::class.java)\n
\n\n

...يقوم Kotlin بإنشاء حقل خاص (private) ودالة getter عامة. لكن JUnit يتطلب حقلًا عامًا وليس دالة getter. هذا هو السر كله.

\n\n

يظهر الخطأ "The @Rule must be public" تحديدًا لأن JUnit لا يرى الحقل الخاص بك — فهو خاص (private).

\n\n

الحل رقم 1: استخدام @get:Rule (الأكثر شيوعًا)

\n\n

أسهل وأشهر طريقة هي تطبيق التعليق التوضيحي (annotation) على دالة getter بدلاً من الحقل. لهذا، يوجد في Kotlin الصيغة @get:Rule:

\n\n
class MainActivityTest {\n\n    @get:Rule\n    val activityTestRule = ActivityTestRule(MainActivity::class.java)\n\n    @Test\n    fun testActivityIsNotNull() {\n        assertNotNull(activityTestRule.activity)\n    }\n}\n
\n\n

ماذا يحدث هنا؟ نقول: "قم بتعليق دالة getter، وليس الحقل". JUnit يرى دالة getter العامة، ويحصل على القاعدة، وكل شيء يعمل. هذا الحل مجرب من قبل مئات المطورين.

\n\n

هذا الحل هو "خطتك أ". إنه موجز، واضح، ولا يتطلب مكتبات إضافية.

\n\n

الحل رقم 2: جعل الحقل عامًا باستخدام @JvmField

\n\n

إذا كنت لا تحب صيغة @get:Rule (على الرغم من أنها ممتازة)، يمكنك استخدام التعليق التوضيحي @JvmField. هذا يجبر Kotlin على إنشاء حقل عام بدون دالة getter:

\n\n
class MainActivityTest {\n\n    @JvmField\n    @Rule\n    val activityTestRule = ActivityTestRule(MainActivity::class.java)\n\n    @Test\n    fun testActivityIsNotNull() {\n        assertNotNull(activityTestRule.activity)\n    }\n}\n
\n\n

يعمل تمامًا مثل كود Java. العيب: كود أكثر قليلاً. الميزة: توضيح صريح أن الحقل يجب أن يكون عامًا.

\n\n

شخصيًا، أفضل @get:Rule — فهو أقصر ويُقرأ بشكل طبيعي أكثر لمطور Kotlin. لكن الخيار لك.

\n\n

الحل رقم 3: استخدام ActivityScenarioRule (الطريقة الحديثة)

\n\n

والآن دعنا نتحدث عما توصي به Google بدلاً من ActivityTestRule. في مكتبة androidx.test.ext:junit يوجد ActivityScenarioRule. إنه أكثر مرونة ويتكامل بشكل أفضل مع اختبارات Android الحديثة.

\n\n

مع هذه القاعدة، نفس القصة — تحتاج إلى استخدام @get:Rule أو @JvmField:

\n\n
class MainActivityTest {\n\n    @get:Rule\n    val activityScenarioRule = ActivityScenarioRule(MainActivity::class.java)\n\n    @Test\n    fun testActivityIsNotNull() {\n        activityScenarioRule.scenario.onActivity { activity ->\n                

المدونات

توصيات الكتب