Compose Test API เวอร์ชัน 2

ตอนนี้ API สำหรับการทดสอบ Compose เวอร์ชัน 2 (createComposeRule, createAndroidComposeRule, runComposeUiTest, runAndroidComposeUiTest ฯลฯ) พร้อมให้ใช้งานแล้วเพื่อปรับปรุงการควบคุมการดำเนินการของ โครูทีน การอัปเดตนี้ไม่ได้ทำซ้ำทั้งพื้นผิว API แต่จะอัปเดตเฉพาะ API ที่สร้างสภาพแวดล้อมการทดสอบเท่านั้น

เราเลิกใช้งาน API เวอร์ชัน 1 แล้ว และขอแนะนำเป็นอย่างยิ่งให้ย้ายข้อมูลไปยัง API ใหม่ การย้ายข้อมูลจะยืนยันว่าการทดสอบของคุณสอดคล้องกับลักษณะการทำงานของโครูทีนมาตรฐานและ หลีกเลี่ยงปัญหาความเข้ากันได้ในอนาคต ดูรายการ API v1 ที่เลิกใช้งานแล้วได้ที่การแมป API

การเปลี่ยนแปลงเหล่านี้รวมอยู่ใน androidx.compose.ui:ui-test-junit4:1.11.0-alpha03+และ androidx.compose.ui:ui-test:1.11.0-alpha03+

ในขณะที่ API v1 อาศัย UnconfinedTestDispatcher แต่ API v2 จะใช้ StandardTestDispatcher โดยค่าเริ่มต้นสำหรับการจัดองค์ประกอบที่ทำงาน การเปลี่ยนแปลงนี้ จะปรับลักษณะการทำงานของการทดสอบ Compose ให้สอดคล้องกับ runTest API มาตรฐาน และให้ การควบคุมลำดับการดำเนินการของโครูทีนอย่างชัดเจน

กำหนดค่าสภาพแวดล้อมการทดสอบ

Compose Test API เวอร์ชัน 2 ใช้ ComposeUiTestConfig เพื่อปรับแต่งสภาพแวดล้อมการทดสอบ API ที่สร้างฟังก์ชันการตั้งค่าสำหรับการทดสอบ เช่น createComposeRule, runComposeUiTest และ API อื่นๆ ที่เกี่ยวข้อง จะยอมรับ ComposeUiTestConfig ออบเจ็กต์การกำหนดค่านี้จะรวม API ที่เกี่ยวข้องกับสิ่งแวดล้อม เช่น effectContext, runTestContext และ testTimeout ไว้ในออบเจ็กต์เดียว

นอกจากนี้ โมเดลการกำหนดค่ายังจัดการ inputMode ด้วย Compose Test v2 API จะบังคับใช้ InputMode.Touch โดยค่าเริ่มต้นเมื่อเริ่มการทดสอบแต่ละครั้งเพื่อให้มั่นใจถึง ความแน่นอนและป้องกันไม่ให้สถานะโหมดอินพุตหลุดระหว่างการทดสอบ

ComposeUiTestConfig เป็นส่วนหนึ่งของ Compose Test v2 API ซึ่งใช้ StandardTestDispatcher โดยค่าเริ่มต้น หากการทดสอบใช้ API เวอร์ชัน 1 โปรดดูย้ายข้อมูลไปยัง API การทดสอบเวอร์ชัน 2 ก่อนนำ ComposeUiTestConfigไปใช้

ย้ายข้อมูลไปยัง ComposeUiTestConfig

ภายในโอเวอร์โหลดสำหรับการสร้างฟังก์ชันการตั้งค่าในการทดสอบ โอเวอร์โหลดหลายรายการ ที่ยอมรับพารามิเตอร์การกำหนดค่าแต่ละรายการ เช่น effectContext, runTestContext หรือ testTimeout จะเลิกใช้งานแล้ว อัปเดตการทดสอบเพื่อใช้ ComposeUiTestConfig แทน ดังที่แสดงในตัวอย่างต่อไปนี้

โหมดป้อนข้อมูลเริ่มต้น

การทดสอบอาจล้มเหลวระหว่างการย้ายข้อมูลหากใช้โหมดอินพุตที่ไม่ใช่แบบสัมผัส ซึ่งกำหนดค่าผ่าน API การวัดผลก่อนเริ่มการทดสอบ ภายในฟังก์ชันการตั้งค่า สำหรับการทดสอบ ระบบจะบังคับใช้ InputMode.Touch โดยค่าเริ่มต้นที่ จุดเริ่มต้นของการทดสอบแต่ละครั้งเพื่อให้มีความแน่นอนมากขึ้นและเพื่อป้องกันการรั่วไหลของสถานะ การลบล้าง สถานะอุปกรณ์โดยรอบและการตั้งค่าก่อนการทดสอบ

หากต้องการแก้ไขปัญหานี้ ให้ระบุโหมดอินพุตที่จำเป็นใน ComposeUiTestConfig ดังนี้

class FocusTest {
    @get:Rule
    val rule = createComposeRule(
        config = ComposeUiTestConfig(inputMode = InputMode.Keyboard)
    )

    @Test
    fun testFocus() {}
}

หากต้องการกำหนดค่าโหมดอินพุตสำหรับกรณีทดสอบแต่ละรายการแทนที่จะเป็นทั้งคลาสทดสอบ ให้ส่ง ComposeUiTestConfig ไปยัง runComposeUiTest ดังนี้

class FocusTest {
    @Test
    fun testTouchMode() = runComposeUiTest {
        // Runs with the default InputMode.Touch
    }

    @Test
    fun testKeyboardMode() = runComposeUiTest(
        ComposeUiTestConfig(inputMode = InputMode.Keyboard)
    ) {
        // Runs with InputMode.Keyboard
    }
}

หากพบปัญหาอื่นๆ เกี่ยวกับการย้ายข้อมูลและวิธีแก้ไข โปรดดูสาเหตุที่พบบ่อยของการไม่ผ่านการตรวจสอบและวิธีแก้ไข

ย้ายข้อมูลไปยัง API การทดสอบ v2

เมื่ออัปเกรดเป็น API เวอร์ชัน 2 โดยทั่วไปคุณจะใช้ค้นหา + แทนที่เพื่ออัปเดต การนำเข้าแพ็กเกจและใช้การเปลี่ยนแปลงใหม่ของ Dispatcher ได้

หรือขอให้ Gemini ทำการย้ายข้อมูลไปยัง API การทดสอบ Compose เวอร์ชัน 2 โดยใช้พรอมต์ต่อไปนี้

ย้ายข้อมูลจาก Testing API v1 ไปยัง Testing API v2

พรอมต์นี้จะใช้คู่มือนี้เพื่อย้ายข้อมูลไปยัง API การทดสอบ v2

Migrate to Compose testing v2 APIs using the official
migration guide.

การใช้พรอมต์ AI

พรอมต์ AI มีไว้เพื่อใช้ภายใน Gemini ใน Android Studio

ดูข้อมูลเพิ่มเติมเกี่ยวกับ Gemini ใน Studio ได้ที่ https://developer.android.com/studio/gemini/overview

ใช้ตารางต่อไปนี้เพื่อแมป API v1 ที่เลิกใช้งานแล้วกับ API v2 ที่ใช้แทน

เลิกใช้งานแล้ว (v1)

การเปลี่ยนทดแทน (v2)

androidx.compose.ui.test.junit4.createComposeRule

androidx.compose.ui.test.junit4.v2.createComposeRule

androidx.compose.ui.test.junit4.createAndroidComposeRule

androidx.compose.ui.test.junit4.v2.createAndroidComposeRule

androidx.compose.ui.test.junit4.createEmptyComposeRule

androidx.compose.ui.test.junit4.v2.createEmptyComposeRule

androidx.compose.ui.test.junit4.AndroidComposeTestRule

androidx.compose.ui.test.junit4.v2.AndroidComposeTestRule

androidx.compose.ui.test.runComposeUiTest

androidx.compose.ui.test.v2.runComposeUiTest

androidx.compose.ui.test.runAndroidComposeUiTest

androidx.compose.ui.test.v2.runAndroidComposeUiTest

androidx.compose.ui.test.runEmptyComposeUiTest

androidx.compose.ui.test.v2.runEmptyComposeUiTest

androidx.compose.ui.test.AndroidComposeUiTestEnvironment

androidx.compose.ui.test.v2.AndroidComposeUiTestEnvironment

ความเข้ากันได้แบบย้อนหลังและข้อยกเว้น

ตอนนี้เราได้เลิกใช้งาน API v1 ที่มีอยู่แล้ว แต่คุณยังคงใช้ UnconfinedTestDispatcherเพื่อรักษาลักษณะการทำงานที่มีอยู่และป้องกันการเปลี่ยนแปลงที่ทำให้เกิดข้อผิดพลาดได้

ข้อยกเว้นเพียงอย่างเดียวที่ลักษณะการทำงานเริ่มต้นมีการเปลี่ยนแปลงมีดังนี้

โปรแกรมจัดส่งการทดสอบเริ่มต้นที่ใช้ในการเรียกใช้การทดสอบในคลาส AndroidComposeUiTestEnvironment ได้เปลี่ยนจาก UnconfinedTestDispatcher เป็น StandardTestDispatcher แล้ว ซึ่งจะส่งผลต่อกรณีที่คุณสร้างอินสแตนซ์โดยใช้ตัวสร้าง หรือคลาสย่อย AndroidComposeUiTestEnvironment และเรียกตัวสร้างนั้น

การเปลี่ยนแปลงที่สำคัญ: ผลกระทบต่อการดำเนินการโครูทีน

ความแตกต่างหลักระหว่าง API เวอร์ชัน 1 กับเวอร์ชัน 2 คือวิธีการส่งโครูทีน

  • API v1 (UnconfinedTestDispatcher): เมื่อเปิดใช้โครูทีน โครูทีนจะ ทำงานทันทีในเธรดปัจจุบัน ซึ่งมักจะเสร็จสิ้นก่อนที่โค้ดทดสอบบรรทัดถัดไปจะทำงาน การดำเนินการทันทีนี้ต่างจากลักษณะการทำงานจริงตรงที่อาจบดบังปัญหาด้านเวลาจริงหรือสภาวะแข่งขันโดยไม่ตั้งใจ ซึ่งอาจเกิดขึ้นในแอปพลิเคชันที่ใช้งานจริง
  • API เวอร์ชัน 2 (StandardTestDispatcher): เมื่อเปิดใช้โครูทีน ระบบจะ จัดคิวและไม่ดำเนินการจนกว่าการทดสอบจะเลื่อนเวลาเสมือนอย่างชัดเจน API การทดสอบ Compose มาตรฐาน (เช่น waitForIdle()) จัดการการซิงค์นี้อยู่แล้ว ดังนั้นการทดสอบส่วนใหญ่ที่ใช้ API มาตรฐานเหล่านี้ควรทำงานต่อไปได้โดยไม่มีการเปลี่ยนแปลง

สาเหตุที่พบบ่อยของการไม่ผ่านการตรวจสอบและวิธีแก้ไข

หากการทดสอบล้มเหลวหลังจากอัปเกรดเป็น v2 แสดงว่าการทดสอบน่าจะมีรูปแบบดังนี้

  • ไม่สำเร็จ: คุณเปิดใช้งานงาน (เช่น ViewModel โหลดข้อมูล) แต่การยืนยันล้มเหลวทันทีเนื่องจากข้อมูลยังอยู่ในสถานะ "กำลังโหลด"
  • สาเหตุ: API v2 จะจัดคิวโครูทีนแทนที่จะเรียกใช้ทันที ระบบจัดคิวงานไว้ แต่ไม่เคยเรียกใช้งานจริงก่อนที่จะตรวจสอบผลลัพธ์
  • แก้ไข: เลื่อนเวลาอย่างชัดเจน คุณต้องบอก Dispatcher v2 อย่างชัดเจน เมื่อต้องดำเนินการ

แนวทางก่อนหน้า

ใน v1 งานจะเปิดตัวและเสร็จสิ้นทันที ในเวอร์ชัน 2 โค้ดต่อไปนี้ จะทำงานไม่สำเร็จเนื่องจาก loadData() ยังไม่ได้ทำงานจริง

// In v1, this launched and finished immediately.
viewModel.loadData()

// In v2, this fails because loadData() hasn't actually run yet!
assertEquals(Success, viewModel.state.value)

ใช้ waitForIdle หรือ runOnIdle เพื่อเรียกใช้งานที่คิวไว้ก่อน ยืนยัน

ตัวเลือกที่ 1: การใช้ waitForIdle จะเลื่อนเวลาจนกว่า UI จะไม่มีการใช้งาน ซึ่งเป็นการยืนยันว่าโครูทีนทำงานแล้ว

viewModel.loadData()

// Explicitly run all queued tasks
composeTestRule.waitForIdle()

assertEquals(Success, viewModel.state.value)

ตัวเลือกที่ 2: การใช้ runOnIdle จะเรียกใช้โค้ดบล็อกในเทรด UI หลังจากที่ UI ไม่ได้ใช้งานแล้ว

viewModel.loadData()

// Run the assertion after the UI is idle
composeTestRule.runOnIdle {
    assertEquals(Success, viewModel.state.value)
}

การซิงค์ด้วยตนเอง

ในสถานการณ์ที่เกี่ยวข้องกับการซิงค์ด้วยตนเอง เช่น เมื่อปิดใช้การเลื่อนอัตโนมัติ การเปิดใช้โครูทีนจะไม่ทำให้เกิดการดำเนินการทันทีเนื่องจากนาฬิกาทดสอบหยุดชั่วคราว หากต้องการเรียกใช้โครูทีนในคิวโดยไม่ เลื่อนนาฬิกาเสมือน ให้ใช้ API runCurrent() ซึ่งจะเรียกใช้งานที่ กำหนดเวลาไว้สำหรับเวลาเสมือนจริงปัจจุบัน

composeTestRule.mainClock.scheduler.runCurrent()

runCurrent()จะดำเนินการงานที่รอดำเนินการในขณะที่ยังคงเวลาเสมือนปัจจุบันไว้ ซึ่งแตกต่างจาก waitForIdle() ที่จะเลื่อนเวลาทดสอบจนกว่า UI จะเสถียร ลักษณะการทำงานนี้ช่วยให้ยืนยันสถานะระดับกลางได้ ซึ่งระบบจะข้ามสถานะดังกล่าวหากมีการเลื่อนเวลาไปที่สถานะว่าง

ระบบจะแสดงตัวกำหนดเวลางานทดสอบที่ใช้ในสภาพแวดล้อมการทดสอบ ตัวกำหนดเวลานี้ใช้ร่วมกับ Kotlin runTest API เพื่อ ซิงค์นาฬิกาทดสอบได้

ย้ายข้อมูลไปยัง runComposeUiTest

หากคุณใช้ Compose Test API ควบคู่ไปกับ Kotlin runTest API เราขอแนะนำอย่างยิ่งให้เปลี่ยนไปใช้ runComposeUiTest

แนวทางก่อนหน้า

การใช้ createComposeRule ร่วมกับ runTest จะสร้างนาฬิกา 2 เรือนแยกกัน เรือนหนึ่งสำหรับ Compose และอีกเรือนหนึ่งสำหรับขอบเขตของโครูทีนทดสอบ การกำหนดค่านี้อาจทำให้คุณต้องซิงค์ตัวกำหนดเวลานัดหมายการทดสอบด้วยตนเอง

@get:Rule
val composeTestRule = createComposeRule()

@Test
fun testWithCoroutines() {
    composeTestRule.setContent {
        var status by remember { mutableStateOf("Loading...") }
        LaunchedEffect(Unit) {
            delay(1000)
            status = "Done!"
        }
        Text(text = status)
    }

    // NOT RECOMMENDED
    // Fails: runTest creates a new, separate scheduler.
    // Advancing time here does NOT advance the compose clock.
    // To fix this without migrating, you would need to share the scheduler
    // by passing 'composeTestRule.mainClock.scheduler' to runTest.
    runTest {
        composeTestRule.onNodeWithText("Loading...").assertIsDisplayed()
        advanceTimeBy(1000)
        composeTestRule.onNodeWithText("Done!").assertIsDisplayed()
    }
}

runComposeUiTest API จะเรียกใช้บล็อกทดสอบโดยอัตโนมัติภายในrunTestขอบเขตของตัวเอง นาฬิกาทดสอบจะซิงค์กับสภาพแวดล้อม Compose ดังนั้นคุณจึงไม่จำเป็นต้องจัดการตัวกำหนดเวลาด้วยตนเองอีกต่อไป

    @Test
    fun testWithCoroutines() = runComposeUiTest {
        setContent {
            var status by remember { mutableStateOf("Loading...") }
            LaunchedEffect(Unit) {
                delay(1000)
                status = "Done!"
            }
            Text(text = status)
        }

        onNodeWithText("Loading...").assertIsDisplayed()
        mainClock.advanceTimeBy(1000 + 16 /* Frame buffer */)
        onNodeWithText("Done!").assertIsDisplayed()
    }
}