מידע בסיסי על אספרסו

במסמך הזה נסביר איך לבצע משימות בדיקה אוטומטיות נפוצות באמצעות 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, אפשר לעיין ב במקורות המידע הבאים.

דוגמיות