גרסאות v2 של ממשקי ה-API לבדיקת כתיבה (createComposeRule, createAndroidComposeRule, runComposeUiTest, runAndroidComposeUiTest וכו') זמינות עכשיו כדי לשפר את השליטה בהרצת קורוטינות. העדכון הזה לא משכפל את כל ממשקי ה-API, אלא רק את אלה שיוצרים את סביבת הבדיקה.
ממשקי API בגרסה 1 הוצאו משימוש, ומומלץ מאוד לעבור לממשקי ה-API החדשים. המיגרציה מאפשרת לוודא שהבדיקות תואמות להתנהגות הרגילה של קורוטינות, ומונעת בעיות תאימות בעתיד. רשימה של ממשקי API מגרסה 1 שהוצאו משימוש זמינה במאמר מיפוי של ממשקי API.
השינויים האלה כלולים ב-androidx.compose.ui:ui-test-junit4:1.11.0-alpha03+ וב-androidx.compose.ui:ui-test:1.11.0-alpha03+.
בעוד שממשקי API בגרסה 1 הסתמכו על UnconfinedTestDispatcher, ממשקי API בגרסה 2 משתמשים ב-StandardTestDispatcher כברירת מחדל להרצת הקומפוזיציה. השינוי הזה מתאים את התנהגות הבדיקה של Compose לממשקי ה-API הרגילים של runTest ומספק שליטה מפורשת בסדר ההרצה של שגרות המשך (coroutine).
הגדרת סביבת הבדיקה
ממשקי ה-API של Compose test v2 משתמשים ב-ComposeUiTestConfig כדי להתאים אישית את סביבת הבדיקה. ממשקי API שיוצרים פונקציות הגדרה לבדיקות, כמו createComposeRule, runComposeUiTest וממשקי API קשורים אחרים, מקבלים ComposeUiTestConfig. אובייקט ההגדרה הזה מרכז ממשקי API שקשורים לסביבה, כמו effectContext, runTestContext ו-testTimeout, באובייקט אחד.
מודל ההגדרות גם מנהל את inputMode. ממשקי ה-API של Compose test v2 אוכפים את InputMode.Touch כברירת מחדל בתחילת כל בדיקה, כדי להבטיח דטרמיניזם ולמנוע דליפה של מצב מצב קלט בין בדיקות.
ComposeUiTestConfig הוא חלק מממשקי ה-API של Compose Test v2, שמשתמשים ב-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 לבדיקות בגרסה 2
כשמשדרגים לגרסה 2 של ממשקי API, בדרך כלל אפשר להשתמש בחיפוש והחלפה כדי לעדכן את ייבוא החבילות וליישם את השינויים החדשים ב-dispatcher.
לחלופין, אפשר לבקש מ-Gemini לבצע מיגרציה לגרסה 2 של ממשקי ה-API לבדיקות של Compose באמצעות ההנחיה הבאה:
הנחיית AI
מעבר מממשקי API לבדיקה בגרסה 1 לממשקי API לבדיקה בגרסה 2
הפרומפט הזה ישתמש במדריך הזה כדי לעבור לגרסה 2 של ממשקי API לבדיקות.
Migrate to Compose testing v2 APIs using the official
migration guide.אתם יכולים להיעזר בטבלה הבאה כדי למפות את ממשקי v1 API שיצאו משימוש לממשקי v2 API שמחליפים אותם:
הוצאה משימוש (גרסה 1) |
החלפה (גרסה 2) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
תאימות לאחור ומקרים חריגים
ממשקי ה-API הקיימים של גרסה 1 הוצאו משימוש, אבל אפשר להמשיך להשתמש בהם בכתובת UnconfinedTestDispatcher כדי לשמור על אופן הפעולה הקיים ולמנוע שינויים שעלולים לשבור את התאימות.
החריג היחיד שבו השתנתה התנהגות ברירת המחדל הוא:
השולח לבדיקה שמוגדר כברירת מחדל ומשמש להרצת קומפוזיציה במחלקה AndroidComposeUiTestEnvironment השתנה מ-UnconfinedTestDispatcher ל-StandardTestDispatcher. הבעיה הזו משפיעה על מקרים שבהם יוצרים מופע באמצעות בנאי, או תת-מחלקה AndroidComposeUiTestEnvironment, ומפעילים את הבנאי הזה.
שינוי מרכזי: השפעה על הרצה של שגרות המשך (coroutine)
ההבדל העיקרי בין גרסה 1 לגרסה 2 של ממשקי ה-API הוא האופן שבו מתבצעת ההפצה של קורוטינות:
- v1 APIs (
UnconfinedTestDispatcher): כשקורוטינה הופעלה, היא בוצעה באופן מיידי בשרשור הנוכחי, ולעתים קרובות הסתיימה לפני שהופעלה השורה הבאה של קוד הבדיקה. בניגוד להתנהגות בסביבת הייצור, ההפעלה המיידית הזו עלולה להסתיר בטעות בעיות אמיתיות בתזמון או תנאי מירוץ שיתרחשו באפליקציה פעילה. - v2 APIs (
StandardTestDispatcher): כשמפעילים שגרת המשך (coroutine), היא מתווספת לתור ולא מופעלת עד שהבדיקה מקדמת באופן מפורש את השעון הווירטואלי. ממשקי API סטנדרטיים של בדיקות כתיבה (כמוwaitForIdle()) כבר מטפלים בסנכרון הזה, ולכן רוב הבדיקות שמסתמכות על ממשקי ה-API הסטנדרטיים האלה ימשיכו לפעול בלי שינויים.
בעיות נפוצות ופתרונות
אם הבדיקות נכשלות אחרי שמשדרגים לגרסה 2, סביר להניח שהן מציגות את הדפוס הבא:
- כשל: אתם מפעילים משימה (לדוגמה, ViewModel טוען נתונים), אבל הטענה נכשלת מיד כי הנתונים עדיין במצב 'טעינה'.
- הגורם: בממשקי API מגרסה 2, קורוטינות מתווספות לתור במקום להתבצע באופן מיידי. המשימה הוכנסה לתור אבל לא בוצעה בפועל לפני שהתוצאה נבדקה.
- תיקון: מקדמים את הזמן באופן מפורש. צריך לציין במפורש ל-dispatcher בגרסה 2 מתי לבצע את העבודה.
הגישה הקודמת
בגרסה 1, המשימה הופעלה והסתיימה מיד. בגרסה 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 כדי להריץ משימות בתור לפני שמשתמשים ב-assert.
אפשרות 1: שימוש ב-waitForIdle מקדם את השעון עד שממשק המשתמש בלי פעילות, וכך מוודאים ששגרת ההמשך (coroutine) פעלה.
viewModel.loadData()
// Explicitly run all queued tasks
composeTestRule.waitForIdle()
assertEquals(Success, viewModel.state.value)
אפשרות 2: שימוש ב-runOnIdle מריץ את בלוק הקוד בשרשור של ממשק המשתמש אחרי שממשק המשתמש לא פעיל.
viewModel.loadData()
// Run the assertion after the UI is idle
composeTestRule.runOnIdle {
assertEquals(Success, viewModel.state.value)
}
סנכרון ידני
בתרחישים שבהם מתבצעת סנכרון ידני, למשל כשמשביתים את ההתקדמות האוטומטית, הפעלה של שגרת המשך (coroutine) לא מובילה להרצה מיידית כי שעון הבדיקה מושהה. כדי להריץ קורוטינות בתור בלי להקדים את השעון הווירטואלי, משתמשים ב-API runCurrent(). הפעולה הזו מריצה משימות שמתוזמנות לשעה הוירטואלית הנוכחית.
composeTestRule.mainClock.scheduler.runCurrent()
בניגוד ל-waitForIdle(), שמעביר את השעון של הבדיקה עד שממשק המשתמש מתייצב, הפקודה runCurrent() מבצעת משימות בהמתנה תוך שמירה על הזמן הווירטואלי הנוכחי. ההתנהגות הזו מאפשרת אימות של מצבי ביניים, שאחרת היו מדלגים עליהם אם השעון היה מתקדם למצב סרק.
מערכת התזמון הבסיסית של הבדיקות שמשמשת בסביבת הבדיקה נחשפת. אפשר להשתמש במתזמן הזה בשילוב עם Kotlin runTest API כדי לסנכרן את שעון הבדיקה.
העברה אל runComposeUiTest
אם אתם משתמשים בממשקי API של בדיקות Compose לצד Kotlin runTest API, מומלץ מאוד לעבור אל runComposeUiTest.
הגישה הקודמת
השימוש ב-createComposeRule יחד עם runTest יוצר שני שעונים נפרדים: אחד ל-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() } }