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

يوفّر 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 in Android*.

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

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

استخدِم Robolectric لإجراء اختبارات واجهة المستخدم على 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 (GMD) Android Gradle تحديد مواصفات أدوات المحاكاة والأجهزة الحقيقية التي يتم فيها إجراء اختبارات الأدوات. قم بإنشاء مواصفات للأجهزة ذات أحجام الشاشات المختلفة لتنفيذ استراتيجية اختبار حيث يجب إجراء اختبارات معينة على أحجام شاشات معينة. باستخدام GMD مع Continuous Integration (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 هو خدمة مدفوعة ذات فئة مجانية. كما تتيح 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 لتنفيذ إجراءات على أدوات المحاكاة في الاختبارات باستخدام أي نوع من اختبارات قياس حالة الآلات، بما في ذلك اختبارات 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 لا يزال في المرحلة الأولية وعليه المتطلبات التالية:

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

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

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

التعليق التوضيحي مطلوب لوضع الجهاز

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

مختبِر StateRestoration

تُستخدَم الفئة 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 مخصّصة يمكنك استخدامها في معاينات الإنشاء. في 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.

مصادر إضافية

المستندات

العيّنات

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