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

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

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

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

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

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

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

لمزيد من المعلومات حول استبدال التبعيات في الاختبارات، يُرجى الاطّلاع على مقالة تضمين التبعيات في 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 في حال حدوث أي أعطال في نقل البيانات بين العمليات.
  • @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 في واجهة HealthConnectClient للتحقّق من الأذونات.permissionController يتم ذلك عادةً قبل كل مكالمة مع العميل.

لاختبار هذه الوظيفة، يمكنك ضبط الأذونات المتاحة باستخدام 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.

لمحاكاة قيم بيانات التعريف في الاختبارات، يمكنك استخدام MetadataTestHelper. يوفّر ذلك دالة الإضافة populatedWithTestValues() التي تحاكي عملية ملء Health Connect لقيم البيانات الوصفية أثناء إدراج السجلّ.

القصاصات

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

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

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