مكتبات وأدوات لاختبار أحجام الشاشات المختلفة

يوفّر نظام التشغيل Android مجموعة متنوعة من الأدوات وواجهات برمجة التطبيقات التي يمكن أن تساعدك في إنشاء اختبارات لأحجام مختلفة للشاشات والنوافذ.

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 لمحاكاة حجم شاشة يبلغ 1000×1000 وحدة بكسل مستقلة عن الكثافة بدقة 480 نقطة لكل بوصة:

@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 (GMD) تحديد مواصفات المحاكيات والأجهزة الحقيقية التي يتم تشغيل اختباراتك المزودة بأدوات عليها. إنشاء مواصفات للأجهزة ذات أحجام الشاشات المختلفة لتنفيذ استراتيجية اختبار يجب فيها إجراء اختبارات معيّنة على أحجام شاشات معيّنة باستخدام GMD مع التكامل المستمر (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 أو خدمة مشابهة لتوفير مجموعة من الأجهزة لتشغيل الاختبارات على أجهزة حقيقية معيّنة قد لا تتمكّن من الوصول إليها، مثل الأجهزة القابلة للطي أو الأجهزة اللوحية بأحجام مختلفة. ‫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" (راجِع القسم التالي) فلترة الاختبارات باستخدام خصائص الجهاز.

جهاز إعداد الإسبريسو

استخدِم 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 لا يزال في مرحلة الإصدار الأوّلي ويستوفي المتطلبات التالية:

  • الإصدار 8.3 من المكوّن الإضافي لنظام Gradle المتوافق مع Android أو إصدار أحدث
  • الإصدار 33.1.10 من "محاكي Android" أو إصدار أحدث
  • جهاز Android افتراضي يعمل بالمستوى 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 annotation

تتيح لك التعليق التوضيحي 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.

مراجع إضافية

المستندات

نماذج

الدروس التطبيقية حول الترميز