ספריות וכלים לבדיקת מסכים בגדלים שונים

ב-Android יש מגוון כלים וממשקי API שיכולים לעזור לכם ליצור בדיקות למסכים ולחלונות בגדלים שונים.

DeviceConfigurationOverride

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

   DeviceConfigurationOverride(
        DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp))
    ) {
        MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping.
    }
איור 1. שימוש ב-DeviceConfigurationOverride כדי להתאים פריסת טאבלט למכשיר קטן יותר של גורם צורה, כמו \*Now ב-Android*.

בנוסף, אפשר להשתמש ברכיב ה-Composable הזה כדי להגדיר את קנה המידה של הגופן, נושאים ומאפיינים אחרים שרוצים לבדוק בגדלים שונים של חלונות.

Robolectric

אפשר להשתמש ב-Robolectric כדי להריץ בדיקות ממשק משתמש מבוססות-תצוגה או בדיקות Compose ב-JVM מקומית – בלי צורך במכשירים או באמולטורים. אתם יכולים להגדיר את Robolectric כך שישתמש בגדלי מסך ספציפיים, בין היתר במאפיינים שימושיים אחרים.

בדוגמה הבאה מ-Now in Android, Robolectric מוגדר לדמות מסך בגודל 1,000x1,000dp ברזולוציה של 480dpi:

@RunWith(RobolectricTestRunner::class)
// Configure Robolectric to use a very large screen size that can fit all of the test sizes.
// This allows enough room to render the content under test without clipping or scaling.
@Config(qualifiers = "w1000dp-h1000dp-480dpi")
class NiaAppScreenSizesScreenshotTests { ... }

אפשר גם להגדיר את המאפיינים המתאימים מגוף הבדיקה, כמו בקטע הקוד הבא מהדוגמה Now in Android:

val (width, height, dpi) = ...

// Set qualifiers from specs.
RuntimeEnvironment.setQualifiers("w${width}dp-h${height}dp-${dpi}dpi")

שימו לב ש-RuntimeEnvironment.setQualifiers() מעדכן את המשאבים של המערכת והאפליקציה בהתאם להגדרה החדשה, אבל לא מפעיל פעולה כלשהי בפעילויות פעילות או ברכיבים אחרים.

מידע נוסף זמין במסמכים בנושא הגדרת המכשיר של Robolectric.

מכשירים בניהול Gradle

הפלאגין של Android Gradle למכשירים בניהול Gradle (GMD) מאפשר לכם להגדיר את המפרטים של הסימולטורים והמכשירים האמיתיים שבהם מריצים את הבדיקות המצוידות במדדים. כדי להטמיע אסטרטגיית בדיקה שבה צריך להריץ בדיקות מסוימות במסכים בגדלים מסוימים, אפשר ליצור מפרטים למכשירים עם מסכים בגדלים שונים. שימוש ב-GMD עם אינטגרציה רציפה (CI) מאפשר לוודא שהבדיקות המתאימות יפעלו לפי הצורך, להקצות ולהפעיל מכונות וירטואליות ולפשט את הגדרת ה-CI.

android {
    testOptions {
        managedDevices {
            devices {
                // Run with ./gradlew nexusOneApi30DebugAndroidTest.
                nexusOneApi30(com.android.build.api.dsl.ManagedVirtualDevice) {
                    device = "Nexus One"
                    apiLevel = 30
                    // Use the AOSP ATD image for better emulator performance
                    systemImageSource = "aosp-atd"
                }
                // Run with ./gradlew  foldApi34DebugAndroidTest.
                foldApi34(com.android.build.api.dsl.ManagedVirtualDevice) {
                    device = "Pixel Fold"
                    apiLevel = 34
                    systemImageSource = "aosp-atd"
                }
            }
        }
    }
}

בפרויקט בדיקות דגימות תוכלו למצוא כמה דוגמאות של GMD.

Firebase Test Lab

אתם יכולים להשתמש ב-Firebase Test Lab (FTL), או בשירות דומה של חוות במכשירים, כדי להריץ את הבדיקות במכשירים אמיתיים ספציפיים שאולי אין לכם גישה אליהם, כמו מכשירים מתקפלים או טאבלטים בגדלים שונים. Firebase Test Lab הוא שירות בתשלום עם תוכנית ללא תשלום. ב-FTL יש גם תמיכה בהרצת בדיקות במהדמנים. השירותים האלה משפרים את המהימנות והמהירות של בדיקות אינסטרומנטליות כי הם יכולים להקצות מכשירים ואמולטורים מראש.

מידע נוסף על שימוש ב-FTL עם GMD זמין במאמר התאמת הבדיקות למספר גדול יותר של מכשירים בניהול Gradle.

בדיקת סינון באמצעות הכלי להרצת בדיקות

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

אפשר להוסיף הערות לבדיקות מסוימות כדי להריץ אותן רק במכשירים מסוימים, ואז להעביר ארגומנט ל-AndroidJUnitRunner באמצעות הפקודה שמריצה את הבדיקות.

לדוגמה, אפשר ליצור הערות שונות:

annotation class TestExpandedWidth
annotation class TestCompactWidth

ולהשתמש בהם בבדיקות שונות:

class MyTestClass {

    @Test
    @TestExpandedWidth
    fun myExample_worksOnTablet() {
        ...
    }

    @Test
    @TestCompactWidth
    fun myExample_worksOnPortraitPhone() {
        ...
    }

}

לאחר מכן תוכלו להשתמש במאפיין android.testInstrumentationRunnerArguments.annotation כשמריצים את הבדיקות כדי לסנן בדיקות ספציפיות. לדוגמה, אם אתם משתמשים במכשירים שמנוהלים על ידי Gradle:

$ ./gradlew pixelTabletApi30DebugAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation='com.sample.TestExpandedWidth'

אם אתם לא משתמשים ב-GMD ומנהלים מכשירי אמולציה ב-CI, קודם עליכם לוודא שהמכשיר או האמולטור הנכונים מוכנים ומחוברים, ואז להעביר את הפרמטר לאחת מהפקודות של Gradle כדי להריץ בדיקות עם כלי למדידת ביצועים:

$ ./gradlew connectedAndroidTest -Pandroid.testInstrumentationRunnerArguments.annotation='com.sample.TestExpandedWidth'

שימו לב שאפשר גם לסנן בדיקות באמצעות מאפייני המכשיר ב-Espresso Device (ראו הקטע הבא).

מכשיר Espresso

משתמשים ב-Espresso Device כדי לבצע פעולות על אמולטורים בבדיקות באמצעות כל סוג של בדיקות מכשירים, כולל בדיקות של Espresso, Compose או UI Automator. הפעולות האלה יכולות לכלול הגדרה של גודל המסך או החלפת המצבים או המצבים המתקפלים של המכשיר. לדוגמה, אפשר לשלוט באמולטור מתקפל ולהגדיר אותו למצב 'על משטח, מסך למעלה'. Espresso Device מכיל גם כללים והערות של JUnit כדי לדרוש תכונות מסוימות:

@RunWith(AndroidJUnit4::class)
class OnDeviceTest {

    @get:Rule(order=1) val activityScenarioRule = activityScenarioRule<MainActivity>()

    @get:Rule(order=2) val screenOrientationRule: ScreenOrientationRule =
        ScreenOrientationRule(ScreenOrientation.PORTRAIT)

    @Test
    fun tabletopMode_playerIsDisplayed() {
        // Set the device to tabletop mode.
        onDevice().setTabletopMode()
        onView(withId(R.id.player)).check(matches(isDisplayed()))
    }
}

שימו לב ש-Espresso Device עדיין נמצא בשלב אלפא ועומד בדרישות הבאות:

  • פלאגין Android Gradle מגרסה 8.3 ואילך
  • אמולטור Android 33.1.10 ואילך
  • מכשיר וירטואלי של Android שפועל עם API ברמה 24 ואילך

סינון בדיקות

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

ההערה RequiresDeviceMode

אפשר להשתמש בהערה RequiresDeviceMode כמה פעמים כדי לציין בדיקה שתופעל רק אם כל הערכים של DeviceMode נתמכים במכשיר.

class OnDeviceTest {
    ...
    @Test
    @RequiresDeviceMode(TABLETOP)
    @RequiresDeviceMode(BOOK)
    fun tabletopMode_playerIdDisplayed() {
        // Set the device to tabletop mode.
        onDevice().setTabletopMode()
        onView(withId(R.id.player)).check(matches(isDisplayed()))
    }
}

נדרשת הערה לתצוגה

התווית RequiresDisplay מאפשרת לציין את רוחב המסך ואת הגובה שלו באמצעות סיווגים לפי גודל, שמגדירים את קטגוריות המאפיינים בהתאם לסיווגים הרשמיים של גודל החלון.

class OnDeviceTest {
    ...
    @Test
    @RequiresDisplay(EXPANDED, COMPACT)
    fun myScreen_expandedWidthCompactHeight() {
        ...
    }
}

שינוי הגודל של המסכים

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

@RunWith(AndroidJUnit4::class)
class ResizeDisplayTest {

    @get:Rule(order = 1) val activityScenarioRule = activityScenarioRule<MainActivity>()

    // Test rule for restoring device to its starting display size when a test case finishes.
    @get:Rule(order = 2) val displaySizeRule: DisplaySizeRule = DisplaySizeRule()

    @Test
    fun resizeWindow_compact() {
        onDevice().setDisplaySize(
            widthSizeClass = WidthSizeClass.COMPACT,
            heightSizeClass = HeightSizeClass.COMPACT
        )
        // Verify visual attributes or state restoration.
    }
}

כשמשנים את הגודל של מסך באמצעות setDisplaySize(), לא משפעים על הצפיפות של המכשיר. לכן, אם מאפיין לא נכנס למכשיר היעד, הבדיקה נכשלת עם UnsupportedDeviceOperationException. כדי למנוע את הפעלת הבדיקות במקרה כזה, משתמשים בהערה RequiresDisplay כדי לסנן אותן:

@RunWith(AndroidJUnit4::class)
class ResizeDisplayTest {

    @get:Rule(order = 1) var activityScenarioRule = activityScenarioRule<MainActivity>()

    // Test rule for restoring device to its starting display size when a test case finishes.
    @get:Rule(order = 2) var displaySizeRule: DisplaySizeRule = DisplaySizeRule()

    /**
     * Setting the display size to EXPANDED would fail in small devices, so the [RequiresDisplay]
     * annotation prevents this test from being run on devices outside the EXPANDED buckets.
     */
    @RequiresDisplay(
        widthSizeClass = WidthSizeClassEnum.EXPANDED,
        heightSizeClass = HeightSizeClassEnum.EXPANDED
    )
    @Test
    fun resizeWindow_expanded() {
        onDevice().setDisplaySize(
            widthSizeClass = WidthSizeClass.EXPANDED,
            heightSizeClass = HeightSizeClass.EXPANDED
        )
        // Verify visual attributes or state restoration.
    }
}

StateRestorationTester

המחלקה StateRestorationTester משמשת לבדיקה של שחזור המצב של רכיבים שניתנים ליצירה מחדש, בלי ליצור מחדש את הפעילויות. כך הבדיקות יהיו מהירות ואמינות יותר, כי יצירת מחדש של פעילות היא תהליך מורכב עם כמה מנגנוני סנכרון:

@Test
fun compactDevice_selectedEmailEmailRetained_afterConfigChange() {
    val stateRestorationTester = StateRestorationTester(composeTestRule)

    // Set content through the StateRestorationTester object.
    stateRestorationTester.setContent {
        MyApp()
    }

    // Simulate a config change.
    stateRestorationTester.emulateSavedInstanceStateRestore()
}

ספריית Window Testing

הספרייה Window Testing מכילה כלי עזר שיעזרו לכם לכתוב בדיקות שמסתמכות על תכונות שקשורות לניהול חלונות או מאמתות אותן, כמו הטמעת פעילות או תכונות מתקפלות. הארטיפקט זמין דרך Maven Repository של Google.

לדוגמה, אפשר להשתמש בפונקציה FoldingFeature() כדי ליצור FoldingFeature בהתאמה אישית, שאפשר להשתמש בו בתצוגות המקדימות של Compose. ב-Java, משתמשים בפונקציה createFoldingFeature().

בתצוגה המקדימה של Compose, אפשר להטמיע את FoldingFeature באופן הבא:

@Preview(showBackground = true, widthDp = 480, heightDp = 480)
@Composable private fun FoldablePreview() =
    MyApplicationTheme {
        ExampleScreen(
            displayFeatures = listOf(FoldingFeature(Rect(0, 240, 480, 240)))
        )
 }

בנוסף, אפשר לדמות תכונות תצוגה בבדיקות ממשק משתמש באמצעות הפונקציה TestWindowLayoutInfo(). בדוגמה הבאה מתבצעת סימולציה של FoldingFeature עם ציר אנכי HALF_OPENED במרכז המסך, ולאחר מכן בודקים אם הפריסה היא הצפויה:

פיתוח נייטיב

import androidx.window.layout.FoldingFeature.Orientation.Companion.VERTICAL
import androidx.window.layout.FoldingFeature.State.Companion.HALF_OPENED
import androidx.window.testing.layout.FoldingFeature
import androidx.window.testing.layout.TestWindowLayoutInfo
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule

@RunWith(AndroidJUnit4::class)
class MediaControlsFoldingFeatureTest {

    @get:Rule(order=1)
    val composeTestRule = createAndroidComposeRule<ComponentActivity>()

    @get:Rule(order=2)
    val windowLayoutInfoPublisherRule = WindowLayoutInfoPublisherRule()

    @Test
    fun foldedWithHinge_foldableUiDisplayed() {
        composeTestRule.setContent {
            MediaPlayerScreen()
        }

        val hinge = FoldingFeature(
            activity = composeTestRule.activity,
            state = HALF_OPENED,
            orientation = VERTICAL,
            size = 2
        )

        val expected = TestWindowLayoutInfo(listOf(hinge))
        windowLayoutInfoPublisherRule.overrideWindowLayoutInfo(expected)

        composeTestRule.waitForIdle()

        // Verify that the folding feature is detected and media controls shown.
        composeTestRule.onNodeWithTag("MEDIA_CONTROLS").assertExists()
    }
}

צפיות

import androidx.window.layout.FoldingFeature.Orientation
import androidx.window.layout.FoldingFeature.State
import androidx.window.testing.layout.FoldingFeature
import androidx.window.testing.layout.TestWindowLayoutInfo
import androidx.window.testing.layout.WindowLayoutInfoPublisherRule

@RunWith(AndroidJUnit4::class)
class MediaControlsFoldingFeatureTest {

    @get:Rule(order=1)
    val activityRule = ActivityScenarioRule(MediaPlayerActivity::class.java)

    @get:Rule(order=2)
    val windowLayoutInfoPublisherRule = WindowLayoutInfoPublisherRule()

    @Test
    fun foldedWithHinge_foldableUiDisplayed() {
        activityRule.scenario.onActivity { activity ->
            val feature = FoldingFeature(
                activity = activity,
                state = State.HALF_OPENED,
                orientation = Orientation.VERTICAL)
            val expected = TestWindowLayoutInfo(listOf(feature))
            windowLayoutInfoPublisherRule.overrideWindowLayoutInfo(expected)
        }

        // Verify that the folding feature is detected and media controls shown.
        onView(withId(R.id.media_controls)).check(matches(isDisplayed()))
    }
}

דוגמאות נוספות זמינות בפרויקט WindowManager.

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

מסמכים

דוגמיות

Codelabs