این سند نحوه تکمیل وظایف معمول تست خودکار را با استفاده از اسپرسو API توضیح می دهد.
اسپرسو API نویسندگان آزمون را تشویق میکند تا در مورد کارهایی که کاربر در هنگام تعامل با برنامه انجام میدهد فکر کنند - مکانیابی عناصر UI و تعامل با آنها. در عین حال، این فریم ورک از دسترسی مستقیم به فعالیتها و نماهای برنامه جلوگیری میکند، زیرا نگه داشتن این اشیاء و کار بر روی آنها در خارج از رشته UI، منبع اصلی شل شدن تست است. بنابراین، متدهایی مانند getView()
و getCurrentActivity()
را در API اسپرسو نخواهید دید. همچنان میتوانید با اجرای زیرکلاسهای ViewAction
و ViewAssertion
خود، با خیال راحت روی نماها کار کنید.
اجزای اصلی اسپرسو شامل موارد زیر است:
- اسپرسو – نقطه ورود به تعامل با نماها (از طریق
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
با متنی که باید تأیید شود، یک 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()
برای بارگذاری نمای هدف استفاده کنید.
برای اطلاعات بیشتر در مورد استفاده از اسپرسو در تست های اندروید به منابع زیر مراجعه کنید.
- CustomMatcherSample : نحوه گسترش Espresso را برای مطابقت با ویژگی اشاره یک شی
EditText
نشان می دهد. - RecyclerViewSample : اقدامات
RecyclerView
برای اسپرسو. - (بیشتر...)