Compose Testing API เวอร์ชัน v2 (createComposeRule, createAndroidComposeRule, runComposeUiTest, runAndroidComposeUiTest ฯลฯ) เผยแพร่แล้วเพื่อปรับปรุงการควบคุมการดำเนินการ Coroutine การอัปเดตนี้ไม่ได้ทำซ้ำแพลตฟอร์ม API ทั้งหมด แต่จะอัปเดตเฉพาะ API ที่สร้างสภาพแวดล้อมการทดสอบเท่านั้น
API เวอร์ชัน 1 เลิกใช้งานแล้ว และเราขอแนะนำอย่างยิ่งให้ย้ายข้อมูลไปยัง API ใหม่ การย้ายข้อมูลจะช่วยยืนยันว่าการทดสอบสอดคล้องกับลักษณะการทำงานของโครูทีนมาตรฐานและหลีกเลี่ยงปัญหาความเข้ากันได้ในอนาคต ดูรายการ API เวอร์ชัน 1 ที่เลิกใช้งานแล้วได้ที่การแมป API
แม้ว่า API v1 จะอาศัย UnconfinedTestDispatcher แต่ API v2 จะใช้ StandardTestDispatcher โดยค่าเริ่มต้นสำหรับ Composition ที่ทำงานอยู่ การเปลี่ยนแปลงนี้จะปรับลักษณะการทำงานของการทดสอบ Compose ให้สอดคล้องกับ API runTest มาตรฐาน และให้การควบคุมลำดับการดำเนินการ Coroutine อย่างชัดเจน
กำหนดค่าสภาพแวดล้อมการทดสอบ
Compose Test v2 API ใช้ ComposeUiTestConfig เพื่อปรับแต่งสภาพแวดล้อมการทดสอบ API ที่สร้างฟังก์ชันการตั้งค่าสำหรับการทดสอบ เช่น createComposeRule, runComposeUiTest และ API อื่นๆ ที่เกี่ยวข้องจะยอมรับ ComposeUiTestConfig ออบเจ็กต์การกำหนดค่านี้จะรวม API ที่เกี่ยวข้องกับสภาพแวดล้อม เช่น effectContext, runTestContext และ testTimeout ไว้ในออบเจ็กต์เดียว
โมเดลการกำหนดค่ายังจัดการ inputMode ด้วย Compose Test v2 API จะบังคับใช้ InputMode.Touch โดยค่าเริ่มต้นเมื่อเริ่มการทดสอบแต่ละครั้งเพื่อให้มั่นใจว่าผลลัพธ์จะเหมือนเดิมและป้องกันไม่ให้สถานะโหมดอินพุตรั่วไหลระหว่างการทดสอบ
ComposeUiTestConfig เป็นส่วนหนึ่งของ Compose Testing v2 API ซึ่งใช้ StandardTestDispatcher โดยค่าเริ่มต้น หากการทดสอบใช้ API เวอร์ชัน 1 โปรดดูย้ายข้อมูลไปยัง API การทดสอบเวอร์ชัน 2 ก่อนใช้ ComposeUiTestConfig
ย้ายข้อมูลไปยัง ComposeUiTestConfig
ภายในโอเวอร์โหลดสำหรับการสร้างฟังก์ชันการตั้งค่าในการทดสอบ โอเวอร์โหลดหลายรายการที่ยอมรับพารามิเตอร์การกำหนดค่าแต่ละรายการ เช่น effectContext, runTestContext หรือ testTimeout จะเลิกใช้งาน อัปเดตการทดสอบเพื่อใช้ ComposeUiTestConfig แทน ดังที่แสดงในตัวอย่างต่อไปนี้
val testConfig = ComposeUiTestConfig( effectContext = EmptyCoroutineContext, runTestContext = EmptyCoroutineContext, testTimeout = 30.seconds ) @get:Rule val rule = createComposeRule(config = testConfig) // OR runComposeUiTest(config = testConfig) {}
โหมดป้อนข้อมูลเริ่มต้น
การทดสอบอาจล้มเหลวระหว่างการย้ายข้อมูลหากใช้โหมดการป้อนข้อมูลที่ไม่ใช่การสัมผัสซึ่งกำหนดค่าผ่าน 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 เพื่อทำการย้ายข้อมูลไปยัง Compose Testing API เวอร์ชัน 2 โดยใช้พรอมต์ต่อไปนี้
พรอมต์ AI
ย้ายข้อมูลจาก API การทดสอบ v1 ไปยัง API การทดสอบ v2
พรอมต์นี้จะใช้คู่มือนี้เพื่อย้ายข้อมูลไปยัง API การทดสอบ v2
Migrate to Compose testing v2 APIs using the official
migration guide.ใช้ตารางต่อไปนี้เพื่อแมป API เวอร์ชัน 1 ที่เลิกใช้งานแล้วกับ API เวอร์ชัน 2 ที่ใช้แทน
เลิกใช้งานแล้ว (v1) |
การเปลี่ยนทดแทน (v2) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ความเข้ากันได้แบบย้อนหลังและข้อยกเว้น
ตอนนี้เราเลิกใช้งาน API v1 ที่มีอยู่แล้ว แต่ยังคงใช้ UnconfinedTestDispatcher เพื่อคงลักษณะการทำงานที่มีอยู่และป้องกันการเปลี่ยนแปลงที่ส่งผลกับส่วนอื่นในระบบ
ข้อยกเว้นเพียงอย่างเดียวที่ลักษณะการทำงานเริ่มต้นมีการเปลี่ยนแปลงมีดังนี้
ตัวจ่ายการทดสอบเริ่มต้นที่ใช้สำหรับการเรียกใช้คอมโพสิชันในคลาส AndroidComposeUiTestEnvironment ได้เปลี่ยนจาก UnconfinedTestDispatcher เป็น StandardTestDispatcher ซึ่งจะส่งผลต่อกรณีที่คุณสร้างอินสแตนซ์โดยใช้คอนสตรัคเตอร์ หรือคลาสย่อย AndroidComposeUiTestEnvironment และเรียกใช้คอนสตรัคเตอร์นั้น
การเปลี่ยนแปลงที่สำคัญ: ผลกระทบต่อการดำเนินการ Coroutine
ความแตกต่างหลักระหว่าง API เวอร์ชัน 1 และเวอร์ชัน 2 คือวิธีจัดส่งโครูทีน
- v1 API (
UnconfinedTestDispatcher): เมื่อเปิดใช้งานโครูทีน ระบบจะดำเนินการทันทีในเทรดปัจจุบัน ซึ่งมักจะเสร็จสิ้นก่อนที่โค้ดทดสอบบรรทัดถัดไปจะทำงาน การดำเนินการทันทีนี้อาจบดบังปัญหาด้านเวลาหรือภาวะแข่งขันที่เกิดขึ้นจริงในแอปพลิเคชันที่ใช้งานอยู่โดยไม่ตั้งใจ ซึ่งแตกต่างจากลักษณะการทำงานในเวอร์ชันที่ใช้งานจริง - v2 APIs (
StandardTestDispatcher): เมื่อเปิดใช้งานโครูทีน ระบบจะจัดคิวและไม่ดำเนินการจนกว่าการทดสอบจะเลื่อนนาฬิกาเสมือนอย่างชัดเจน API ทดสอบ Compose มาตรฐาน (เช่นwaitForIdle()) จัดการการซิงค์นี้อยู่แล้ว ดังนั้นการทดสอบส่วนใหญ่ที่ใช้ API มาตรฐานเหล่านี้ควรทำงานต่อไปได้โดยไม่ต้องทำการเปลี่ยนแปลงใดๆ
สาเหตุที่พบบ่อยของการไม่ผ่านการตรวจสอบและวิธีแก้ไข
หากการทดสอบไม่สำเร็จหลังจากอัปเกรดเป็น v2 แสดงว่าการทดสอบอาจมีรูปแบบต่อไปนี้
- ไม่สำเร็จ: คุณเปิดใช้งานงาน (เช่น ViewModel โหลดข้อมูล) แต่การยืนยันไม่สำเร็จทันทีเนื่องจากข้อมูลยังอยู่ในสถานะ "กำลังโหลด"
- สาเหตุ: เมื่อใช้ API เวอร์ชัน 2 ระบบจะจัดคิวโครูทีนแทนที่จะดำเนินการทันที งานอยู่ในคิวแต่ไม่เคยทำงานจริงก่อนที่จะมีการตรวจสอบผลลัพธ์
- วิธีแก้ไข: เลื่อนเวลาอย่างชัดเจน คุณต้องบอกผู้มอบหมายงาน 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() } }