ไลบรารีและเครื่องมือเพื่อทดสอบหน้าจอขนาดต่างๆ

Android มีเครื่องมือและ API มากมายที่สามารถช่วยคุณสร้างการทดสอบสำหรับ หน้าจอและหน้าต่างขนาดต่างๆ

DeviceConfigurationOverride

คอมโพสิเบิล DeviceConfigurationOverride ช่วยให้คุณลบล้างแอตทริบิวต์การกําหนดค่าเพื่อทดสอบหน้าจอและหน้าต่างหลายขนาดในเลย์เอาต์คอมโพสิเบิลได้ การลบล้าง ForcedSize จะปรับให้เหมาะกับเลย์เอาต์ใดก็ได้ในพื้นที่ว่าง ซึ่งจะช่วยให้คุณทำการทดสอบ UI บนหน้าจอทุกขนาดได้ เช่น คุณสามารถใช้รูปแบบโทรศัพท์ขนาดเล็กเพื่อทำการทดสอบ UI ทั้งหมด ซึ่งรวมถึงการทดสอบ UI สำหรับโทรศัพท์ขนาดใหญ่ โทรศัพท์แบบพับ และแท็บเล็ต

   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*

นอกจากนี้ คุณยังสามารถใช้ Composable นี้เพื่อตั้งค่าขนาดตัวอักษร ธีม และอื่นๆ ที่คุณอาจต้องการทดสอบกับหน้าต่างขนาดต่างๆ

Robolectric

ใช้ Robolectric เพื่อเรียกใช้การทดสอบ UI แบบเขียนหรือแบบอิงตามมุมมองใน 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 { ... }

คุณยังกำหนดตัวระบุจากเนื้อหาทดสอบตามที่ทำในข้อมูลโค้ดนี้ได้ด้วย ตัวอย่าง 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 กับการรวมอย่างต่อเนื่อง (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 หลายรายการได้ในโปรเจ็กต์ testing-samples

Firebase Test Lab

ใช้ Firebase Test Lab (FTL) หรือบริการฟาร์มอุปกรณ์ที่คล้ายกันเพื่อเรียกใช้ การทดสอบในอุปกรณ์จริงบางอย่างที่คุณอาจเข้าถึงไม่ได้ เช่น อุปกรณ์แบบพับได้หรือแท็บเล็ตขนาดต่างๆ Firebase Test Lab เป็นผลิตภัณฑ์แบบมีค่าใช้จ่าย ด้วยรุ่นฟรี FTL ยังรองรับการเรียกใช้การทดสอบในโปรแกรมจำลองด้วย บริการเหล่านี้ช่วยเพิ่มความน่าเชื่อถือและความเร็วในการทดสอบที่มีเครื่องมือวัดผล เนื่องจากสามารถจัดสรรอุปกรณ์และโปรแกรมจำลองล่วงหน้าได้

ดูข้อมูลเกี่ยวกับการใช้ FTL กับ GMD ได้ที่ปรับขนาดการทดสอบด้วยอุปกรณ์ที่จัดการโดย Gradle

ทดสอบการกรองด้วยโปรแกรมรันทดสอบ

กลยุทธ์การทดสอบที่ดีที่สุดไม่ควรยืนยันสิ่งเดียวกันซ้ำ ดังนั้นการทดสอบ UI ส่วนใหญ่จึงไม่จำเป็นต้องทําในอุปกรณ์หลายเครื่อง โดยทั่วไป คุณสามารถกรอง UI โดยทำการทดสอบทั้งหมดหรือส่วนใหญ่ในรูปแบบของอุปกรณ์โทรศัพท์ และเปิดเฉพาะชุดย่อยใน อุปกรณ์ที่มีหน้าจอหลายขนาด

คุณสามารถใส่คำอธิบายประกอบการทดสอบบางรายการให้เรียกใช้เฉพาะกับอุปกรณ์บางเครื่องแล้วผ่านการตรวจสอบ อาร์กิวเมนต์ไปยัง 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 ยังมีกฎ 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 ยังอยู่ในขั้นอัลฟ่าและมีคุณสมบัติดังต่อไปนี้ ข้อกำหนด

  • ปลั๊กอิน Android Gradle 8.3 ขึ้นไป
  • โปรแกรมจำลอง Android 33.1.10 ขึ้นไป
  • อุปกรณ์เสมือน Android ที่ใช้ API ระดับ 24 ขึ้นไป

กรองการทดสอบ

อุปกรณ์ Espresso สามารถอ่านคุณสมบัติของอุปกรณ์ที่เชื่อมต่อเพื่อให้คุณทำสิ่งต่อไปนี้ได้ กรองการทดสอบโดยใช้คำอธิบายประกอบ หากไม่เป็นไปตามข้อกำหนดที่มีคำอธิบายประกอบ ระบบจะข้ามการทดสอบ

ต้องใช้คำอธิบายประกอบ DeviceMode

คุณใช้คำอธิบายประกอบ 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 ของ 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)))
        )
 }

นอกจากนี้ คุณยังจําลองฟีเจอร์การแสดงผลในการทดสอบ UI ได้โดยใช้ฟังก์ชัน 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

แหล่งข้อมูลเพิ่มเติม

เอกสารประกอบ

ตัวอย่าง

Codelabs