גרסאות v2 של ממשקי ה-API לבדיקות של Compose (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 ומספק
שליטה מפורשת בסדר הביצוע של קורוטינות.
מיפוי API
כשמשדרגים לגרסה 2 של ממשקי ה-API, בדרך כלל אפשר להשתמש בחיפוש והחלפה כדי לעדכן את הייבוא של החבילות ולאמץ את השינויים החדשים ב-dispatcher.
אפשר גם לבקש מ-Gemini לבצע מיגרציה לגרסה 2 של ממשקי ה-API של בדיקת יצירה באמצעות ההנחיה הבאה:
הנחיית AI
מעבר מממשקי API לבדיקה בגרסה 1 לממשקי API לבדיקה בגרסה 2
ההנחיה הזו תשתמש במדריך הזה כדי לעבור לגרסה 2 של ממשקי ה-API לבדיקות.
Migrate to Compose testing v2 APIs using the official
migration guide.בטבלה הבאה מפורטים ממשקי API מגרסה 1 שהוצאו משימוש והגרסאות החדשות שלהם:
הוצאה משימוש (גרסה 1) |
החלפה (גרסה 2) |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
תאימות לאחור וחריגים
ממשקי ה-API הקיימים של גרסה 1 הוצאו משימוש, אבל הם ממשיכים להשתמש ב-UnconfinedTestDispatcher כדי לשמור על ההתנהגות הקיימת ולמנוע שינויים שעלולים לשבור את התאימות.
החריג היחיד שבו התנהגות ברירת המחדל השתנתה הוא:
השולח לבדיקה שמוגדר כברירת מחדל ומשמש להרצת קומפוזיציה במחלקה AndroidComposeUiTestEnvironment השתנה מ-UnconfinedTestDispatcher ל-StandardTestDispatcher. הבעיה הזו משפיעה על מקרים שבהם יוצרים מופע באמצעות בנאי, או תת-מחלקה AndroidComposeUiTestEnvironment, ומפעילים את הבנאי הזה.
שינוי מרכזי: השפעה על הביצוע של קורוטינות
ההבדל העיקרי בין גרסה 1 לגרסה 2 של ממשקי ה-API הוא האופן שבו מתבצעת ההפצה של קורוטינות:
- v1 APIs (
UnconfinedTestDispatcher): כשקורוטינה הופעלה, היא בוצעה באופן מיידי בשרשור הנוכחי, ולרוב הסתיימה לפני שהופעלה השורה הבאה של קוד הבדיקה. בניגוד להתנהגות בסביבת הייצור, ההפעלה המיידית הזו עלולה להסתיר בטעות בעיות תזמון אמיתיות או תנאי מירוץ שיתרחשו באפליקציה פעילה. - v2 APIs (
StandardTestDispatcher): כשמפעילים קורוטינה, היא מתווספת לתור ולא מופעלת עד שהבדיקה מקדמת באופן מפורש את השעון הווירטואלי. ממשקי API סטנדרטיים של כתיבת הודעות (כמוwaitForIdle()) כבר מטפלים בסנכרון הזה, ולכן רוב הבדיקות שמסתמכות על ממשקי ה-API הסטנדרטיים האלה ימשיכו לפעול ללא שינויים.
שגיאות נפוצות ופתרונות
אם הבדיקות נכשלות אחרי שמשדרגים לגרסה 2, סביר להניח שהן מציגות את הדפוס הבא:
- כשל: אתם מפעילים משימה (לדוגמה, ViewModel טוען נתונים), אבל הטענה נכשלת באופן מיידי כי הנתונים עדיין במצב 'טעינה'.
- הסיבה: בממשקי API מגרסה 2, קורוטינות מתווספות לתור במקום להיות מופעלות באופן מיידי. המשימה הוכנסה לתור אבל לא בוצעה בפועל לפני שהתוצאה נבדקה.
- תיקון: קידום הזמן באופן מפורש. צריך לציין באופן מפורש למרכז הבקרה בגרסה 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 כדי להריץ משימות בתור לפני ההצהרה.
אפשרות 1: שימוש ב-waitForIdle מקדם את השעון עד שממשק המשתמש לא פעיל, וכך מוודאים שהקורוטינה פעלה.
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)
}
סנכרון ידני
בתרחישים שבהם מתבצעת סנכרון ידני, למשל כשמשביתים את ההתקדמות האוטומטית, הפעלה של קורוטינה לא מובילה להפעלה מיידית כי השעון של הבדיקה מושהה. כדי להריץ קורוטינות בתור בלי להקדים את השעון הווירטואלי, משתמשים ב-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() } }
הגישה המומלצת
ה-API runComposeUiTest מריץ אוטומטית את בלוק הבדיקה בהיקף משלו.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() } }