يوضّح هذا المستند كيفية إكمال مهام الاختبار الآلية الشائعة باستخدام 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، راجع الموارد التالية.
عيّنات
- CustomMatcherعيّن:
يوضّح كيفية توسيع Espresso لمطابقة خاصية التلميح لكائن
EditText
. - RecyclerViewSample:
RecyclerView
إجراء لتحضير قهوة Espresso. - (المزيد...)