ตอนนี้ API สำหรับการทดสอบ Compose เวอร์ชัน 2 (createComposeRule,
createAndroidComposeRule, runComposeUiTest,
runAndroidComposeUiTest ฯลฯ) พร้อมให้ใช้งานแล้วเพื่อปรับปรุงการควบคุมการดำเนินการของ
โครูทีน การอัปเดตนี้ไม่ได้ทำซ้ำทั้งพื้นผิว API
เฉพาะ API ที่สร้างสภาพแวดล้อมการทดสอบเท่านั้นที่ได้รับการอัปเดต
เราได้เลิกใช้งาน API เวอร์ชัน 1 แล้ว และขอแนะนำอย่างยิ่งให้ย้ายข้อมูลไปยัง API ใหม่ การย้ายข้อมูลจะช่วยยืนยันว่าการทดสอบของคุณสอดคล้องกับลักษณะการทำงานของโครูทีนมาตรฐานและ หลีกเลี่ยงปัญหาความเข้ากันได้ในอนาคต ดูรายการ API v1 ที่เลิกใช้งานแล้วได้ที่ การแมป API
ในขณะที่ API v1 อาศัย UnconfinedTestDispatcher แต่ API v2 จะใช้
StandardTestDispatcher โดยค่าเริ่มต้นสำหรับการจัดองค์ประกอบที่ทำงาน การเปลี่ยนแปลงนี้
ทําให้ลักษณะการทํางานของการทดสอบ Compose สอดคล้องกับ runTest API มาตรฐานและช่วยให้
ควบคุมลําดับการเรียกใช้โครูทีนได้อย่างชัดเจน
กำหนดค่าสภาพแวดล้อมการทดสอบ
Compose Test API เวอร์ชัน 2 ใช้ ComposeUiTestConfig เพื่อปรับแต่งสภาพแวดล้อมการทดสอบ
API ที่สร้างฟังก์ชันการตั้งค่าสำหรับการทดสอบ เช่น
createComposeRule, runComposeUiTest และ API อื่นๆ ที่เกี่ยวข้อง
ยอมรับ ComposeUiTestConfig ออบเจ็กต์การกำหนดค่านี้จะรวม
API ที่เกี่ยวข้องกับสภาพแวดล้อม เช่น effectContext, runTestContext
และ testTimeout ไว้ในออบเจ็กต์เดียว
โมเดลการกำหนดค่ายังจัดการ inputMode ด้วย API การทดสอบ v2 ของ Compose
จะบังคับใช้ InputMode.Touch โดยค่าเริ่มต้นเมื่อเริ่มการทดสอบแต่ละครั้งเพื่อให้มั่นใจ
ถึงความแน่นอนและป้องกันไม่ให้สถานะโหมดอินพุตหลุดระหว่างการทดสอบ
ComposeUiTestConfig เป็นส่วนหนึ่งของ Compose Testing API เวอร์ชัน 2 ซึ่งใช้ StandardTestDispatcher โดยค่าเริ่มต้น หากการทดสอบใช้ API v1 โปรดดูย้ายข้อมูลไปยัง API การทดสอบ v2 ก่อนนำ
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 เวอร์ชัน 1 ไปยัง Testing API เวอร์ชัน 2
พรอมต์นี้จะใช้คำแนะนำนี้เพื่อย้ายข้อมูลไปยัง 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 จะจัดคิวโครูทีนแทนที่จะเรียกใช้ทันที ระบบจัดคิวงานแล้ว แต่ไม่เคยเรียกใช้งานจริงก่อนที่จะตรวจสอบผลลัพธ์
- แก้ไข: เลื่อนเวลาอย่างชัดเจน คุณต้องบอกตัวจัดสรรงาน 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() ซึ่งจะเรียกใช้ Task
ที่กำหนดเวลาไว้สำหรับเวลาเสมือนจริงปัจจุบัน
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ขอบเขตของตัวเอง นาฬิกาทดสอบจะซิงค์กับสภาพแวดล้อมการเขียน คุณจึงไม่ต้องจัดการตัวกำหนดตารางเวลาด้วยตนเองอีกต่อไป
@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() } }