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

ตอนนี้ 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 มาตรฐาน และช่วยให้ ควบคุมลําดับการเรียกใช้โครูทีนได้อย่างชัดเจน

การแมป API

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

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

ย้ายข้อมูลจาก API การทดสอบ v1 ไปยัง 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 เพื่อรักษาลักษณะการทำงานที่มีอยู่และป้องกันการเปลี่ยนแปลงที่ทำให้เกิดข้อผิดพลาดได้

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

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

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

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

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

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

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

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

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

ใน v1 งานจะเปิดตัวและเสร็จสิ้นทันที ใน v2 โค้ดต่อไปนี้จะทำงานไม่สำเร็จเนื่องจาก 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()

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

ระบบจะแสดงตัวกำหนดเวลางานทดสอบพื้นฐานที่ใช้ในสภาพแวดล้อมการทดสอบ ตัวกำหนดเวลานี้ใช้ร่วมกับ 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()
    }
}