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

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

مقدمة

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

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

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

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

ما يجب معرفته

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

ما ستتعرَّف عليه

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

  • كيفية كتابة اختبارات الوحدة وتنفيذها على Android
  • كيفية استخدام التطوير المستند إلى الاختبار
  • كيفية اختيار الاختبارات الآلية والاختبارات المحلية

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

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

  • إعداد الاختبارات المحلية والمبرمَجة وتنفيذها وتفسيرها في نظام التشغيل Android
  • كتابة اختبارات الوحدات في Android باستخدام JUnit4 وHemcrest.
  • كتابة اختبارات LiveData وViewModel البسيطة

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

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

للبدء، يُرجى تنزيل الرمز:

تنزيل ملف Zip

بدلاً من ذلك، يمكنك إنشاء نسخة طبق الأصل من مستودع Github للرمز:

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

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

الخطوة 1: تشغيل نموذج التطبيق

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

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

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

يستند تطبيق قائمة المهام إلى نموذج الهندسة المعمارية الزرقاء الشائعة واختباره (باستخدام إصدار البنية التفاعلية من النموذج). يتّبع التطبيق البنية من دليل إلى بنية التطبيق. وهي تستخدم ViewViews مع أجزاء ومستودع وغرفة. إذا كنت على دراية بأي من الأمثلة التالية، يكون لهذا التطبيق بنية مشابهة:

من المهم فهم البنية العامة للتطبيق بدلاً من فهم عميق للمنطق في أي طبقة.

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

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

.addedittask

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

.data

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

.statistics

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

.taskdetail

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

.tasks

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

.util

فئات الأدوات المساعدة: الصفوف المشتركة المستخدمة في أجزاء مختلفة من التطبيق، مثلاً لتنسيق التمرير السريع الذي يتم استخدامه في شاشات متعددة.

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

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

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

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

ويُستخدَم نظام التشغيل Android في اختبارات Android التي تم قياسها، بالإضافة إلى رمز مثلث أخضر وأحمر.

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

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

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

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

الخطوة الثانية: تعذّر إجراء الاختبار

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

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) التي تتحقق من أن القيمة تساوي 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.

ExampleMachineedTest

@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. في مجموعة المصدر main، في todoapp.statistics، افتح StatisticsUtils.kt.
  2. ابحث عن الدالة getActiveAndCompletedStats.

AnalyticsUtils.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" أدوات لإنشاء الرموز البديلة للمساعدة في تنفيذ الاختبارات على هذه الوظيفة.

  1. انقر بزر الماوس الأيمن على getActiveAndCompletedStats، ثم اختَر إنشاء &gt؛ اختبار.

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

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

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

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

  1. اختَر الدليل test (وليس androidTest) لأنك ستكتب الاختبارات المحلية.
  2. انقر على حسنًا.
  3. لاحِظ الصف الدراسي StatisticsUtilsTest في test/statistics/.

الخطوة الثانية: كتابة دالة الاختبار الأولى

ستُجري اختبارًا للتحقق من:

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

AnalyticsUtilsTest.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)

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

AnalyticsUtilsTest.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: إضافة تبعية الهمكرست

ونظرًا لأن اختباراتك تعمل كوثائق لما ينفذه الرمز، فإنها تكون رائعة عندما يكون من السهل على الإنسان قراءتها. قارِن التأكيدَين التاليَين:

assertEquals(result.completedTasksPercent, 0f)

// versus

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

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

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

app/build.gradle

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

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

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

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

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

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

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

  1. عدِّل اختبار getActiveAndCompletedStats_noCompleted_returnsHundredZero() لاستخدام assertThat من Humestest بدلاً من 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` إذا طُلب منك ذلك.

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

AnalyticsUtilsTest.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. شغِّل الاختبار المعدَّل للتأكّد من أنه لا يزال مفعَّلاً.

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

هذه مهمة اختيارية للممارسة.

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

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

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

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

الخطوة الثانية: كتابة اختبار للخطأ

هناك خطأ في رمز 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())، يجب أن تكون كلتا النسبتين صفرًا.
  2. إذا حدث خطأ أثناء تحميل المهام، ستكون القائمة null، ويجب أن تكون النسب المئوية 0f.
  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. عليك إجراء اختباراتك مرة أخرى والتأكّد من اجتياز جميع الاختبارات الآن.

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

  • دائمًا ما ترتبط الوظائف الجديدة باختبارات مرتبطة؛ لذا تعمل اختباراتك كمستندات لما يفعله الرمز.
  • تعمل اختباراتك على التحقق من النتائج الصحيحة والحماية من الأخطاء التي رأيتها من قبل.

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

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

AnalyticsUtilsTest.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))
    }
}

AnalyticsUtils.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.


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



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

TasksViewmodel.kt

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

الخطوة الأولى: إنشاء فئة ClassViewmodelTest

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

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

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

الخطوة الثانية: ابدأ كتابة TestView.

في هذه الخطوة، يمكنك إضافة اختبار نموذج المشاهدة لاختبار أنه عند استدعاء الطريقة 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 فئات وطرقًا توفر لك إصدارات من المكونات مثل التطبيقات والأنشطة المخصصة للاختبارات. عندما يكون لديك اختبار محلي حيث تريد محاكاة فئات إطار عمل Android (مثل سياق التطبيق)، اتبع هذه الخطوات لإعداد اختبار AndroidX بشكل صحيح:

  1. إضافة تبعيات AndroidX Test والاعتماديات الخارجية
  2. إضافة الاعتمادية لمكتبة Robolectric Test
  3. إضافة تعليقات توضيحية إلى الصف باستخدام مُجري اختبار AndroidJunit4
  4. كتابة رمز اختبار AndroidX

عليك إكمال هذه الخطوات ثم لفهم ما تفعله معًا.

الخطوة الثالثة. إضافة تبعيات الدرجات

  1. انسخ هذه الارتباطات في ملف تطبيقك على build.gradle وحدة التطبيق الخاص بك لإضافة تبعيات اختبار AndroidX الأساسية والتابعة، بالإضافة إلى اعتمادية اختبار 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"

الخطوة الرابعة: إضافة مشغّل اختبار JUnit

  1. أضِف @RunWith(AndroidJUnit4::class) أعلى صف الاختبار.

TasksViewmodelTest.kt

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

الخطوة الخامسة. استخدام اختبار AndroidX

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

  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؟

ما المقصود باختبار AndroidX؟

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

ApplicationProvider.getApplicationContext()

ومن فوائد واجهات برمجة تطبيقات Test Android أنّها مُصمَّمة للعمل على كلٍّ من الاختبارات المحلية والاختبارات التي يتم قياسها. وهذا أمر رائع للأسباب التالية:

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

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

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

ما هي Robolectric؟

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

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

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

يتيح مُجري اختبار AndroidJUnit4 لاختبار AndroidX إجراء اختبارك بشكل مختلف اعتمادًا على ما إذا كان يتم إجراؤه على اختبارات محلية أو لا.

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

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

ويعود السبب في ذلك إلى اختبار AndroidX وبرنامج تشغيل اختبار 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 يتطلب Java 9. بدلاً من محاولة ضبط "استوديو Android" لاستخدام Java 9، في هذا الدرس التطبيقي، احتفِظ بالهدف واجمع حزمة تطوير البرامج (SDK) بنسبة 28.

باختصار:

  • يمكن أن تأتي اختبارات النماذج الخالصة عادةً في مجموعة المصدر test لأن رمزها لا يتطلب عادةً Android.
  • يمكنك استخدام مكتبة اختبار Android للحصول على إصدارات تجريبية من مكونات مثل التطبيقات والأنشطة.
  • إذا كنت بحاجة إلى تشغيل رمز 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

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

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

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

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

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...
}

الخطوة الثانية: إضافة LiveLiveTestUtil.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 الموضّح أعلاه. للحصول على شرح كامل لهذا الصف، اطّلع على مشاركة المدونة هذه.

الخطوة الثالثة. يُرجى استخدام getOrAPendingValue لكتابة التأكيد.

في هذه الخطوة، يمكنك استخدام الطريقة 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 آخر.

الخطوة الأولى: كتابة اختبار طرق العرض الخاصة بك

ستكتب 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. إجراء الاختبار.

الخطوة الثانية: مقارنة الاختبار بالحلّ

قارِن حلّك بالحلول أدناه.

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() نفسها التي تعمل بنظام التشغيل Android.
  • عليك استدعاء الطريقة setFiltering، مع تمرير رقم نوع الفلتر ALL_TASKS.
  • تأكد من صحة tasksAddViewVisible باستخدام الطريقة getOrAwaitNextValue.

الخطوة الثالثة. إضافة قاعدة @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())

سيؤدي هذا الإجراء إلى استخدام المثيل نفسه لجميع الاختبارات. وهذا أمر يجب تجنّبه، لأنه يجب أن يكون لكل اختبار نسخة جديدة من الموضوع قيد الاختبار (وViewView في هذه الحالة).

ويجب أن يظهر الرمز النهائي لـ 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".

تنزيل ملف Zip

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

  • طريقة إجراء الاختبارات من "استوديو Android"
  • الفرق بين اختبارات test (المحلية) واختبارات قياس حالة التطبيق (androidTest).
  • طريقة كتابة اختبارات الوحدات المحلية باستخدام JUnit وHamcrest
  • إعداد اختبارات طريقة العرض باستخدام مكتبة اختبارات AndroidX.

دورة Udacity:

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

فيديوهات:

غير ذلك:

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