هذا الدرس العملي حول الترميز هو جزء من دورة "تطبيقات متقدّمة متوافقة مع نظام Android باستخدام لغة Kotlin". ستستفيد إلى أقصى حدّ من هذه الدورة التدريبية إذا تابعت دروس الترميز بالتسلسل، ولكن هذا ليس إلزاميًا. يمكنك الاطّلاع على جميع دورات الترميز في الدورة التدريبية على الصفحة المقصودة لدورات الترميز في "تطبيقات متقدمة متوافقة مع نظام Android باستخدام لغة Kotlin".
مقدمة
عندما نفّذت الميزة الأولى في تطبيقك الأول، من المحتمل أنّك شغّلت الرمز للتحقّق من أنّه يعمل على النحو المتوقّع. لقد أجريت اختبارًا، ولكنّه كان اختبارًا يدويًا. مع استمرار إضافة الميزات وتعديلها، من المحتمل أنّك واصلت أيضًا تشغيل الرمز البرمجي والتحقّق من عمله. لكنّ تنفيذ ذلك يدويًا في كل مرة أمر مرهق وعُرضة للأخطاء ولا يمكن توسيعه.
تتميّز أجهزة الكمبيوتر بقدرتها على التوسّع والأتمتة. لذلك، يكتب المطوّرون في الشركات الكبيرة والصغيرة اختبارات مبرمَجة، وهي اختبارات يتم تشغيلها بواسطة برامج ولا تتطلّب منك تشغيل التطبيق يدويًا للتحقّق من عمل الرمز.
ستتعرّف في هذه السلسلة من الدروس التطبيقية حول الترميز على كيفية إنشاء مجموعة من الاختبارات (المعروفة باسم مجموعة الاختبارات) لتطبيق واقعي.
يغطّي هذا الدرس العملي الأول أساسيات الاختبار على Android، وستكتب اختباراتك الأولى وتتعرّف على كيفية اختبار LiveData
وViewModel
.
ما يجب معرفته
يجب أن تكون على دراية بما يلي:
- لغة البرمجة Kotlin
- مكتبات Android Jetpack الأساسية التالية:
ViewModel
وLiveData
- بنية التطبيق، باتّباع النمط الوارد في دليل بنية التطبيق والدروس التطبيقية حول أساسيات Android
أهداف الدورة التعليمية
ستتعرّف على المواضيع التالية:
- كيفية كتابة اختبارات الوحدات وتشغيلها على Android
- كيفية استخدام أسلوب التطوير المستند إلى الاختبار
- كيفية اختيار الاختبارات المبرمَجة والاختبارات المحلية
ستتعرّف على المكتبات ومفاهيم الرموز البرمجية التالية:
الإجراءات التي ستنفذّها
- إعداد الاختبارات المحلية واختبارات الأجهزة وتشغيلها وتفسير نتائجها في Android
- كتابة اختبارات الوحدات في Android باستخدام JUnit4 وHamcrest
- اكتب اختبارات بسيطة
LiveData
وViewModel
.
في هذه السلسلة من الدروس التطبيقية، ستعمل على تطبيق TO-DO Notes الذي يتيح لك تدوين المهام المطلوب إكمالها وعرضها في قائمة. يمكنك بعد ذلك وضع علامة "مكتملة" أو "غير مكتملة" عليها أو فلترتها أو حذفها.
هذا التطبيق مكتوب بلغة Kotlin، ويتضمّن عدة شاشات، ويستخدم مكوّنات Jetpack، ويتبع بنية دليل بنية التطبيق. من خلال التعرّف على كيفية اختبار هذا التطبيق، ستتمكّن من اختبار التطبيقات التي تستخدم المكتبات والبنية نفسها.
للبدء، نزِّل الرمز باتّباع الخطوات التالية:
بدلاً من ذلك، يمكنك استنساخ مستودع 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. إذا كنت على دراية بأي من الأمثلة أدناه، فإنّ هذا التطبيق يتضمّن بنية مشابهة:
- درس Room with a View التطبيقي حول الترميز
- البرامج التعليمية حول الترميز في دورة Android Kotlin Fundamentals التدريبية
- الدروس التطبيقية حول الترميز في التدريب المتقدّم على Android
- نموذج Sunflower لنظام التشغيل Android
- دورة "تطوير تطبيقات Android باستخدام لغة Kotlin" التدريبية على Udacity
من المهم أن تفهم البنية العامة للتطبيق أكثر من أن يكون لديك فهم عميق للمنطق في أي طبقة.
في ما يلي ملخّص للحِزم التي ستظهر لك:
الحزمة: | |
| شاشة إضافة مهمة أو تعديلها: رمز طبقة واجهة المستخدم لإضافة مهمة أو تعديلها |
| طبقة البيانات: تتعامل هذه الطبقة مع طبقة البيانات الخاصة بالمهام. ويحتوي على رمز قاعدة البيانات والشبكة والمستودع. |
| شاشة الإحصاءات: رمز طبقة واجهة المستخدِم لشاشة الإحصاءات |
| شاشة تفاصيل المهمة: رمز طبقة واجهة المستخدم لمهمة واحدة. |
| شاشة المهام: رمز طبقة واجهة المستخدم لقائمة جميع المهام |
| فئات الأدوات المساعدة: فئات مشترَكة تُستخدَم في أجزاء مختلفة من التطبيق، مثل تخطيط التحديث بالسحب المستخدَم على شاشات متعددة. |
طبقة البيانات (.data)
يتضمّن هذا التطبيق طبقة شبكة محاكاة في حزمة remote وطبقة قاعدة بيانات في حزمة local. لتبسيط الأمر، في هذا المشروع، يتم محاكاة طبقة الشبكة باستخدام HashMap
مع تأخير فقط، بدلاً من إجراء طلبات شبكة حقيقية.
تتولّى DefaultTasksRepository
التنسيق أو الوساطة بين طبقة الشبكة وطبقة قاعدة البيانات، وهي التي تعرض البيانات في طبقة واجهة المستخدم.
طبقة واجهة المستخدم ( .addedittask و.statistics و.taskdetail و.tasks)
تحتوي كل حزمة من حِزم طبقة واجهة المستخدم على جزء ونموذج عرض، بالإضافة إلى أي فئات أخرى مطلوبة لواجهة المستخدم (مثل أداة ربط لقائمة المهام). TaskActivity
هو النشاط الذي يحتوي على جميع الأجزاء.
التنقّل
يتم التحكّم في التنقّل في التطبيق من خلال مكوّن التنقّل. يتم تحديدها في الملف nav_graph.xml
. يتم بدء التنقّل في نماذج العرض باستخدام الفئة Event
، وتحدّد نماذج العرض أيضًا المَعلمات التي سيتم تمريرها. تراقب الأجزاء Event
وتنفّذ عملية التنقّل الفعلية بين الشاشات.
في هذه المهمة، ستنفّذ اختباراتك الأولى.
- في "استوديو 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: إجراء اختبار محلي
- افتح المجلد
test
إلى أن تعثر على الملف ExampleUnitTest.kt. - انقر بزر الماوس الأيمن على هذا الملف واختَر تشغيل ExampleUnitTest.
من المفترض أن تظهر لك النتيجة التالية في نافذة التشغيل في أسفل الشاشة:
- لاحظ علامات الاختيار الخضراء ووسِّع نتائج الاختبار للتأكّد من اجتياز اختبار واحد باسم
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.
- أضِف
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
}
}
- أجرِ الاختبار.
- في نتائج الاختبار، ستلاحظ علامة X بجانب الاختبار.
- يُرجى أيضًا ملاحظة ما يلي:
- يؤدي تعذُّر تأكيد واحد إلى تعذُّر الاختبار بأكمله.
- يتم إعلامك بالقيمة المتوقّعة (3) مقارنةً بالقيمة التي تم احتسابها فعليًا (2).
- يتم توجيهك إلى سطر التأكيد الذي تعذّر تنفيذه
(ExampleUnitTest.kt:16)
.
الخطوة 3: تنفيذ اختبار مزوَّد بأدوات
تكون الاختبارات التي تتضمّن أدوات القياس في مجموعة المصادر androidTest
.
- افتح مجموعة المستندات المصدر
androidTest
. - أريد إجراء الاختبار الذي يحمل الاسم
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: إنشاء فئة اختبار
- في مجموعة المصادر
main
، افتحStatisticsUtils.kt
فيtodoapp.statistics
. - ابحث عن الدالة
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 أدوات لإنشاء نماذج اختبارية تساعدك في تنفيذ الاختبارات لهذه الدالة.
- انقر بزر الماوس الأيمن على
getActiveAndCompletedStats
واختَر إنشاء > اختبار.
يظهر مربّع الحوار إنشاء اختبار:
- غيِّر اسم الفئة: إلى
StatisticsUtilsTest
(بدلاً منStatisticsUtilsKtTest
، من الأفضل عدم تضمين KT في اسم فئة الاختبار). - احتفِظ بالإعدادات التلقائية المتبقية. JUnit 4 هي مكتبة الاختبار المناسبة. حزمة الوجهة صحيحة (فهي تعكس موقع الفئة
StatisticsUtils
) ولست بحاجة إلى وضع علامة في أي من مربّعات الاختيار (سيؤدي ذلك إلى إنشاء رمز إضافي، ولكن ستكتب اختبارك من البداية). - اضغط على حسنًا.
يظهر مربّع الحوار اختيار دليل الوجهة:
ستُجري اختبارًا محليًا لأنّ الدالة تنفّذ عمليات حسابية ولن تتضمّن أي رمز برمجي خاص بنظام Android. لذلك، لا حاجة إلى تشغيله على جهاز حقيقي أو محاكى.
- اختَر الدليل
test
(وليسandroidTest
) لأنّك ستكتب اختبارات محلية. - انقر على موافق.
- لاحظ الفئة
StatisticsUtilsTest
التي تم إنشاؤها فيtest/statistics/
.
الخطوة 2: كتابة دالة الاختبار الأولى
ستكتب اختبارًا يتحقّق مما يلي:
- إذا لم تكن هناك مهام مكتملة ومهمة نشطة واحدة،
- أنّ النسبة المئوية للاختبارات النشطة هي %100
- ونسبة المهام المكتملة هي %0.
- فتح "
StatisticsUtilsTest
" - أنشئ دالة باسم
getActiveAndCompletedStats_noCompleted_returnsHundredZero
.
StatisticsUtilsTest.kt
class StatisticsUtilsTest {
fun getActiveAndCompletedStats_noCompleted_returnsHundredZero() {
// Create an active task
// Call your function
// Check the result
}
}
- أضِف التعليق التوضيحي
@Test
فوق اسم الدالة للإشارة إلى أنّها اختبار. - أنشئ قائمة بالمهام.
// Create an active task
val tasks = listOf<Task>(
Task("title", "desc", isCompleted = false)
)
- اتّصِل بـ
getActiveAndCompletedStats
لإجراء هذه المهام.
// Call your function
val result = getActiveAndCompletedStats(tasks)
- تأكَّد من أنّ قيمة
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)
}
}
- نفِّذ الاختبار (انقر بزر الماوس الأيمن على
StatisticsUtilsTest
واختَر تشغيل).
يجب أن تستوفي ما يلي:
الخطوة 3: إضافة تبعية Hamcrest
بما أنّ الاختبارات تعمل كمستندات توضّح ما يفعله الرمز البرمجي، من الجيد أن تكون هذه الاختبارات قابلة للقراءة من قِبل الإنسان. قارِن بين التأكيدَين التاليَين:
assertEquals(result.completedTasksPercent, 0f)
// versus
assertThat(result.completedTasksPercent, `is`(0f))
تبدو الجملة الثانية أقرب إلى جملة من تأليف إنسان. تتم كتابته باستخدام إطار عمل تأكيد يُسمى Hamcrest. Truth library هي أداة جيدة أخرى لكتابة تأكيدات قابلة للقراءة. ستستخدم Hamcrest في هذا الدرس العملي لكتابة تأكيدات.
- افتح
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 لكتابة تأكيدات
- عدِّل اختبار
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))
}
}
- نفِّذ الاختبار المعدَّل للتأكّد من أنّه لا يزال يعمل.
لن يعلّمك هذا الدرس التطبيقي حول الترميز جميع تفاصيل Hamcrest، لذا إذا أردت معرفة المزيد، يمكنك الاطّلاع على البرنامج التعليمي الرسمي.
هذه مهمة اختيارية للتدريب.
في هذه المهمة، ستكتب المزيد من الاختبارات باستخدام JUnit وHamcrest. ستكتب أيضًا اختبارات باستخدام استراتيجية مستمدّة من ممارسة البرنامج التطوير المستند إلى الاختبار. تطوير يستند إلى الاختبار أو TDD هو أسلوب برمجة يركّز على كتابة الاختبارات أولاً بدلاً من كتابة رمز الميزة أولاً. بعد ذلك، تكتب رمز الميزة بهدف اجتياز الاختبارات.
الخطوة 1: كتابة الاختبارات
اكتب اختبارات عندما تكون لديك قائمة مهام عادية:
- إذا كانت هناك مهمة واحدة مكتملة ولا توجد مهام نشطة، يجب أن تكون نسبة
activeTasks
هي0f
، ونسبة المهام المكتملة هي100f
. - إذا كانت هناك مهمتان مكتملتان وثلاث مهام نشطة، يجب أن تكون النسبة المئوية للمهام المكتملة
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()
)
}
لإصلاح الرمز وكتابة الاختبارات، عليك استخدام أسلوب التطوير المستند إلى الاختبار. تتّبع عملية التطوير المستند إلى الاختبار الخطوات التالية.
- اكتب الاختبار باستخدام بنية "المعطى" و"عندما" و"إذًا"، مع اسم يتّبع الاصطلاح.
- أكِّد تعذُّر إجراء الاختبار.
- اكتب الحد الأدنى من الرمز البرمجي لاجتياز الاختبار.
- كرِّر ذلك مع جميع الاختبارات.
بدلاً من البدء بإصلاح الخطأ، ستبدأ بكتابة الاختبارات أولاً. بعد ذلك، يمكنك التأكّد من أنّ لديك اختبارات تحميك من إعادة إدخال هذه الأخطاء عن طريق الخطأ في المستقبل.
- إذا كانت هناك قائمة فارغة (
emptyList()
)، يجب أن تكون كلتا النسبتين المئويتين 0f. - في حال حدوث خطأ أثناء تحميل المهام، ستكون القائمة
null
، ويجب أن تكون كلتا النسبتين المئويتين 0f. - نفِّذ اختباراتك وتأكَّد من فشلها:
الخطوة 3: إصلاح الخطأ
بعد أن أصبحت لديك اختباراتك، عليك إصلاح الخطأ.
- أصلِح الخطأ في
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
)
}
}
- أعِد إجراء الاختبارات وتأكَّد من أنّها اجتازت جميعها.
من خلال اتّباع أسلوب التطوير المستند إلى الاختبار وكتابة الاختبارات أولاً، ساعدت في ضمان ما يلي:
- تتضمّن الوظائف الجديدة دائمًا اختبارات مرتبطة بها، وبالتالي تعمل اختباراتك كوثائق توضّح ما تفعله التعليمات البرمجية.
- تتحقّق اختباراتك من النتائج الصحيحة وتحميك من الأخطاء التي سبق أن واجهتها.
الحلّ: كتابة المزيد من الاختبارات
في ما يلي جميع الاختبارات ورمز الميزة المقابل.
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
.
- افتح الفئة التي تريد اختبارها، في حزمة
tasks
،TasksViewModel.
- في الرمز، انقر بزر الماوس الأيمن على اسم الفئة
TasksViewModel
-> إنشاء -> اختبار.
- في شاشة إنشاء اختبار، انقر على حسنًا للقبول (لا حاجة إلى تغيير أي من الإعدادات التلقائية).
- في مربّع الحوار اختيار دليل الوجهة، اختَر الدليل test.
الخطوة 2: بدء كتابة اختبار ViewModel
في هذه الخطوة، ستضيف اختبارًا لنموذج العرض من أجل التأكّد من أنّه عند استدعاء الطريقة addNewTask
، يتم تشغيل Event
لفتح نافذة المهمة الجديدة.
- أنشئ اختبارًا جديدًا باسم
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 بشكلٍ صحيح:
- إضافة التبعيتَين الأساسية والخارجية AndroidX Test
- أضِف تبعية مكتبة Robolectric Testing.
- إضافة تعليقات توضيحية إلى الفئة باستخدام مشغّل اختبار AndroidJunit4
- كتابة رمز AndroidX Test
ستكمل هذه الخطوات وبعد ذلك ستفهم ما تفعله معًا.
الخطوة 3: إضافة تبعيات Gradle
- انسخ هذه التبعيات إلى ملف
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
- أضِف
@RunWith(AndroidJUnit4::class)
فوق الصف التجريبي.
TasksViewModelTest.kt
@RunWith(AndroidJUnit4::class)
class TasksViewModelTest {
// Test code
}
الخطوة 5: استخدام AndroidX Test
في هذه المرحلة، يمكنك استخدام مكتبة AndroidX Test. ويشمل ذلك الطريقة ApplicationProvider.getApplicationContex
t
التي تحصل على Application Context.
- أنشئ
TasksViewModel
باستخدامApplicationProvider.getApplicationContext()
من مكتبة AndroidX للاختبار.
TasksViewModelTest.kt
// Given a fresh ViewModel
val tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
- الاتصال بـ "
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
}
- نفِّذ الاختبار للتأكّد من أنّه يعمل.
المفهوم: كيف تعمل مكتبة 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.
- أضِف السطر التالي إلى ملف 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
، ننصحك باتّخاذ الإجراءَين التاليَين:
- استخدام
InstantTaskExecutorRule
- ضمان
LiveData
المراقبة
الخطوة 1: استخدام InstantTaskExecutorRule
InstantTaskExecutorRule
هي قاعدة JUnit. عند استخدامها مع التعليق التوضيحي @get:Rule
، يؤدي ذلك إلى تشغيل بعض الرموز في الفئة InstantTaskExecutorRule
قبل الاختبارات وبعدها (لمشاهدة الرمز الدقيق، يمكنك استخدام اختصار لوحة المفاتيح Command+B لعرض الملف).
تنفّذ هذه القاعدة جميع المهام التي تعمل في الخلفية والمرتبطة بمكوّنات البنية في سلسلة التعليمات نفسها، وذلك حتى تظهر نتائج الاختبار بشكل متزامن وبترتيب قابل للتكرار. عند كتابة اختبارات تتضمّن اختبار LiveData، استخدِم هذه القاعدة.
- أضِف تبعية Gradle لمكتبة الاختبار الأساسية لمكوّنات البنية (التي تحتوي على هذه القاعدة).
app/build.gradle
testImplementation "androidx.arch.core:core-testing:$archTestingVersion"
- فتح "
TasksViewModelTest.kt
" - أضِف
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
كي تتمكّن من
- تفعيل أي أحداث
onChanged
- تشغيل أي عمليات تحويل
للحصول على السلوك المتوقّع 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
لتسهيل إضافة المراقبين.
- أنشئ ملف Kotlin جديدًا باسم
LiveDataTestUtil.kt
في مجموعة المصادرtest
.
- انسخ الرمز أدناه والصِقه.
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
.
- احصل على قيمة
LiveData
لـnewTaskEvent
باستخدامgetOrAwaitValue
.
val value = tasksViewModel.newTaskEvent.getOrAwaitValue()
- تأكَّد من أنّ القيمة ليست فارغة.
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()))
}
}
- نفِّذ الرمز وشاهِد الاختبار ينجح.
بعد أن تعرّفت على كيفية كتابة اختبار، اكتب اختبارًا بنفسك. في هذه الخطوة، استخدِم المهارات التي تعلّمتها للتدريب على كتابة اختبار TasksViewModel
آخر.
الخطوة 1: كتابة اختبار ViewModel
ستكتب setFilterAllTasks_tasksAddViewVisible()
. يجب أن يتحقّق هذا الاختبار من أنّه إذا ضبطت نوع الفلتر لعرض جميع المهام، سيظهر الزر إضافة مهمة.
- باستخدام
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.
- نفِّذ اختبارك.
الخطوة 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
.
- أنشئ متغيّر مثيل
lateinit
باسمtasksViewModel|
. - أنشئ طريقة باسم
setupViewModel
. - أضِف تعليقًا توضيحيًا باستخدام
@Before
. - انقل رمز إنشاء نموذج العرض إلى
setupViewModel
.
TasksViewModelTest
// Subject under test
private lateinit var tasksViewModel: TasksViewModel
@Before
fun setupViewModel() {
tasksViewModel = TasksViewModel(ApplicationProvider.getApplicationContext())
}
- شغِّل الرمز البرمجي.
تحذير
لا تنفِّذ ما يلي، ولا تبدأ
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.
تناول هذا الدرس التطبيقي حول الترميز ما يلي:
- كيفية تنفيذ الاختبارات من "استوديو Android"
- الفرق بين الاختبارات المحلية (
test
) واختبارات الأدوات (androidTest
) - كيفية كتابة اختبارات الوحدات المحلية باستخدام JUnit وHamcrest
- إعداد اختبارات ViewModel باستخدام مكتبة AndroidX Test
دورة Udacity التدريبية:
مستندات مطوّري تطبيقات Android:
- دليل حول بنية التطبيق
- JUnit4
- Hamcrest
- مكتبة Robolectric Testing
- مكتبة الاختبار AndroidX
- مكتبة الاختبار الأساسية لمكوّنات AndroidX Architecture
- مجموعات المصادر
- الاختبار من سطر الأوامر
فيديوهات:
غير ذلك:
للحصول على روابط تؤدي إلى دروس برمجية أخرى في هذه الدورة التدريبية، يمكنك الانتقال إلى الصفحة المقصودة للدروس البرمجية المتقدّمة حول Android بلغة Kotlin.