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

ב-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*
.

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

Robolectric

משתמשים ב-Robolectric כדי להריץ בדיקות ממשק משתמש של כתיבה או צפייה מבוססות-צפייה ב-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 בפרויקט testing-samples.

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 Emulator מגרסה 33.1.10 ואילך
  • מכשיר ו��ר��ואלי של Android ש��ועל עם API ברמה 24 ואילך

סינון הבדיקות

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

ההערה 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() {
        ...
    }
}

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

משתמשים בשיטה 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.

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

מסמכים

דוגמיות

שיעורי Lab