ตอนนี้ 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 โดยใช้พรอมต์ต่อไปนี้
พรอมต์ AI
ย้ายข้อมูลจาก API การทดสอบ v1 ไปยัง API การทดสอบ v2
พรอมต์นี้จะใช้คู่มือนี้เพื่อย้ายข้อมูลไปยัง API การทดสอบ v2
Migrate to Compose testing v2 APIs using the official
migration guide.ใช้ตารางต่อไปนี้เพื่อแมป API v1 ที่เลิกใช้งานแล้วกับ API v2 ที่ใช้แทน
เลิกใช้งานแล้ว (v1) |
การเปลี่ยนแทน (v2) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ความเข้ากันได้แบบย้อนหลังและข้อยกเว้น
ตอนนี้เราได้เลิกใช้งาน 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() } }