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

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

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

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

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

נקודת הכניסה לספרייה היא המחלקה 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—through the 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(), שמדמה את האכלוס של ערכי המטא-נתונים על ידי Health Connect במהלך הוספת הרשומה.

כרטיסים

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

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

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