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

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

DeviceConfigurationOverride

DeviceConfigurationOverride Composable ช่วยให้คุณลบล้างแอตทริบิวต์การกำหนดค่าเพื่อทดสอบขนาดหน้าจอและหน้าต่างหลายขนาดในเลย์เอาต์ Compose ได้ การลบล้าง 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 in Android*

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

Robolectric

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

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

ปลั๊กอิน Android Gradle สำหรับอุปกรณ์ที่มีการจัดการจาก 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

การกรองการทดสอบด้วย Test Runner

กลยุทธ์การทดสอบที่ดีที่สุดไม่ควรยืนยันสิ่งเดียวกัน 2 ครั้ง ดังนั้นการทดสอบ 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 Device (ดูส่วนถัดไป) ยังกรองการทดสอบโดยใช้พร็อพเพอร์ตี้ของอุปกรณ์ได้ด้วย

อุปกรณ์ Espresso

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

  • ปลั๊กอิน Android Gradle 8.3 ขึ้นไป
  • โปรแกรมจำลองของ Android 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

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

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

ในตัวอย่าง Compose คุณอาจใช้ 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

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

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

ตัวอย่าง

Codelab