בניית בדיקות יחידה מקומית

בדיקה מקומית פועלת ישירות בתחנת העבודה שלכם, ולא במכשיר Android או במהדר. לכן, כדי להריץ בדיקות, הכלי משתמש במכונה הווירטואלית המקומית של Java‏ (JVM) ולא במכשיר Android. בדיקות מקומיות מאפשרות לכם להעריך את הלוגיקה של האפליקציה מהר יותר. עם זאת, העובדה שלא ניתן לקיים אינטראקציה עם מסגרת Android יוצרת מגבלה על סוגי הבדיקות שאפשר להריץ.

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

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

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

במאמר מה כדאי לבדוק מפורט מידע על שיטות נפוצות לבדיקת יחידות ב-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 Plug-in כולל ספרייה שמכילה את כל ממשקי ה-API של מסגרת Android, בהתאם לגרסה שבה נעשה שימוש בפרויקט. בספרייה נמצאים כל השיטות הציבוריות ה-methods של ממשקי ה-API האלה, אבל הקוד שבתוך השיטות הוסר. אם בכלל של השיטות שניגשים אליהן, הבדיקה גורמת לחריגה.

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

הדמיית יחסי תלות של Android

בעיה נפוצה היא גילוי ששימוש במשאב מחרוזת בכיתה. אפשר לקבל משאבי מחרוזות באמצעות קריאה ל-method getString() ב-Context בכיתה. עם זאת, אי אפשר להשתמש ב-Context או באף אחת מהשיטות שלו בבדיקה מקומית, כי הן שייכות למסגרת של Android. במצב אידיאלי, השיחה אל getString() תהיה עברו מהכיתה, אבל לא תמיד זה מעשי. הפתרון הוא ליצור מודל מק"ט או סטאב של Context שתמיד מחזיר את אותו ערך כשמפעילים את השיטה getString() שלו.

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

כדי להוסיף אובייקט מדומה לבדיקת היחידה המקומית באמצעות Mockito, מבצעים את הפעולות הבאות מודל תכנות:

  1. כוללים את התלות בספריית Mockito בקובץ build.gradle, כפי שמתואר בקטע הגדרת סביבת הבדיקה.
  2. בתחילת ההגדרה של הכיתה בבדיקת היחידה, מוסיפים את הפקודה הערה אחת (@RunWith(MockitoJUnitRunner.class)). ההערה הזו מורה למנהל הבדיקות של Mockito לאמת שהשימוש שלכם במסגרת תקין, ומפשטת את האינטוליזציה של אובייקטי הדמיון.
  3. כדי ליצור אובייקט מדומה של תלות ב-Android, מוסיפים את ההערה @Mock לפני הצהרת השדה.
  4. כדי לפגוע בהתנהגות של התלות, אפשר לציין תנאי של הערך המוחזר כשהתנאי מתקיים באמצעות הפונקציה 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, אפשר לעיין בחומר העזר בנושא Mockito API ובכיתה SharedPreferencesHelperTest בקוד לדוגמה. כדאי לנסות גם את Codelab בנושא בדיקות ב-Android.

שגיאה: "השיטה ... לא mocked"

ספריית Mockable Android גורמת להפעלת חריגה אם מנסים לגשת לאחת מהשיטות שלה עם ההודעה Error: "Method ... not mocked.

אם החריגות שמזוהות בעייתיות בבדיקות שלך, אפשר לשנות את כך ש-methods יחזירו null או אפס, בהתאם . כדי לעשות זאת, צריך להוסיף את ההגדרות הבאות לחשבון של הפרויקט קובץ build.gradle ברמה העליונה ב-Groovy:

android {
  ...
  testOptions {
    unitTests.returnDefaultValues = true
  }