يوضّح هذا المستند كيفية إعداد مجموعة متنوعة من اختبارات الإسبريسو الشائعة.
مطابقة طريقة عرض بجانب طريقة عرض أخرى
يمكن أن يحتوي التخطيط على طرق عرض معينة ليست فريدة في حد ذاتها. على سبيل المثال، يمكن أن يحتوي زر الاتصال المتكرر في جدول جهات الاتصال على نفس R.id
، وأن يحتوي على النص نفسه، وأن يكون له نفس خصائص أزرار الاتصال الأخرى ضمن التدرّج الهرمي للعرض.
على سبيل المثال، في هذا النشاط، تتكرر طريقة العرض التي تتضمن النص "7"
في عدة صفوف:
وفي كثير من الأحيان، سيتم إقران طريقة العرض غير الفريدة مع بعض التصنيفات الفريدة التي تقع بجانبها، مثل اسم جهة الاتصال بجوار زر الاتصال. في هذه الحالة، يمكنك استخدام
مُطابق hasSibling()
لتضييق نطاق التحديد:
Kotlin
onView(allOf(withText("7"), hasSibling(withText("item: 0")))) .perform(click())
Java
onView(allOf(withText("7"), hasSibling(withText("item: 0")))) .perform(click());
مطابقة طريقة عرض داخل شريط الإجراءات
يحتوي ActionBarTestActivity
على شريطَي إجراءات مختلفَين: شريط إجراءات عادي وشريط إجراءات سياقي يتم إنشاؤه من قائمة الخيارات. يحتوي كلا شريطي الإجراءات على عنصر واحد مرئي دائمًا وعنصران مرئيان فقط في القائمة الكاملة. عند النقر على عنصر، يتم تغيير TextView إلى محتوى العنصر الذي تم النقر عليه.
ومن السهل أيضًا مطابقة الرموز المرئية في كلٍ من شريطي الإجراءات، كما هو موضّح في مقتطف الرمز التالي:
Kotlin
fun testClickActionBarItem() { // We make sure the contextual action bar is hidden. onView(withId(R.id.hide_contextual_action_bar)) .perform(click()) // Click on the icon - we can find it by the r.Id. onView(withId(R.id.action_save)) .perform(click()) // Verify that we have really clicked on the icon // by checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Save"))) }
Java
public void testClickActionBarItem() { // We make sure the contextual action bar is hidden. onView(withId(R.id.hide_contextual_action_bar)) .perform(click()); // Click on the icon - we can find it by the r.Id. onView(withId(R.id.action_save)) .perform(click()); // Verify that we have really clicked on the icon // by checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Save"))); }
يبدو الرمز متطابقًا مع شريط الإجراءات السياقي:
Kotlin
fun testClickActionModeItem() { // Make sure we show the contextual action bar. onView(withId(R.id.show_contextual_action_bar)) .perform(click()) // Click on the icon. onView((withId(R.id.action_lock))) .perform(click()) // Verify that we have really clicked on the icon // by checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Lock"))) }
Java
public void testClickActionModeItem() { // Make sure we show the contextual action bar. onView(withId(R.id.show_contextual_action_bar)) .perform(click()); // Click on the icon. onView((withId(R.id.action_lock))) .perform(click()); // Verify that we have really clicked on the icon // by checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Lock"))); }
يعد النقر على العناصر في القائمة الكاملة أصعب قليلاً بالنسبة لشريط الإجراءات العادي، حيث تحتوي بعض الأجهزة على زر القائمة الكاملة للأجهزة، والذي يفتح العناصر التجاوزية في قائمة الخيارات، وبعض الأجهزة تحتوي على زر قائمة كاملة للبرنامج، والذي يفتح قائمة كاملة عادية. لحسن الحظ، يتعامل Espresso مع ذلك بالنسبة لنا.
بالنسبة إلى شريط الإجراءات العادي:
Kotlin
fun testActionBarOverflow() { // Make sure we hide the contextual action bar. onView(withId(R.id.hide_contextual_action_bar)) .perform(click()) // Open the options menu OR open the overflow menu, depending on whether // the device has a hardware or software overflow menu button. openActionBarOverflowOrOptionsMenu( ApplicationProvider.getApplicationContext<Context>()) // Click the item. onView(withText("World")) .perform(click()) // Verify that we have really clicked on the icon by checking // the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("World"))) }
Java
public void testActionBarOverflow() { // Make sure we hide the contextual action bar. onView(withId(R.id.hide_contextual_action_bar)) .perform(click()); // Open the options menu OR open the overflow menu, depending on whether // the device has a hardware or software overflow menu button. openActionBarOverflowOrOptionsMenu( ApplicationProvider.getApplicationContext()); // Click the item. onView(withText("World")) .perform(click()); // Verify that we have really clicked on the icon by checking // the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("World"))); }
إليك كيف يظهر هذا على الأجهزة التي تحتوي على زر القائمة الكاملة للأجهزة:
أما بالنسبة إلى شريط الإجراءات السياقي، فالأمر سهل مرة أخرى:
Kotlin
fun testActionModeOverflow() { // Show the contextual action bar. onView(withId(R.id.show_contextual_action_bar)) .perform(click()) // Open the overflow menu from contextual action mode. openContextualActionModeOverflowMenu() // Click on the item. onView(withText("Key")) .perform(click()) // Verify that we have really clicked on the icon by // checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Key"))) } }
Java
public void testActionModeOverflow() { // Show the contextual action bar. onView(withId(R.id.show_contextual_action_bar)) .perform(click()); // Open the overflow menu from contextual action mode. openContextualActionModeOverflowMenu(); // Click on the item. onView(withText("Key")) .perform(click()); // Verify that we have really clicked on the icon by // checking the TextView content. onView(withId(R.id.text_action_bar_result)) .check(matches(withText("Key"))); } }
للاطّلاع على الرمز الكامل لهذه النماذج، يمكنك الاطّلاع على النموذج
ActionBarTest.java
على GitHub.
التأكيد على عدم ظهور العرض
بعد تنفيذ سلسلة من الإجراءات، ستحتاج بالتأكيد إلى تأكيد حالة واجهة المستخدم التي تخضع للاختبار. في بعض الأحيان، قد تكون هذه حالة سلبية، مثل عند عدم حدوث شيء ما. تجدر الإشارة إلى أنه يمكنك تحويل أي أداة مطابقة طرق عرض
لعبة إلى ViewAssertion
باستخدام ViewAssertions.matches()
.
في المثال أدناه، نأخذ مُطابق isDisplayed()
ونعكسه باستخدام مُطابق not()
القياسي:
Kotlin
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isDisplayed import androidx.test.espresso.matcher.ViewMatchers.withId import org.hamcrest.Matchers.not onView(withId(R.id.bottom_left)) .check(matches(not(isDisplayed())))
Java
import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.assertion.ViewAssertions.matches; import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed; import static androidx.test.espresso.matcher.ViewMatchers.withId; import static org.hamcrest.Matchers.not; onView(withId(R.id.bottom_left)) .check(matches(not(isDisplayed())));
يعمل الأسلوب أعلاه إذا كان العرض لا يزال جزءًا من التسلسل الهرمي. وإذا لم يكن الأمر كذلك، ستحصل على NoMatchingViewException
وعليك استخدام ViewAssertions.doesNotExist()
.
التأكيد على عدم توفّر العرض
إذا اختفت طريقة العرض من التسلسل الهرمي لطريقة العرض، وهو ما يمكن أن يحدث عندما يؤدي أحد الإجراءات إلى الانتقال إلى نشاط آخر، عليك استخدام ViewAssertions.doesNotExist()
:
Kotlin
import androidx.test.espresso.Espresso.onView import androidx.test.espresso.assertion.ViewAssertions.doesNotExist import androidx.test.espresso.matcher.ViewMatchers.withId onView(withId(R.id.bottom_left)) .check(doesNotExist())
Java
import static androidx.test.espresso.Espresso.onView; import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist; import static androidx.test.espresso.matcher.ViewMatchers.withId; onView(withId(R.id.bottom_left)) .check(doesNotExist());
التأكيد على أنّ عنصر البيانات ليس في محوّل
ولإثبات أن عنصر بيانات معيّن ليس ضمن AdapterView
، عليك تنفيذ
الأمور بشكل مختلف قليلاً. علينا العثور على AdapterView
الذي يهمّنا
والاستجواب في البيانات التي تحتفظ بها. لسنا بحاجة إلى استخدام onData()
.
بدلاً من ذلك، نستخدم onView()
للعثور على AdapterView
ثم نستخدم مُطابقًا آخر للعمل على البيانات داخل العرض.
المُطابق أولاً:
Kotlin
private fun withAdaptedData(dataMatcher: Matcher<Any>): Matcher<View> { return object : TypeSafeMatcher<View>() { override fun describeTo(description: Description) { description.appendText("with class name: ") dataMatcher.describeTo(description) } public override fun matchesSafely(view: View) : Boolean { if (view !is AdapterView<*>) { return false } val adapter = view.adapter for (i in 0 until adapter.count) { if (dataMatcher.matches(adapter.getItem(i))) { return true } } return false } } }
Java
private static Matcher<View> withAdaptedData(final Matcher<Object> dataMatcher) { return new TypeSafeMatcher<View>() { @Override public void describeTo(Description description) { description.appendText("with class name: "); dataMatcher.describeTo(description); } @Override public boolean matchesSafely(View view) { if (!(view instanceof AdapterView)) { return false; } @SuppressWarnings("rawtypes") Adapter adapter = ((AdapterView) view).getAdapter(); for (int i = 0; i < adapter.getCount(); i++) { if (dataMatcher.matches(adapter.getItem(i))) { return true; } } return false; } }; }
كل ما نحتاج إليه بعد ذلك هو onView()
للعثور على AdapterView
:
Kotlin
fun testDataItemNotInAdapter() { onView(withId(R.id.list)) .check(matches(not(withAdaptedData(withItemContent("item: 168"))))) } }
Java
@SuppressWarnings("unchecked") public void testDataItemNotInAdapter() { onView(withId(R.id.list)) .check(matches(not(withAdaptedData(withItemContent("item: 168"))))); } }
لدينا تأكيد لن ينجح في حال تواجد عنصر يساوي "item: 168" في عرض المحول مع قائمة المعرفات.
للاطّلاع على العيّنة الكاملة، يمكنك الاطّلاع على الطريقة testDataItemNotInAdapter()
ضمن الفئة
AdapterViewTest.java
على GitHub.
استخدام معالِج مخصّص للإخفاق
ومن خلال استبدال FailureHandler
التلقائي في Espresso بأخرى مخصّصة، يمكنك
معالجة المزيد من الأخطاء، مثل أخذ لقطة شاشة أو تمرير
معلومات تصحيح أخطاء إضافية.
يوضح مثال CustomFailureHandlerTest
كيفية تنفيذ معالج مخصّص للإخفاقات:
Kotlin
private class CustomFailureHandler(targetContext: Context) : FailureHandler { private val delegate: FailureHandler init { delegate = DefaultFailureHandler(targetContext) } override fun handle(error: Throwable, viewMatcher: Matcher<View>) { try { delegate.handle(error, viewMatcher) } catch (e: NoMatchingViewException) { throw MySpecialException(e) } } }
Java
private static class CustomFailureHandler implements FailureHandler { private final FailureHandler delegate; public CustomFailureHandler(Context targetContext) { delegate = new DefaultFailureHandler(targetContext); } @Override public void handle(Throwable error, Matcher<View> viewMatcher) { try { delegate.handle(error, viewMatcher); } catch (NoMatchingViewException e) { throw new MySpecialException(e); } } }
يطرح معالج الأخطاء هذا MySpecialException
بدلاً من NoMatchingViewException
ويفوّض جميع عمليات الإخفاق الأخرى إلى DefaultFailureHandler
. يمكن تسجيل CustomFailureHandler
لدى Espresso
من خلال طريقة setUp()
للاختبار:
Kotlin
@Throws(Exception::class) override fun setUp() { super.setUp() getActivity() setFailureHandler(CustomFailureHandler( ApplicationProvider.getApplicationContext<Context>())) }
Java
@Override public void setUp() throws Exception { super.setUp(); getActivity(); setFailureHandler(new CustomFailureHandler( ApplicationProvider.getApplicationContext())); }
لمزيد من المعلومات، يُرجى الاطّلاع على واجهة
FailureHandler
و
Espresso.setFailureHandler()
.
استهداف النوافذ غير التلقائية
يتيح Android استخدام عدة نوافذ. عادةً ما تكون هذه المعلومات واضحة للمستخدمين
ومطوّر التطبيقات، ولكن في بعض الحالات تظهر نوافذ متعددة، على سبيل المثال عندما يتم رسم نافذة الإكمال التلقائي فوق نافذة التطبيق الرئيسية في
أداة البحث. لتبسيط الأمور، يستخدم Espresso تلقائيًا إرشاديًا لتخمين Window
الذي تنوي التفاعل معه. تكاد تكون هذه الإرشادات مفيدة على الدوام، ولكن في حالات نادرة، ستحتاج إلى تحديد النافذة التي يجب أن يستهدفها التفاعل. ويمكنك إجراء ذلك من خلال توفير أداة مطابقة نافذة الجذر
الخاصة بك أو أداة مطابقة Root
:
Kotlin
onView(withText("South China Sea")) .inRoot(withDecorView(not(`is`(getActivity().getWindow().getDecorView())))) .perform(click())
Java
onView(withText("South China Sea")) .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView())))) .perform(click());
وكما هي الحال مع السمة
ViewMatchers
،
نوفّر مجموعة من رموز
RootMatchers
التي يتم تقديمها مسبقًا.
يمكنك في أي وقت تنفيذ كائن Matcher
الخاص بك.
يمكنك إلقاء نظرة على نموذج MultiWindowTest على GitHub.
مطابقة رأس أو تذييل في عرض القائمة
تتم إضافة الرؤوس والتذييلات إلى ListViews
باستخدام الطريقتَين addHeaderView()
وaddFooterView()
. لضمان أن يعرف Espresso.onData()
كائن البيانات المطلوب مطابقته، احرص على تمرير قيمة كائن البيانات المُعدّة مسبقًا كمَعلمة ثانية إلى addHeaderView()
وaddFooterView()
. مثلاً:
Kotlin
const val FOOTER = "FOOTER" ... val footerView = layoutInflater.inflate(R.layout.list_item, listView, false) footerView.findViewById<TextView>(R.id.item_content).text = "count:" footerView.findViewById<TextView>(R.id.item_size).text = data.size.toString listView.addFooterView(footerView, FOOTER, true)
Java
public static final String FOOTER = "FOOTER"; ... View footerView = layoutInflater.inflate(R.layout.list_item, listView, false); footerView.findViewById<TextView>(R.id.item_content).setText("count:"); footerView.findViewById<TextView>(R.id.item_size).setText(String.valueOf(data.size())); listView.addFooterView(footerView, FOOTER, true);
بعد ذلك، يمكنك كتابة مطابقة للتذييل:
Kotlin
import org.hamcrest.Matchers.allOf import org.hamcrest.Matchers.instanceOf import org.hamcrest.Matchers.`is` fun isFooter(): Matcher<Any> { return allOf(`is`(instanceOf(String::class.java)), `is`(LongListActivity.FOOTER)) }
Java
import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; @SuppressWarnings("unchecked") public static Matcher<Object> isFooter() { return allOf(is(instanceOf(String.class)), is(LongListActivity.FOOTER)); }
ويعد تحميل العرض في الاختبار أمرًا بسيطًا:
Kotlin
import androidx.test.espresso.Espresso.onData import androidx.test.espresso.action.ViewActions.click import androidx.test.espresso.sample.LongListMatchers.isFooter fun testClickFooter() { onData(isFooter()) .perform(click()) // ... }
Java
import static androidx.test.espresso.Espresso.onData; import static androidx.test.espresso.action.ViewActions.click; import static androidx.test.espresso.sample.LongListMatchers.isFooter; public void testClickFooter() { onData(isFooter()) .perform(click()); // ... }
يمكنك إلقاء نظرة على النموذج الكامل للرمز الذي يمكنك العثور عليه في طريقة testClickFooter()
الخاصة بالموقع الإلكتروني
AdapterViewTest.java
على GitHub.