ה-codelab הזה הוא חלק מהקורס Advanced Android in Kotlin (פיתוח מתקדם ל-Android ב-Kotlin). כדי להפיק את המרב מהקורס הזה, מומלץ לעבוד על ה-codelabs לפי הסדר, אבל זה לא חובה. כל ה-codelab של הקורס מפורטים בדף הנחיתה של ה-codelab בנושא Android מתקדם ב-Kotlin.
מבוא
כשמטמיעים את התכונה הראשונה באפליקציה הראשונה, סביר להניח שמריצים את הקוד כדי לוודא שהיא פועלת כמצופה. ביצעת בדיקה, אבל בדיקה ידנית. בטח המשכתם להריץ את הקוד ולוודא שהוא פועל, ככל שהמשכתם להוסיף ולעדכן תכונות. אבל לעשות את זה ידנית בכל פעם זה מעייף, מועד לטעויות ולא ניתן להרחבה.
מחשבים מצוינים בהתאמת העומס ובאוטומציה! לכן מפתחים בחברות גדולות וקטנות כותבים בדיקות אוטומטיות, שהן בדיקות שמורצות על ידי תוכנה ולא דורשות הפעלה ידנית של האפליקציה כדי לוודא שהקוד פועל.
בסדרת ה-codelab הזו תלמדו איך ליצור אוסף של בדיקות (שנקרא חבילת בדיקות) לאפליקציה מהעולם האמיתי.
ב-codelab הראשון הזה נסביר את היסודות של בדיקות ב-Android. תכתבו את הבדיקות הראשונות שלכם ותלמדו איך לבדוק LiveData
ו-ViewModel
.
מה שכדאי לדעת
חשוב שתכירו את:
- שפת התכנות Kotlin
- ספריות הליבה הבאות של Android Jetpack:
ViewModel
ו-LiveData
- ארכיטקטורת האפליקציה, בהתאם לתבנית מהמדריך לארכיטקטורת אפליקציות ומסדנאות ה-codelab בנושא יסודות Android
מה תלמדו
במאמר הזה נסביר על הנושאים הבאים:
- איך כותבים ומריצים בדיקות יחידה ב-Android
- איך משתמשים בפיתוח מונחה-בדיקות
- איך בוחרים בין בדיקות עם מכשור לבדיקות מקומיות
תלמדו על הספריות ועל מושגי הקוד הבאים:
הפעולות שתבצעו:
- הגדרה, הפעלה וניתוח של בדיקות מקומיות ובדיקות עם מכשור ב-Android.
- כתיבת בדיקות יחידה ב-Android באמצעות JUnit4 ו-Hamcrest.
- כתיבת בדיקות פשוטות של
LiveData
ושלViewModel
.
בסדרת ה-codelabs הזו, תעבדו עם אפליקציית TO-DO Notes. האפליקציה מאפשרת לכם לרשום משימות לביצוע ולהציג אותן ברשימה. אחר כך תוכלו לסמן אותן כהשלמה או כלא השלמה, לסנן אותן או למחוק אותן.
האפליקציה הזו כתובה ב-Kotlin, יש לה כמה מסכים, היא משתמשת ברכיבי Jetpack ומבוססת על הארכיטקטורה שמוסברת במדריך לארכיטקטורת אפליקציות. אם תלמדו איך לבדוק את האפליקציה הזו, תוכלו לבדוק גם אפליקציות שמשתמשות באותן ספריות ובאותה ארכיטקטורה.
כדי להתחיל, מורידים את הקוד:
אפשרות אחרת היא לשכפל את מאגר 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. אם אתם מכירים את אחת מהדוגמאות שבהמשך, האפליקציה הזו מבוססת על ארכיטקטורה דומה:
- Room with a View Codelab
- Android Kotlin Fundamentals training codelabs
- Codelabs מתקדמים בנושא Android
- Android Sunflower Sample
- קורס ההדרכה של Udacity בנושא פיתוח אפליקציות ל-Android באמצעות Kotlin
חשוב יותר להבין את הארכיטקטורה הכללית של האפליקציה מאשר להבין לעומק את הלוגיקה בכל אחת מהשכבות.
סיכום החבילות שזמינות:
חבילה: | |
| מסך הוספה או עריכה של משימה: קוד שכבת ממשק המשתמש להוספה או לעריכה של משימה. |
| שכבת הנתונים: השכבה הזו עוסקת בשכבת הנתונים של המשימות. הוא מכיל את מסד הנתונים, הרשת וקוד המאגר. |
| מסך הנתונים הסטטיסטיים: קוד שכבת ממשק המשתמש של מסך הנתונים הסטטיסטיים. |
| מסך פרטי המשימה: קוד שכבת ממשק המשתמש של משימה אחת. |
| מסך המשימות: קוד שכבת ממשק המשתמש לרשימה של כל המשימות. |
| מחלקות כלי עזר: מחלקות משותפות שמשמשות בחלקים שונים של האפליקציה, למשל עבור פריסת הרענון בהחלקה שמשמשת בכמה מסכים. |
שכבת נתונים (.data)
האפליקציה הזו כוללת שכבת רשת מדומה בחבילה remote ושכבת מסד נתונים בחבילה local. כדי לפשט את הדברים, בפרויקט הזה שכבת הרשת מדומה באמצעות HashMap
עם השהיה, במקום לבצע בקשות רשת אמיתיות.
השכבה DefaultTasksRepository
מתאמת או מתווכת בין שכבת הרשת לשכבת מסד הנתונים, והיא זו שמחזירה נתונים לשכבת ממשק המשתמש.
שכבת ממשק המשתמש (.addedittask, .statistics, .taskdetail, .tasks)
כל אחת מהחבילות של שכבת ממשק המשתמש מכילה קטע ו-ViewModel, וגם כל מחלקה אחרת שנדרשת לממשק המשתמש (כמו מתאם לרשימת המשימות). TaskActivity
היא הפעילות שמכילה את כל המקטעים.
ניווט
הניווט באפליקציה נשלט על ידי רכיב הניווט. הוא מוגדר בקובץ nav_graph.xml
. הניווט מופעל במודלים של התצוגה באמצעות המחלקה Event
. המודלים של התצוגה גם קובעים אילו ארגומנטים להעביר. הקטעים עוקבים אחרי ה-Event
ומבצעים את הניווט בפועל בין המסכים.
במשימה הזו תריצו את הבדיקות הראשונות.
- ב-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: מריצים בדיקה מקומית
- פותחים את התיקייה
test
עד שמוצאים את הקובץ ExampleUnitTest.kt. - לוחצים עליו לחיצה ימנית ובוחרים באפשרות Run ExampleUnitTest.
הפלט הבא אמור להופיע בחלון Run (הפעלה) בחלק התחתון של המסך:
- שימו לב לסימני הוי הירוקים והרחיבו את תוצאות הבדיקה כדי לוודא שבדיקה אחת בשם
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.
- מוסיפים את
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
}
}
- מריצים את הבדיקה.
- בתוצאות הבדיקה, שימו לב לסימן X ליד הבדיקה.
- חשוב לשים לב גם לנקודות הבאות:
- אם טענת אימות אחת נכשלת, כל הבדיקה נכשלת.
- מוצג לכם הערך הצפוי (3) לעומת הערך שחושב בפועל (2).
- אתם מועברים לשורה של הטענה שנכשלה
(ExampleUnitTest.kt:16)
.
שלב 3: מריצים בדיקה עם מכשור
בדיקות עם אינסטרומנטציה נמצאות בערכת המקור androidTest
.
- פותחים את ערכת המקור
androidTest
. - מריצים את הבדיקה שנקראת
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: יצירת כיתת בדיקה
- במקור
main
, בתיקייהtodoapp.statistics
, פותחים אתStatisticsUtils.kt
. - מחפשים את הפונקציה
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 של בדיקה שיעזרו לכם להטמיע את הבדיקות של הפונקציה הזו.
- לוחצים לחיצה ימנית על
getActiveAndCompletedStats
ובוחרים באפשרות יצירה > בדיקה.
תיבת הדו-שיח יצירת בדיקה נפתחת:
- משנים את שם המחלקה: ל-
StatisticsUtilsTest
(במקוםStatisticsUtilsKtTest
; עדיף לא לכלול את KT בשם מחלקת הבדיקה). - משאירים את שאר הגדרות ברירת המחדל. JUnit 4 היא ספריית הבדיקות המתאימה. חבילת היעד נכונה (היא משקפת את המיקום של המחלקה
StatisticsUtils
) ואין צורך לסמן אף אחת מתיבות הסימון (הסימון רק יוצר קוד נוסף, אבל אתם תכתבו את הבדיקה מאפס). - לוחצים על אישור.
תיבת הדו-שיח בחירת ספריית יעד נפתחת:
תבצעו בדיקה מקומית כי הפונקציה מבצעת חישובים מתמטיים ולא כוללת קוד ספציפי ל-Android. לכן, אין צורך להריץ אותו במכשיר אמיתי או במכשיר וירטואלי.
- בוחרים בספרייה
test
(ולאandroidTest
) כי תכתבו בדיקות מקומיות. - לוחצים על אישור.
- שימו לב לכיתה
StatisticsUtilsTest
שנוצרה ב-test/statistics/
.
שלב 2: כותבים את פונקציית הבדיקה הראשונה
תכתבו בדיקה שתבדוק:
- אם אין משימות שהושלמו ויש משימה פעילה אחת,
- שאחוז הבדיקות הפעילות הוא 100%,
- ואחוז המשימות שהושלמו הוא 0%.
- פתיחת
StatisticsUtilsTest
. - יוצרים פונקציה בשם
getActiveAndCompletedStats_noCompleted_returnsHundredZero
.
StatisticsUtilsTest.kt
class StatisticsUtilsTest {
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
// Create an active task
// Call your function
// Check the result
}
}
- מוסיפים את ההערה
@Test
מעל שם הפונקציה כדי לציין שמדובר בבדיקה. - ליצור רשימת משימות.
// Create an active task
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
- אפשר להתקשר אל
getActiveAndCompletedStats
עם המשימות האלה.
// Call your function
val result = getActiveAndCompletedStats(tasks)
- כדאי להשתמש באסרטיביות כדי לוודא שערך המאפיין
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)
}
}
- מריצים את הבדיקה (לוחצים לחיצה ימנית על
StatisticsUtilsTest
ובוחרים באפשרות הפעלה).
הוא צריך לעבור:
שלב 3: מוסיפים את התלות ב-Hamcrest
הבדיקות משמשות כתיעוד של מה שהקוד עושה, ולכן חשוב שהן יהיו קריאות לבני אדם. משווים בין שתי הטענות הבאות:
assertEquals(result.completedTasksPercent, 0f)
// versus
assertThat(result.completedTasksPercent, `is`(0f))
הטענה השנייה נשמעת הרבה יותר כמו משפט שנכתב על ידי אדם. הוא נכתב באמצעות מסגרת טענות שנקראת Hamcrest. כלי טוב נוסף לכתיבת טענות קריאות הוא ספריית Truth. תשתמשו ב-Hamcrest ב-codelab הזה כדי לכתוב הצהרות.
- פותחים את
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 לכתיבת טענות
- מעדכנים את הבדיקה
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))
}
}
- מריצים את הבדיקה המעודכנת כדי לוודא שהיא עדיין פועלת.
ב-Codelab הזה לא נלמד את כל הפרטים על Hamcrest, ולכן אם אתם רוצים ללמוד עוד, כדאי לעיין במדריך הרשמי.
זו משימה אופציונלית לתרגול.
במשימה הזו תכתבו עוד בדיקות באמצעות JUnit ו-Hamcrest. תכתבו גם בדיקות באמצעות אסטרטגיה שנגזרת מהשיטה פיתוח מונחה בדיקות. פיתוח מונחה בדיקות (TDD) הוא גישה בתחום התכנות שגורסת שבמקום לכתוב קוד של תכונה קודם, כותבים קודם את הבדיקות. לאחר מכן כותבים את קוד התכונה במטרה לעבור את הבדיקות.
שלב 1. כתיבת הבדיקות
כתיבת בדיקות כשמדובר ברשימת משימות רגילה:
- אם יש משימה אחת שהושלמה ואין משימות פעילות, אחוז
activeTasks
צריך להיות0f
, ואחוז המשימות שהושלמו צריך להיות100f
. - אם יש שתי משימות שהושלמו ושלוש משימות פעילות, אחוז ההשלמה צריך להיות
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()
)
}
כדי לתקן את הקוד ולכתוב בדיקות, תשתמשו בפיתוח מונחה בדיקות. פיתוח מבוסס-בדיקות מתבצע לפי השלבים הבאים.
- כותבים את הבדיקה באמצעות המבנה Given, When, Then, ועם שם שמתאים למוסכמה.
- מאשרים שהבדיקה נכשלה.
- כותבים את הקוד המינימלי כדי שהבדיקה תעבור.
- חוזרים על הפעולה לכל המבחנים.
במקום להתחיל בתיקון הבאג, מתחילים בכתיבת הבדיקות. אחרי זה תוכלו לוודא שיש לכם בדיקות שיגנו עליכם מפני החזרת הבאגים האלה בטעות בעתיד.
- אם יש רשימה ריקה (
emptyList()
), שני האחוזים צריכים להיות 0f. - אם הייתה שגיאה בטעינת המשימות, הרשימה תהיה
null
, ושני אחוזי ההתקדמות יהיו 0f. - מריצים את הבדיקות ומוודאים שהן נכשלות:
שלב 3. תיקון הבאג
אחרי שיוצרים את הבדיקות, מתקנים את הבאג.
- כדי לתקן את הבאג ב-
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
)
}
}
- מריצים את הבדיקות שוב ומוודאים שכל הבדיקות עוברות.
בעזרת 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
.
- פותחים את הכיתה שרוצים לבדוק בחבילה
tasks
TasksViewModel.
- בקטע הקוד, לוחצים לחיצה ימנית על שם המחלקה
TasksViewModel
-> יצירה -> בדיקה.
- במסך יצירת בדיקה, לוחצים על אישור כדי לאשר (אין צורך לשנות את הגדרות ברירת המחדל).
- בתיבת הדו-שיח בחירת ספריית יעד, בוחרים את הספרייה test.
שלב 2. התחלת כתיבת הבדיקה של ViewModel
בשלב הזה מוסיפים בדיקה של מודל התצוגה כדי לבדוק שכאשר קוראים לשיטה addNewTask
, מופעלת השיטה Event
לפתיחת חלון המשימה החדש.
- יוצרים בדיקה חדשה בשם
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 בצורה נכונה:
- מוסיפים את יחסי התלות של ליבת AndroidX Test ושל ext
- מוסיפים את התלות ב-Robolectric Testing library
- הוספת הערות לכיתה באמצעות AndroidJunit4 test runner
- כתיבת קוד של AndroidX Test
תצטרכו להשלים את השלבים האלה ואז להבין מה הם עושים ביחד.
שלב 3. הוספת יחסי התלות של Gradle
- מעתינים את התלויות האלה לקובץ
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
- מוסיפים את
@RunWith(AndroidJUnit4::class)
מעל הכיתה לבדיקה.
TasksViewModelTest.kt
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
// Test code
}
שלב 5. שימוש ב-AndroidX Test
בשלב הזה אפשר להשתמש בספריית הבדיקות של AndroidX. כולל השיטה ApplicationProvider.getApplicationContex
t
, שמקבלת הקשר של אפליקציה.
- יוצרים
TasksViewModel
באמצעותApplicationProvider.getApplicationContext()
מספריית הבדיקות של AndroidX.
TasksViewModelTest.kt
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
- מתקשרים אל
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
}
- מריצים את הבדיקה כדי לוודא שהיא פועלת.
מושג: איך פועל 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.
- מוסיפים את השורה הבאה לקובץ 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
, מומלץ לבצע שתי פעולות:
- שימוש ב-
InstantTaskExecutorRule
- איך מוודאים שמתבצעת תצפית על
LiveData
שלב 1. שימוש ב-InstantTaskExecutorRule
InstantTaskExecutorRule
הוא כלל JUnit. כשמשתמשים בה עם ההערה @get:Rule
, חלק מהקוד בכיתה InstantTaskExecutorRule
מופעל לפני הבדיקות ואחריהן (כדי לראות את הקוד המדויק, אפשר להשתמש במקש הקיצור Command+B כדי להציג את הקובץ).
הכלל הזה מפעיל את כל עבודות הרקע שקשורות לרכיבי הארכיטקטורה באותו השרשור, כך שתוצאות הבדיקה מתקבלות באופן סינכרוני ובסדר שניתן לשחזור. אם אתם כותבים בדיקות שכוללות בדיקה של LiveData, כדאי להשתמש בכלל הזה.
- מוסיפים את יחסי התלות של Gradle עבור ספריית הליבה של Architecture Components (שכוללת את הכלל הזה).
app/build.gradle
testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
- פתיחה של
TasksViewModelTest.kt
- מוסיפים את
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
כדי
- להפעיל אירועים מסוג
onChanged
. - להפעיל טרנספורמציות.
כדי לקבל את ההתנהגות הצפויה של 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.
- יוצרים קובץ Kotlin חדש בשם
LiveDataTestUtil.kt
בערכת המקורtest
.
- מעתיקים ומדביקים את הקוד שבהמשך.
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
.
- מקבלים את הערך
LiveData
שלnewTaskEvent
באמצעותgetOrAwaitValue
.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
- בודקים שהערך הוא לא 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()))
}
}
- מריצים את הקוד ורואים שהבדיקה עוברת.
עכשיו, אחרי שראית איך כותבים בדיקה, אפשר לכתוב בדיקה משלך. בשלב הזה, תתאמנו בכתיבת עוד TasksViewModel
מבחן, תוך שימוש בכישורים שלמדתם.
שלב 1. כתיבת בדיקה משלכם ל-ViewModel
תכתבו setFilterAllTasks_tasksAddViewVisible()
. בבדיקה הזו צריך לוודא שאם הגדרתם את סוג המסנן להצגת כל המשימות, לחצן הוספת משימה מוצג.
- בעזרת
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.
קובעת אם לחצן הוספת המשימה יהיה גלוי.
- מריצים את הבדיקה.
שלב 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
באמצעות אותה הצהרת AndroidXApplicationProvider.getApplicationContext()
. - מבצעים קריאה לשיטה
setFiltering
ומעבירים את סוג המסנןALL_TASKS
enum. - בודקים שהערך של
tasksAddViewVisible
הוא true באמצעות השיטהgetOrAwaitNextValue
.
שלב 3. הוספת כלל @Before
שימו לב שבתחילת שתי הבדיקות מוגדר TasksViewModel
.
TasksViewModelTest
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
אם יש לכם קוד הגדרה שחוזר על עצמו בכמה בדיקות, אתם יכולים להשתמש בהערה @Before כדי ליצור שיטת הגדרה ולהסיר קוד שחוזר על עצמו. מכיוון שכל הבדיקות האלה יבדקו את TasksViewModel
וצריכות מודל תצוגה, צריך להעביר את הקוד הזה לבלוק @Before
.
- יוצרים משתנה מופע
lateinit
בשםtasksViewModel|
. - יוצרים שיטה בשם
setupViewModel
. - להוסיף הערות באמצעות
@Before
. - מעבירים את קוד יצירת המופע של מודל התצוגה אל
setupViewModel
.
TasksViewModelTest
// Subject under test
private lateinit var tasksViewModel: TasksViewModel
@Before
fun setupViewModel() {
tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
}
- מריצים את הקוד.
אזהרה
לא מבצעים את הפעולות הבאות, לא מאתחלים את
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.
ב-Codelab הזה למדנו על:
- איך מריצים בדיקות מ-Android Studio.
- ההבדל בין בדיקות מקומיות (
test
) לבין בדיקות של כלי מדידה (androidTest
). - איך כותבים בדיקות יחידה מקומיות באמצעות JUnit ו-Hamcrest.
- הגדרת בדיקות של ViewModel באמצעות ספריית הבדיקות של AndroidX.
קורס ב-Udacity:
מסמכי תיעוד למפתחי Android:
- מדריך לארכיטקטורת אפליקציות
- JUnit4
- Hamcrest
- ספריית הבדיקות Robolectric
- AndroidX Test Library
- AndroidX Architecture Components Core Test Library
- קבוצות מקור
- בדיקה משורת הפקודה
סרטי וידאו:
אחר:
קישורים למדריכי Codelab נוספים בקורס הזה זמינים בדף הנחיתה של מדריכי Codelab בנושא Android מתקדם ב-Kotlin.