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

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

DeviceConfigurationOverride

רכיב ה-Composable‏ 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 כדי להריץ בדיקות של ממשקי משתמש מבוססי-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-managed devices‏ (GMD) מאפשר להגדיר את המפרטים של האמולטורים והמכשירים האמיתיים שבהם מופעלות הבדיקות instrumented. יצירת מפרטים למכשירים עם גדלים שונים של מסכים כדי להטמיע אסטרטגיית בדיקה שבה בדיקות מסוימות צריכות להתבצע בגדלים מסוימים של מסכים. שימוש ב-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 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 Plugin מגרסה 8.3 ואילך
  • Android Emulator מגרסה 33.1.10 ואילך
  • מכשיר וירטואלי של Android עם API ברמה 24 ומעלה

סינון בדיקות

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

נדרשת הערה מסוג 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 של 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