کتابخانه ها و ابزارهایی برای آزمایش اندازه های مختلف صفحه نمایش

اندروید ابزارها و APIهای متنوعی را ارائه می‌دهد که می‌توانند به شما در ایجاد تست برای اندازه‌های مختلف صفحه نمایش و پنجره کمک کنند.

لغو پیکربندی دستگاه

کامپوننت DeviceConfigurationOverride به شما امکان می‌دهد ویژگی‌های پیکربندی را برای آزمایش چندین اندازه صفحه نمایش و پنجره در طرح‌بندی‌های Compose نادیده بگیرید. Override کردن ForcedSize هر طرح‌بندی را در فضای موجود متناسب می‌کند، که به شما امکان می‌دهد هر تست رابط کاربری را روی هر اندازه صفحه نمایشی اجرا کنید. به عنوان مثال، می‌توانید از یک گوشی با فرم فاکتور کوچک برای اجرای تمام تست‌های رابط کاربری خود، از جمله تست‌های رابط کاربری برای گوشی‌های بزرگ، گوشی‌های تاشو و تبلت‌ها، استفاده کنید.

   DeviceConfigurationOverride(
        DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp))
    ) {
        MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping.
    }
شکل ۱. استفاده از DeviceConfigurationOverride برای جا دادن طرح‌بندی تبلت در داخل یک دستگاه با فرم فاکتور کوچکتر، مانند \*Now in Android*.

علاوه بر این، می‌توانید از این composable برای تنظیم مقیاس فونت، تم‌ها و سایر ویژگی‌هایی که ممکن است بخواهید در اندازه‌های مختلف پنجره آزمایش کنید، استفاده کنید.

روبولکتریک

از Robolectric برای اجرای تست‌های رابط کاربری Compose یا view-based روی 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 { ... }

همچنین می‌توانید qualifierها را از بدنه تست تنظیم کنید، همانطور که در این قطعه کد از مثال 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 اندروید با نام Gradle-managed devices (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 (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()))
    }
}

توجه داشته باشید که دستگاه اسپرسو هنوز در مرحله آلفا است و الزامات زیر را دارد:

  • افزونه اندروید گریدل ۸.۳ یا بالاتر
  • شبیه‌ساز اندروید ۳۳.۱.۱۰ یا بالاتر
  • دستگاه مجازی اندروید که API سطح ۲۴ یا بالاتر را اجرا می‌کند

آزمایش‌های فیلتر

دستگاه اسپرسو می‌تواند ویژگی‌های دستگاه‌های متصل را بخواند تا شما را قادر به فیلتر کردن تست‌ها با استفاده از حاشیه‌نویسی‌ها کند. اگر الزامات حاشیه‌نویسی‌شده برآورده نشوند، تست‌ها رد می‌شوند.

حاشیه‌نویسی RequiredDeviceMode

حاشیه‌نویسی 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 برای آزمایش بازیابی وضعیت برای کامپوننت‌های قابل ترکیب بدون ایجاد مجدد فعالیت‌ها استفاده می‌شود. این امر باعث می‌شود آزمایش‌ها سریع‌تر و قابل اعتمادتر شوند، زیرا بازسازی فعالیت یک فرآیند پیچیده با مکانیسم‌های همگام‌سازی متعدد است:

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

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

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

کتابخانه تست پنجره

کتابخانه‌ی تست پنجره شامل ابزارهایی است که به شما در نوشتن تست‌هایی که به ویژگی‌های مرتبط با مدیریت پنجره، مانند تعبیه‌ی فعالیت یا ویژگی‌های تاشو، متکی هستند یا آن‌ها را تأیید می‌کنند، کمک می‌کند. این مصنوع از طریق مخزن Maven گوگل در دسترس است.

برای مثال، می‌توانید از تابع FoldingFeature() برای تولید یک FoldingFeature سفارشی استفاده کنید که می‌توانید در پیش‌نمایش‌های Compose از آن استفاده کنید. در جاوا، از تابع 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 بیابید.

منابع اضافی

مستندات

نمونه‌ها

کدلبز