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

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

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 لضبط تنسيق جهاز لوحي في جهاز أصغر حجمًا، كما هو الحال في \*الآن في Android*

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

الألعاب السلسة

استخدِم Robolectric لتشغيل اختبارات واجهة المستخدم المستندة إلى Compose أو العرض على JVM محليًا، بدون الحاجة إلى أجهزة أو محاكيات. يمكنك ضبط Robolectric لاستخدام أحجام شاشة معيّنة، بالإضافة إلى خصائص مفيدة أخرى.

في المثال التالي من الميزات الجديدة في Android، تم ضبط Robolectric لمحاكاة حجم شاشة يبلغ 1000x1000 dp بدرجة دقة 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 { ... }

يمكنك أيضًا ضبط المؤهّلات من نص الاختبار كما هو موضّح في المقتطف التالي من مثال الميزات المتوفّرة الآن على Android:

val (width, height, dpi) = ...

// Set qualifiers from specs.
RuntimeEnvironment.setQualifiers("w${width}dp-h${height}dp-${dpi}dpi")

يُرجى العِلم أنّ RuntimeEnvironment.setQualifiers() يعدّل موارد النظام والتطبيق باستخدام الإعدادات الجديدة، ولكنّه لا يُشغّل أي إجراء في الأنشطة النشطة أو المكوّنات الأخرى.

يمكنك قراءة المزيد في وثائق ضبط الجهاز Robolectric.

الأجهزة المُدارة من Gradle

يتيح لك المكوّن الإضافي الأجهزة المُدارة من Gradle (GMD) في Android Gradle تحديد مواصفات المحاكيات والأجهزة الحقيقية التي يتم فيها تنفيذ اختباراتك المزوّدة بأدوات قياس الأداء. إنشاء مواصفات للأجهزة التي تتضمَّن لأحجام الشاشات المختلفة لتنفيذ استراتيجية اختبار حيث يجب أن تكون بعض الاختبارات أن يتم تشغيلها على أحجام شاشات معينة. باستخدام أداة 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 (FTL)، أو خدمة مزرعة مشابهة للأجهزة، لتشغيل اختبارات على أجهزة حقيقية محدّدة قد لا تتمكن من الوصول إليها، مثل الهواتف القابلة للطي أو الأجهزة اللوحية بأحجام مختلفة. "مركز الاختبار الافتراضي من Firebase" هو خدمة مدفوعة تتضمّن فئة مجانية. كما تتيح 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 أو 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 Emulator أو إصدار أحدث
  • جهاز 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 تحديد عرض شاشة الجهاز وارتفاعها باستخدام فئات المقاس التي تحدّد مجموعات السمات وفقًا لفئات حجم النوافذ الرسمية.

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()
}

مكتبة اختبار النوافذ

تحتوي مكتبة اختبار النوافذ على أدوات لمساعدتك في كتابة الاختبارات التي تعتمد على الميزات المتعلّقة بإدارة النوافذ أو تتحقّق منها، مثل تضمين النشاط أو الميزات القابلة للطي. يتوفّر العنصر من خلال ملف Maven Repository في Google.

على سبيل المثال، يمكنك استخدام الدالة FoldingFeature() لإنشاء مخصصة FoldingFeature، والتي يمكنك استخدامها في معاينات إنشاء. في 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.

مصادر إضافية

المستندات

نماذج

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