أساسيات الاختبار

هذا الدرس العملي حول الترميز هو جزء من دورة "تطبيقات متقدّمة متوافقة مع نظام Android باستخدام لغة Kotlin". ستستفيد إلى أقصى حدّ من هذه الدورة التدريبية إذا تابعت دروس الترميز بالتسلسل، ولكن هذا ليس إلزاميًا. يمكنك الاطّلاع على جميع دورات الترميز في الدورة التدريبية على الصفحة المقصودة لدورات الترميز في "تطبيقات متقدمة متوافقة مع نظام Android باستخدام لغة Kotlin".

مقدمة

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

تتميّز أجهزة الكمبيوتر بقدرتها على التوسّع والأتمتة. لذلك، يكتب المطوّرون في الشركات الكبيرة والصغيرة اختبارات مبرمَجة، وهي اختبارات يتم تشغيلها بواسطة برامج ولا تتطلّب منك تشغيل التطبيق يدويًا للتحقّق من عمل الرمز.

ستتعرّف في هذه السلسلة من الدروس التطبيقية حول الترميز على كيفية إنشاء مجموعة من الاختبارات (المعروفة باسم مجموعة الاختبارات) لتطبيق واقعي.

يغطّي هذا الدرس العملي الأول أساسيات الاختبار على Android، وستكتب اختباراتك الأولى وتتعرّف على كيفية اختبار LiveData وViewModel.

ما يجب معرفته

يجب أن تكون على دراية بما يلي:

أهداف الدورة التعليمية

ستتعرّف على المواضيع التالية:

  • كيفية كتابة اختبارات الوحدات وتشغيلها على Android
  • كيفية استخدام أسلوب التطوير المستند إلى الاختبار
  • كيفية اختيار الاختبارات المبرمَجة والاختبارات المحلية

ستتعرّف على المكتبات ومفاهيم الرموز البرمجية التالية:

الإجراءات التي ستنفذّها

  • إعداد الاختبارات المحلية واختبارات الأجهزة وتشغيلها وتفسير نتائجها في Android
  • كتابة اختبارات الوحدات في Android باستخدام JUnit4 وHamcrest
  • اكتب اختبارات بسيطة LiveData وViewModel.

في هذه السلسلة من الدروس التطبيقية، ستعمل على تطبيق TO-DO Notes الذي يتيح لك تدوين المهام المطلوب إكمالها وعرضها في قائمة. يمكنك بعد ذلك وضع علامة "مكتملة" أو "غير مكتملة" عليها أو فلترتها أو حذفها.

هذا التطبيق مكتوب بلغة Kotlin، ويتضمّن عدة شاشات، ويستخدم مكوّنات Jetpack، ويتبع بنية دليل بنية التطبيق. من خلال التعرّف على كيفية اختبار هذا التطبيق، ستتمكّن من اختبار التطبيقات التي تستخدم المكتبات والبنية نفسها.

للبدء، نزِّل الرمز باتّباع الخطوات التالية:

تنزيل ملف Zip

بدلاً من ذلك، يمكنك استنساخ مستودع Github للرمز:

$ git clone https://github.com/googlecodelabs/android-testing.git
$ cd android-testing
$ git checkout starter_code

في هذه المهمة، ستشغّل التطبيق وتستكشف قاعدة الرموز البرمجية.

الخطوة 1: تشغيل تطبيق العيّنة

بعد تنزيل تطبيق TO-DO، افتحه في Android Studio وشغِّله. يجب أن يتم تجميعها. استكشِف التطبيق باتّباع الخطوات التالية:

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

الخطوة 2: استكشاف نموذج رمز التطبيق

يستند تطبيق TO-DO إلى نموذج الاختبار والتصميم الشائع Architecture Blueprints (باستخدام إصدار التصميم التفاعلي من النموذج). يتبع التطبيق البنية الواردة في دليل بنية التطبيق. يستخدم هذا التطبيق ViewModels مع Fragments ومستودعًا وRoom. إذا كنت على دراية بأي من الأمثلة أدناه، فإنّ هذا التطبيق يتضمّن بنية مشابهة:

من المهم أن تفهم البنية العامة للتطبيق أكثر من أن يكون لديك فهم عميق للمنطق في أي طبقة.

في ما يلي ملخّص للحِزم التي ستظهر لك:

الحزمة: com.example.android.architecture.blueprints.todoapp

.addedittask

شاشة إضافة مهمة أو تعديلها: رمز طبقة واجهة المستخدم لإضافة مهمة أو تعديلها

.data

طبقة البيانات: تتعامل هذه الطبقة مع طبقة البيانات الخاصة بالمهام. ويحتوي على رمز قاعدة البيانات والشبكة والمستودع.

.statistics

شاشة الإحصاءات: رمز طبقة واجهة المستخدِم لشاشة الإحصاءات

.taskdetail

شاشة تفاصيل المهمة: رمز طبقة واجهة المستخدم لمهمة واحدة.

.tasks

شاشة المهام: رمز طبقة واجهة المستخدم لقائمة جميع المهام

.util

فئات الأدوات المساعدة: فئات مشترَكة تُستخدَم في أجزاء مختلفة من التطبيق، مثل تخطيط التحديث بالسحب المستخدَم على شاشات متعددة.

طبقة البيانات (.data)

يتضمّن هذا التطبيق طبقة شبكة محاكاة في حزمة remote وطبقة قاعدة بيانات في حزمة local. لتبسيط الأمر، في هذا المشروع، يتم محاكاة طبقة الشبكة باستخدام HashMap مع تأخير فقط، بدلاً من إجراء طلبات شبكة حقيقية.

تتولّى DefaultTasksRepository التنسيق أو الوساطة بين طبقة الشبكة وطبقة قاعدة البيانات، وهي التي تعرض البيانات في طبقة واجهة المستخدم.

طبقة واجهة المستخدم ( .addedittask و.statistics و.taskdetail و.tasks)

تحتوي كل حزمة من حِزم طبقة واجهة المستخدم على جزء ونموذج عرض، بالإضافة إلى أي فئات أخرى مطلوبة لواجهة المستخدم (مثل أداة ربط لقائمة المهام). TaskActivity هو النشاط الذي يحتوي على جميع الأجزاء.

التنقّل

يتم التحكّم في التنقّل في التطبيق من خلال مكوّن التنقّل. يتم تحديدها في الملف nav_graph.xml. يتم بدء التنقّل في نماذج العرض باستخدام الفئة Event، وتحدّد نماذج العرض أيضًا المَعلمات التي سيتم تمريرها. تراقب الأجزاء Event وتنفّذ عملية التنقّل الفعلية بين الشاشات.

في هذه المهمة، ستنفّذ اختباراتك الأولى.

  1. في "استوديو Android"، افتح اللوحة المشروع وابحث عن هذه المجلدات الثلاثة:
  • com.example.android.architecture.blueprints.todoapp
  • com.example.android.architecture.blueprints.todoapp (androidTest)
  • com.example.android.architecture.blueprints.todoapp (test)

تُعرف هذه المجلدات باسم مجموعات المصادر. مجموعات المصادر هي مجلدات تحتوي على الرمز المصدري لتطبيقك. تحتوي مجموعات المصادر، التي تظهر باللون الأخضر (androidTest وtest)، على اختباراتك. عند إنشاء مشروع Android جديد، ستحصل على مجموعات المصادر الثلاث التالية تلقائيًا. وهي:

  • main: يحتوي على رمز تطبيقك. تتم مشاركة هذا الرمز بين جميع الإصدارات المختلفة من التطبيق التي يمكنك إنشاؤها (المعروفة باسم إصدارات الإنشاء).
  • androidTest: يحتوي على اختبارات تُعرف باسم الاختبارات المبرمَجة.
  • test: يحتوي على اختبارات تُعرف باسم الاختبارات المحلية.

يختلف الاختبارات المحلية عن الاختبارات المبرمَجة في طريقة تنفيذها.

الاختبارات المحلية (مجموعة المصادر test)

يتم إجراء هذه الاختبارات محليًا على JVM في جهاز التطوير ولا تتطلّب محاكيًا أو جهازًا فعليًا. لهذا السبب، تعمل هذه المحاكاة بسرعة، ولكن دقتها أقل، ما يعني أنّها لا تتصرف كما لو كانت في العالم الحقيقي.

في Android Studio، يتم تمثيل الاختبارات المحلية برمز مثلث أخضر وأحمر.

الاختبارات المزوّدة بأدوات (مجموعة المصادر androidTest)

يتم إجراء هذه الاختبارات على أجهزة Android حقيقية أو محاكية، لذا فهي تعكس ما سيحدث في العالم الحقيقي، ولكنها أيضًا أبطأ بكثير.

في "استوديو Android"، يتم تمثيل الاختبارات المزوّدة بأدوات من خلال رمز Android مع مثلث أخضر وأحمر.

الخطوة 1: إجراء اختبار محلي

  1. افتح المجلد test إلى أن تعثر على الملف ExampleUnitTest.kt.
  2. انقر بزر الماوس الأيمن على هذا الملف واختَر تشغيل ExampleUnitTest.

من المفترض أن تظهر لك النتيجة التالية في نافذة التشغيل في أسفل الشاشة:

  1. لاحظ علامات الاختيار الخضراء ووسِّع نتائج الاختبار للتأكّد من اجتياز اختبار واحد باسم addition_isCorrect. يسرّنا معرفة أنّ عملية الإضافة تعمل على النحو المتوقّع.

الخطوة 2: إيقاف الاختبار

في ما يلي الاختبار الذي أجريته للتو.

ExampleUnitTest.kt

// A test class is just a normal class
class ExampleUnitTest {

   // Each test is annotated with @Test (this is a Junit annotation)
   @Test
   fun addition_isCorrect() {
       // Here you are checking that 4 is the same as 2+2
       assertEquals(4, 2 + 2)
   }
}

لاحظ أنّ الاختبارات

  • هي فئة في إحدى مجموعات مصادر الاختبار.
  • تحتوي على دوال تبدأ بالتعليق التوضيحي @Test (كل دالة هي اختبار واحد).
  • عادةً ما تحتوي على عبارات تأكيد.

يستخدم Android مكتبة الاختبار JUnit لإجراء الاختبارات (في هذا الدرس التطبيقي حول الترميز، يتم استخدام JUnit4). تأتي كل من التأكيدات والتعليق التوضيحي @Test من JUnit.

التأكيد هو أساس الاختبار. وهي عبارة عن بيان رمزي يتحقّق من أنّ الرمز البرمجي أو التطبيق يعملان على النحو المتوقّع. في هذه الحالة، يكون التأكيد assertEquals(4, 2 + 2) الذي يتحقّق من أنّ 4 تساوي 2 + 2.

لمعرفة شكل الاختبار الفاشل، أضِف تأكيدًا يمكنك بسهولة ملاحظة أنّه سيفشل. سيتحقّق من أنّ 3 تساوي 1+1.

  1. أضِف assertEquals(3, 1 + 1) إلى اختبار addition_isCorrect.

ExampleUnitTest.kt

class ExampleUnitTest {

   // Each test is annotated with @Test (this is a Junit annotation)
   @Test
   fun addition_isCorrect() {
       assertEquals(4, 2 + 2)
       assertEquals(3, 1 + 1) // This should fail
   }
}
  1. أجرِ الاختبار.
  1. في نتائج الاختبار، ستلاحظ علامة X بجانب الاختبار.

  1. يُرجى أيضًا ملاحظة ما يلي:
  • يؤدي تعذُّر تأكيد واحد إلى تعذُّر الاختبار بأكمله.
  • يتم إعلامك بالقيمة المتوقّعة (3) مقارنةً بالقيمة التي تم احتسابها فعليًا (2).
  • يتم توجيهك إلى سطر التأكيد الذي تعذّر تنفيذه (ExampleUnitTest.kt:16).

الخطوة 3: تنفيذ اختبار مزوَّد بأدوات

تكون الاختبارات التي تتضمّن أدوات القياس في مجموعة المصادر androidTest.

  1. افتح مجموعة المستندات المصدر androidTest.
  2. أريد إجراء الاختبار الذي يحمل الاسم ExampleInstrumentedTest.

ExampleInstrumentedTest

@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
    @Test
    fun useAppContext() {
        // Context of the app under test.
        val appContext = InstrumentationRegistry.getInstrumentation().targetContext
        assertEquals("com.example.android.architecture.blueprints.reactive",
            appContext.packageName)
    }
}

على عكس الاختبار المحلي، يتم إجراء هذا الاختبار على جهاز (في المثال أدناه، هاتف Pixel 2 محاكى):

إذا كان لديك جهاز متصل أو محاكي قيد التشغيل، من المفترض أن ترى الاختبار يعمل على المحاكي.

في هذه المهمة، ستكتب اختبارات للوظيفة getActiveAndCompleteStats التي تحتسب نسبة إحصاءات المهام النشطة والمكتملة في تطبيقك. ويمكنك الاطّلاع على هذه الأرقام في شاشة الإحصاءات في التطبيق.

الخطوة 1: إنشاء فئة اختبار

  1. في مجموعة المصادر main، افتح StatisticsUtils.kt في todoapp.statistics.
  2. ابحث عن الدالة getActiveAndCompletedStats.

StatisticsUtils.kt

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

   val totalTasks = tasks!!.size
   val numberOfActiveTasks = tasks.count { it.isActive }
   val activePercent = 100 * numberOfActiveTasks / totalTasks
   val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks

   return StatsResult(
       activeTasksPercent = activePercent.toFloat(),
       completedTasksPercent = completePercent.toFloat()
   )
  
}

data class StatsResult(val activeTasksPercent: Float, val completedTasksPercent: Float)

تقبل الدالة getActiveAndCompletedStats قائمة بالمهام وتعرض StatsResult. ‫StatsResult هي فئة بيانات تحتوي على رقمَين، وهما النسبة المئوية للمهام المكتملة والنسبة المئوية للمهام النشطة.

يوفّر لك Android Studio أدوات لإنشاء نماذج اختبارية تساعدك في تنفيذ الاختبارات لهذه الدالة.

  1. انقر بزر الماوس الأيمن على getActiveAndCompletedStats واختَر إنشاء > اختبار.

يظهر مربّع الحوار إنشاء اختبار:

  1. غيِّر اسم الفئة: إلى StatisticsUtilsTest (بدلاً من StatisticsUtilsKtTest، من الأفضل عدم تضمين KT في اسم فئة الاختبار).
  2. احتفِظ بالإعدادات التلقائية المتبقية. JUnit 4 هي مكتبة الاختبار المناسبة. حزمة الوجهة صحيحة (فهي تعكس موقع الفئة StatisticsUtils) ولست بحاجة إلى وضع علامة في أي من مربّعات الاختيار (سيؤدي ذلك إلى إنشاء رمز إضافي، ولكن ستكتب اختبارك من البداية).
  3. اضغط على حسنًا.

يظهر مربّع الحوار اختيار دليل الوجهة:

ستُجري اختبارًا محليًا لأنّ الدالة تنفّذ عمليات حسابية ولن تتضمّن أي رمز برمجي خاص بنظام Android. لذلك، لا حاجة إلى تشغيله على جهاز حقيقي أو محاكى.

  1. اختَر الدليل test (وليس androidTest) لأنّك ستكتب اختبارات محلية.
  2. انقر على موافق.
  3. لاحظ الفئة StatisticsUtilsTest التي تم إنشاؤها في test/statistics/.

الخطوة 2: كتابة دالة الاختبار الأولى

ستكتب اختبارًا يتحقّق مما يلي:

  • إذا لم تكن هناك مهام مكتملة ومهمة نشطة واحدة،
  • أنّ النسبة المئوية للاختبارات النشطة هي %100
  • ونسبة المهام المكتملة هي %0.
  1. فتح "StatisticsUtilsTest"
  2. أنشئ دالة باسم getActiveAndCompletedStats_noCompleted_returnsHundredZero.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
        // Create an active task

        // Call your function

        // Check the result
    }
}
  1. أضِف التعليق التوضيحي @Test فوق اسم الدالة للإشارة إلى أنّها اختبار.
  2. أنشئ قائمة بالمهام.
// Create an active task 
val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
  1. اتّصِل بـ getActiveAndCompletedStats لإجراء هذه المهام.
// Call your function
val result = getActiveAndCompletedStats(tasks)
  1. تأكَّد من أنّ قيمة result هي القيمة المتوقّعة، وذلك باستخدام التأكيدات.
// Check the result
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

في ما يلي الرمز الكامل.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {

        // Create an active task (the false makes this active)
        val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
        // Call your function
        val result = getActiveAndCompletedStats(tasks)

        // Check the result
        assertEquals(result.completedTasksPercent, 0f)
        assertEquals(result.activeTasksPercent, 100f)
    }
}
  1. نفِّذ الاختبار (انقر بزر الماوس الأيمن على StatisticsUtilsTest واختَر تشغيل).

يجب أن تستوفي ما يلي:

الخطوة 3: إضافة تبعية Hamcrest

بما أنّ الاختبارات تعمل كمستندات توضّح ما يفعله الرمز البرمجي، من الجيد أن تكون هذه الاختبارات قابلة للقراءة من قِبل الإنسان. قارِن بين التأكيدَين التاليَين:

assertEquals(result.completedTasksPercent, 0f)

// versus

assertThat(result.completedTasksPercent, `is`(0f))

تبدو الجملة الثانية أقرب إلى جملة من تأليف إنسان. تتم كتابته باستخدام إطار عمل تأكيد يُسمى Hamcrest. Truth library هي أداة جيدة أخرى لكتابة تأكيدات قابلة للقراءة. ستستخدم Hamcrest في هذا الدرس العملي لكتابة تأكيدات.

  1. افتح build.grade (Module: app) وأضِف التبعية التالية.

app/build.gradle

dependencies {
    // Other dependencies
    testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"
}

عادةً، تستخدم implementation عند إضافة تبعية، ولكنك تستخدم هنا testImplementation. عندما تكون مستعدًا لمشاركة تطبيقك مع العالم، من الأفضل عدم زيادة حجم حزمة APK بأي من رموز الاختبار أو التبعيات في تطبيقك. يمكنك تحديد ما إذا كان يجب تضمين مكتبة في الرمز البرمجي الرئيسي أو رمز الاختبار باستخدام إعدادات Gradle. في ما يلي عمليات الضبط الأكثر شيوعًا:

  • implementation—تتوفّر التبعية في جميع مجموعات المصادر، بما في ذلك مجموعات مصادر الاختبار.
  • testImplementation: لا تتوفّر التبعية إلا في مجموعة مصادر الاختبار.
  • androidTestImplementation: لا يتوفّر العنصر التابع إلا في مجموعة المصادر androidTest.

يحدّد الإعداد الذي تستخدمه الأماكن التي يمكن استخدام التبعية فيها. إذا كتبت:

testImplementation "org.hamcrest:hamcrest-all:$hamcrestVersion"

وهذا يعني أنّ Hamcrest لن تكون متاحة إلا في مجموعة مصادر الاختبار. ويضمن أيضًا عدم تضمين Hamcrest في تطبيقك النهائي.

الخطوة 4: استخدام Hamcrest لكتابة تأكيدات

  1. عدِّل اختبار getActiveAndCompletedStats_noCompleted_returnsHundredZero() لاستخدام assertThat من Hamcrest بدلاً من assertEquals.
// REPLACE
assertEquals(result.completedTasksPercent, 0f)
assertEquals(result.activeTasksPercent, 100f)

// WITH
assertThat(result.activeTasksPercent, `is`(100f))
assertThat(result.completedTasksPercent, `is`(0f))

يمكنك استخدام عملية الاستيراد import org.hamcrest.Matchers.`is` إذا طُلب منك ذلك.

سيبدو الاختبار النهائي على النحو الموضّح في الرمز البرمجي أدناه.

StatisticsUtilsTest.kt

import com.example.android.architecture.blueprints.todoapp.data.Task
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.`is`
import org.junit.Test

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {

        // Create an active tasks (the false makes this active)
        val tasks = listOf<Task>(
            Task("title", "desc", isCompleted = false)
        )
        // Call your function
        val result = getActiveAndCompletedStats(tasks)

        // Check the result
        assertThat(result.activeTasksPercent, `is`(100f))
        assertThat(result.completedTasksPercent, `is`(0f))

    }
}
  1. نفِّذ الاختبار المعدَّل للتأكّد من أنّه لا يزال يعمل.

لن يعلّمك هذا الدرس التطبيقي حول الترميز جميع تفاصيل Hamcrest، لذا إذا أردت معرفة المزيد، يمكنك الاطّلاع على البرنامج التعليمي الرسمي.

هذه مهمة اختيارية للتدريب.

في هذه المهمة، ستكتب المزيد من الاختبارات باستخدام JUnit وHamcrest. ستكتب أيضًا اختبارات باستخدام استراتيجية مستمدّة من ممارسة البرنامج التطوير المستند إلى الاختبار. تطوير يستند إلى الاختبار أو TDD هو أسلوب برمجة يركّز على كتابة الاختبارات أولاً بدلاً من كتابة رمز الميزة أولاً. بعد ذلك، تكتب رمز الميزة بهدف اجتياز الاختبارات.

الخطوة 1: كتابة الاختبارات

اكتب اختبارات عندما تكون لديك قائمة مهام عادية:

  1. إذا كانت هناك مهمة واحدة مكتملة ولا توجد مهام نشطة، يجب أن تكون نسبة activeTasks هي 0f، ونسبة المهام المكتملة هي 100f .
  2. إذا كانت هناك مهمتان مكتملتان وثلاث مهام نشطة، يجب أن تكون النسبة المئوية للمهام المكتملة 40f والنسبة المئوية للمهام النشطة 60f.

الخطوة 2: كتابة اختبار لتحديد خطأ

يحتوي الرمز البرمجي الخاص بـ getActiveAndCompletedStats كما هو مكتوب على خطأ. لاحظ كيف لا يتعامل بشكل صحيح مع ما يحدث إذا كانت القائمة فارغة أو فارغة. في كلتا الحالتَين، يجب أن تكون كلتا النسبتين المئويتين صفرًا.

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

   val totalTasks = tasks!!.size
   val numberOfActiveTasks = tasks.count { it.isActive }
   val activePercent = 100 * numberOfActiveTasks / totalTasks
   val completePercent = 100 * (totalTasks - numberOfActiveTasks) / totalTasks

   return StatsResult(
       activeTasksPercent = activePercent.toFloat(),
       completedTasksPercent = completePercent.toFloat()
   )
  
}

لإصلاح الرمز وكتابة الاختبارات، عليك استخدام أسلوب التطوير المستند إلى الاختبار. تتّبع عملية التطوير المستند إلى الاختبار الخطوات التالية.

  1. اكتب الاختبار باستخدام بنية "المعطى" و"عندما" و"إذًا"، مع اسم يتّبع الاصطلاح.
  2. أكِّد تعذُّر إجراء الاختبار.
  3. اكتب الحد الأدنى من الرمز البرمجي لاجتياز الاختبار.
  4. كرِّر ذلك مع جميع الاختبارات.

بدلاً من البدء بإصلاح الخطأ، ستبدأ بكتابة الاختبارات أولاً. بعد ذلك، يمكنك التأكّد من أنّ لديك اختبارات تحميك من إعادة إدخال هذه الأخطاء عن طريق الخطأ في المستقبل.

  1. إذا كانت هناك قائمة فارغة (emptyList())، يجب أن تكون كلتا النسبتين المئويتين 0f.
  2. في حال حدوث خطأ أثناء تحميل المهام، ستكون القائمة null، ويجب أن تكون كلتا النسبتين المئويتين 0f.
  3. نفِّذ اختباراتك وتأكَّد من فشلها:

الخطوة 3: إصلاح الخطأ

بعد أن أصبحت لديك اختباراتك، عليك إصلاح الخطأ.

  1. أصلِح الخطأ في getActiveAndCompletedStats من خلال عرض 0f إذا كانت قيمة tasks هي null أو فارغة:
internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

    return if (tasks == null || tasks.isEmpty()) {
        StatsResult(0f, 0f)
    } else {
        val totalTasks = tasks.size
        val numberOfActiveTasks = tasks.count { it.isActive }
        StatsResult(
            activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
            completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
        )
    }
}
  1. أعِد إجراء الاختبارات وتأكَّد من أنّها اجتازت جميعها.

من خلال اتّباع أسلوب التطوير المستند إلى الاختبار وكتابة الاختبارات أولاً، ساعدت في ضمان ما يلي:

  • تتضمّن الوظائف الجديدة دائمًا اختبارات مرتبطة بها، وبالتالي تعمل اختباراتك كوثائق توضّح ما تفعله التعليمات البرمجية.
  • تتحقّق اختباراتك من النتائج الصحيحة وتحميك من الأخطاء التي سبق أن واجهتها.

الحلّ: كتابة المزيد من الاختبارات

في ما يلي جميع الاختبارات ورمز الميزة المقابل.

StatisticsUtilsTest.kt

class StatisticsUtilsTest {

    @Test
    fun getActiveAndCompletedStats_noCompleted_returnsHundredZero {
        val tasks = listOf(
            Task("title", "desc", isCompleted = false)
        )
        // When the list of tasks is computed with an active task
        val result = getActiveAndCompletedStats(tasks)

        // Then the percentages are 100 and 0
        assertThat(result.activeTasksPercent, `is`(100f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }

    @Test
    fun getActiveAndCompletedStats_noActive_returnsZeroHundred() {
        val tasks = listOf(
            Task("title", "desc", isCompleted = true)
        )
        // When the list of tasks is computed with a completed task
        val result = getActiveAndCompletedStats(tasks)

        // Then the percentages are 0 and 100
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(100f))
    }

    @Test
    fun getActiveAndCompletedStats_both_returnsFortySixty() {
        // Given 3 completed tasks and 2 active tasks
        val tasks = listOf(
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = true),
            Task("title", "desc", isCompleted = false),
            Task("title", "desc", isCompleted = false)
        )
        // When the list of tasks is computed
        val result = getActiveAndCompletedStats(tasks)

        // Then the result is 40-60
        assertThat(result.activeTasksPercent, `is`(40f))
        assertThat(result.completedTasksPercent, `is`(60f))
    }

    @Test
    fun getActiveAndCompletedStats_error_returnsZeros() {
        // When there's an error loading stats
        val result = getActiveAndCompletedStats(null)

        // Both active and completed tasks are 0
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }

    @Test
    fun getActiveAndCompletedStats_empty_returnsZeros() {
        // When there are no tasks
        val result = getActiveAndCompletedStats(emptyList())

        // Both active and completed tasks are 0
        assertThat(result.activeTasksPercent, `is`(0f))
        assertThat(result.completedTasksPercent, `is`(0f))
    }
}

StatisticsUtils.kt

internal fun getActiveAndCompletedStats(tasks: List<Task>?): StatsResult {

    return if (tasks == null || tasks.isEmpty()) {
        StatsResult(0f, 0f)
    } else {
        val totalTasks = tasks.size
        val numberOfActiveTasks = tasks.count { it.isActive }
        StatsResult(
            activeTasksPercent = 100f * numberOfActiveTasks / tasks.size,
            completedTasksPercent = 100f * (totalTasks - numberOfActiveTasks) / tasks.size
        )
    }
}

أحسنت في إتقان أساسيات كتابة الاختبارات وتشغيلها. بعد ذلك، ستتعرّف على كيفية كتابة اختبارات ViewModel وLiveData الأساسية.

في بقية الدرس العملي، ستتعلّم كيفية كتابة اختبارات لفئتَين من فئات Android الشائعة في معظم التطبيقات، وهما ViewModel وLiveData.

تبدأ بكتابة اختبارات للرمز TasksViewModel.


ستركّز على الاختبارات التي تتضمّن جميع منطقها في نموذج العرض ولا تعتمد على رمز المستودع. يتضمّن رمز المستودع رمزًا غير متزامن وقواعد بيانات وطلبات شبكة، وكل ذلك يزيد من تعقيد الاختبار. سنتجنّب ذلك في الوقت الحالي ونركّز على كتابة اختبارات لوظائف ViewModel التي لا تختبر أي شيء في المستودع مباشرةً.



سيتحقّق الاختبار الذي ستكتبه من أنّه عند استدعاء الطريقة addNewTask، يتم تشغيل Event لفتح نافذة المهمة الجديدة. إليك رمز التطبيق الذي ستختبره.

TasksViewModel.kt

fun addNewTask() {
   _newTaskEvent.value = Event(Unit)
}

الخطوة 1: إنشاء فئة TasksViewModelTest

باتّباع الخطوات نفسها التي اتّبعتها لـ StatisticsUtilTest، ستنشئ في هذه الخطوة ملف اختبار لـ TasksViewModelTest.

  1. افتح الفئة التي تريد اختبارها، في حزمة tasks، TasksViewModel.
  2. في الرمز، انقر بزر الماوس الأيمن على اسم الفئة TasksViewModel -> إنشاء -> اختبار.

  1. في شاشة إنشاء اختبار، انقر على حسنًا للقبول (لا حاجة إلى تغيير أي من الإعدادات التلقائية).
  2. في مربّع الحوار اختيار دليل الوجهة، اختَر الدليل test.

الخطوة 2: بدء كتابة اختبار ViewModel

في هذه الخطوة، ستضيف اختبارًا لنموذج العرض من أجل التأكّد من أنّه عند استدعاء الطريقة addNewTask، يتم تشغيل Event لفتح نافذة المهمة الجديدة.

  1. أنشئ اختبارًا جديدًا باسم addNewTask_setsNewTaskEvent.

TasksViewModelTest.kt

class TasksViewModelTest {

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh TasksViewModel


        // When adding a new task


        // Then the new task event is triggered

    }
    
}

ماذا عن سياق التطبيق؟

عند إنشاء مثيل من TasksViewModel للاختبار، يتطلّب المنشئ سياق التطبيق. ولكن في هذا الاختبار، لن تنشئ تطبيقًا كاملاً يتضمّن أنشطة وواجهة مستخدم وأجزاءً، فكيف يمكنك الحصول على سياق تطبيق؟

TasksViewModelTest.kt

// Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(???)

تتضمّن مكتبات AndroidX Test فئات وطُرقًا توفّر لك إصدارات من المكوّنات، مثل التطبيقات والأنشطة، والمخصّصة للاختبارات. عند إجراء اختبار محلي تحتاج فيه إلى فئات محاكاة لإطار عمل Android(مثل سياق التطبيق)، اتّبِع الخطوات التالية لإعداد AndroidX Test بشكلٍ صحيح:

  1. إضافة التبعيتَين الأساسية والخارجية AndroidX Test
  2. أضِف تبعية مكتبة Robolectric Testing.
  3. إضافة تعليقات توضيحية إلى الفئة باستخدام مشغّل اختبار AndroidJunit4
  4. كتابة رمز AndroidX Test

ستكمل هذه الخطوات وبعد ذلك ستفهم ما تفعله معًا.

الخطوة 3: إضافة تبعيات Gradle

  1. انسخ هذه التبعيات إلى ملف build.gradle في وحدة تطبيقك لإضافة التبعيات الأساسية لـ AndroidX Test وext، بالإضافة إلى تبعية اختبار Robolectric.

app/build.gradle

    // AndroidX Test - JVM testing
testImplementation "androidx.test.ext:junit-ktx:$androidXTestExtKotlinRunnerVersion"

    testImplementation "androidx.test:core-ktx:$androidXTestCoreVersion"

 testImplementation "org.robolectric:robolectric:$robolectricVersion"

الخطوة 4: إضافة أداة تشغيل اختبار JUnit

  1. أضِف @RunWith(AndroidJUnit4::class) فوق الصف التجريبي.

TasksViewModelTest.kt

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
    // Test code
}

الخطوة 5: استخدام AndroidX Test

في هذه المرحلة، يمكنك استخدام مكتبة AndroidX Test. ويشمل ذلك الطريقة ApplicationProvider.getApplicationContext التي تحصل على Application Context.

  1. أنشئ TasksViewModel باستخدام ApplicationProvider.getApplicationContext() من مكتبة AndroidX للاختبار.

TasksViewModelTest.kt

// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
  1. الاتصال بـ "addNewTask" على tasksViewModel

TasksViewModelTest.kt

tasksViewModel.addNewTask()

في هذه المرحلة، من المفترض أن يبدو اختبارك على النحو الموضّح في الرمز البرمجي أدناه.

TasksViewModelTest.kt

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        // TODO test LiveData
    }
  1. نفِّذ الاختبار للتأكّد من أنّه يعمل.

المفهوم: كيف تعمل مكتبة AndroidX Test؟

ما هي مكتبة AndroidX Test؟

‫AndroidX Test هي مجموعة من المكتبات المخصّصة للاختبار. ويتضمّن هذا الإطار فئات وطُرقًا تمنحك إصدارات من المكوّنات، مثل التطبيقات والأنشطة، والمخصّصة للاختبارات. على سبيل المثال، هذا الرمز الذي كتبته هو مثال على دالة AndroidX Test للحصول على سياق تطبيق.

ApplicationProvider.getApplicationContext()

من مزايا واجهات برمجة التطبيقات AndroidX Test أنّها مصمَّمة للعمل مع الاختبارات المحلية والاختبارات التي تتطلّب تشغيل التطبيق. هذا أمر جيد للأسباب التالية:

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

على سبيل المثال، بما أنّك كتبت الرمز باستخدام مكتبات AndroidX Test، يمكنك نقل الفئة TasksViewModelTest من المجلد test إلى المجلد androidTest وسيظل بإمكانك تشغيل الاختبارات. يعمل getApplicationContext() بشكلٍ مختلف قليلاً حسب ما إذا كان يتم تنفيذه كاختبار محلي أو اختبار مزوّد بأدوات:

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

ما هي Robolectric؟

توفّر Robolectric بيئة Android المحاكية التي تستخدمها AndroidX Test لإجراء الاختبارات المحلية. ‫Robolectric هي مكتبة تنشئ بيئة Android محاكاة للاختبارات وتعمل بشكل أسرع من تشغيل محاكي أو تشغيل الاختبارات على جهاز. بدون تبعية Robolectric، سيظهر لك الخطأ التالي:

ما هي وظيفة @RunWith(AndroidJUnit4::class)؟

مشغّل الاختبار هو أحد مكوّنات JUnit التي تُشغّل الاختبارات. وبدون أداة تشغيل الاختبار، لن يتم تشغيل اختباراتك. يتوفّر برنامج تشغيل اختبار تلقائي تقدّمه JUnit ويمكنك الحصول عليه تلقائيًا. يستبدل @RunWith أداة تشغيل الاختبار التلقائية.

يتيح مشغّل الاختبار AndroidJUnit4 تشغيل اختبار AndroidX بطريقة مختلفة استنادًا إلى ما إذا كانت الاختبارات محلية أو تتطلّب استخدام أدوات.

الخطوة السادسة. إصلاح تحذيرات Robolectric

عند تشغيل الرمز، لاحظ أنّه يتم استخدام Robolectric.

وبفضل AndroidX Test وAndroidJunit4، يتم ذلك بدون أن تكتب أي سطر من رموز Robolectric البرمجية مباشرةً.

قد يظهر لك تحذيران.

  • No such manifest file: ./AndroidManifest.xml
  • "WARN: Android SDK 29 requires Java 9..."

يمكنك حلّ التحذير No such manifest file: ./AndroidManifest.xml من خلال تعديل ملف Gradle.

  1. أضِف السطر التالي إلى ملف Gradle حتى يتم استخدام بيان Android الصحيح. يتيح لك الخيار includeAndroidResources الوصول إلى موارد Android في اختبارات الوحدات، بما في ذلك ملف AndroidManifest.

app/build.gradle

    // Always show the result of every unit test when running via command line, even if it passes.
    testOptions.unitTests {
        includeAndroidResources = true

        // ... 
    }

التحذير "WARN: Android SDK 29 requires Java 9..." أكثر تعقيدًا. يتطلّب إجراء الاختبارات على Android Q الإصدار 9 من Java. بدلاً من محاولة ضبط "استوديو Android" لاستخدام Java 9، احتفِظ في هذا الدرس التطبيقي بمستوى حزمة تطوير البرامج (SDK) المستهدَف ومستوى حزمة تطوير البرامج (SDK) المستخدم في التجميع على 28.

في ما يلي ملخّص:

  • يمكن عادةً وضع اختبارات نماذج العرض البحتة في مجموعة المصادر test لأنّ الرمز البرمجي الخاص بها لا يتطلّب عادةً نظام Android.
  • يمكنك استخدام مكتبةAndroidX test للحصول على إصدارات تجريبية من المكوّنات، مثل التطبيقات والأنشطة.
  • إذا كنت بحاجة إلى تنفيذ رمز Android محاكى في مجموعة المصادر test، يمكنك إضافة تبعية Robolectric والتعليق التوضيحي @RunWith(AndroidJUnit4::class).

تهانينا، أنت تستخدم كلاً من مكتبة الاختبار AndroidX وRobolectric لتنفيذ اختبار. لم تنتهِ اختباراتك (لم تكتب عبارة تأكيد بعد، بل تظهر لك فقط // TODO test LiveData). ستتعلّم كيفية كتابة عبارات التأكيد باستخدام LiveData في الخطوة التالية.

في هذه المهمة، ستتعرّف على كيفية تأكيد قيمة LiveData بشكلٍ صحيح.

إليك المكان الذي توقفت فيه بدون اختبار نموذج العرض addNewTask_setsNewTaskEvent.

TasksViewModelTest.kt

    @Test
    fun addNewTask_setsNewTaskEvent() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        // TODO test LiveData
    }
    

لاختبار LiveData، ننصحك باتّخاذ الإجراءَين التاليَين:

  1. استخدام InstantTaskExecutorRule
  2. ضمان LiveData المراقبة

الخطوة 1: استخدام InstantTaskExecutorRule

InstantTaskExecutorRule هي قاعدة JUnit. عند استخدامها مع التعليق التوضيحي @get:Rule، يؤدي ذلك إلى تشغيل بعض الرموز في الفئة InstantTaskExecutorRule قبل الاختبارات وبعدها (لمشاهدة الرمز الدقيق، يمكنك استخدام اختصار لوحة المفاتيح Command+B لعرض الملف).

تنفّذ هذه القاعدة جميع المهام التي تعمل في الخلفية والمرتبطة بمكوّنات البنية في سلسلة التعليمات نفسها، وذلك حتى تظهر نتائج الاختبار بشكل متزامن وبترتيب قابل للتكرار. عند كتابة اختبارات تتضمّن اختبار LiveData، استخدِم هذه القاعدة.

  1. أضِف تبعية Gradle لمكتبة الاختبار الأساسية لمكوّنات البنية (التي تحتوي على هذه القاعدة).

app/build.gradle

testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
  1. فتح "TasksViewModelTest.kt"
  2. أضِف InstantTaskExecutorRule داخل فئة TasksViewModelTest.

TasksViewModelTest.kt

class TasksViewModelTest {
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()
    
    // Other code...
}

الخطوة 2: إضافة فئة LiveDataTestUtil.kt

تتمثل خطوتك التالية في التأكّد من رصد LiveData الذي تختبره.

عند استخدام LiveData، يكون لديك عادةً نشاط أو جزء (LifecycleOwner) يراقب LiveData.

viewModel.resultLiveData.observe(fragment, Observer {
    // Observer code here
})

هذه الملاحظة مهمة. يجب أن يكون لديك مراقبون نشطون على LiveData كي تتمكّن من

للحصول على السلوك المتوقّع LiveData لـ LiveData في نموذج العرض، عليك مراقبة LiveData باستخدام LifecycleOwner.

وهذا يطرح مشكلة: في اختبار TasksViewModel، ليس لديك نشاط أو جزء لمراقبة LiveData. لتجنُّب ذلك، يمكنك استخدام طريقة observeForever، التي تضمن مراقبة LiveData باستمرار، بدون الحاجة إلى LifecycleOwner. عندما observeForever، عليك تذكُّر إزالة المراقب وإلا ستتعرّض لخطر تسرُّب بيانات المراقب.

يبدو هذا الرمز مشابهًا للرمز أدناه. فحصها:

@Test
fun addNewTask_setsNewTaskEvent() {

    // Given a fresh ViewModel
    val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())


    // Create observer - no need for it to do anything!
    val observer = Observer<Event<Unit>> {}
    try {

        // Observe the LiveData forever
        tasksViewModel.newTaskEvent.observeForever(observer)

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.value
        assertThat(value?.getContentIfNotHandled(), (not(nullValue())))

    } finally {
        // Whatever happens, don't forget to remove the observer!
        tasksViewModel.newTaskEvent.removeObserver(observer)
    }
}

هذا مقدار كبير من الرموز النموذجية لمراقبة LiveData واحد في الاختبار! هناك بضع طرق للتخلص من هذا النص النموذجي. ستنشئ دالة إضافة تسمى LiveDataTestUtil لتسهيل إضافة المراقبين.

  1. أنشئ ملف Kotlin جديدًا باسم LiveDataTestUtil.kt في مجموعة المصادر test.


  1. انسخ الرمز أدناه والصِقه.

LiveDataTestUtil.kt

import androidx.annotation.VisibleForTesting
import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException


@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun <T> LiveData<T>.getOrAwaitValue(
    time: Long = 2,
    timeUnit: TimeUnit = TimeUnit.SECONDS,
    afterObserve: () -> Unit = {}
): T {
    var data: T? = null
    val latch = CountDownLatch(1)
    val observer = object : Observer<T> {
        override fun onChanged(o: T?) {
            data = o
            latch.countDown()
            this@getOrAwaitValue.removeObserver(this)
        }
    }
    this.observeForever(observer)

    try {
        afterObserve.invoke()

        // Don't wait indefinitely if the LiveData is not set.
        if (!latch.await(time, timeUnit)) {
            throw TimeoutException("LiveData value was never set.")
        }

    } finally {
        this.removeObserver(observer)
    }

    @Suppress("UNCHECKED_CAST")
    return data as T
}

هذه طريقة معقّدة إلى حدّ ما. تنشئ هذه الدالة دالة إضافة Kotlin باسم getOrAwaitValue تضيف أداة مراقبة، وتحصل على قيمة LiveData، ثم تزيل أداة المراقبة، وهي في الأساس نسخة قصيرة وقابلة لإعادة الاستخدام من رمز observeForever الموضّح أعلاه. للحصول على شرح كامل لهذه الفئة، يمكنك الاطّلاع على مشاركة المدونة هذه.

الخطوة 3: استخدام getOrAwaitValue لكتابة التأكيد

في هذه الخطوة، ستستخدم طريقة getOrAwaitValue وتكتب عبارة تأكيد تتحقّق من أنّه تم تشغيل newTaskEvent.

  1. احصل على قيمة LiveData لـ newTaskEvent باستخدام getOrAwaitValue.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
  1. تأكَّد من أنّ القيمة ليست فارغة.
assertThat(value.getContentIfNotHandled(), (not(nullValue())))

يجب أن يبدو الاختبار الكامل مثل الرمز البرمجي أدناه.

import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.example.android.architecture.blueprints.todoapp.getOrAwaitValue
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.not
import org.hamcrest.Matchers.nullValue
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {

    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()


    @Test
    fun addNewTask_setsNewTaskEvent() {
        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.getOrAwaitValue()

        assertThat(value.getContentIfNotHandled(), not(nullValue()))


    }

}
  1. نفِّذ الرمز وشاهِد الاختبار ينجح.

بعد أن تعرّفت على كيفية كتابة اختبار، اكتب اختبارًا بنفسك. في هذه الخطوة، استخدِم المهارات التي تعلّمتها للتدريب على كتابة اختبار TasksViewModel آخر.

الخطوة 1: كتابة اختبار ViewModel

ستكتب setFilterAllTasks_tasksAddViewVisible(). يجب أن يتحقّق هذا الاختبار من أنّه إذا ضبطت نوع الفلتر لعرض جميع المهام، سيظهر الزر إضافة مهمة.

  1. باستخدام addNewTask_setsNewTaskEvent() كمرجع، اكتب اختبارًا في TasksViewModelTest باسم setFilterAllTasks_tasksAddViewVisible() يضبط وضع الفلترة على ALL_TASKS ويتأكّد من أنّ tasksAddViewVisible LiveData هي true.


استخدِم الرمز أدناه للبدء.

TasksViewModelTest

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel

        // When the filter type is ALL_TASKS

        // Then the "Add task" action is visible
        
    }

ملاحظة:

  • قيمة التعداد TasksFilterType لجميع المهام هي ALL_TASKS.
  • يتم التحكّم في إمكانية ظهور الزر لإضافة مهمة من خلال LiveData tasksAddViewVisible.
  1. نفِّذ اختبارك.

الخطوة 2: مقارنة الاختبار بالحل

قارِن الحل الذي توصلت إليه بالحل أدناه.

TasksViewModelTest

    @Test
    fun setFilterAllTasks_tasksAddViewVisible() {

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

        // When the filter type is ALL_TASKS
        tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)

        // Then the "Add task" action is visible
        assertThat(tasksViewModel.tasksAddViewVisible.getOrAwaitValue(), `is`(true))
    }

تحقَّق مما إذا كنت تجري أيًا مما يلي:

  • يمكنك إنشاء tasksViewModel باستخدام عبارة ApplicationProvider.getApplicationContext() نفسها في AndroidX.
  • يمكنك استدعاء الطريقة setFiltering، مع تمرير تعداد نوع الفلتر ALL_TASKS.
  • يمكنك التأكّد من أنّ قيمة tasksAddViewVisible هي "صحيح" باستخدام الطريقة getOrAwaitNextValue.

الخطوة 3: إضافة قاعدة @Before

لاحظ كيف تحدّد TasksViewModel في بداية كلّ من اختبارَيك.

TasksViewModelTest

        // Given a fresh ViewModel
        val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

عندما يكون لديك رمز إعداد متكرّر لعدة اختبارات، يمكنك استخدام التعليق التوضيحي ‎@Before لإنشاء طريقة إعداد وإزالة الرمز المتكرّر. بما أنّ جميع هذه الاختبارات ستختبر TasksViewModel، وتحتاج إلى نموذج عرض، ننقل هذا الرمز إلى كتلة @Before.

  1. أنشئ متغيّر مثيل lateinit باسم tasksViewModel|.
  2. أنشئ طريقة باسم setupViewModel.
  3. أضِف تعليقًا توضيحيًا باستخدام @Before.
  4. انقل رمز إنشاء نموذج العرض إلى setupViewModel.

TasksViewModelTest

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }
  1. شغِّل الرمز البرمجي.

تحذير

لا تنفِّذ ما يلي، ولا تبدأ

tasksViewModel

مع تعريفها:

val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())

سيؤدي ذلك إلى استخدام المثيل نفسه لجميع الاختبارات. يجب تجنُّب ذلك لأنّ كل اختبار يجب أن يتضمّن نسخة جديدة من العنصر الخاضع للاختبار (ViewModel في هذه الحالة).

يجب أن يبدو الرمز النهائي لـ TasksViewModelTest على النحو التالي.

TasksViewModelTest

@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {

    // Subject under test
    private lateinit var tasksViewModel: TasksViewModel

    // Executes each task synchronously using Architecture Components.
    @get:Rule
    var instantExecutorRule = InstantTaskExecutorRule()

    @Before
    fun setupViewModel() {
        tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
    }


    @Test
    fun addNewTask_setsNewTaskEvent() {

        // When adding a new task
        tasksViewModel.addNewTask()

        // Then the new task event is triggered
        val value = tasksViewModel.newTaskEvent.awaitNextValue()
        assertThat(
            value?.getContentIfNotHandled(), (not(nullValue()))
        )
    }

    @Test
    fun getTasksAddViewVisible() {

        // When the filter type is ALL_TASKS
        tasksViewModel.setFiltering(TasksFilterType.ALL_TASKS)

        // Then the "Add task" action is visible
        assertThat(tasksViewModel.tasksAddViewVisible.awaitNextValue(), `is`(true))
    }
    
}

انقر على هنا للاطّلاع على الفرق بين الرمز الذي بدأت به والرمز النهائي.

لتنزيل الرمز البرمجي الخاص ببرنامج التدريب العملي المكتمل، يمكنك استخدام أمر git أدناه:

$ git clone https://github.com/googlecodelabs/android-testing.git
$ cd android-testing
$ git checkout end_codelab_1


يمكنك بدلاً من ذلك تنزيل المستودع كملف Zip وفك ضغطه وفتحه في Android Studio.

تنزيل ملف Zip

تناول هذا الدرس التطبيقي حول الترميز ما يلي:

  • كيفية تنفيذ الاختبارات من "استوديو Android"
  • الفرق بين الاختبارات المحلية (test) واختبارات الأدوات (androidTest)
  • كيفية كتابة اختبارات الوحدات المحلية باستخدام JUnit وHamcrest
  • إعداد اختبارات ViewModel باستخدام مكتبة AndroidX Test

دورة Udacity التدريبية:

مستندات مطوّري تطبيقات Android:

فيديوهات:

غير ذلك:

للحصول على روابط تؤدي إلى دروس برمجية أخرى في هذه الدورة التدريبية، يمكنك الانتقال إلى الصفحة المقصودة للدروس البرمجية المتقدّمة حول Android بلغة Kotlin.