במסמך הזה נסביר איך לבצע משימות בדיקה אוטומטיות נפוצות באמצעות Espresso API.
Espresso API מעודד את מחברי הבדיקה לחשוב על מה שמשתמש עשוי
בזמן אינטראקציה עם האפליקציה – איתור רכיבי ממשק משתמש ואינטראקציה
איתם. במקביל, תוכנת ה-framework מונעת גישה ישירה לפעילויות
והצפיות של האפליקציה כי מחזיקים את האובייקטים האלה
עליהם מחוץ לשרשור ממשק המשתמש הוא מקור עיקרי למהירות הבדיקה. לכן,
לא מוצגות שיטות כמו getView()
ו-getCurrentActivity()
ב-Espresso API.
עדיין אפשר לבצע פעולות על תצוגות באופן בטוח על ידי הטמעת קטגוריות משנה משלך של
ViewAction
וגם ViewAssertion
רכיבי API
הרכיבים העיקריים של אספרסו כוללים:
- Espresso – נקודת כניסה לאינטראקציות עם צפיות (דרך
onView()
וonData()
). חושף גם ממשקי API שלא בהכרח קשורים לתצוגה מפורטת כלשהי, בתורpressBack()
. - ViewMatchers – אוסף של אובייקטים שמטמיעים את
ממשק
Matcher<? super View>
. אפשר להעביר אחד או יותר מהפרטים האלה אל שיטהonView()
לאיתור תצוגה בהיררכיית התצוגות הנוכחית. - ViewActions – אוסף של
ViewAction
אובייקטים שניתן להעביר אל השיטהViewInteraction.perform()
, למשלclick()
. - ViewAssertions – אוסף של
ViewAssertion
אובייקטים שיכולים להיות עבר את השיטהViewInteraction.check()
. בדרך כלל משתמשים תואם טענת נכוֹנוּת (assertion), שמשתמשת בהתאמה של תצוגה כדי להצהיר על מצב התצוגה המפורטת שנבחרה.
דוגמה:
Kotlin
// 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()))
Java
// 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()
לוקחת
שצפויה להתאים לתצוגה אחת — ואחת בלבד — בתצוגה הנוכחית
ההיררכיה. התאמות הן עוצמתיות והם יהיו מוכרים לאנשים שהשתמשו
באמצעות Mockito או JUnit. אם אתם לא מכירים את משחקי המזלג,
מומלץ להתחיל מעיון מהיר
מצגת.
לעיתים קרובות לתצוגה הרצויה יש ערך R.id
ייחודי והתאמת withId
פשוטה
לצמצם את החיפוש בתצוגה. אבל יש הרבה מקרים לגיטימיים שבהם אתם
לא ניתן לקבוע את R.id
בזמן הפיתוח של הבדיקה. לדוגמה, התצוגה הספציפית
לא יכול להיות R.id
או R.id
אינו ייחודי. זה יכול להיות
האינסטרומנטציה בודקת שכתיבה היא שבירה ומסובכת, כי הדרך הרגילה
גישה לתצוגה – עם findViewById()
– לא עובדת. לכן, ייתכן
צריכים לגשת אל חברים פרטיים בפעילות או במקטע שמחזיקים את התצוגה, או
מוצאים מאגר עם R.id
מוכר ועוברים לתוכן שלו בשביל
תצוגה מסוימת.
אפליקציית Espresso מטפלת בבעיה הזו בצורה חלקה בכך שהיא מאפשרת לכם לצמצם את התצוגה
באמצעות אובייקטים קיימים מסוג ViewMatcher
או באמצעות אובייקטים בהתאמה אישית משלכם.
כדי לחפש תצוגה לפי R.id
שלה, פשוט קוראים ל-onView()
:
Kotlin
onView(withId(R.id.my_view))
Java
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!"
אפשר להשתמש בשילוב כדי לצמצם את החיפוש
תואמים:
Kotlin
onView(allOf(withId(R.id.my_view), withText("Hello!")))
Java
onView(allOf(withId(R.id.my_view), withText("Hello!")));
אתם יכולים גם לבחור שלא לבטל אף אחת מהתאמות ההתאמות:
Kotlin
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))))
Java
onView(allOf(withId(R.id.my_view), not(withText("Unwanted"))));
ראו ViewMatchers
בשביל התאמת הצפיות שסופקה על ידי Espresso.
שיקולים
- באפליקציה שמתנהלת בצורה תקינה, כל הצפיות שהמשתמש יכול לבצע איתן אינטראקציה
להכיל טקסט תיאורי או תיאור תוכן. צפייה
שיפור הנגישות של אפליקציות
פרטים. אם אין לך אפשרות לצמצם חיפוש באמצעות
withText()
אוwithContentDescription()
, כדאי להתייחס אליו כאל באג נגישות. - מומלץ להשתמש בכלי ההתאמה הכי פחות תיאורי שמוצא את התצוגה המפורטת הרצויה
עבור. אין לציין יותר מדי כי זה יאלץ את המסגרת לבצע יותר עבודה
הוא הכרחי. לדוגמה, אם ניתן לזהות תצוגה מפורטת לפי הטקסט שלה,
אינו חייב לציין שניתן להקצות את התצוגה גם מ-
TextView
. הרבה צפיות ב-R.id
של התצוגה צריכות להספיק. - אם תצוגת היעד נמצאת בתוך
AdapterView
, כמוListView
,GridView
אוSpinner
– יכול להיות שהשיטהonView()
לא תפעל. באלה במקרים כאלה, צריך להשתמש ב-onData()
במקום זאת.
ביצוע פעולה בתצוגה
לאחר שתמצאו כלי התאמה מתאים לתצוגת היעד, ייתכן
לבצע בו מכונות ViewAction
באמצעות שיטת הביצוע.
לדוגמה, כדי ללחוץ על התצוגה:
Kotlin
onView(...).perform(click())
Java
onView(...).perform(click());
אפשר לבצע יותר מפעולה אחת באמצעות קריאה אחת לביצוע:
Kotlin
onView(...).perform(typeText("Hello"), click())
Java
onView(...).perform(typeText("Hello"), click());
אם התצוגה שאיתה עובדים נמצאת בתוך ScrollView
(אנכי או
אופקי), כדאי לבצע את הפעולות הקודמות שמחייבות את התצוגה
מוצג - למשל click()
ו-typeText()
- עם scrollTo()
. הזה
מבטיח שהתצוגה תוצג לפני שממשיכים לפעולה השנייה:
Kotlin
onView(...).perform(scrollTo(), click())
Java
onView(...).perform(scrollTo(), click());
ראו ViewActions
עבור פעולות הצפייה שסופקו על ידי Espresso.
בדיקת טענות נכונות (assertions) של התצוגה
אפשר להחיל טענות נכוֹנוּת על התצוגה המפורטת שנבחרה באמצעות check()
. טענת הנכוֹנוּת (assertion) הנפוצה ביותר היא טענת הנכוֹנוּת (assertion) matches()
. הוא משתמש
אובייקט ViewMatcher
להצהרת מצב של התצוגה המפורטת שנבחרה.
לדוגמה, כדי לבדוק אם התצוגה מכילה את הטקסט "Hello!"
:
Kotlin
onView(...).check(matches(withText("Hello!")))
Java
onView(...).check(matches(withText("Hello!")));
אם ברצונך לטעון ש-"Hello!"
הוא התוכן של התצוגה המפורטת, הפעולות הבאות נחשבות כשיטות עבודה לא מומלצות:
Kotlin
// Don't use assertions like withText inside onView. onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()))
Java
// Don't use assertions like withText inside onView. onView(allOf(withId(...), withText("Hello!"))).check(matches(isDisplayed()));
לעומת זאת, אם אתם רוצים להצהיר שתצוגה עם הטקסט "Hello!"
היא
גלוי - לדוגמה לאחר שינוי בדגל החשיפה של תצוגות -
תקין.
הצגת בדיקה פשוטה של טענת נכוֹנוּת (assertion)
בדוגמה הזו, SimpleActivity
מכיל Button
וגם TextView
. כאשר
בלחיצה על הלחצן, התוכן של TextView
ישתנה ל-"Hello Espresso!"
.
כך בודקים את השימוש ב-Espresso:
לוחצים על הלחצן
השלב הראשון הוא לחפש נכס שיעזור לכם למצוא את הלחצן.
בלחצן ב-SimpleActivity
יש R.id
ייחודי, כמצופה.
Kotlin
onView(withId(R.id.button_simple))
Java
onView(withId(R.id.button_simple));
עכשיו מבצעים את הקליק:
Kotlin
onView(withId(R.id.button_simple)).perform(click())
Java
onView(withId(R.id.button_simple)).perform(click());
אימות הטקסט ב-TextView
גם לTextView
עם הטקסט לאימות יש R.id
ייחודי:
Kotlin
onView(withId(R.id.text_simple))
Java
onView(withId(R.id.text_simple));
עכשיו מאמתים את הטקסט של התוכן:
Kotlin
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")))
Java
onView(withId(R.id.text_simple)).check(matches(withText("Hello Espresso!")));
בדיקת טעינת הנתונים בתצוגות המתאם
AdapterView
הוא סוג מיוחד של ווידג'ט שטוען את הנתונים שלו באופן דינמי
מתאם. הדוגמה הכי נפוצה של AdapterView
היא ListView
. בתור
בניגוד לווידג'טים סטטיים כמו LinearLayout
, רק חלק
ניתן לטעון AdapterView
צאצאים להיררכיית התצוגות הנוכחית. קובץ
בחיפוש של onView()
לא יזוהו תצוגות שלא נטענו.
אפליקציית Espresso מטפלת בבעיה הזו באמצעות נקודת כניסה נפרדת של onData()
,
כאשר ניתן לטעון תחילה את פריט המתאם המדובר, ולמקד אותו לפני
פועלת עליו או על צאצאיו.
אזהרה: הטמעות מותאמות אישית של
ל-AdapterView
עשויות להיות בעיות עם onData()
אם הם מפרים חוזי ירושה, במיוחד
ממשק API של getItem()
. במקרים כאלה, דרך הפעולה הטובה ביותר היא
ארגון מחדש של קוד האפליקציה. אם אין לכם אפשרות לעשות זאת, תוכלו להטמיע
תואם ל-AdapterViewProtocol
בהתאמה אישית. לקבלת מידע נוסף, אפשר לבקר
צריך לבדוק את ברירת המחדל
הכיתה AdapterViewProtocols
סופקה על ידי Espresso.
בדיקה פשוטה של תצוגת מתאם
הבדיקה הפשוטה הזו מדגימה איך להשתמש ב-onData()
. SimpleActivity
מכיל
Spinner
עם כמה פריטים שמייצגים סוגי משקאות קפה. כאשר
הפריט נבחר, יש TextView
שמשתנה ל-"One %s a day!"
,
%s
מייצג את הפריט שנבחר.
מטרת הבדיקה היא לפתוח את Spinner
, לבחור פריט ספציפי
צריך לוודא שהשדה TextView
מכיל את הפריט. כי הכיתה Spinner
נמצאת
ב-AdapterView
, מומלץ להשתמש ב-onData()
במקום ב-onView()
למשך
שתואמים לפריט.
פתיחה של בחירת הפריט
Kotlin
onView(withId(R.id.spinner_simple)).perform(click())
Java
onView(withId(R.id.spinner_simple)).perform(click());
בחירת פריט
עבור בחירת הפריט, השדה Spinner
יוצר ListView
עם התוכן שלו.
התצוגה הזו עלולה להיות ארוכה מאוד, ויכול להיות שהרכיב לא ייכלל בתצוגה
ההיררכיה. באמצעות onData()
, אנחנו מאלצים את הרכיב הרצוי לתצוגה
ההיררכיה. הפריטים ברכיב Spinner
הם מחרוזות, ולכן אנחנו רוצים להתאים פריט
שווה למחרוזת "Americano"
:
Kotlin
onData(allOf(`is`(instanceOf(String::class.java)), `is`("Americano"))).perform(click())
Java
onData(allOf(is(instanceOf(String.class)), is("Americano"))).perform(click());
צריך לוודא שהטקסט נכון
Kotlin
onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))))
Java
onView(withId(R.id.spinnertext_simple)) .check(matches(withText(containsString("Americano"))));
ניפוי באגים
Espresso מספק מידע שימושי על תוצאות ניפוי הבאגים כשבדיקה נכשלת:
רישום
Espresso רושמת ביומן את כל הפעולות של תצוגות ל-Logcat. לדוגמה:
ViewInteraction: Performing 'single click' action on view with text: Espresso
הצגת ההיררכיה
המערכת של Espresso מדפיסה את היררכיית התצוגות בהודעה החריגה כאשר onView()
נכשל.
- אם
onView()
לא מוצא את תצוגת היעד,NoMatchingViewException
שודר. אפשר לבדוק את היררכיית התצוגות במחרוזת החריגה כדי לנתח למה ההתאמה לא תאמה לאף אחת מהצפיות. - אם הפונקציה
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 עבור הסבר.
אזהרות בתצוגת מתאם
חברת Espresso מזהירה את המשתמשים מפני הנוכחות של ווידג'טים מסוג AdapterView
. כאשר onView()
הפעולה גורמת לווידג'טים של NoMatchingViewException
ו-AdapterView
שכבר קיימת בהיררכיית התצוגות, הפתרון הנפוץ ביותר הוא להשתמש ב-onData()
.
הודעת החריגה תכלול אזהרה עם רשימה של תצוגות המתאמים.
אפשר להשתמש במידע הזה כדי להפעיל את onData()
לטעינה של תצוגת היעד.
מקורות מידע נוספים
למידע נוסף על השימוש ב-Espresso בבדיקות Android, אפשר לעיין ב במקורות המידע הבאים.
דוגמיות
- CustomMatcherSample:
תמונה שמציגה איך להרחיב את Espresso כך שיתאימו למאפיין הרמז של אובייקט
EditText
. - RecyclerViewSample:
RecyclerView
פעולות ל-Espresso. - (עוד...)