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