إنشاء اختبارات للوحدات باستخدام مكتبة Health Connect Testing

مكتبة "اختبارات Health Connect" (androidx.health.connect:connect-testing) تبسيط إنشاء الاختبارات التلقائية. يمكنك استخدام هذه المكتبة لإثبات الملكية. سلوك تطبيقك والتحقق من استجابته بشكل صحيح والحالات غير الشائعة، والتي يصعب اختبارها يدويًا.

يمكنك استخدام المكتبة لإنشاء اختبارات وحدات محلية، والتي تتحقّق عادةً من سلوك الفئات في تطبيقك التي تتفاعل مع عميل Health Connect.

لبدء استخدام المكتبة، أضفها كتبعية اختبارية:

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

نقطة الدخول إلى المكتبة هي الصف FakeHealthConnectClient، الذي استخدامها في الاختبارات لاستبدال HealthConnectClient. FakeHealthConnectClient على الميزات التالية:

  • تمثيل في الذاكرة للسجلات، حتى تتمكن من إدراج وإزالة وحذف قراءتها
  • إنشاء رموز التغيير وتتبُّع التغييرات
  • تقسيم السجلّات والتغييرات على صفحات
  • تتوفّر الردود المجمّعة مع العناصر الأساسية.
  • السماح لأي دالة بطرح استثناءات
  • عبارة عن FakePermissionController يمكن استخدامه لمحاكاة عمليات التحقّق من الأذونات

لمعرفة المزيد حول استبدال التبعيات في الاختبارات، اقرأ تطبيق Dependency Injection في Android لمعرفة المزيد عن المنتجات المزيّفة، يُرجى قراءة استخدام مرات الظهور التجريبي في Android

فعلى سبيل المثال، إذا كانت الفئة التي تتفاعل مع العميل تسمى HealthConnectManager وتتطلب HealthConnectClient كتبعية، الشكل:

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

في الاختبارات، يمكنك تمرير قيمة زائفة إلى صفك قيد الاختبار بدلاً من ذلك:

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 لأي خلل في عملية النقل IPC.
  • @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)
}

التجميع

ليس لطلبات التجميع عمليات تنفيذ وهمية. بدلاً من ذلك، تستخدِم طلبات التجميع نماذج تستخدِم وظائف يمكنك برمجة سلوكها بطريقة معيّنة. يمكنك الوصول إلى صفحة التلميحات في السمة 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 السمة 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)
}

بيانات الاختبار

لا تتضمّن المكتبة واجهات برمجة تطبيقات لإنشاء بيانات مزيفة حتى الآن، ولكن يمكنك استخدام البيانات والأدوات التي تستخدمها المكتبة في Android Code Search.

البذور

تسمح لك السمة overrides في FakeHealthConnectClient ببرمجة (أو إيقاف) أي من دوالها بحيث تكون لها استثناءات عند استدعائها. يمكن أن تؤدي استدعاءات التجميع أيضًا إلى عرض بيانات عشوائية، كما أنها تتيح الإضافة إلى قائمة الانتظار. إجابات متعددة. يمكنك الاطّلاع على Stub وMutableStub للحصول على مزيد من المعلومات.

ملخص الحالات الهامشية

  • التأكّد من أنّ تطبيقك يعمل على النحو المتوقّع عندما يطرح العميل استثناءات راجِع مستندات كل دالة لمعرفة الاستثناءات التي يجب التحقّق منها.
  • تأكَّد من أنّ كلّ مكالمة تجريها مع العميل تسبقَها عملية التحقّق المناسبة من الأذونات.
  • تأكّد من صحة تنفيذ التقسيم على صفحات.
  • تحقَّق مما يحدث عند جلب صفحات متعددة ولكن انتهت صلاحية صفحة واحدة. الرمز المميز.