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

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

DeviceConfigurationOverride

הפונקציה הקומפוזבילית DeviceConfigurationOverride מאפשרת לשנות את מאפייני ההגדרות האישיות כדי לבדוק כמה גדלים של מסכים וחלונות בפריסות של Compose. השינוי של 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 in Android*.

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

Robolectric

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

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

@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"
                }
            }
        }
    }
}

בפרויקט testing-samples יש כמה דוגמאות ל-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

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

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

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

משתמשים ב-method‏ 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 של 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