שימוש במכפילים לבדיקה ב-Android

כשבודקים רכיב או מערכת של רכיבים, עושים זאת בבידוד. לדוגמה, כדי לבדוק ViewModel, לא צריך להפעיל אמולטור ולהפעיל ממשק משתמש כי הוא לא תלוי (או לא אמור להיות תלוי) במסגרת Android.

עם זאת, יכול להיות שהנושא שנבדק תלוי בגורמים אחרים כדי לפעול. לדוגמה, יכול להיות ש-ViewModel יהיה תלוי במאגר נתונים כדי לפעול.

כשצריך לספק תלות לנושא הבדיקה, שיטה נפוצה היא ליצור אובייקט כפול לבדיקה (או אובייקט בדיקה). עותקים כפולים לבדיקה הם אובייקטים שנראים ומתנהגים כמו רכיבים באפליקציה, אבל הם נוצרים בבדיקה כדי לספק התנהגות או נתונים ספציפיים. היתרונות העיקריים הם שהם מאפשרים לבצע את הבדיקות מהר יותר ובקלות רבה יותר.

סוגי דמויי-בדיקה

יש סוגים שונים של דמויי מודולים לבדיקה:

מזויף עותק כפול לבדיקה שיש לו הטמעה 'פועלת' של הכיתה, אבל היא מוטמעת בצורה שמתאימה לבדיקות אבל לא מתאימה לסביבת הייצור.

דוגמה: מסד נתונים בזיכרון.

לא צריך מסגרת לזיוף (mocking) כדי ליצור עותקים מזויפים, והם קלים. הן מומלצות.

Mock עותק כפול לבדיקה שמתנהג כפי שתכננתם אותו, ויש לו ציפיות לגבי האינטראקציות שלו. בדיקות של מודלים מדומים ייכשל אם האינטראקציות שלהם לא תואמות לדרישות שהגדרתם. בדרך כלל יוצרים מודלים מדומים באמצעות מסגרת ליצירת מודלים מדומים כדי להשיג את כל זה.

דוגמה: אימות ששיטה במסד נתונים הוזמנה בדיוק פעם אחת.

Stub עותק בדיקה שמתנהג כפי שתכננתם אותו, אבל אין לו ציפיות לגבי האינטראקציות שלו. בדרך כלל נוצרים באמצעות מסגרת של בדיקת מודלים. עדיף להשתמש ב-Fakes במקום ב-stubs, מטעמי פשטות.
dummy עותק כפול לבדיקה שמוענק אבל לא נעשה בו שימוש, למשל אם צריך רק לספק אותו כפרמטר.

דוגמה: פונקציה ריקה שהועברה כקריאה חוזרת (callback) של קליק.

מרגל מעטפת על אובייקט אמיתי, שמנהלת גם מעקב אחרי מידע נוסף, בדומה ל-mocks. בדרך כלל, מומלץ להימנע מהם כדי לא להוסיף מורכבות. לכן, מומלץ להשתמש במודלים מזויפים או במודלים מדומים במקום במודלים של ריגול.
צל זיוף שנעשה בו שימוש ב-Robolectric.

דוגמה לשימוש בזיוף

נניח שאתם רוצים לבצע בדיקת יחידה של ViewModel שמבוסס על ממשק שנקרא UserRepository וחושף את שם המשתמש הראשון לממשק המשתמש. כדי ליצור מופע כפול מזויף לבדיקה, מטמיעים את הממשק ומחזירים נתונים ידועים.

object FakeUserRepository : UserRepository {
    fun getUsers() = listOf(UserAlice, UserBob)
}

val const UserAlice = User("Alice")
val const UserBob = User("Bob")

UserRepository המזויף לא צריך להיות תלוי במקורות הנתונים המקומיים והמרוחקים שבהם נעשה שימוש בגרסת הייצור. הקובץ נמצא בקבוצת מקורות הבדיקה ולא יישלח עם אפליקציית הייצור.

תלות מזויפת יכולה להחזיר נתונים ידועים בלי להסתמך על מקורות נתונים מרוחקים
איור 1: תלות מזויפת בבדיקת יחידה.

הבדיקה הבאה מוודאת ש-ViewModel חושף בצורה נכונה את שם המשתמש הראשון לתצוגה.

@Test
fun viewModelA_loadsUsers_showsFirstUser() {
    // Given a VM using fake data
    val viewModel = ViewModelA(FakeUserRepository) // Kicks off data load on init

    // Verify that the exposed data is correct
    assertEquals(viewModel.firstUserName, UserAlice.name)
}

החלפת UserRepository במודל מזויף היא קלה בבדיקת יחידה, כי ה-ViewModel נוצר על ידי הבודק. עם זאת, יכול להיות מאתגר להחליף רכיבים שרירותיים בבדיקות גדולות יותר.

החלפת רכיבים והזרקת תלות

כשאין לבדיקות שליטה על יצירת המערכות שנבדקות, החלפת רכיבים ברכיבי בדיקה כפולים הופכת למורכבת יותר, ודורשת שהארכיטקטורה של האפליקציה תעמוד בדרישות של עיצוב ניתן לבדיקה.

גם בבדיקות גדולות מקצה לקצה כדאי להשתמש בעותקים כפולים לבדיקה, כמו בדיקת ממשק משתמש עם מכשירי מדידה שמנווטת דרך תהליך משתמש מלא באפליקציה. במקרה כזה, כדאי להפוך את הבדיקה לאטומה. בבדיקה אטומה נמנעים מכל יחסי התלות החיצוניים, כמו אחזור נתונים מהאינטרנט. כך אפשר לשפר את האמינות והביצועים.

איור 2: בדיקה גדולה שמכסה את רוב האפליקציה ומזייפת נתונים מרוחקים.

אפשר לתכנן את האפליקציה כך שתהיה לה גמישות כזו באופן ידני, אבל מומלץ להשתמש ב-Dependency Injection (הזרקת יחסי תלות) כמו Hilt כדי להחליף רכיבים באפליקציה בזמן הבדיקה. מדריך הבדיקה של Hilt

השלבים הבאים

בדף שיטות בדיקה מוסבר איך אפשר לשפר את הפרודוקטיביות באמצעות סוגים שונים של בדיקות.