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

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

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

רובולקטרי

אפשר להשתמש ב-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

הפלאגין Gradle-managed devices (GMD) של Android Gradle מאפשרת להגדיר את המפרט של אמולטורים ומכשירים אמיתיים שהבדיקות האינסטרומנטליות שלך פועלות. אפשר ליצור מפרטי מכשירים בגדלים שונים של מסכים כדי להטמיע אסטרטגיית בדיקה שבה בדיקות מסוימות צריכות לפעול בגדלים מסוימים של מסכים. שימוש ב-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 (ראו הקטע הבא).

מכשיר אספרסו

משתמשים במכשיר אספרסו כדי לבצע פעולות על אמולטורים בבדיקות באמצעות כל בדיקות כלים אינסטרומנטליים, כולל בדיקות אספרסו, נייטיב או אוטומציה של ממשק המשתמש. הפעולות האלה עשויות לכלול הגדרת גודל המסך או החלפת מצבים או תנוחות של מכשיר מתקפל. לדוגמה, אפשר לשלוט במהדורת אמולטור מתקפלת ולהגדיר אותה למצב שולחני. מכשיר אספרסו מכיל גם כללים והערות של 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 33.1.10 ואילך
  • מכשיר וירטואלי של Android שפועל ב-API ברמה 24 ואילך

סינון בדיקות

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

הערת DeviceMode

אפשר להשתמש בהערה 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 מכילה כלי עזר שיעזרו לכם לכתוב בדיקות שמסתמכות על תכונות שקשורות לניהול חלונות או מאמתות אותן, כמו הטמעת פעילות או תכונות מתקפלות. פריט המידע שנוצר בתהליך הפיתוח (Artifact) זמין דרך Google Maven Repository.

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

בתצוגה מקדימה של פיתוח נייטיב, אפשר להטמיע את 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