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

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 เพื่อปรับเลย์เอาต์แท็บเล็ตให้พอดีกับอุปกรณ์ที่มีขนาดเล็กลง ดังที่แสดงใน "*พร้อมใช้งานใน Android แล้ว*"

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

Robolectric

ใช้ Robolectric เพื่อเรียกใช้การทดสอบ UI ของ Compose หรือ UI ตามมุมมองใน JVMในเครื่องโดยไม่ต้องใช้อุปกรณ์หรือโปรแกรมจำลอง คุณสามารถกําหนดค่า Robolectric ให้ใช้ขนาดหน้าจอที่เฉพาะเจาะจง รวมถึงพร็อพเพอร์ตี้อื่นๆ ที่มีประโยชน์

ในตัวอย่างต่อไปนี้จากตอนนี้ใน 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 { ... }

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

ใช้ 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 Device ยังอยู่ในระยะอัลฟ่าและมีข้อกำหนดต่อไปนี้

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

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

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

คำอธิบายประกอบ 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()
}

ไลบรารีการทดสอบกรอบเวลา

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