יצירת בדיקות יחידה באמצעות ספריית הבדיקות של Health Connect

ספריית הבדיקות של Health Connect‏ (androidx.health.connect:connect-testing) מפשטת את יצירת הבדיקות האוטומטיות. אפשר להשתמש בספרייה הזו כדי לאמת את ההתנהגות של האפליקציה ולוודא שהיא מגיבה בצורה נכונה למקרים לא שכיחים, שקשה לבדוק אותם באופן ידני.

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

כדי להתחיל להשתמש בספרייה, מוסיפים אותה כתלות לבדיקה:

 testImplementation("androidx.health.connect:connect-testing:1.0.0-alpha03")

נקודת הכניסה לספרייה היא המחלקה FakeHealthConnectClient, שבה משתמשים בבדיקות כדי להחליף את HealthConnectClient. ‫FakeHealthConnectClient כולל את התכונות הבאות:

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

מידע נוסף על החלפת יחסי תלות בבדיקות זמין במאמר בנושא הזרקת תלות ב-Android. במאמר שימוש ב-test doubles ב-Android אפשר לקרוא מידע נוסף על זיופים.

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

class HealthConnectManager(
    private val healthConnectClient: HealthConnectClient,
    ...
) { }

בבדיקות, אפשר להעביר fake למחלקה שנבדקת במקום זאת:

import androidx.health.connect.client.testing.ExperimentalTestingApi
import androidx.health.connect.client.testing.FakeHealthConnectClient
import kotlinx.coroutines.test.runTest

@OptIn(ExperimentalTestingApi::class)
class HealthConnectManagerTest {

    @Test
    fun readRecords_filterByActivity() = runTest {
        // Create a Fake with 2 running records.
        val fake = FakeHealthConnectClient()
        fake.insertRecords(listOf(fakeRunRecord1, fakeBikeRecord1))

        // Create a manager that depends on the fake.
        val manager = HealthConnectManager(fake)

        // Read running records only.
        val runningRecords = manager.fetchReport(activity = Running)

        // Verify that the records were filtered correctly.
        assertTrue(runningRecords.size == 1)
    }
}

הבדיקה הזו מוודאת שהפונקציה הדמיונית fetchReport ב-HealthConnectManager מסננת רשומות לפי פעילות בצורה תקינה.

אימות חריגים

כמעט כל קריאה ל-HealthConnectClient יכולה להחזיר חריגות. לדוגמה, בתיעוד של insertRecords מוזכרים החריגים הבאים:

  • @throws android.os.RemoteException לכל כשל בהעברת נתונים בין תהליכים.
  • @throws SecurityException לבקשות עם גישה לא מורשית.
  • @throws java.io.IOException לכל בעיה שקשורה לקלט/פלט בדיסק.

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

import androidx.health.connect.client.testing.stubs.stub

@Test
fun addRecords_throwsRemoteException_errorIsExposed() {
    // Create Fake that throws a RemoteException
    // when insertRecords is called.
    val fake = FakeHealthConnectClient()
    fake.overrides.insertRecords = stub { throw RemoteException() }

    // Create a manager that depends on the fake.
    val manager = HealthConnectManager(fake)

    // Insert a record.
    manager.addRecords(fakeRunRecord1)

    // Verify that the manager is exposing an error.
    assertTrue(manager.errors.size == 1)
}

צבירה

אין הטמעות מזויפות של קריאות מצטברות. במקום זאת, קריאות מצטברות משתמשות ב-stubs שאפשר לתכנת כך שיתנהגו בצורה מסוימת. אפשר לגשת ל-stub דרך המאפיין overrides של FakeHealthConnectClient.

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

import androidx.health.connect.client.testing.AggregationResult
import androidx.health.connect.client.records.HeartRateRecord
import androidx.health.connect.client.records.ExerciseSessionRecord
import java.time.Duration

@Test
fun aggregate() {
    // Create a fake result.
    val result =
        AggregationResult(metrics =
            buildMap {
                put(HeartRateRecord.BPM_AVG, 74.0)
                put(
                    ExerciseSessionRecord.EXERCISE_DURATION_TOTAL,
                    Duration.ofMinutes(30)
                )
            }
        )

    // Create a fake that always returns the fake
    // result when aggregate() is called.
    val fake = FakeHealthConnectClient()
    fake.overrides.aggregate = stub(result)

לאחר מכן, תוכלו לוודא שהכיתה שנבדקת, HealthConnectManager במקרה הזה, עיבדה את התוצאה בצורה נכונה:

// Create a manager that depends on the fake.
val manager = HealthConnectManager(fake)
// Call the function that in turn calls aggregate on the client.
val report = manager.getHeartRateReport()

// Verify that the manager is exposing an error.
assertThat(report.bpmAverage).isEqualTo(74.0)

הרשאות

ספריית הבדיקות כוללת את FakePermissionController, שאפשר להעביר אותו כתלות אל FakeHealthConnectClient.

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

כדי לבדוק את הפונקציונליות הזו, אפשר להגדיר אילו הרשאות יהיו זמינות באמצעות FakePermissionController:

import androidx.health.connect.client.testing.FakePermissionController

@Test
fun newRecords_noPermissions_errorIsExposed() {
    // Create a permission controller with no permissions.
    val permissionController = FakePermissionController(grantAll = false)

    // Create a fake client with the permission controller.
    val fake = FakeHealthConnectClient(permissionController = permissionController)

    // Create a manager that depends on the fake.
    val manager = HealthConnectManager(fake)

    // Call addRecords so that the permission check is made.
    manager.addRecords(fakeRunRecord1)

    // Verify that the manager is exposing an error.
    assertThat(manager.errors).hasSize(1)
}

עימוד

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

הנושא שנבדק, HealthConnectManager בדוגמה שלנו, יכול לציין את גודל הדף ב-ReadRecordsRequest:

fun fetchRecordsReport(pageSize: Int = 1000) }
    val pagedRequest =
        ReadRecordsRequest(
            timeRangeFilter = ...,
            recordType = ...,
            pageToken = page1.pageToken,
            pageSize = pageSize,
        )
    val page = client.readRecords(pagedRequest)
    ...

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

@Test
fun readRecords_multiplePages() = runTest {

    // Create a Fake with 2 running records.
    val fake = FakeHealthConnectClient()
    fake.insertRecords(generateRunningRecords(5))

    // Create a manager that depends on the fake.
    val manager = HealthConnectManager(fake)

    // Read records with a page size of 2.
    val report = manager.generateReport(pageSize = 2)

    // Verify that all the pages were processed correctly.
    assertTrue(report.records.size == 5)
}

נתונים לבדיקה

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

כדי ליצור ערכי מטא-נתונים מדומים בבדיקות, אפשר להשתמש ב-MetadataTestHelper. הפונקציה הזו מספקת את populatedWithTestValues() extension function, שמדמה את האכלוס של ערכי מטא-נתונים על ידי Health Connect במהלך הוספת רשומה.

כרטיסים

המאפיין overrides של FakeHealthConnectClient מאפשר לתכנת (או לשמור מקום) לכל אחת מהפונקציות שלו כך שהן יחזירו חריגים כשהן מופעלות. קריאות צבירה יכולות להחזיר גם נתונים שרירותיים, והן תומכות בהוספה לתור של כמה תגובות. מידע נוסף זמין במאמרים בנושא Stub וMutableStub.

סיכום של מקרי קצה

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