יסודות הבדיקה

ה-codelab הזה הוא חלק מהקורס Advanced Android in Kotlin (פיתוח מתקדם ל-Android ב-Kotlin). כדי להפיק את המרב מהקורס הזה, מומלץ לעבוד על ה-codelabs לפי הסדר, אבל זה לא חובה. כל ה-codelab של הקורס מפורטים בדף הנחיתה של ה-codelab בנושא Android מתקדם ב-Kotlin.

מבוא

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

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

בסדרת ה-codelab הזו תלמדו איך ליצור אוסף של בדיקות (שנקרא חבילת בדיקות) לאפליקציה מהעולם האמיתי.

ב-codelab הראשון הזה נסביר את היסודות של בדיקות ב-Android. תכתבו את הבדיקות הראשונות שלכם ותלמדו איך לבדוק LiveData ו-ViewModel.

מה שכדאי לדעת

חשוב שתכירו את:

מה תלמדו

במאמר הזה נסביר על הנושאים הבאים:

  • איך כותבים ומריצים בדיקות יחידה ב-Android
  • איך משתמשים בפיתוח מונחה-בדיקות
  • איך בוחרים בין בדיקות עם מכשור לבדיקות מקומיות

תלמדו על הספריות ועל מושגי הקוד הבאים:

הפעולות שתבצעו:

  • הגדרה, הפעלה וניתוח של בדיקות מקומיות ובדיקות עם מכשור ב-Android.
  • כתיבת בדיקות יחידה ב-Android באמצעות JUnit4 ו-Hamcrest.
  • כתיבת בדיקות פשוטות של LiveData ושל ViewModel.

בסדרת ה-codelabs הזו, תעבדו עם אפליקציית TO-DO Notes. האפליקציה מאפשרת לכם לרשום משימות לביצוע ולהציג אותן ברשימה. אחר כך תוכלו לסמן אותן כהשלמה או כלא השלמה, לסנן אותן או למחוק אותן.

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

כדי להתחיל, מורידים את הקוד:

הורדת קובץ Zip

אפשרות אחרת היא לשכפל את מאגר GitHub של הקוד:

$ git clone https://github.com/googlecodelabs/android-testing.git
$ cd android-testing
$ git checkout starter_code

במשימה הזו תפעילו את האפליקציה ותבחנו את בסיס הקוד.

שלב 1: מריצים את אפליקציית הדוגמה

אחרי שמורידים את אפליקציית רשימת המשימות, פותחים אותה ב-Android Studio ומריצים אותה. הקוד אמור לעבור קומפילציה. כדי לבדוק את האפליקציה:

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

שלב 2: בודקים את קוד האפליקציה לדוגמה

אפליקציית רשימת המשימות מבוססת על דגימת הבדיקה והארכיטקטורה של Architecture Blueprints (באמצעות גרסת הארכיטקטורה התגובתית של הדגימה). האפליקציה פועלת לפי הארכיטקטורה שמוסברת במדריך לארכיטקטורת אפליקציות. הוא משתמש ב-ViewModels עם Fragments, במאגר וב-Room. אם אתם מכירים את אחת מהדוגמאות שבהמשך, האפליקציה הזו מבוססת על ארכיטקטורה דומה:

חשוב יותר להבין את הארכיטקטורה הכללית של האפליקציה מאשר להבין לעומק את הלוגיקה בכל אחת מהשכבות.

סיכום החבילות שזמינות:

חבילה: com.example.android.architecture.blueprints.todoapp

.addedittask

מסך הוספה או עריכה של משימה: קוד שכבת ממשק המשתמש להוספה או לעריכה של משימה.

.data

שכבת הנתונים: השכבה הזו עוסקת בשכבת הנתונים של המשימות. הוא מכיל את מסד הנתונים, הרשת וקוד המאגר.

.statistics

מסך הנתונים הסטטיסטיים: קוד שכבת ממשק המשתמש של מסך הנתונים הסטטיסטיים.

.taskdetail

מסך פרטי המשימה: קוד שכבת ממשק המשתמש של משימה אחת.

.tasks

מסך המשימות: קוד שכבת ממשק המשתמש לרשימה של כל המשימות.

.util

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

שכבת נתונים (‎.data)

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

השכבה DefaultTasksRepository מתאמת או מתווכת בין שכבת הרשת לשכבת מסד הנתונים, והיא זו שמחזירה נתונים לשכבת ממשק המשתמש.

שכבת ממשק המשתמש (‎.addedittask, .statistics, .taskdetail, .tasks)

כל אחת מהחבילות של שכבת ממשק המשתמש מכילה קטע ו-ViewModel, וגם כל מחלקה אחרת שנדרשת לממשק המשתמש (כמו מתאם לרשימת המשימות). ‫TaskActivity היא הפעילות שמכילה את כל המקטעים.

ניווט

הניווט באפליקציה נשלט על ידי רכיב הניווט. הוא מוגדר בקובץ nav_graph.xml. הניווט מופעל במודלים של התצוגה באמצעות המחלקה Event. המודלים של התצוגה גם קובעים אילו ארגומנטים להעביר. הקטעים עוקבים אחרי ה-Event ומבצעים את הניווט בפועל בין המסכים.

במשימה הזו תריצו את הבדיקות הראשונות.

  1. ב-Android Studio, פותחים את החלונית Project ומחפשים את שלוש התיקיות הבאות:
  • com.example.android.architecture.blueprints.todoapp
  • com.example.android.architecture.blueprints.todoapp (androidTest)
  • com.example.android.architecture.blueprints.todoapp (test)

התיקיות האלה נקראות source sets. ערכות מקור הן תיקיות שמכילות את קוד המקור של האפליקציה. ערכות המקור, שמסומנות בצבע ירוק (androidTest ו-test), מכילות את הבדיקות. כשיוצרים פרויקט חדש ל-Android, מקבלים כברירת מחדל את שלושת מקורות הנתונים הבאים. סוגי המשנה הם:

  • main: מכיל את קוד האפליקציה. הקוד הזה משותף לכל הגרסאות השונות של האפליקציה שאפשר ליצור (שנקראות וריאציות של build).
  • androidTest: מכיל בדיקות שנקראות בדיקות עם מכשור.
  • test: מכיל בדיקות שנקראות בדיקות מקומיות.

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

בדיקות מקומיות (test קבוצת מקור)

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

ב-Android Studio, בדיקות מקומיות מיוצגות על ידי סמל משולש ירוק ואדום.

בדיקות עם מכשור (androidTest ערכת מקור)

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

ב-Android Studio, בדיקות עם מכשור מיוצגות על ידי סמל של Android עם משולש ירוק ואדום.

שלב 1: מריצים בדיקה מקומית

  1. פותחים את התיקייה test עד שמוצאים את הקובץ ExampleUnitTest.kt.
  2. לוחצים עליו לחיצה ימנית ובוחרים באפשרות Run ExampleUnitTest.

הפלט הבא אמור להופיע בחלון Run (הפעלה) בחלק התחתון של המסך:

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

שלב 2: גורמים לכשל בבדיקה

למטה מופיעה הבדיקה שהרצתם.

ExampleUnitTest.kt

// A test class is just a normal class
class ExampleUnitTest {

   // Each test is annotated with @Test (this is a Junit annotation)
   @Test
   fun addition_isCorrect() {
       // Here you are checking that 4 is the same as 2+2
       assertEquals(4, 2 + 2)
   }
}

שימו לב שהבדיקות

  • הם מחלקה באחת מקבוצות המקור לבדיקה.
  • מכילים פונקציות שמתחילות בהערה @Test (כל פונקציה היא בדיקה אחת).
  • בדרך כלל מכילים הצהרות.

‫Android משתמשת בספריית הבדיקות JUnit לבדיקות (ב-codelab הזה JUnit4). גם הצהרות וגם ההערה @Test מגיעים מ-JUnit.

טענה היא ליבת הבדיקה. זוהי הצהרת קוד שבודקת שהקוד או האפליקציה התנהגו כצפוי. במקרה הזה, הטענה היא assertEquals(4, 2 + 2), שבודקת אם 4 שווה ל-2 + 2.

כדי לראות איך נראה מבחן שנכשל, מוסיפים טענה שאפשר לראות בקלות שהיא תיכשל. המערכת תבדוק אם 3 שווה ל-1+1.

  1. מוסיפים את assertEquals(3, 1 + 1) לבדיקה addition_isCorrect.

ExampleUnitTest.kt

class ExampleUnitTest {

   // Each test is annotated with @Test (this is a Junit annotation)
   @Test
   fun addition_isCorrect() {
       assertEquals(4, 2 + 2)
       assertEquals(3, 1 + 1) // This should fail
   }
}
  1. מריצים את הבדיקה.
  1. בתוצאות הבדיקה, שימו לב לסימן X ליד הבדיקה.

  1. חשוב לשים לב גם לנקודות הבאות:
  • אם טענת אימות אחת נכשלת, כל הבדיקה נכשלת.
  • מוצג לכם הערך הצפוי (3) לעומת הערך שחושב בפועל (2).
  • אתם מועברים לשורה של הטענה שנכשלה (ExampleUnitTest.kt:16).

שלב 3: מריצים בדיקה עם מכשור

בדיקות עם אינסטרומנטציה נמצאות בערכת המקור androidTest.

  1. פותחים את ערכת המקור androidTest.
  2. מריצים את הבדיקה שנקראת ExampleInstrumentedTest.

ExampleInstrumentedTest

@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
    @Test
    fun useAppContext() {
        // Context of the app under test.
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        assertEquals("com.example.android.architecture.blueprints.reactive",
            appContext.packageName)
    }
}

בניגוד לבדיקה המקומית, הבדיקה הזו מופעלת במכשיר (בדוגמה שלמטה, טלפון Pixel 2 מדומה):

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

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

שלב 1: יצירת כיתת בדיקה

  1. במקור main, בתיקייה todoapp.statistics, פותחים את StatisticsUtils.kt.
  2. מחפשים את הפונקציה getActiveAndCompletedStats.

StatisticsUtils.kt

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

   val totalTasks = tasks!!.size
   val numberOfActiveTasks = tasks.count { it.isActive }
   val activePercent = 100 * numberOfActiveTasks / totalTasks
   val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks

   return StatsResult(
       activeTasksPercent = activePercent.toFloat(),
       completedTasksPercent = completePercent.toFloat()
   )
  
}

data class StatsResult(val activeTasksPercent: Float, val completedTasksPercent: Float)

הפונקציה getActiveAndCompletedStats מקבלת רשימה של משימות ומחזירה StatsResult. ‫StatsResult היא מחלקת נתונים שמכילה שני מספרים: אחוז המשימות שהושלמו ואחוז המשימות שפעילות.

ב-Android Studio יש כלים ליצירת stub של בדיקה שיעזרו לכם להטמיע את הבדיקות של הפונקציה הזו.

  1. לוחצים לחיצה ימנית על getActiveAndCompletedStats ובוחרים באפשרות יצירה > בדיקה.

תיבת הדו-שיח יצירת בדיקה נפתחת:

  1. משנים את שם המחלקה: ל-StatisticsUtilsTest (במקום StatisticsUtilsKtTest; עדיף לא לכלול את KT בשם מחלקת הבדיקה).
  2. משאירים את שאר הגדרות ברירת המחדל. ‫JUnit 4 היא ספריית הבדיקות המתאימה. חבילת היעד נכונה (היא משקפת את המיקום של המחלקה StatisticsUtils) ואין צורך לסמן אף אחת מתיבות הסימון (הסימון רק יוצר קוד נוסף, אבל אתם תכתבו את הבדיקה מאפס).
  3. לוחצים על אישור.

תיבת הדו-שיח בחירת ספריית יעד נפתחת:

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

  1. בוחרים בספרייה test (ולא androidTest) כי תכתבו בדיקות מקומיות.
  2. לוחצים על אישור.
  3. שימו לב לכיתה StatisticsUtilsTest שנוצרה ב-test/statistics/.

שלב 2: כותבים את פונקציית הבדיקה הראשונה

תכתבו בדיקה שתבדוק:

  • אם אין משימות שהושלמו ויש משימה פעילה אחת,
  • שאחוז הבדיקות הפעילות הוא 100%,
  • ואחוז המשימות שהושלמו הוא 0%.
  1. פתיחת StatisticsUtilsTest.
  2. יוצרים פונקציה בשם getActiveAndCompletedStats_noCompleted_returnsHundredZero.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
        // Create an active task

        // Call your function

        // Check the result
    }
}
  1. מוסיפים את ההערה @Test מעל שם הפונקציה כדי לציין שמדובר בבדיקה.
  2. ליצור רשימת משימות.
// Create an active task 
val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
  1. אפשר להתקשר אל getActiveAndCompletedStats עם המשימות האלה.
// Call your function
val result = getActiveAndCompletedStats(tasks)
  1. כדאי להשתמש באסרטיביות כדי לוודא שערך המאפיין result הוא מה שציפיתם.
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

זה הקוד המלא.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {

        // Create an active task (the false makes this active)
        val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
        // Call your function
        val result = getActiveAndCompletedStats(tasks)

        // Check the result
        assertEquals(result.completedTasksPercent, 0f)
        assertEquals(result.activeTasksPercent, 100f)
    }
}
  1. מריצים את הבדיקה (לוחצים לחיצה ימנית על StatisticsUtilsTest ובוחרים באפשרות הפעלה).

הוא צריך לעבור:

שלב 3: מוסיפים את התלות ב-Hamcrest

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

assertEquals(result.completedTasksPercent, 0f)

// versus

assertThat(result.completedTasksPercent, `is`(0f))

הטענה השנייה נשמעת הרבה יותר כמו משפט שנכתב על ידי אדם. הוא נכתב באמצעות מסגרת טענות שנקראת Hamcrest. כלי טוב נוסף לכתיבת טענות קריאות הוא ספריית Truth. תשתמשו ב-Hamcrest ב-codelab הזה כדי לכתוב הצהרות.

  1. פותחים את build.grade (Module: app) ומוסיפים את יחסי התלות הבאים.

app/build.gradle

dependencies {
    // Other dependencies
    testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
}

בדרך כלל משתמשים ב-implementation כשמוסיפים יחסי תלות, אבל כאן משתמשים ב-testImplementation. כשמוכנים לפרסם את האפליקציה לכל העולם, מומלץ לא להגדיל את הגודל של קובץ ה-APK עם קוד בדיקה או תלות כלשהי באפליקציה. אפשר להגדיר אם ספריה מסוימת תיכלל בקוד הראשי או בקוד הבדיקה באמצעות הגדרות Gradle. ההגדרות הנפוצות ביותר הן:

  • implementation—הרכיב התלוי זמין בכל קבוצות המקור, כולל קבוצות המקור של הבדיקות.
  • testImplementation – התלות זמינה רק במערך מקורות הבדיקה.
  • androidTestImplementation – התלות זמינה רק בערכת המקור androidTest.

התצורה שבה משתמשים מגדירה איפה אפשר להשתמש בתלות. אם כותבים:

testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"

המשמעות היא ש-Hamcrest יהיה זמין רק בקבוצת המקור של הבדיקה. היא גם מוודאת שספריית Hamcrest לא תיכלל באפליקציה הסופית.

שלב 4: שימוש ב-Hamcrest לכתיבת טענות

  1. מעדכנים את הבדיקה getActiveAndCompletedStats_noCompleted_returnsHundredZero() לשימוש ב-assertThat של Hamcrest במקום ב-assertEquals.
// REPLACE
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

// WITH
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))

אם תתבקשו, תוכלו להשתמש באפשרות הייבוא import org.hamcrest.Matchers.`is`.

הבדיקה הסופית תיראה כמו הקוד שבהמשך.

StatisticsUtilsTest.kt

import com.example.android.architecture.blueprints.todoapp.data.Task
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.Test

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {

        // Create an active tasks (the false makes this active)
        val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
        // Call your function
        val result = getActiveAndCompletedStats(tasks)

        // Check the result
        assertThat(result.activeTasksPercent, `is`(100f))
        assertThat(result.completedTasksPercent, `is`(0f))

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

ב-Codelab הזה לא נלמד את כל הפרטים על Hamcrest, ולכן אם אתם רוצים ללמוד עוד, כדאי לעיין במדריך הרשמי.

זו משימה אופציונלית לתרגול.

במשימה הזו תכתבו עוד בדיקות באמצעות JUnit ו-Hamcrest. תכתבו גם בדיקות באמצעות אסטרטגיה שנגזרת מהשיטה פיתוח מונחה בדיקות. פיתוח מונחה בדיקות (TDD) הוא גישה בתחום התכנות שגורסת שבמקום לכתוב קוד של תכונה קודם, כותבים קודם את הבדיקות. לאחר מכן כותבים את קוד התכונה במטרה לעבור את הבדיקות.

שלב 1. כתיבת הבדיקות

כתיבת בדיקות כשמדובר ברשימת משימות רגילה:

  1. אם יש משימה אחת שהושלמה ואין משימות פעילות, אחוז activeTasks צריך להיות 0f, ואחוז המשימות שהושלמו צריך להיות 100f .
  2. אם יש שתי משימות שהושלמו ושלוש משימות פעילות, אחוז ההשלמה צריך להיות 40f ואחוז הפעילות צריך להיות 60f.

שלב 2. כתיבת בדיקה לבאג

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

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

   val totalTasks = tasks!!.size
   val numberOfActiveTasks = tasks.count { it.isActive }
   val activePercent = 100 * numberOfActiveTasks / totalTasks
   val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks

   return StatsResult(
       activeTasksPercent = activePercent.toFloat(),
       completedTasksPercent = completePercent.toFloat()
   )
  
}

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

  1. כותבים את הבדיקה באמצעות המבנה Given, When, Then, ועם שם שמתאים למוסכמה.
  2. מאשרים שהבדיקה נכשלה.
  3. כותבים את הקוד המינימלי כדי שהבדיקה תעבור.
  4. חוזרים על הפעולה לכל המבחנים.

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

  1. אם יש רשימה ריקה (emptyList()), שני האחוזים צריכים להיות 0f.
  2. אם הייתה שגיאה בטעינת המשימות, הרשימה תהיה null, ושני אחוזי ההתקדמות יהיו 0f.
  3. מריצים את הבדיקות ומוודאים שהן נכשלות:

שלב 3. תיקון הבאג

אחרי שיוצרים את הבדיקות, מתקנים את הבאג.

  1. כדי לתקן את הבאג ב-getActiveAndCompletedStats, מחזירים את 0f אם tasks הוא null או ריק:
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

    return if (tasks == null || tasks.isEmpty()) {
        StatsResult(0f, 0f)
    } else {
        val totalTasks = tasks.size
        val numberOfActiveTasks = tasks.count { it.isActive }
        StatsResult(
            activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
            completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
        )
    }
}
  1. מריצים את הבדיקות שוב ומוודאים שכל הבדיקות עוברות.

בעזרת TDD וכתיבת הבדיקות קודם, אתם יכולים לוודא ש:

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

פתרון: כתיבת בדיקות נוספות

אלה כל הבדיקות וקוד התכונה המתאים.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {
        val tasks = listOf(
            Task("title", "desc", isCompleted = false)
        )
        // When the list of tasks is computed with an active task
        val result = getActiveAndCompletedStats(tasks)

        // Then the percentages are 100 and 0
        assertThat(result.activeTasksPercent, `is`(100f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }

    @Test
    fun getActiveAndCompletedStats_noActive_returnsZeroHundred() {
        val tasks = listOf(
            Task("title", "desc", isCompleted = true)
        )
        // When the list of tasks is computed with a completed task
        val result = getActiveAndCompletedStats(tasks)

        // Then the percentages are 0 and 100
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(100f))
    }

    @Test
    fun getActiveAndCompletedStats_both_returnsFortySixty() {
        // Given 3 completed tasks and 2 active tasks
        val tasks = listOf(
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = false),
            Task("title", "desc", isCompleted = false)
        )
        // When the list of tasks is computed
        val result = getActiveAndCompletedStats(tasks)

        // Then the result is 40-60
        assertThat(result.activeTasksPercent, `is`(40f))
        assertThat(result.completedTasksPercent, `is`(60f))
    }

    @Test
    fun getActiveAndCompletedStats_error_returnsZeros() {
        // When there's an error loading stats
        val result = getActiveAndCompletedStats(null)

        // Both active and completed tasks are 0
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }

    @Test
    fun getActiveAndCompletedStats_empty_returnsZeros() {
        // When there are no tasks
        val result = getActiveAndCompletedStats(emptyList())

        // Both active and completed tasks are 0
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }
}

StatisticsUtils.kt

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

    return if (tasks == null || tasks.isEmpty()) {
        StatsResult(0f, 0f)
    } else {
        val totalTasks = tasks.size
        val numberOfActiveTasks = tasks.count { it.isActive }
        StatsResult(
            activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
            completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
        )
    }
}

כל הכבוד על ההתקדמות שלך בלימוד היסודות של כתיבת בדיקות והרצה שלהן. בהמשך נסביר איך לכתוב בדיקות בסיסיות של ViewModel ושל LiveData.

בהמשך ה-codelab, נלמד איך לכתוב בדיקות לשתי מחלקות Android שמשותפות לרוב האפליקציות – ViewModel ו-LiveData.

מתחילים בכתיבת בדיקות ל-TasksViewModel.


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



הבדיקה שתכתבו תבדוק שכשתפעילו את השיטה addNewTask, יופעל Event לפתיחת חלון המשימה החדש. זה קוד האפליקציה שתבדקו.

TasksViewModel.kt

fun addNewTask() {
   _newTaskEvent.value = Event(Unit)
}

שלב 1. יצירת מחלקה TasksViewModelTest

בשלב הזה, יוצרים קובץ בדיקה בשם TasksViewModelTest, באותה דרך שבה יצרתם את StatisticsUtilTest.

  1. פותחים את הכיתה שרוצים לבדוק בחבילה tasks TasksViewModel.
  2. בקטע הקוד, לוחצים לחיצה ימנית על שם המחלקה TasksViewModel -> יצירה -> בדיקה.

  1. במסך יצירת בדיקה, לוחצים על אישור כדי לאשר (אין צורך לשנות את הגדרות ברירת המחדל).
  2. בתיבת הדו-שיח בחירת ספריית יעד, בוחרים את הספרייה test.

שלב 2. התחלת כתיבת הבדיקה של ViewModel

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

  1. יוצרים בדיקה חדשה בשם addNewTask_setsNewTaskEvent.

TasksViewModelTest.kt

class TasksViewModelTest {

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh TasksViewModel


        // When adding a new task


        // Then the new task event is triggered

    }
    
}

מה לגבי הקשר של האפליקציה?

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

TasksViewModelTest.kt

// Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(???)

ספריות הבדיקה של AndroidX כוללות מחלקות ושיטות שמספקות לכם גרסאות של רכיבים כמו Applications ו-Activities שמיועדות לבדיקות. אם אתם מבצעים בדיקה מקומית שבה אתם צריכים מחלקות ממוסגרת של Android מדומה(כמו Application Context), אתם צריכים לבצע את השלבים הבאים כדי להגדיר את AndroidX Test בצורה נכונה:

  1. מוסיפים את יחסי התלות של ליבת AndroidX Test ושל ext
  2. מוסיפים את התלות ב-Robolectric Testing library
  3. הוספת הערות לכיתה באמצעות AndroidJunit4 test runner
  4. כתיבת קוד של AndroidX Test

תצטרכו להשלים את השלבים האלה ואז להבין מה הם עושים ביחד.

שלב 3. הוספת יחסי התלות של Gradle

  1. מעתינים את התלויות האלה לקובץ build.gradle של מודול האפליקציה כדי להוסיף את התלויות של ליבת AndroidX Test ושל ext, וגם את התלות של בדיקת Robolectric.

app/build.gradle

    // AndroidX Test - JVM testing
testImplementation "androidx.test.ext:junit-ktx:$androidXTestExtKotlinRunnerVersion"

    testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"

 testImplementation "org.robolectric:robolectric:$robolectricVersion"

שלב 4. הוספת JUnit Test Runner

  1. מוסיפים את @RunWith(AndroidJUnit4::class)מעל הכיתה לבדיקה.

TasksViewModelTest.kt

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
    // Test code
}

שלב 5. שימוש ב-AndroidX Test

בשלב הזה אפשר להשתמש בספריית הבדיקות של AndroidX. כולל השיטה ApplicationProvider.getApplicationContext, שמקבלת הקשר של אפליקציה.

  1. יוצרים TasksViewModel באמצעות ApplicationProvider.getApplicationContext()מספריית הבדיקות של AndroidX.

TasksViewModelTest.kt

// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
  1. מתקשרים אל addNewTask במספר tasksViewModel.

TasksViewModelTest.kt

tasksViewModel.addNewTask()

בשלב הזה, הבדיקה שלכם אמורה להיראות כמו הקוד שבהמשך.

TasksViewModelTest.kt

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        // TODO test LiveData
    }
  1. מריצים את הבדיקה כדי לוודא שהיא פועלת.

מושג: איך פועל AndroidX Test?

מה זה AndroidX Test?

‫AndroidX Test היא אוסף של ספריות לבדיקה. הוא כולל מחלקות ושיטות שנותנות לכם גרסאות של רכיבים כמו Applications ו-Activities, שמיועדות לבדיקות. לדוגמה, הקוד שכתבת הוא דוגמה לפונקציית AndroidX Test לקבלת הקשר של אפליקציה.

ApplicationProvider.getApplicationContext()

אחד היתרונות של ממשקי ה-API של AndroidX Test הוא שהם מיועדים לפעול גם בבדיקות מקומיות וגם בבדיקות עם מכשור. זה נחמד כי:

  • אפשר להריץ את אותה בדיקה כבדיקה מקומית או כבדיקה עם מכשור.
  • לא צריך ללמוד ממשקי API שונים לבדיקות מקומיות ולבדיקות עם מכשור.

לדוגמה, אם כתבתם את הקוד באמצעות ספריות AndroidX Test, תוכלו להעביר את המחלקה TasksViewModelTest מהתיקייה test לתיקייה androidTest והבדיקות עדיין יפעלו. הפונקציה getApplicationContext() פועלת באופן שונה במקצת בהתאם לסוג הבדיקה: מקומית או עם מכשור:

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

מה זה Robolectric?

סביבת Android המדומה שבה משתמשת AndroidX Test לבדיקות מקומיות מסופקת על ידי Robolectric. ‫Robolectric היא ספריה שיוצרת סביבת Android מדומה לבדיקות, והיא פועלת מהר יותר מאשר הפעלה של אמולטור או הרצה במכשיר. אם לא תציינו את התלות ב-Robolectric, תקבלו את השגיאה הבאה:

מה עושה @RunWith(AndroidJUnit4::class)?

test runner הוא רכיב JUnit שמריץ בדיקות. בלי מפעיל בדיקות, הבדיקות לא יפעלו. יש רכיב ברירת מחדל להרצת בדיקות שסופק על ידי JUnit, והוא מתווסף באופן אוטומטי. ‫@RunWith מחליף את כלי ההרצה של הבדיקות שמוגדר כברירת מחדל.

הכלי AndroidJUnit4 להרצת בדיקות מאפשר ל-AndroidX Test להריץ את הבדיקות בצורה שונה, בהתאם לסוג הבדיקה – בדיקה עם מכשור או בדיקה מקומית.

שלב 6. פתרון בעיות שקשורות לאזהרות של Robolectric

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

בזכות AndroidX Test ו-AndroidJunit4 test runner, הפעולה הזו מתבצעת בלי שתצטרכו לכתוב שורת קוד אחת של Robolectric באופן ישיר.

יכול להיות שיוצגו לכם שתי אזהרות.

  • No such manifest file: ./AndroidManifest.xml
  • "WARN: Android SDK 29 requires Java 9..."

כדי לתקן את האזהרה No such manifest file: ./AndroidManifest.xml, צריך לעדכן את קובץ ה-Gradle.

  1. מוסיפים את השורה הבאה לקובץ gradle כדי להשתמש במניפסט הנכון של Android. האפשרות includeAndroidResources מאפשרת לכם לגשת למשאבי Android בבדיקות היחידה, כולל קובץ AndroidManifest.

app/build.gradle

    // Always show the result of every unit test when running via command line, even if it passes.
    testOptions.unitTests {
        includeAndroidResources = true

        // ... 
    }

האזהרה "WARN: Android SDK 29 requires Java 9..." מורכבת יותר. כדי להריץ בדיקות ב-Android Q נדרשת Java 9. במקום לנסות להגדיר את Android Studio לשימוש ב-Java 9, ב-codelab הזה צריך להגדיר את ה-SDK לטירגוט ולקומפילציה לגרסה 28.

לסיכום:

  • בדרך כלל אפשר להוסיף את הבדיקות של מודל הצפייה הטהור לערכת המקור test כי הקוד שלהן לא דורש בדרך כלל Android.
  • אתם יכולים להשתמש בספרייתAndroidX test כדי לקבל גרסאות בדיקה של רכיבים כמו Applications ו-Activities.
  • אם אתם צריכים להריץ קוד Android מדומה בtest קבוצת המקור, אתם יכולים להוסיף את התלות Robolectric ואת ההערה @RunWith(AndroidJUnit4::class).

כל הכבוד, אתם משתמשים גם בספריית הבדיקות של AndroidX וגם ב-Robolectric כדי להריץ בדיקה. הבדיקה שלך לא הסתיימה (עדיין לא כתבת הצהרת assert, כתוב רק // TODO test LiveData). בהמשך תלמד איך לכתוב הצהרות assert באמצעות LiveData.

במשימה הזו נסביר איך להגדיר נכון את הערך LiveData.

כאן אפשר לראות את המקום שבו הפסקתם בלי addNewTask_setsNewTaskEvent בדיקת מודל התצוגה.

TasksViewModelTest.kt

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        // TODO test LiveData
    }
    

כדי לבדוק את LiveData, מומלץ לבצע שתי פעולות:

  1. שימוש ב-InstantTaskExecutorRule
  2. איך מוודאים שמתבצעת תצפית על LiveData

שלב 1. שימוש ב-InstantTaskExecutorRule

InstantTaskExecutorRule הוא כלל JUnit. כשמשתמשים בה עם ההערה @get:Rule, חלק מהקוד בכיתה InstantTaskExecutorRule מופעל לפני הבדיקות ואחריהן (כדי לראות את הקוד המדויק, אפשר להשתמש במקש הקיצור Command+B כדי להציג את הקובץ).

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

  1. מוסיפים את יחסי התלות של Gradle עבור ספריית הליבה של Architecture Components (שכוללת את הכלל הזה).

app/build.gradle

testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
  1. פתיחה של TasksViewModelTest.kt
  2. מוסיפים את InstantTaskExecutorRule בתוך הכיתה TasksViewModelTest.

TasksViewModelTest.kt

class TasksViewModelTest {
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()
    
    // Other code...
}

שלב 2. הוספה של המחלקה LiveDataTestUtil.kt

השלב הבא הוא לוודא שהמערכת עוקבת אחרי LiveData שאתם בודקים.

כשמשתמשים ב-LiveData, בדרך כלל יש פעילות או קטע (LifecycleOwner) שמתבוננים ב-LiveData.

viewModel.resultLiveData.observe(fragment, Observer {
    // Observer code here
})

ההערה הזו חשובה. צריך משתמשים פעילים עם הרשאת צפייה ב-LiveData כדי

כדי לקבל את ההתנהגות הצפויה של LiveData מודל הצפייה LiveData, צריך לעקוב אחרי LiveData באמצעות LifecycleOwner.

זה יוצר בעיה: בבדיקה TasksViewModel אין פעילות או קטע שאפשר לצפות בהם ב-LiveData. כדי לעקוף את הבעיה הזו, אפשר להשתמש בשיטה observeForever, שמבטיחה שהמצב LiveData ייבדק כל הזמן, בלי צורך ב-LifecycleOwner. כשobserveForever, חשוב לזכור להסיר את המשקיף כדי למנוע דליפה של נתוני משקיף.

הקוד ייראה בערך כמו הקוד שבהמשך. בודקים את זה:

@Test
fun addNewTask_setsNewTaskEvent() {

    // Given a fresh ViewModel
    val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())


    // Create observer - no need for it to do anything!
    val observer = Observer<Event<Unit>> {}
    try {

        // Observe the LiveData forever
        tasksViewModel.newTaskEvent.observeForever(observer)

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.value
        assertThat(value?.getContentIfNotHandled(), (not(nullValue())))

    } finally {
        // Whatever happens, don't forget to remove the observer!
        tasksViewModel.newTaskEvent.removeObserver(observer)
    }
}

זה הרבה קוד boilerplate כדי לצפות ב-LiveData אחד בבדיקה! יש כמה דרכים להיפטר מהטקסט הקבוע הזה. תיצרו פונקציית הרחבה בשם LiveDataTestUtil כדי להקל על הוספת משתנים מסוג Observer.

  1. יוצרים קובץ Kotlin חדש בשם LiveDataTestUtil.kt בערכת המקור test.


  1. מעתיקים ומדביקים את הקוד שבהמשך.

LiveDataTestUtil.kt

import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException


@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
    time: Long = 2,
    timeUnit: TimeUnit = TimeUnit.SECONDS,
    afterObserve: () -> Unit = {}
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer<T> {
        override fun onChanged(o: T?) {
            data = o
            latch.countDown()
            this@getOrAwaitValue.removeObserver(this)
        }
    }
    this.observeForever(observer)

    try {
        afterObserve.invoke()

        // Don't wait indefinitely if the LiveData is not set.
        if (!latch.await(time, timeUnit)) {
            throw TimeoutException("LiveData value was never set.")
        }

    } finally {
        this.removeObserver(observer)
    }

    @Suppress("UNCHECKED_CAST")
    return data as T
}

זו שיטה די מורכבת. היא יוצרת פונקציית הרחבה של Kotlin בשם getOrAwaitValue שמוסיפה observer, מקבלת את הערך LiveData ואז מנקה את ה-observer – בעצם גרסה קצרה וניתנת לשימוש חוזר של הקוד observeForever שמוצג למעלה. הסבר מלא על הסוג הזה מופיע בפוסט הזה בבלוג.

שלב 3. שימוש ב-getOrAwaitValue כדי לכתוב את הטענה

בשלב הזה משתמשים בשיטה getOrAwaitValue וכותבים הצהרת assert שבודקת אם הופעל newTaskEvent.

  1. מקבלים את הערך LiveData של newTaskEvent באמצעות getOrAwaitValue.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
  1. בודקים שהערך הוא לא null.
assertThat(value.getContentIfNotHandled(), (not(nullValue())))

הבדיקה המלאה צריכה להיראות כמו הקוד שבהמשך.

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.example.android.architecture.blueprints.todoapp.getOrAwaitValue
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.nullValue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {

    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()


    @Test
    fun addNewTask_setsNewTaskEvent() {
        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.getOrAwaitValue()

        assertThat(value.getContentIfNotHandled(), not(nullValue()))


    }

}
  1. מריצים את הקוד ורואים שהבדיקה עוברת.

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

שלב 1. כתיבת בדיקה משלכם ל-ViewModel

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

  1. בעזרת addNewTask_setsNewTaskEvent() כנקודת ייחוס, כותבים בדיקה ב-TasksViewModelTest בשם setFilterAllTasks_tasksAddViewVisible() שמגדירה את מצב הסינון ל-ALL_TASKS ומוודאת ש-tasksAddViewVisible LiveData הוא true.


כדי להתחיל, צריך להשתמש בקוד שלמטה.

TasksViewModelTest

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel

        // When the filter type is ALL_TASKS

        // Then the "Add task" action is visible
        
    }

הערה:

  • ה-enum של כל המשימות הוא TasksFilterType ALL_TASKS.
  • ההגדרה LiveData tasksAddViewVisible. קובעת אם לחצן הוספת המשימה יהיה גלוי.
  1. מריצים את הבדיקה.

שלב 2. השוואה בין הבדיקה לפתרון

משווים את הפתרון שלכם לפתרון שבהמשך.

TasksViewModelTest

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When the filter type is ALL_TASKS
        tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)

        // Then the "Add task" action is visible
        assertThat(tasksViewModel.tasksAddViewVisible.getOrAwaitValue(), `is`(true))
    }

כדאי לבדוק אם אתם מבצעים את הפעולות הבאות:

  • אתם יוצרים את tasksViewModel באמצעות אותה הצהרת AndroidX‏ ApplicationProvider.getApplicationContext().
  • מבצעים קריאה לשיטה setFiltering ומעבירים את סוג המסנן ALL_TASKS enum.
  • בודקים שהערך של tasksAddViewVisible הוא true באמצעות השיטה getOrAwaitNextValue.

שלב 3. הוספת כלל @Before

שימו לב שבתחילת שתי הבדיקות מוגדר TasksViewModel.

TasksViewModelTest

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

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

  1. יוצרים משתנה מופע lateinit בשם tasksViewModel|.
  2. יוצרים שיטה בשם setupViewModel.
  3. להוסיף הערות באמצעות @Before.
  4. מעבירים את קוד יצירת המופע של מודל התצוגה אל setupViewModel.

TasksViewModelTest

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }
  1. מריצים את הקוד.

אזהרה

לא מבצעים את הפעולות הבאות, לא מאתחלים את

tasksViewModel

עם ההגדרה שלו:

val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

כתוצאה מכך, אותו מופע ישמש לכל הבדיקות. מומלץ להימנע מכך, כי לכל בדיקה צריך להיות מופע חדש של הנושא שנבדק (במקרה הזה, ViewModel).

הקוד הסופי של TasksViewModelTest אמור להיראות כמו הקוד שבהמשך.

TasksViewModelTest

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    // Executes each task synchronously using Architecture Components.
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }


    @Test
    fun addNewTask_setsNewTaskEvent() {

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.awaitNextValue()
        assertThat(
            value?.getContentIfNotHandled(), (not(nullValue()))
        )
    }

    @Test
    fun getTasksAddViewVisible() {

        // When the filter type is ALL_TASKS
        tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)

        // Then the "Add task" action is visible
        assertThat(tasksViewModel.tasksAddViewVisible.awaitNextValue(), `is`(true))
    }
    
}

לוחצים על כאן כדי לראות את ההבדלים בין הקוד שהתחלתם איתו לבין הקוד הסופי.

כדי להוריד את הקוד של ה-codelab המוגמר, אפשר להשתמש בפקודת ה-git הבאה:

$ git clone https://github.com/googlecodelabs/android-testing.git
$ cd android-testing
$ git checkout end_codelab_1


אפשר גם להוריד את המאגר כקובץ ZIP, לבטל את הדחיסה שלו ולפתוח אותו ב-Android Studio.

הורדת קובץ Zip

ב-Codelab הזה למדנו על:

  • איך מריצים בדיקות מ-Android Studio.
  • ההבדל בין בדיקות מקומיות (test) לבין בדיקות של כלי מדידה (androidTest).
  • איך כותבים בדיקות יחידה מקומיות באמצעות JUnit ו-Hamcrest.
  • הגדרת בדיקות של ViewModel באמצעות ספריית הבדיקות של AndroidX.

קורס ב-Udacity:

מסמכי תיעוד למפתחי Android:

סרטי וידאו:

אחר:

קישורים למדריכי Codelab נוספים בקורס הזה זמינים בדף הנחיתה של מדריכי Codelab בנושא Android מתקדם ב-Kotlin.