בדיקה מקומית פועלת ישירות בתחנת העבודה שלכם, ולא במכשיר Android או באמולטור. לכן, כדי להריץ בדיקות, הוא משתמש במכונה הווירטואלית המקומית של Java (JVM) במקום במכשיר Android. בדיקות מקומיות מאפשרות לכם להעריך את הלוגיקה של האפליקציה מהר יותר. עם זאת, חוסר היכולת לקיים אינטראקציה עם מסגרת Android יוצר מגבלה על סוגי הבדיקות שאפשר להריץ.
בדיקת יחידה מאמתת את ההתנהגות של קטע קוד קטן, היחידה שנבדקת. הוא עושה את זה על ידי הרצת הקוד ובדיקת התוצאה.
בדיקות יחידה הן בדרך כלל פשוטות, אבל ההגדרה שלהן יכולה להיות בעייתית אם היחידה שנבדקת לא תוכננה מראש כך שיהיה אפשר לבדוק אותה:
- הקוד שרוצים לאמת צריך להיות נגיש מבדיקה. לדוגמה, אי אפשר לבדוק שיטה פרטית באופן ישיר. במקום זאת, בודקים את המחלקה באמצעות ממשקי ה-API הציבוריים שלה.
- כדי להריץ בדיקות יחידה בבידוד, צריך להחליף את יחסי התלות של היחידה שנבדקת ברכיבים שאתם שולטים בהם, כמו רכיבים מזויפים או כפילי בדיקה אחרים. זה בעייתי במיוחד אם הקוד שלכם תלוי ב-Android framework.
במאמר What to test מפורט מידע על אסטרטגיות נפוצות לבדיקות יחידה ב-Android.
מיקום הבדיקות המקומיות
כברירת מחדל, קובצי המקור של בדיקות יחידה מקומיות נמצאים ב-module-name/src/test/. הספרייה הזו כבר קיימת כשיוצרים פרויקט חדש באמצעות Android Studio.
הוספת יחסי תלות לבדיקה
צריך גם להגדיר את יחסי התלות של הבדיקה בפרויקט כדי להשתמש בממשקי ה-API הרגילים שמספקת מסגרת הבדיקה של JUnit.
כדי לעשות את זה, פותחים את הקובץ build.gradle של מודול האפליקציה ומציינים את הספריות הבאות כיחסי תלות. משתמשים בפונקציה testImplementation כדי לציין שהם חלים על קבוצת המקורות של הבדיקה המקומית, ולא על האפליקציה:
dependencies {
// Required -- JUnit 4 framework
testImplementation "junit:junit:$jUnitVersion"
// Optional -- Robolectric environment
testImplementation "androidx.test:core:$androidXTestVersion"
// Optional -- Mockito framework
testImplementation "org.mockito:mockito-core:$mockitoVersion"
// Optional -- mockito-kotlin
testImplementation "org.mockito.kotlin:mockito-kotlin:$mockitoKotlinVersion"
// Optional -- Mockk framework
testImplementation "io.mockk:mockk:$mockkVersion"
}
יצירת מחלקה לבדיקת יחידה מקומית
אתם כותבים את מחלקת בדיקת היחידה המקומית כמחלקת בדיקה של JUnit 4.
כדי לעשות את זה, יוצרים מחלקה שמכילה שיטת בדיקה אחת או יותר, בדרך כלל ב-module-name/src/test/. שיטת בדיקה מתחילה בהערה @Test ומכילה את הקוד להפעלה ולאימות של היבט אחד של הרכיב שרוצים לבדוק.
בדוגמה הבאה מוסבר איך להטמיע מחלקה של בדיקת יחידה מקומית. פונקציית הבדיקה emailValidator_correctEmailSimple_returnsTrue() מנסה לאמת את isValidEmail(),שהיא פונקציה בתוך האפליקציה. פונקציית הבדיקה תחזיר את הערך true אם גם isValidEmail() תחזיר את הערך true.
Kotlin
import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Test class EmailValidatorTest { @Test fun emailValidator_CorrectEmailSimple_ReturnsTrue() { assertTrue(EmailValidator.isValidEmail("name@email.com")) } }
Java
import org.junit.Test; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; class EmailValidatorTest { @Test public void emailValidator_CorrectEmailSimple_ReturnsTrue() { assertTrue(EmailValidator.isValidEmail("name@email.com")); } }
כדאי ליצור בדיקות קריאות שמעריכות אם הרכיבים באפליקציה מחזירים את התוצאות הצפויות. מומלץ להשתמש בספריית טענות נכונות (assertions) כמו junit.Assert, Hamcrest או Truth. קטע הקוד שלמעלה הוא דוגמה לשימוש ב-junit.Assert.
ספריית Android שניתן לדמות
כשמריצים בדיקות יחידה מקומיות, הפלאגין של Android ל-Gradle כולל ספרייה שמכילה את כל ממשקי ה-API של מסגרת Android, בהתאם לגרסה שבה משתמשים בפרויקט. הספרייה מכילה את כל ה-methods וה-classes הציבוריים של ממשקי ה-API האלה, אבל הקוד שבתוך ה-methods הוסר. אם מתבצעת גישה לאחת מהשיטות, הבדיקה מקפיצה הודעת שגיאה.
כך אפשר לבנות בדיקות מקומיות כשמפנים למחלקות ב-Android framework, כמו Context. והכי חשוב, היא מאפשרת להשתמש ב-mocking framework עם מחלקות של Android.
הדמיה של יחסי תלות ב-Android
בעיה נפוצה היא לגלות שמחלקה משתמשת במשאב מחרוזת. כדי לקבל משאבי מחרוזות, אפשר להפעיל את ה-method getString() בכיתה Context. עם זאת, בדיקה מקומית לא יכולה להשתמש ב-Context או בכל אחת מהשיטות שלה, כי הן שייכות ל-Android framework. באופן אידיאלי, הקריאה ל-getString() תועבר מחוץ למחלקה, אבל זה לא תמיד מעשי. הפתרון הוא ליצור דמה או סטאב של Context שתמיד מחזיר את אותו ערך כשמופעלת ה-method getString().
בעזרת ספריית Mockable Android וספריות Mocking כמו Mockito או MockK, אתם יכולים לתכנת את ההתנהגות של ה-mocks של מחלקות Android בבדיקות היחידה.
כדי להוסיף אובייקט מדומה לבדיקת היחידה המקומית באמצעות Mockito, פועלים לפי מודל התכנות הבא:
- מוסיפים את התלות בספריית Mockito לקובץ
build.gradle, כמו שמתואר בקטע הגדרת סביבת הבדיקה. - בתחילת ההגדרה של מחלקת בדיקת היחידה, מוסיפים את ההערה
@RunWith(MockitoJUnitRunner.class). ההערה הזו מורה ל-Mockito test runner לוודא שהשימוש שלכם ב-framework נכון, והיא מפשטת את תהליך האתחול של האובייקטים המדומים. - כדי ליצור אובייקט מדומה לתלות ב-Android, מוסיפים את ההערה
@Mockלפני הצהרת השדה. - כדי להחליף את ההתנהגות של התלות, אפשר לציין תנאי וערך החזרה כשהתנאי מתקיים באמצעות השיטות
when()ו-thenReturn().
בדוגמה הבאה אפשר לראות איך יוצרים בדיקת יחידה שמשתמשת באובייקט מדומה של Context ב-Kotlin שנוצר באמצעות Mockito-Kotlin.
import android.content.Context
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnitRunner
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
private const val FAKE_STRING = "HELLO WORLD"
@RunWith(MockitoJUnitRunner::class)
class MockedContextTest {
@Mock
private lateinit var mockContext: Context
@Test
fun readStringFromContext_LocalizedString() {
// Given a mocked Context injected into the object under test...
val mockContext = mock<Context> {
on { getString(R.string.name_label) } doReturn FAKE_STRING
}
val myObjectUnderTest = ClassUnderTest(mockContext)
// ...when the string is returned from the object under test...
val result: String = myObjectUnderTest.getName()
// ...then the result should be the expected one.
assertEquals(result, FAKE_STRING)
}
}
למידע נוסף על השימוש ב-Mockito framework, קראו את מאמרי העזרה של Mockito API ואת הכיתה SharedPreferencesHelperTest בקוד לדוגמה. כדאי גם לנסות את Codelab בנושא בדיקות ב-Android.
שגיאה: "Method ... not mocked"
ספריית Mockable Android זורקת חריגה אם מנסים לגשת לאחת מהשיטות שלה עם ההודעה Error: "Method ... not mocked.
אם החריגות שנזרקות בעייתיות לבדיקות שלכם, אתם יכולים לשנות את ההתנהגות כך שהשיטות יחזירו null או אפס, בהתאם לסוג ההחזרה. כדי לעשות את זה, מוסיפים את ההגדרות הבאות לקובץ build.gradle ברמה העליונה של הפרויקט ב-Groovy:
android {
...
testOptions {
unitTests.returnDefaultValues = true
}