أساسيات الإسبريسو

يوضّح هذا المستند كيفية إكمال مهام الاختبار الآلية الشائعة باستخدام Espresso API.

تشجِّع واجهة برمجة تطبيقات Espresso مؤلفي الاختبار على التفكير في ما قد يفعله المستخدم أثناء تفاعله مع التطبيق - تحديد موقع عناصر واجهة المستخدم والتفاعل معها. في الوقت نفسه، يمنع إطار العمل الوصول المباشر إلى الأنشطة وطرق عرض التطبيق لأن التمسك بهذه الكائنات والعمل عليها خارج مؤشر ترابط واجهة المستخدم هو مصدر رئيسي لتشوهات الاختبار. بالتالي، لن تظهر لك طُرق مثل getView() وgetCurrentActivity() في Espresso API. لا يزال بإمكانك تشغيل المشاهدات بأمان من خلال تنفيذ فئتك الفرعية ViewAction وViewAssertion.

مكوّنات واجهة برمجة التطبيقات

تشمل المكونات الأساسية لقهوة الإسبريسو ما يلي:

  • Espresso: هي نقطة دخول إلى التفاعلات مع المشاهدات (عبر onView() وonData())، كما تعرض واجهات برمجة التطبيقات التي لا تكون بالضرورة مرتبطة بأي طريقة عرض، مثل pressBack().
  • ViewMatchers – مجموعة من الكائنات التي تنفّذ واجهة Matcher<? super View>. يمكنك تمرير واحد أو أكثر من هذه العناصر إلى طريقة onView() لتحديد ملف شخصي ضمن التدرّج الهرمي للملفات الشخصية الحالي.
  • ViewActions – مجموعة من عناصر ViewAction يمكن تمريرها إلى طريقة ViewInteraction.perform()، مثل click().
  • ViewAssertions – مجموعة من عناصر ViewAssertion يمكن أن تتخطى طريقة ViewInteraction.check(). في معظم الأحيان، ستستخدم تأكيد التطابقات، الذي يستخدم أداة مطابقة الملف الشخصي لتأكيد حالة طريقة العرض المحددة حاليًا.

مثال:

Kotlin

// withId(R.id.my_view) is a ViewMatcher
// click() is a ViewAction
// matches(isDisplayed()) is a ViewAssertion
onView(withId(R.id.my_view))
    .perform(click())
    .check(matches(isDisplayed()))

Java

// withId(R.id.my_view) is a ViewMatcher
// click() is a ViewAction
// matches(isDisplayed()) is a ViewAssertion
onView(withId(R.id.my_view))
    .perform(click())
    .check(matches(isDisplayed()));

البحث عن طريقة عرض

في الغالبية العظمى من الحالات، تستخدم الطريقة onView() مُطابق مهمات يُتوقع أن تتطابق مع عرض واحد فقط، وعرض واحد ضمن التدرّج الهرمي الحالي لطريقة العرض. إن خبراء المطابقة أقوياء، وسيكونون على دراية بأولئك الذين استخدموها مع Mockito أو JUnit. إذا لم تكن معتادًا على استخدام ألعاب مطابقة الأفكار، ننصحك بإلقاء نظرة سريعة على هذا العرض التقديمي.

وغالبًا ما يكون للملف الشخصي المطلوب قيمة R.id فريدة، بينما يتم تضييق نطاق البحث عن الملف الشخصي أسفل طريقة مطابقة withId البسيطة. ومع ذلك، هناك العديد من الحالات المشروعة التي لا يمكنك فيها تحديد R.id في وقت تطوير الاختبار. على سبيل المثال، قد لا يتضمّن الملف الشخصي المحدّد R.id أو لا يكون R.id فريدًا. وقد يؤدي ذلك إلى جعل كتابة اختبارات الأدوات العادية هشة ومعقدة لأن الطريقة العادية للوصول إلى العرض - باستخدام findViewById() - لا تعمل. وبالتالي، قد تحتاج إلى الوصول إلى العناصر الخاصة في النشاط أو الجزء الذين يحملون العرض أو البحث عن حاوية ذات R.id معروفة والانتقال إلى محتواها للاطّلاع على عرض معيّن.

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

إن العثور على عرض من خلال R.id أمر سهل تمامًا مثل طلب onView():

Kotlin

onView(withId(R.id.my_view))

Java

onView(withId(R.id.my_view));

في بعض الأحيان، تتم مشاركة قيم R.id بين ملفات شخصية متعددة. وعندما يحدث ذلك، فإنّ محاولة استخدام R.id معيّنة تعطيك استثناءً، مثل AmbiguousViewMatcherException. تزوِّدك رسالة الاستثناء بتمثيل نصي للتدرّج الهرمي الحالي لطريقة العرض، والذي يمكنك البحث عنه والعثور عليه من طرق العرض التي تطابق طريقة العرض R.id غير الفريدة:

java.lang.RuntimeException:
androidx.test.espresso.AmbiguousViewMatcherException
This matcher matches multiple views in the hierarchy: (withId: is <123456789>)

...

+----->SomeView{id=123456789, res-name=plus_one_standard_ann_button,
visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true,
window-focus=true, is-focused=false, is-focusable=false, enabled=true,
selected=false, is-layout-requested=false, text=,
root-is-layout-requested=false, x=0.0, y=625.0, child-count=1}
****MATCHES****
|
+------>OtherView{id=123456789, res-name=plus_one_standard_ann_button,
visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true,
window-focus=true, is-focused=false, is-focusable=true, enabled=true,
selected=false, is-layout-requested=false, text=Hello!,
root-is-layout-requested=false, x=0.0, y=0.0, child-count=1}
****MATCHES****

من خلال استعراض السمات المختلفة للملفات الشخصية، قد تجد خصائص يمكن التعرّف عليها بشكلٍ فريد. في المثال أعلاه، تحتوي إحدى طرق العرض على النص "Hello!". يمكنك استخدام هذا لتضييق نطاق البحث باستخدام مُطابقات المجموعات:

Kotlin

onView(allOf(withId(R.id.my_view), withText("Hello!")))

Java

onView(allOf(withId(R.id.my_view), withText("Hello!")));

يمكنك أيضًا اختيار عدم التراجع عن أي من المطابقات:

Kotlin

onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))

Java

onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))));

اطّلِع على ViewMatchers للاطّلاع على نتائج مطابقة المشاهدات التي توفّرها Espresso.

الاعتبارات

  • يجب أن تحتوي جميع طرق العرض التي يمكن للمستخدم التفاعل معها على نص وصفي أو وصف للمحتوى في التطبيق ذي الأداء الجيد. راجع إتاحة الوصول إلى التطبيقات بشكل أكبر للحصول على مزيد من التفاصيل. إذا لم تتمكن من تضييق نطاق البحث باستخدام withText() أو withContentDescription()، يمكنك التعامل معهما على أنّه خطأ في تسهيل الاستخدام.
  • استخدِم المُطابق الأقل وصفية الذي يعثر على طريقة العرض الواحدة التي تبحث عنها. لا تبالغ في التحديد لأن ذلك سيجبر إطار العمل على القيام بعمل أكثر مما هو ضروري. على سبيل المثال، إذا كان يمكن التعرّف على الملف الشخصي بشكلٍ فريد من خلال نصه، لا تحتاج إلى تحديد إمكانية تخصيص العرض أيضًا من خلال السمة TextView. بالنسبة إلى الكثير من المشاهدات، من المفترض أن يكون R.id من المشاهدات كافيًا.
  • قد لا تعمل طريقة onView() في حال كانت طريقة العرض المستهدَفة داخل AdapterView، مثل ListView أو GridView أو Spinner. في هذه الحالات، يجب استخدام onData() بدلاً من ذلك.

تنفيذ إجراء على إحدى طرق العرض

عند العثور على مطابقة مناسبة للعرض الهدف، من الممكن تنفيذ نُسخ ViewAction عليه باستخدام طريقة التنفيذ.

على سبيل المثال، للنقر على طريقة العرض:

Kotlin

onView(...).perform(click())

Java

onView(...).perform(click());

يمكنك تنفيذ أكثر من إجراء باستدعاء واحد فقط:

Kotlin

onView(...).perform(typeText("Hello"), click())

Java

onView(...).perform(typeText("Hello"), click());

إذا كان الملف الشخصي الذي تعمل معه يقع داخل ScrollView (عمودي أو أفقي)، ضع في اعتبارك الإجراءات السابقة التي تتطلب عرض الملف الشخصي، مثل click() وtypeText()، باستخدام scrollTo(). ويضمن ذلك عرض العرض قبل المتابعة إلى الإجراء الآخر:

Kotlin

onView(...).perform(scrollTo(), click())

Java

onView(...).perform(scrollTo(), click());

يمكنك الاطّلاع على ViewActions للاطّلاع على إجراءات المشاهدة التي توفّرها Espresso.

التحقق من تأكيدات المشاهدة

يمكن تطبيق التأكيدات على الملف الشخصي المحدّد حاليًا باستخدام الطريقة check(). أكثر أنواع التأكيد استخدامًا هي تأكيد matches(). تستخدِم هذه الطريقة كائن ViewMatcher لتأكيد حالة العرض المحدَّد حاليًا.

على سبيل المثال، للتحقق من احتواء العرض على النص "Hello!":

Kotlin

onView(...).check(matches(withText("Hello!")))

Java

onView(...).check(matches(withText("Hello!")));

إذا أردت التأكيد على أنّ "Hello!" هو محتوى من المشاهدات، يُعتبر ما يلي ممارسة سيئة:

Kotlin

// Don't use assertions like withText inside onView.
onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()))

Java

// Don't use assertions like withText inside onView.
onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()));

من ناحية أخرى، إذا كنت تريد التأكّد من أنّ الملف الشخصي الذي يتضمّن النص "Hello!" مرئي، مثلاً بعد تغيير علامة مستوى الرؤية، لا بأس في ذلك.

عرض اختبار بسيط لتأكيد التأكيد

في هذا المثال، تحتوي السمة SimpleActivity على Button وTextView. عند النقر على الزر، يتغير محتوى TextView إلى "Hello Espresso!".

إليك كيفية اختبار هذه الميزة باستخدام Espresso:

انقر على الزر

الخطوة الأولى هي البحث عن خاصية تساعد في العثور على الزر. للزرّ في SimpleActivity سمة R.id فريدة، كما هو متوقّع.

Kotlin

onView(withId(R.id.button_simple))

Java

onView(withId(R.id.button_simple));

الآن لإجراء النقر:

Kotlin

onView(withId(R.id.button_simple)).perform(click())

Java

onView(withId(R.id.button_simple)).perform(click());

التحقّق من نص TextView

تتضمّن السمة TextView التي تتضمّن النص المطلوب إثبات الملكية قيمة R.id فريدة أيضًا:

Kotlin

onView(withId(R.id.text_simple))

Java

onView(withId(R.id.text_simple));

إليك الخطوات التي يجب اتّباعها للتحقّق من نص المحتوى:

Kotlin

onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")))

Java

onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));

التحقّق من تحميل البيانات في طرق عرض المهايئات

AdapterView هي نوع خاص من التطبيقات المصغّرة تُحمِّل بياناتها ديناميكيًا من المحوِّل. المثال الأكثر شيوعًا على AdapterView هو ListView. وعلى عكس التطبيقات المصغّرة الثابتة مثل LinearLayout، يمكن تحميل مجموعة فرعية فقط من عناصر AdapterView الثانوية في التدرج الهرمي لطريقة العرض الحالية. لن يؤدي بحث onView() البسيط إلى العثور على الملفات الشخصية التي لم يتم تحميلها حاليًا.

يتعامل قهوة الإسبريسو مع هذه المشكلة من خلال توفير نقطة دخول منفصلة لجهاز onData() يمكنها تحميل المحوّل المعنيّ أولاً، ما يجعلها موضع التركيز قبل تشغيله أو تشغيل أي من عناصره الثانوية.

تحذير: قد تواجه عمليات التنفيذ المخصّصة للسمة AdapterView مشاكل في طريقة onData() في حال خرقت عقود التوريث، ولا سيما واجهة برمجة التطبيقات getItem(). في مثل هذه الحالات، يكون أفضل إجراء هو إعادة بناء رمز التطبيق لديك. إذا لم تتمكّن من إجراء ذلك، يمكنك تطبيق سمة AdapterViewProtocol مخصّصة مطابقة. لمزيد من المعلومات، يمكنك الاطّلاع على فئة AdapterViewProtocols التلقائية التي توفّرها Espresso.

اختبار بسيط لعرض المحوِّل

يعرض هذا الاختبار البسيط كيفية استخدام onData(). تحتوي SimpleActivity على Spinner مع بعض العناصر التي تمثل أنواعًا من مشروبات القهوة. عند اختيار عنصر، هناك علامة TextView تتغير إلى "One %s a day!"، حيث تمثل %s العنصر المحدد.

الهدف من هذا الاختبار هو فتح Spinner واختيار عنصر محدّد ثم التأكّد من أنّ TextView يحتوي على العنصر. بما أنّ الفئة Spinner تستند إلى AdapterView، ننصح باستخدام onData() بدلاً من onView() لمطابقة العنصر.

فتح اختيار العنصر

Kotlin

onView(withId(R.id.spinner_simple)).perform(click())

Java

onView(withId(R.id.spinner_simple)).perform(click());

اختيار عنصر

بالنسبة إلى اختيار العنصر، تنشئ Spinner عنصر ListView يتضمن محتواه. ويمكن أن يكون هذا العرض طويلاً جدًا، وقد لا تتم إضافة العنصر إلى التسلسل الهرمي لطريقة العرض. باستخدام onData()، نفرض العنصر المطلوب في التسلسل الهرمي للعرض. العناصر الموجودة في Spinner هي سلاسل، لذلك نريد مطابقة عنصر يساوي السلسلة "Americano":

Kotlin

onData(allOf(`is`(instanceOf(String::class.java)),
        `is`("Americano"))).perform(click())

Java

onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());

التأكّد من صحة النص

Kotlin

onView(withId(R.id.spinnertext_simple))
    .check(matches(withText(containsString("Americano"))))

Java

onView(withId(R.id.spinnertext_simple))
    .check(matches(withText(containsString("Americano"))));

تصحيح الأخطاء

يوفر إصدار Espresso معلومات مفيدة لتصحيح الأخطاء عند إجراء اختبار:

التسجيل

يسجل Espresso جميع إجراءات العرض لـ Logcat. مثلاً:

ViewInteraction: Performing 'single click' action on view with text: Espresso

عرض التدرّج الهرمي

تطبع قهوة الإسبريسو العرض الهرمي لطريقة العرض في رسالة الاستثناء عند تعذُّر تنفيذ onView().

  • إذا لم يعثر onView() على العرض المستهدَف، سيتم طرح NoMatchingViewException. يمكنك فحص التسلسل الهرمي لطريقة العرض في سلسلة الاستثناء لتحليل سبب عدم تطابق المُطابق مع أي طرق عرض.
  • وإذا عثرَت onView() على مشاهدات متعدّدة تتطابق مع المُطابق المحدد، يتم عرض AmbiguousViewMatcherException. تتم طباعة العرض الهرمي لطريقة العرض ويتم تمييز جميع طرق العرض التي تمت مطابقتها بالعلامة MATCHES:
java.lang.RuntimeException:
androidx.test.espresso.AmbiguousViewMatcherException
This matcher matches multiple views in the hierarchy: (withId: is <123456789>)

...

+----->SomeView{id=123456789, res-name=plus_one_standard_ann_button,
visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true,
window-focus=true, is-focused=false, is-focusable=false, enabled=true,
selected=false, is-layout-requested=false, text=,
root-is-layout-requested=false, x=0.0, y=625.0, child-count=1}
****MATCHES****
|
+------>OtherView{id=123456789, res-name=plus_one_standard_ann_button,
visibility=VISIBLE, width=523, height=48, has-focus=false, has-focusable=true,
window-focus=true, is-focused=false, is-focusable=true, enabled=true,
selected=false, is-layout-requested=false, text=Hello!,
root-is-layout-requested=false, x=0.0, y=0.0, child-count=1}
****MATCHES****

عند التعامل مع العرض الهرمي المعقد لطريقة العرض أو السلوك غير المتوقّع للتطبيقات المصغّرة، من المفيد دائمًا استخدام العرض الهرمي في "استوديو Android" للحصول على شرح.

تحذيرات بشأن عرض المحوِّل

يحذر الإسبرسو المستخدمين من توفّر تطبيقات AdapterView المصغّرة. عندما تعرض عملية onView() الأداة NoMatchingViewException وAdapterView في التدرّج الهرمي لطريقة العرض، يكون الحل الأكثر شيوعًا هو استخدام onData(). ستشتمل رسالة الاستثناء على تحذير يضم قائمة بمرّات عرض المحوّل. يمكنك استخدام هذه المعلومات لاستدعاء onData() لتحميل العرض المستهدَف.

مراجع إضافية

لمزيد من المعلومات حول استخدام Espresso في اختبارات Android، راجع الموارد التالية.

عيّنات