اصول اولیه اسپرسو

این سند نحوه تکمیل وظایف معمول تست خودکار را با استفاده از اسپرسو API توضیح می دهد.

اسپرسو API نویسندگان آزمون را تشویق می‌کند تا در مورد کارهایی که کاربر در هنگام تعامل با برنامه انجام می‌دهد فکر کنند - مکان‌یابی عناصر UI و تعامل با آنها. در عین حال، این فریم ورک از دسترسی مستقیم به فعالیت‌ها و نماهای برنامه جلوگیری می‌کند، زیرا نگه داشتن این اشیاء و کار بر روی آن‌ها در خارج از رشته UI، منبع اصلی شل شدن تست است. بنابراین، متدهایی مانند getView() و getCurrentActivity() را در API اسپرسو نخواهید دید. همچنان می‌توانید با اجرای زیرکلاس‌های ViewAction و ViewAssertion خود، با خیال راحت روی نماها کار کنید.

اجزای API

اجزای اصلی اسپرسو شامل موارد زیر است:

  • اسپرسو – نقطه ورود به تعامل با نماها (از طریق onView() و onData() ). همچنین APIهایی را که لزوماً به هیچ دیدگاهی مانند pressBack() مرتبط نیستند، نشان می دهد.
  • ViewMatchers – مجموعه ای از اشیاء که Matcher<? super View> رابط. شما می توانید یک یا چند مورد از اینها را به متد onView() بفرستید تا یک view را در سلسله مراتب view فعلی قرار دهید.
  • ViewActions – مجموعه ای از اشیاء ViewAction که می توانند به متد ViewInteraction.perform() ارسال شوند، مانند click() .
  • ViewAssertions – مجموعه ای از اشیاء ViewAssertion که می توان از روش ViewInteraction.check() عبور داد. بیشتر اوقات، از ادعای مطابقت استفاده می‌کنید، که از یک تطبیق View برای اثبات وضعیت نمای انتخابی فعلی استفاده می‌کند.

مثال:

کاتلین
// 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()))
جاوا
// 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() یک تطبیق دهنده hamcrest می گیرد که انتظار می رود با یک و تنها یک نمای در سلسله مراتب نمای فعلی مطابقت داشته باشد. Match ها قدرتمند هستند و برای کسانی که از آنها با Mockito یا JUnit استفاده کرده اند آشنا خواهند بود. اگر با همکرست همسان آشنایی ندارید، پیشنهاد می کنیم با نگاهی گذرا به این ارائه شروع کنید.

اغلب نمای مورد نظر دارای یک R.id منحصر به فرد است و یک تطبیق ساده withId جستجوی view را محدود می کند. با این حال، موارد قانونی زیادی وجود دارد که نمی‌توانید R.id در زمان توسعه آزمایش تعیین کنید. برای مثال، نمای خاص ممکن است R.id نداشته باشد یا R.id منحصر به فرد نباشد. این می تواند تست های ابزار دقیق معمولی را شکننده و برای نوشتن پیچیده کند، زیرا روش معمولی برای دسترسی به view - با findViewById() - کار نمی کند. بنابراین، ممکن است لازم باشد به اعضای خصوصی Activity یا Fragment که نمای را نگه می‌دارند دسترسی داشته باشید یا یک محفظه با R.id شناخته شده پیدا کنید و به محتوای آن برای نمای خاص بروید.

اسپرسو با استفاده از اشیاء ViewMatcher موجود یا موارد سفارشی خود، این مشکل را به خوبی حل می کند.

یافتن یک view توسط R.id آن به سادگی فراخوانی onView() است:

کاتلین
onView(withId(R.id.my_view))
جاوا
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!" . می توانید از این برای محدود کردن جستجوی خود با استفاده از تطبیق های ترکیبی استفاده کنید:

کاتلین
onView(allOf(withId(R.id.my_view), withText("Hello!")))
جاوا
onView(allOf(withId(R.id.my_view), withText("Hello!")));

همچنین می توانید انتخاب کنید که هیچ یک از تطابق ها معکوس نشود:

کاتلین
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))
جاوا
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))));

برای مشاهده مطابقت‌های ارائه شده توسط Espresso به ViewMatchers مراجعه کنید.

ملاحظات
  • در یک برنامه کاربردی، همه نماهایی که کاربر می تواند با آنها تعامل داشته باشد، باید یا حاوی متن توصیفی باشد یا یک توضیح محتوا داشته باشد. برای جزئیات بیشتر به دسترسی بیشتر برنامه ها مراجعه کنید. اگر نمی‌توانید جستجو را با استفاده از withText() یا withContentDescription() محدود کنید، آن را به عنوان یک اشکال دسترسی در نظر بگیرید.
  • از کمترین تطبیق توصیفی استفاده کنید که نمای مورد نظر شما را پیدا کند. بیش از حد مشخص نکنید زیرا این کار چارچوب را مجبور می کند تا بیش از آنچه لازم است کار کند. به عنوان مثال، اگر یک نما به طور منحصر به فرد با متن آن قابل شناسایی است، لازم نیست مشخص کنید که نما از TextView نیز قابل انتساب باشد. برای بسیاری از نماها، R.id نما باید کافی باشد.
  • اگر نمای هدف در داخل AdapterView باشد - مانند ListView ، GridView یا Spinner - ممکن است متد onView() کار نکند. در این موارد، به جای آن باید از onData() استفاده کنید.
یک عمل را روی یک نما انجام دهید

هنگامی که یک تطبیق مناسب برای نمای هدف پیدا کردید، می‌توانید نمونه‌هایی از ViewAction را با استفاده از روش اجرا روی آن انجام دهید.

به عنوان مثال، برای کلیک بر روی نمایش:

کاتلین
onView(...).perform(click())
جاوا
onView(...).perform(click());

با یک فراخوانی می توانید بیش از یک عمل را اجرا کنید:

کاتلین
onView(...).perform(typeText("Hello"), click())
جاوا
onView(...).perform(typeText("Hello"), click());

اگر نمایی که با آن کار می کنید در داخل یک ScrollView (عمودی یا افقی) قرار دارد، اقدامات قبلی را در نظر بگیرید که نیاز به نمایش آن دارند – مانند click() و typeText() – با scrollTo() . این تضمین می کند که نمای قبل از اقدام به عمل دیگر نمایش داده می شود:

کاتلین
onView(...).perform(scrollTo(), click())
جاوا
onView(...).perform(scrollTo(), click());

برای مشاهده عملکردهای ارائه شده توسط اسپرسو به ViewActions مراجعه کنید.

اظهارنظرهای مشاهده را بررسی کنید

اظهارات را می توان با متد check() در نمای فعلی انتخاب شده اعمال کرد. بیشترین استفاده از ادعای matches() است. از یک شی ViewMatcher برای اثبات وضعیت نمای انتخاب شده فعلی استفاده می کند.

به عنوان مثال، برای بررسی اینکه یک view دارای متن "Hello!" :

کاتلین
onView(...).check(matches(withText("Hello!")))
جاوا
onView(...).check(matches(withText("Hello!")));

اگر می خواهید ادعا کنید که "Hello!" محتوای این دیدگاه است، موارد زیر عمل بد تلقی می شود:

کاتلین
// Don't use assertions like withText inside onView.
onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()))
جاوا
// Don't use assertions like withText inside onView.
onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()));

از سوی دیگر، اگر می خواهید ادعا کنید که نمایی با متن "Hello!" قابل مشاهده است - به عنوان مثال پس از تغییر پرچم مشاهده نماها - کد خوب است.

مشاهده آزمون ادعای ساده

در این مثال، SimpleActivity شامل یک Button و یک TextView است. با کلیک بر روی دکمه، محتوای TextView به "Hello Espresso!" .

در اینجا نحوه آزمایش این مورد با اسپرسو آمده است:

روی دکمه کلیک کنید

اولین قدم این است که به دنبال خاصیتی باشید که به یافتن دکمه کمک کند. همانطور که انتظار می رود دکمه موجود در SimpleActivity دارای یک R.id منحصر به فرد است.

کاتلین
onView(withId(R.id.button_simple))
جاوا
onView(withId(R.id.button_simple));

حالا برای انجام کلیک:

کاتلین
onView(withId(R.id.button_simple)).perform(click())
جاوا
onView(withId(R.id.button_simple)).perform(click());
متن TextView را تأیید کنید

TextView با متنی که باید تأیید شود، یک R.id منحصر به فرد نیز دارد:

کاتلین
onView(withId(R.id.text_simple))
جاوا
onView(withId(R.id.text_simple));

حال برای تایید متن محتوا:

کاتلین
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")))
جاوا
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));
بارگیری داده ها را در نمای آداپتور بررسی کنید

AdapterView نوع خاصی از ویجت است که داده های خود را به صورت پویا از یک آداپتور بارگیری می کند. رایج ترین نمونه AdapterView ListView است. برخلاف ویجت‌های ثابت مانند LinearLayout ، تنها زیر مجموعه‌ای از فرزندان AdapterView ممکن است در سلسله مراتب نمای فعلی بارگیری شوند. یک جستجوی ساده onView() نماهایی را که در حال حاضر بارگذاری نشده اند پیدا نمی کند.

اسپرسو با ارائه یک نقطه ورودی جداگانه onData() که قادر است ابتدا آیتم آداپتور مورد نظر را بارگیری کند، آن را قبل از کار بر روی آن یا هر یک از فرزندانش در کانون توجه قرار می دهد.

اخطار: پیاده‌سازی‌های سفارشی AdapterView می‌توانند با متد onData() مشکل داشته باشند اگر قراردادهای وراثت، به‌ویژه getItem() API را بشکنند. در چنین مواردی، بهترین اقدام این است که کد برنامه خود را تغییر دهید. اگر نمی توانید این کار را انجام دهید، می توانید یک AdapterViewProtocol سفارشی منطبق را پیاده سازی کنید. برای اطلاعات بیشتر، نگاهی به کلاس AdapterViewProtocols پیش فرض ارائه شده توسط Espresso بیندازید.

آزمایش ساده نمایش آداپتور

این تست ساده نحوه استفاده از onData() را نشان می دهد. SimpleActivity شامل یک Spinner با چند آیتم است که نشان دهنده انواع نوشیدنی های قهوه است. هنگامی که یک مورد انتخاب می شود، یک TextView وجود دارد که به "One %sa day!" ، جایی که %s نشان دهنده مورد انتخاب شده است.

هدف از این آزمایش باز کردن Spinner ، انتخاب یک مورد خاص و بررسی اینکه TextView حاوی آیتم است. از آنجایی که کلاس Spinner مبتنی بر AdapterView است، توصیه می‌شود از onData() به جای onView() برای تطبیق آیتم استفاده کنید.

انتخاب مورد را باز کنید
کاتلین
onView(withId(R.id.spinner_simple)).perform(click())
جاوا
onView(withId(R.id.spinner_simple)).perform(click());
یک مورد را انتخاب کنید

برای انتخاب مورد، Spinner یک ListView با محتویات آن ایجاد می کند. این نما می تواند بسیار طولانی باشد و ممکن است این عنصر به سلسله مراتب view کمک نکند. با استفاده از onData() عنصر مورد نظر خود را وارد سلسله مراتب view می کنیم. آیتم‌های موجود در Spinner رشته‌هایی هستند، بنابراین می‌خواهیم آیتمی را با رشته "Americano" مطابقت دهیم:

کاتلین
onData(allOf(`is`(instanceOf(String::class.java)),
        `is`("Americano"))).perform(click())
جاوا
onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());
تأیید کنید متن صحیح است
کاتلین
onView(withId(R.id.spinnertext_simple))
    .check(matches(withText(containsString("Americano"))))
جاوا
onView(withId(R.id.spinnertext_simple))
    .check(matches(withText(containsString("Americano"))));
اشکال زدایی

اسپرسو اطلاعات مفیدی برای اشکال زدایی در صورت شکست یک تست ارائه می دهد:

ورود به سیستم

اسپرسو تمام اکشن‌های نمایش را به logcat ثبت می‌کند. به عنوان مثال:

ViewInteraction: Performing 'single click' action on view with text: Espresso
مشاهده سلسله مراتب

اسپرسو سلسله مراتب view را در پیام استثنا چاپ می کند وقتی که onView() ناموفق باشد.

  • اگر onView() نمای هدف را پیدا نکند، یک NoMatchingViewException پرتاب می شود. می‌توانید سلسله‌مراتب view را در رشته استثنا بررسی کنید تا بررسی کنید که چرا تطبیق با هیچ نما مطابقت ندارد.
  • اگر 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 Studio برای توضیح مفید است.

هشدارهای نمای آداپتور

اسپرسو در مورد وجود ویجت های AdapterView به کاربران هشدار می دهد. هنگامی که یک عملیات onView() یک NoMatchingViewException می اندازد و ویجت های AdapterView در سلسله مراتب view وجود دارند، رایج ترین راه حل استفاده onData() است. پیام استثنا شامل یک هشدار با لیستی از نماهای آداپتور خواهد بود. می توانید از این اطلاعات برای فراخوانی onData() برای بارگذاری نمای هدف استفاده کنید.

منابع اضافی

برای اطلاعات بیشتر در مورد استفاده از اسپرسو در تست های اندروید به منابع زیر مراجعه کنید.

نمونه ها