این سند نحوه راهاندازی انواع آزمایشهای رایج Espresso را شرح میدهد.
مطابقت دادن یک نما در کنار نمای دیگر
یک چیدمان میتواند شامل نماهای خاصی باشد که بهخودیخود منحصربهفرد نیستند. برای مثال، دکمه تماس تکراری در جدول مخاطبین میتواند همان R.id را داشته باشد، حاوی همان نوشتار باشد، و همان ویژگیهای دکمههای تماس دیگر را در سلسلهمراتب نما داشته باشد.
برای مثال، در این فعالیت، نمای دارای نوشتار "7" در چندین ردیف تکرار میشود:

اغلب، نمای غیریکتا با برچسب یکتایی که در کنار آن قرار دارد جفت میشود،
مثلاً نام مخاطب در کنار دکمه تماس. در این حالت،
میتوانید از تطبیقدهنده hasSibling() برای محدود کردن انتخابتان استفاده کنید:
کاتلین
onView(allOf(withText("7"), hasSibling(withText("item: 0")))) .perform(click())
جاوا
onView(allOf(withText("7"), hasSibling(withText("item: 0")))) .perform(click());
مطابقت دادن نمایی که داخل نوار اقدام است
ActionBarTestActivity دو نوار کنش متفاوت دارد: نوار کنش معمولی و نوار کنش بافتاری که از منوِ گزینهها ایجاد میشود. هر دو نوار اقدام یک مورد دارند که همیشه نمایان است و دو مورد دارند که فقط در منوی سرریز نمایان هستند. وقتی روی موردی کلیک میشود، TextView را به محتوای مورد کلیکشده تغییر میدهد.
مطابقت دادن نمادهای قابلمشاهده در هر دو نوار اقدام، همانطور که در تکهکد زیر نشان داده شده است، ساده است:
کاتلین
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"))) }
جاوا
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"))); }

کد برای نوار کنش بافتاری یکسان بهنظر میرسد:
کاتلین
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"))) }
جاوا
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 این کار را برای ما انجام میدهد.
برای نوار اقدام معمولی:
کاتلین
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"))) }
جاوا
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"))); }

این ویژگی در دستگاههای دارای دکمه منوِ سرریز سختافزاری به این صورت است:

برای نوار کنش بافتاری، این کار دوباره بسیار آسان است:
کاتلین
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"))) } }
جاوا
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 مشاهده کنید.
تأیید اینکه نمایی نمایش داده نمیشود
پساز انجام مجموعهای از کنشها، قطعاً میخواهید وضعیت میانای کاربر تحت آزمایش را تأیید کنید. گاهی اوقات، این ممکن است یک مورد منفی باشد، مانند زمانی که
چیزی اتفاق نمیافتد. بهخاطر داشته باشید که میتوانید هر تطبیقدهنده نمای hamcrest را بااستفاده از ViewAssertions.matches() به ViewAssertion تبدیل کنید.
در مثال زیر، ما مطابقتدهنده isDisplayed() را میگیریم و آن را بااستفاده از مطابقتدهنده استاندارد not() معکوس میکنیم:
کاتلین
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())))
جاوا
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() استفاده کنید:
کاتلین
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())
جاوا
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 استفاده میکنیم و سپس از تطبیقدهنده دیگری برای کار روی دادههای داخل نما استفاده میکنیم.
ابتدا مطابقتدهنده:
کاتلین
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 } } }
جاوا
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 را پیدا کنیم:
کاتلین
fun testDataItemNotInAdapter() { onView(withId(R.id.list)) .check(matches(not(withAdaptedData(withItemContent("item: 168"))))) } }
جاوا
@SuppressWarnings("unchecked") public void testDataItemNotInAdapter() { onView(withId(R.id.list)) .check(matches(not(withAdaptedData(withItemContent("item: 168"))))); } }
و ما یک ادعا داریم که اگر موردی برابر با «مورد: ۱۶۸» در نمای آداپتور با فهرست شناسه وجود داشته باشد، ناموفق خواهد بود.
برای دیدن نمونه کامل، به روش testDataItemNotInAdapter() در کلاس
AdapterViewTest.java
در GitHub نگاه کنید.
استفاده از گرداننده خرابی سفارشی
جایگزین کردن FailureHandler پیشفرض در Espresso با یک سفارشی امکان
رسیدگی به خطاهای اضافی یا متفاوت را فراهم میکند، مانند گرفتن نماگرفت یا ارسال
اطلاعات اشکالزدایی اضافی.
مثال CustomFailureHandlerTest نشان میدهد که چگونه یک کنترلکننده خرابی سفارشی را پیادهسازی کنید:
کاتلین
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) } } }
جاوا
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); } } }
این کنترلکننده خطا بهجای NoMatchingViewException، MySpecialException را برمیگرداند و همه خطاهای دیگر را به DefaultFailureHandler واگذار میکند. CustomFailureHandler را میتوان با Espresso در روش setUp() آزمایش ثبت کرد:
کاتلین
@Throws(Exception::class) override fun setUp() { super.setUp() getActivity() setFailureHandler(CustomFailureHandler( ApplicationProvider.getApplicationContext<Context>())) }
جاوا
@Override public void setUp() throws Exception { super.setUp(); getActivity(); setFailureHandler(new CustomFailureHandler( ApplicationProvider.getApplicationContext())); }
برای اطلاعات بیشتر، به میانای
FailureHandler
و
Espresso.setFailureHandler() مراجعه کنید.
هدفگیری پنجرههای غیرپیشفرض
Android از چند پنجره پشتیبانی میکند. معمولاً این موضوع برای کاربران و توسعهدهنده نرمافزار شفاف است، اما در برخی موارد چندین پنجره قابلمشاهده است، مثلاً وقتی پنجره تکمیل خودکار روی پنجره اصلی برنامه در ابزاره جستجو کشیده میشود. برای ساده کردن کارها، Espresso بهطور پیشفرض از روشی اکتشافی استفاده میکند تا
حدس بزند قصد دارید با کدام Window تعامل داشته باشید. این روش اکتشافی تقریباً همیشه کافی است؛ بااینحال، در موارد نادر، باید مشخص کنید که تعامل کدام پنجره را هدف قرار دهد. میتوانید این کار را با ارائه تطبیقدهنده پنجره ریشه خودتان یا تطبیقدهنده Root انجام دهید:
کاتلین
onView(withText("South China Sea")) .inRoot(withDecorView(not(`is`(getActivity().getWindow().getDecorView())))) .perform(click())
جاوا
onView(withText("South China Sea")) .inRoot(withDecorView(not(is(getActivity().getWindow().getDecorView())))) .perform(click());
همانند
ViewMatchers،
مجموعهای از
RootMatchers
ازپیش ارائهشده را ارائه میدهیم.
البته همیشه میتوانید شیء Matcher خودتان را پیادهسازی کنید.
به نمونه MultipleWindowTest در GitHub نگاهی بیندازید.
مطابقت دادن سرصفحه یا پانویس در نمای فهرستی
سرصفحهها و پانویسها بااستفاده از روشهای addHeaderView() و addFooterView() به ListViews اضافه میشوند. برای اطمینان از اینکه Espresso.onData() میداند کدام شیء داده را مطابقت دهد، حتماً مقدار شیء داده ازپیشتنظیمشده را بهعنوان پارامتر دوم به addHeaderView() و addFooterView() ارسال کنید. برای مثال:
کاتلین
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)
جاوا
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);
سپس میتوانید مطابقتدهندهای برای پانویس بنویسید:
کاتلین
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)) }
جاوا
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)); }
و بار کردن نما در آزمایش ساده است:
کاتلین
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()) // ... }
جاوا
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 قرار دارد ببینید.