יכולת פעולה הדדית

‫Compose משתלב עם סביבות בדיקה נפוצות.

יכולת פעולה הדדית עם Espresso

באפליקציה היברידית, אפשר למצוא רכיבי Compose בתוך היררכיות של תצוגות ותצוגות בתוך רכיבי Compose (דרך הרכיב AndroidView).

לא צריך לבצע פעולות מיוחדות כדי להתאים בין שני הסוגים. אתם מתאימים תצוגות באמצעות onView של Espresso, ורכיבי Compose באמצעות ComposeTestRule.

@Test
fun androidViewInteropTest() {
    // Check the initial state of a TextView that depends on a Compose state.
    Espresso.onView(withText("Hello Views")).check(matches(isDisplayed()))
    // Click on the Compose button that changes the state.
    composeTestRule.onNodeWithText("Click here").performClick()
    // Check the new value.
    Espresso.onView(withText("Hello Compose")).check(matches(isDisplayed()))
}

הוספת סמנטיקה ברמת התצוגה לבדיקות יכולת פעולה הדדית של פיתוח נייטיב

הגדרת היקף החיפושים ב-Compose לתצוגות ספציפיות

כשמעבירים ממשקי משתמש מורכבים ל-Compose, יכול להיות שתיתקלו ברכיבי Compose זהים שמוטמעים בתוך כמה תצוגות Android מסורתיות – למשל בתוך RecyclerView או ViewPager. בתרחישים כאלה, חיפוש רגיל ב-Compose כמו onNodeWithText("Save") עלול להיכשל עם השגיאה "Multiple nodes found".

במקום לשנות את קוד הייצור כדי להוסיף תגי בדיקה דינמיים שיבדילו בין הרכיבים האלה, אתם יכולים להגדיר את היקף הבדיקה של Compose ישירות לתצוגת Android ספציפית.

משתמשים ב-API של onRootWithViewInteraction בכלל הבדיקה. הפונקציה הזו מקבלת ViewInteraction של Espresso, ומאפשרת לכם להשתמש ב-Espresso כדי לבודד תצוגת קונטיינר ספציפית ולבצע אינטראקציות של Compose רק בתוך ההיררכיה הזו.

אינטראקציה עם פריט ברשימה

אם אתם צריכים לבצע אינטראקציה עם רכיב Compose בתוך שורה ספציפית ב-RecyclerView, אתם יכולים להשתמש ב-Espresso כדי לאתר את השורה, ואז להגדיר את היקף האינטראקציה של Compose לשורה הזו. הפעולה הזו מתעלמת מרכיבי Compose זהים בכל שאר השורות.

@Test
fun testComposeButtonInsideRecyclerViewItem() = runComposeUiTest {
    // Scroll to the desired position using Espresso
    Espresso.onView(withId(recyclerViewId))
        .perform(RecyclerViewActions.scrollToPosition<MyViewHolder>(3))

    // Define an Espresso ViewInteraction that uniquely identifies the row
    val rowView = Espresso.onView(
        allOf(
            withId(rootViewId),
            hasDescendant(withText("Item #3"))
        )
    )

    // Scope the Compose search strictly to that specific row View
    onRootWithViewInteraction(rowView)
        .onNode(hasText("Like"))
        .performClick()
}

פתרון בעיות של חוסר בהירות ב-ViewPagers

כשיש בזיכרון כמה מקטעים עם פריסות Compose זהות בו-זמנית, אפשר להגדיר את היקף החיפוש למזהה התצוגה ברמה הבסיסית (root) של המקטע הספציפי כדי למנוע אי-בהירות בהתאמה.

@Test
fun testComposeButtonInsideViewPagerItem() = runComposeUiTest {
    // Swipe to the desired page using Espresso
    Espresso.onView(withId(viewPagerViewId)).perform(swipeLeft())

    // Identify the specific container view using Espresso
    val fragmentB = Espresso.onView(withId(fragmentRootViewId))

    // The generic text "Save" is now unique within this view scope
    onRootWithViewInteraction(fragmentB)
        .onNode(hasText("Save"))
        .assertIsDisplayed()
}

יכולת פעולה הדדית עם UiAutomator

כברירת מחדל, אפשר לגשת לתכנים קומפוזביליים מ-UiAutomator רק באמצעות התיאורים הנוחים שלהם (טקסט מוצג, תיאור תוכן וכו'). אם רוצים לגשת לכל רכיב Composable שמשתמש ב-Modifier.testTag, צריך להפעיל את המאפיין הסמנטי testTagsAsResourceId בעץ המשנה של רכיב ה-Composable הספציפי. הפעלת ההתנהגות הזו שימושית לרכיבים קומפוזביליים שאין להם כינוי ייחודי אחר, כמו רכיבים קומפוזביליים שניתן לגלול (לדוגמה, LazyColumn).

כדי להבטיח שכל רכיבי ה-Composable בתצוגת העץ עם Modifier.testTag יהיו נגישים מ-UiAutomator, צריך להפעיל את המאפיין הסמנטי רק פעם אחת ברמה גבוהה בהיררכיית רכיבי ה-Composable.

Scaffold(
    // Enables for all composables in the hierarchy.
    modifier = Modifier.semantics {
        testTagsAsResourceId = true
    }
){
    // Modifier.testTag is accessible from UiAutomator for composables nested here.
    LazyColumn(
        modifier = Modifier.testTag("myLazyColumn")
    ){
        // Content
    }
}

כל רכיב Composable עם Modifier.testTag(tag) יכול להיות נגיש באמצעות By.res(resourceName), עם אותו tag כמו resourceName.

val device = UiDevice.getInstance(getInstrumentation())

val lazyColumn: UiObject2 = device.findObject(By.res("myLazyColumn"))
// Some interaction with the lazyColumn.

מקורות מידע נוספים

  • בדיקת אפליקציות ב-Android: דף הנחיתה הראשי של בדיקות ב-Android מספק תצוגה רחבה יותר של העקרונות והשיטות לבדיקה.
  • יסודות הבדיקה: מידע נוסף על מושגי הליבה של בדיקת אפליקציה ל-Android.
  • בדיקות מקומיות: אתם יכולים להריץ בדיקות מסוימות באופן מקומי, בתחנת העבודה שלכם.
  • בדיקות אינסטרומנטציה: מומלץ להריץ גם בדיקות אינסטרומנטציה. כלומר, בדיקות שפועלות ישירות במכשיר.
  • אינטגרציה רציפה (CI): אינטגרציה רציפה מאפשרת לכם לשלב את הבדיקות בצינור עיבוד הנתונים לפריסה.
  • בדיקה של גדלי מסך שונים: יש כל כך הרבה מכשירים זמינים למשתמשים, לכן כדאי לבדוק גדלי מסך שונים.
  • ‫Espresso: למרות שהכלי הזה מיועד לממשקי משתמש שמבוססים על תצוגות, הידע ב-Espresso עדיין יכול לעזור בהיבטים מסוימים של בדיקות ב-Compose.