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

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

إلغاء إعداد الجهاز

.

يتيح لك عنصر 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 في Android*.

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

Robolectric

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

في المثال التالي من قسم Now in Android، تم ضبط Robolectric لمحاكاة حجم شاشة 1000x1000 بكسل مستقل الكثافة بدرجة دقة 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

يتيح لك المكوّن الإضافي لنظام Gradle المتوافق مع Android (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-عيّنات.

Firebase Test Lab

استخدام مركز الاختبار الافتراضي لمنصة Firebase (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 Device (انظر القسم التالي) أيضًا فلترة الاختبارات باستخدام خصائص الجهاز.

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

يجب استخدام Espresso Device لتنفيذ إجراءات على المحاكيات في الاختبارات باستخدام أي نوع من الاختبارات الآلية، بما في ذلك اختبارات Espresso أو Compose أو UI Automator. قد تتضمن هذه الإجراءات ضبط حجم الشاشة أو التبديل بين الحالات أو الوضعيات القابلة للطي. على سبيل المثال، يمكنك التحكم في محاكي قابل للطي وضبطه على وضع "الشاشة المسطحة". يحتوي جهاز Espresso أيضًا على قواعد 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 لا تزال في مرحلة الإصدار الأولي وتتضمّن المتطلبات التالية:

  • الإصدار 8.3 من المكوّن الإضافي لنظام Gradle المتوافق مع Android أو إصدار أحدث
  • الإصدار 33.1.10 أو إصدار أحدث من Android Emulator
  • جهاز Android افتراضي يعمل بالمستوى 24 أو أعلى من واجهة برمجة التطبيقات

فلترة الاختبارات

يمكن لجهاز Espresso قراءة خصائص الأجهزة المتصلة لتتمكّن من فلترة الاختبارات باستخدام التعليقات التوضيحية. إذا لم يتم استيفاء المتطلبات ذات التعليقات التوضيحية، يتم تخطي الاختبارات.

التعليق التوضيحي isDeviceMode

يمكن استخدام التعليق التوضيحي 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 الذي يضم أدوات Google.

على سبيل المثال، يمكنك استخدام الدالة 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.

مراجع إضافية

المستندات

العيّنات

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