فهرست‌های اسپرسو

Espresso سازوکارهایی برای پیمایش به یا اقدام روی یک مورد خاص برای دو نوع فهرست ارائه می‌دهد: نماهای آداپتور و نماهای بازیافت‌کننده.

هنگام کار با فهرست‌ها، به‌ویژه فهرست‌هایی که با شیء RecyclerView یا AdapterView ایجاد شده‌اند، ممکن است نمایی که به آن علاقه دارید حتی روی صفحه نباشد، زیرا فقط تعداد کمی از فرزندان نمایش داده می‌شوند و هنگام پیمایش بازیافت می‌شوند. در این مورد نمی‌توان از روش scrollTo() استفاده کرد چون به نمای موجود نیاز دارد.

تعامل با موارد فهرست نمای آداپتور

به‌جای استفاده از روش onView()، جستجویتان را با onData() شروع کنید و مطابقت‌دهنده‌ای برای داده‌هایی که از نمای موردنظرتان برای مطابقت پشتیبانی می‌کنند ارائه دهید. Espresso تمام کار پیدا کردن ردیف در شیء Adapter و نمایان کردن مورد در نمای دید را انجام می‌دهد.

تطبیق داده‌ها بااستفاده از تطبیق‌دهنده نمای سفارشی

فعالیت زیر حاوی ListView است که توسط SimpleAdapter پشتیبانی می‌شود که داده‌های هر ردیف را در شیء Map<String, Object> نگه می‌دارد.

فعالیت فهرست که درحال‌حاضر در صفحه نشان داده می‌شود حاوی فهرستی با ۲۳ مورد است. هر مورد دارای یک عدد است که به‌عنوان «رشته» ذخیره شده است و به عدد دیگری نگاشت شده است که به‌جای آن به‌عنوان «شیء» ذخیره شده است.

هر نقشه دو ورودی دارد: کلید "STR" که حاوی «رشته» است، مثل "item: x"، و کلید "LEN" که حاوی Integer است، که نشان‌دهنده طول محتوا است. برای مثال:

{"STR" : "item: 0", "LEN": 7}

کد مربوط به کلیک روی ردیف دارای «مورد: ۵۰» به این شکل است:

کاتلین

onData(allOf(`is`(instanceOf(Map::class.java)), hasEntry(equalTo("STR"),
        `is`("item: 50")))).perform(click())

جاوا

onData(allOf(is(instanceOf(Map.class)), hasEntry(equalTo("STR"), is("item: 50"))))
    .perform(click());

توجه داشته باشید که Espresso درصورت نیاز به‌طور خودکار در فهرست پیمایش می‌کند.

بیایید Matcher<Object> را در onData() ازهم جدا کنیم. روش is(instanceOf(Map.class)) جستجو را به هر موردی از AdapterView محدود می‌کند که توسط شیء Map پشتیبانی می‌شود.

در مورد ما، این جنبه از پُرسمان با هر ردیف از نمای فهرستی مطابقت دارد، اما ما می‌خواهیم به‌طور خاص روی یک مورد کلیک کنیم، بنابراین جستجو را با موارد زیر محدودتر می‌کنیم:

کاتلین

hasEntry(equalTo("STR"), `is`("item: 50"))

جاوا

hasEntry(equalTo("STR"), is("item: 50"))

این Matcher<String, Object> با هر Map که حاوی ورودی‌ای با کلید "STR" و مقدار "item: 50" باشد مطابقت خواهد داشت. چون کد جستجوی این مورد طولانی است و می‌خواهیم در مکان‌های دیگر از آن مجدداً استفاده کنیم، بیایید یک تطبیق‌دهنده withItemContent سفارشی برای آن بنویسیم:

کاتلین

return object : BoundedMatcher<Object, Map>(Map::class.java) {
    override fun matchesSafely(map: Map): Boolean {
        return hasEntry(equalTo("STR"), itemTextMatcher).matches(map)
    }

    override fun describeTo(description: Description) {
        description.appendText("with item content: ")
        itemTextMatcher.describeTo(description)
    }
}

جاوا

return new BoundedMatcher<Object, Map>(Map.class) {
    @Override
    public boolean matchesSafely(Map map) {
        return hasEntry(equalTo("STR"), itemTextMatcher).matches(map);
    }

    @Override
    public void describeTo(Description description) {
        description.appendText("with item content: ");
        itemTextMatcher.describeTo(description);
    }
};

از BoundedMatcher به‌عنوان پایه استفاده می‌کنید زیرا فقط با اشیای نوع Map مطابقت دارد. روش matchesSafely() را لغو کنید، مطابقت‌دهنده یافت‌شده در مرحله قبل را وارد کنید، و آن را با Matcher<String> که می‌توانید به‌عنوان آرگومان ارسال کنید مطابقت دهید. این کار به شما امکان می‌دهد با withItemContent(equalTo("foo")) تماس بگیرید. برای اختصار کد، می‌توانید مطابقت‌دهنده دیگری ایجاد کنید که ازقبل equalTo() را فراخوانی می‌کند و شیء String را می‌پذیرد:

کاتلین

fun withItemContent(expectedText: String): Matcher<Object> {
    checkNotNull(expectedText)
    return withItemContent(equalTo(expectedText))
}

جاوا

public static Matcher<Object> withItemContent(String expectedText) {
    checkNotNull(expectedText);
    return withItemContent(equalTo(expectedText));
}

اکنون کد کلیک کردن روی مورد ساده است:

کاتلین

onData(withItemContent("item: 50")).perform(click())

جاوا

onData(withItemContent("item: 50")).perform(click());

برای کد کامل این آزمایش، به روش testClickOnItem50() در کلاس AdapterViewTest و این تطبیق‌دهنده سفارشی LongListMatchers در GitHub نگاهی بیندازید.

مطابقت دادن با نمای فرزند خاص

نمونه بالا کلیکی را در وسط کل ردیف ListView صادر می‌کند. اما اگر بخواهیم روی فرزند خاصی از ردیف عملیات انجام دهیم چه؟ برای مثال، می‌خواهیم روی ستون دوم ردیف LongListActivity کلیک کنیم که String.length محتوا را در ستون اول نمایش می‌دهد:

در این مثال، استخراج کردن فقط طول یک محتوای خاص مفید خواهد بود. این فرایند شامل تعیین مقدار ستون دوم در یک ردیف است.

کافی است مشخصات onChildView() را به پیاده‌سازی DataInteraction اضافه کنید:

کاتلین

onData(withItemContent("item: 60"))
    .onChildView(withId(R.id.item_size))
    .perform(click())

جاوا

onData(withItemContent("item: 60"))
    .onChildView(withId(R.id.item_size))
    .perform(click());

تعامل با موارد فهرست نمای بازیافت‌کننده

اشیاء RecyclerView متفاوت از اشیاء AdapterView کار می‌کنند، بنابراین نمی‌توان از onData() برای تعامل با آن‌ها استفاده کرد.

برای تعامل با RecyclerViews بااستفاده از Espresso، می‌توانید از بسته espresso-contrib استفاده کنید که مجموعه‌ای از RecyclerViewActions دارد که می‌توان از آن برای پیمایش به موقعیت‌ها یا انجام کنش‌ها روی موارد استفاده کرد:

  • scrollTo() - به «نمای» منطبق پیمایش می‌کند، اگر وجود داشته باشد.
  • scrollToHolder() - به View Holder منطبق پیمایش می‌کند، اگر وجود داشته باشد.
  • scrollToPosition() - به موقعیت خاصی پیمایش می‌کند.
  • actionOnHolderItem() - «کنش نمایشی» را روی «نگهدارنده نمای» منطبق اجرا می‌کند.
  • actionOnItem() - «کنش نمای» منطبق را اجرا می‌کند.
  • actionOnItemAtPosition() - ViewAction را در نمای موقعیت خاصی اجرا می‌کند.

قطعه‌های زیر چند نمونه از نمونه RecyclerViewSample را نشان می‌دهند:

کاتلین

@Test(expected = PerformException::class)
fun itemWithText_doesNotExist() {
    // Attempt to scroll to an item that contains the special text.
    onView(ViewMatchers.withId(R.id.recyclerView))
        .perform(
            // scrollTo will fail the test if no item matches.
            RecyclerViewActions.scrollTo(
                hasDescendant(withText("not in the list"))
            )
        )
}

جاوا

@Test(expected = PerformException.class)
public void itemWithText_doesNotExist() {
    // Attempt to scroll to an item that contains the special text.
    onView(ViewMatchers.withId(R.id.recyclerView))
            // scrollTo will fail the test if no item matches.
            .perform(RecyclerViewActions.scrollTo(
                    hasDescendant(withText("not in the list"))
            ));
}

کاتلین

@Test fun scrollToItemBelowFold_checkItsText() {
    // First, scroll to the position that needs to be matched and click on it.
    onView(ViewMatchers.withId(R.id.recyclerView))
        .perform(
            RecyclerViewActions.actionOnItemAtPosition(
                ITEM_BELOW_THE_FOLD,
                click()
            )
        )

    // Match the text in an item below the fold and check that it's displayed.
    val itemElementText = "${activityRule.activity.resources
        .getString(R.string.item_element_text)} ${ITEM_BELOW_THE_FOLD.toString()}"
    onView(withText(itemElementText)).check(matches(isDisplayed()))
}

جاوا

@Test
public void scrollToItemBelowFold_checkItsText() {
    // First, scroll to the position that needs to be matched and click on it.
    onView(ViewMatchers.withId(R.id.recyclerView))
            .perform(RecyclerViewActions.actionOnItemAtPosition(ITEM_BELOW_THE_FOLD,
            click()));

    // Match the text in an item below the fold and check that it's displayed.
    String itemElementText = activityRule.getActivity().getResources()
            .getString(R.string.item_element_text)
            + String.valueOf(ITEM_BELOW_THE_FOLD);
    onView(withText(itemElementText)).check(matches(isDisplayed()));
}

کاتلین

@Test fun itemInMiddleOfList_hasSpecialText() {
    // First, scroll to the view holder using the isInTheMiddle() matcher.
    onView(ViewMatchers.withId(R.id.recyclerView))
        .perform(RecyclerViewActions.scrollToHolder(isInTheMiddle()))

    // Check that the item has the special text.
    val middleElementText = activityRule.activity.resources
            .getString(R.string.middle)
    onView(withText(middleElementText)).check(matches(isDisplayed()))
}

جاوا

@Test
public void itemInMiddleOfList_hasSpecialText() {
    // First, scroll to the view holder using the isInTheMiddle() matcher.
    onView(ViewMatchers.withId(R.id.recyclerView))
            .perform(RecyclerViewActions.scrollToHolder(isInTheMiddle()));

    // Check that the item has the special text.
    String middleElementText =
            activityRule.getActivity().getResources()
            .getString(R.string.middle);
    onView(withText(middleElementText)).check(matches(isDisplayed()));
}

منابع بیشتر

برای اطلاعات بیشتر درباره استفاده از فهرست‌های Espresso در آزمایش‌های Android، به منابع زیر مراجعه کنید.

نمونه‌ها

  • DataAdapterSample: نقطه ورود onData() را برای Espresso، برای فهرست‌ها و اشیاء AdapterView نمایش می‌دهد.