النمط المعماري MVI في Kotlin Multiplatform. الجزء 3: الاختبار





هذه المقالة هي الأخيرة في سلسلة حول تطبيق النمط المعماري MVI في Kotlin Multiplatform. في شطري السابقة ( جزء 1 و جزء 2 )، ونحن نتذكر ما هو MVI، وخلق القطط العامة وحدة لتحميل الصور القط ودمجها في التطبيقات دائرة الرقابة الداخلية والروبوت.



في هذا الجزء ، سنغطي وحدة Kittens باختبارات الوحدة والتكامل. سوف نتعرف على القيود الحالية للاختبار في Kotlin Multiplatform ، وسنتعرف على كيفية التغلب عليها وحتى نجعلها تعمل لصالحنا.



يتوفر نموذج مشروع محدث على GitHub .



مقدمة



ليس هناك شك في أن الاختبار هو خطوة مهمة في تطوير البرمجيات. بالطبع ، يبطئ العملية ، لكن في نفس الوقت:



  • يسمح لك بالتحقق من حالات الحافة التي يصعب التقاطها يدويًا ؛

  • يقلل من فرصة الانحدار عند إضافة ميزات جديدة وإصلاح الأخطاء وإعادة البناء ؛

  • يجبرك على تفكيك وتكوين الكود الخاص بك.



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



"في الواقع ، فإن نسبة الوقت المستغرق في القراءة مقابل الكتابة تزيد عن 10 إلى 1. نحن نقرأ باستمرار الرموز القديمة كجزء من الجهود المبذولة لكتابة رمز جديد. ... [لذلك] جعلها سهلة القراءة تجعل الكتابة أسهل. " - روبرت سي مارتن ، "الكود النظيف: دليل لبرمجيات أجايل"


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



يعد الاختبار على جميع الأنظمة الأساسية المدعومة أمرًا مهمًا لأنه قد تكون هناك اختلافات في سلوك الكود. على سبيل المثال ، يحتوي Kotlin / Native على نموذج ذاكرة خاص ، كما يعطي Kotlin / JS أحيانًا نتائج غير متوقعة.



قبل أن نذهب إلى أبعد من ذلك ، تجدر الإشارة إلى بعض قيود الاختبار في Kotlin Multiplatform. أكبرها هو عدم وجود أي مكتبة ساخرة لـ Kotlin / Native و Kotlin / JS. قد يبدو هذا عيبًا كبيرًا ، لكنني شخصياً أعتبره ميزة. كان الاختبار في Kotlin Multiplatform صعبًا للغاية بالنسبة لي: كان علي إنشاء واجهات لكل تبعية وكتابة تطبيقات الاختبار الخاصة بهم (مزيفة). استغرق الأمر وقتًا طويلاً ، لكن في مرحلة ما أدركت أن قضاء الوقت في التجريد هو استثمار يؤدي إلى كود أنظف. 



لقد لاحظت أيضًا أن التعديلات اللاحقة على هذا الرمز تستغرق وقتًا أقل. لماذا هذا؟ لأن تفاعل الطبقة مع تبعياتها لا يتم تسميته (سخرية). في معظم الحالات ، يكفي تحديث تطبيقات الاختبار الخاصة بهم. ليست هناك حاجة للتعمق في كل طريقة اختبار لتحديث النماذج. نتيجة لذلك ، توقفت عن استخدام المكتبات الساخرة حتى في تطوير Android القياسي. أوصي بقراءة المقال التالي: " السخرية ليست عملية - استخدم المنتجات المقلدة " برافين سوناوان .



خطة



دعونا نتذكر ما لدينا في وحدة Kittens وما يجب اختباره.



  • KittenStore هو المكون الرئيسي للوحدة. يحتوي تطبيق KittenStoreImpl على معظم منطق الأعمال. هذا هو أول شيء سنختبره.

  • KittenComponent هي واجهة الوحدة النمطية ونقطة التكامل لجميع المكونات الداخلية. سنغطي هذا المكون باختبارات التكامل.

  • KittenView هي واجهة عامة تمثل تبعية واجهة المستخدم لـ KittenComponent.

  • KittenDataSource هي واجهة وصول داخلية للويب لها تطبيقات خاصة بالنظام الأساسي لنظامي التشغيل iOS و Android.



من أجل فهم أفضل لهيكل الوحدة ، سأقدم مخطط UML الخاص بها:







الخطة كما يلي:



  • اختبار KittenStore
    • إنشاء تطبيق تجريبي لـ KittenStore.Parser

    • إنشاء تطبيق تجريبي لـ KittenStore.Network

    • اختبارات وحدة الكتابة لـ KittenStoreImpl



  • اختبار KittenComponent
    • إنشاء تطبيق اختباري لـ KittenDataSource

    • إنشاء تطبيق اختبار KittenView

    • اختبارات تكامل الكتابة لـ KittenComponent



  • اختبارات التشغيل

  • الاستنتاجات





اختبار وحدة KittenStore



تحتوي واجهة KittenStore على فئة التنفيذ الخاصة بها - KittenStoreImpl. هذا ما سنختبره. لها تبعيتان (واجهات داخلية) ، محددتان مباشرة في الفصل نفسه. لنبدأ بكتابة تطبيقات الاختبار لهم.



اختبار تنفيذ KittenStore.Parser



هذا المكون مسؤول عن طلبات الشبكة. هذا ما تبدو عليه واجهته:



واجهة الشبكة {
تحميل المرح () : ربما < سلسلة >
}
عرض الخام KittenStoreImpl.kt مستضاف مع ❤ بواسطة GitHub


قبل كتابة تنفيذ اختباري لواجهة الشبكة ، نحتاج إلى الإجابة على سؤال مهم واحد: ما هي البيانات التي يعيدها الخادم؟ الجواب هو أن الخادم يعيد مجموعة عشوائية من روابط الصور ، في كل مرة مجموعة مختلفة. في الحياة الواقعية ، يتم استخدام تنسيق JSON ، ولكن نظرًا لأن لدينا تجريد Parser ، فنحن لا نهتم بالتنسيق في اختبارات الوحدة.



يمكن للتنفيذ الحقيقي تبديل التدفقات ، بحيث يمكن تجميد المشتركين في Kotlin / Native. سيكون من الرائع تصميم هذا السلوك للتأكد من أن الكود يعالج كل شيء بشكل صحيح.



لذلك ، يجب أن يحتوي تطبيقنا الاختباري للشبكة على الميزات التالية:



  • يجب إرجاع مجموعة غير فارغة من الصفوف المختلفة لكل طلب ؛

  • يجب أن يكون تنسيق الاستجابة شائعًا للشبكة والمحلل اللغوي ؛

  • يجب أن يكون قادرًا على محاكاة أخطاء الشبكة (ربما يجب أن يكتمل بدون استجابة) ؛

  • يجب أن يكون من الممكن محاكاة تنسيق استجابة غير صالح (للتحقق من وجود أخطاء في Parser) ؛

  • ينبغي أن يكون من الممكن محاكاة تأخيرات الاستجابة (للتحقق من مرحلة التمهيد) ؛

  • يجب أن يكون قابلاً للتجميد في Kotlin / Native (فقط في حالة).



قد يبدو تطبيق الاختبار نفسه كما يلي:



فئة TestKittenStoreNetwork (
خاصة فال جدولة : TestScheduler
) : KittenStoreImpl . الشبكة {
var images: List<String>? by AtomicReference<List<String>?>(null)
private var seed: Int by AtomicInt()
override fun load(): Maybe<String> =
singleFromFunction { images }
.notNull()
.map { it.joinToString(separator = SEPARATOR) }
.observeOn(scheduler)
fun generateImages(): List<String> {
val images = List(MAX_IMAGES) { "Img${seed + it}" }
this.images = images
seed += MAX_IMAGES
return images
}
private companion object {
private const val MAX_IMAGES = 50
private const val SEPARATOR = ";"
}
}
view raw تمت استضافة TestKittenStoreNetwork.kt مع ❤ بواسطة GitHub


يحتوي TestKittenStoreNetwork على مخزن خيوط (تمامًا مثل الخادم الحقيقي) ويمكنه إنشاؤه. لكل طلب ، يتم ترميز قائمة الأسطر الحالية في سطر واحد. إذا كانت خاصية "الصور" تساوي صفرًا ، فحينئذٍ سيتم إنهاء "ربما" فقط ، وهو ما يجب اعتباره خطأ.



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



بالإضافة إلى ذلك ، يحتوي TestScheduler على وضع "معالجة يدوية" خاص يسمح لنا بمحاكاة زمن انتقال الشبكة.



اختبار تنفيذ KittenStore.Parser



هذا المكون مسؤول عن تحليل الاستجابات من الخادم. ها هي واجهته:



محلل الواجهة {
متعة تحليل ( سلمان : سلسلة ) : ربما < قائمة < سلسلة >>
}
عرض الخام KittenStoreImpl.kt مستضاف مع ❤ بواسطة GitHub


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



إليك تطبيق تجريبي:



class TestKittenStoreParser : KittenStoreImpl.Parser {
override fun parse(json: String): Maybe<List<String>> =
json
.toSingle()
.filter { it != "" }
.map { it.split(SEPARATOR) }
.observeOn(TestScheduler())
private companion object {
private const val SEPARATOR = ";"
}
}
view raw تمت استضافة TestKittenStoreParser.kt مع ❤ بواسطة GitHub


كما هو الحال مع Network ، يتم استخدام TestScheduler لتجميد المشتركين والتحقق من توافقهم مع نموذج ذاكرة Kotlin / Native. تتم محاكاة أخطاء معالجة الاستجابة إذا كانت سلسلة الإدخال فارغة.



اختبارات الوحدة لـ KittenStoreImpl



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



تتمثل الخطوة الأولى في إنشاء مثيلات لتطبيقات الاختبار الخاصة بنا:



فئة KittenStoreTest {
محلل val الخاص = TestKittenStoreParser ()
خاص فال networkScheduler = TestScheduler ()
خاصة فال شبكة = TestKittenStoreNetwork (networkScheduler)
متجر المرح الخاص () : KittenStore = KittenStoreImpl (شبكة ، محلل)
// ...
}
عرض الخام KittenStoreTest.kt مستضاف مع ❤ بواسطة GitHub


يستخدم KittenStoreImpl mainScheduler ، لذا فإن الخطوة التالية هي تجاوزه:



فئة KittenStoreTest {
شبكة val الخاصة = TestKittenStoreNetwork ()
private val parser = TestKittenStoreParser()
private fun store(): KittenStore = KittenStoreImpl(network, parser)
@BeforeTest
fun before() {
overrideSchedulers(main = { TestScheduler() })
}
@AfterTest
fun after() {
overrideSchedulers()
}
// ...
}
view raw KittenStoreTest.kt hosted with ❤ by GitHub


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



@اختبار
متعة loads_images_WHEN_created () {
صور val = network.generateImages ()
مخزن val = مخزن ()
assertEquals ( State.Data.Images (urls = images)، store.state. data )
}
عرض الخام KittenStoreTest.kt مستضاف مع ❤ بواسطة GitHub


ماذا فعلنا:



  • الصور التي تم إنشاؤها على الشبكة ؛

  • إنشاء مثيل جديد من KittenStoreImpl ؛

  • تأكد من احتواء الدولة على قائمة السلاسل الصحيحة.



سيناريو آخر نحتاج إلى التفكير فيه هو الحصول على KittenStore.Intent.Reload. في هذه الحالة ، يجب إعادة تحميل القائمة من الشبكة.



@اختبار
متعة reloads_images_WHEN_Intent_Reload () {
network.generateImages ()
مخزن val = مخزن ()
val newImages = network.generateImages ()
store.onNext ( Intent.Reload ) تحميل
assertEquals ( State.Data.Images (urls = newImages) ، store.state. بيانات )
}
عرض الخام KittenStoreTest.kt مستضاف مع ❤ بواسطة GitHub


خطوات الاختبار:



  • توليد صور المصدر ؛

  • إنشاء مثيل لـ KittenStoreImpl ؛

  • توليد صور جديدة

  • إرسال النية.

  • تأكد من أن الحالة تحتوي على صور جديدة.



أخيرًا ، دعنا نختبر السيناريو التالي: عند تعيين علامة isLoading أثناء تحميل الصور.



@اختبار
المتعة هي Loading_true_WHEN_loading () {
networkScheduler.isManualProcessing = صحيح
network.generateImages ()
مخزن val = مخزن ()
assertTrue (store.state.isLoading)
}
عرض الخام KittenStoreTest.kt مستضاف مع ❤ بواسطة GitHub


لقد قمنا بتمكين المعالجة اليدوية لـ TestScheduler - الآن لن تتم معالجة المهام تلقائيًا. يتيح لنا هذا التحقق من الحالة أثناء انتظار الرد.



اختبار التكامل KittenComponent



كما ذكرت أعلاه ، فإن KittenComponent هي نقطة تكامل الوحدة بأكملها. يمكننا تغطيتها باختبارات التكامل. دعنا نلقي نظرة على API الخاص به:



الداخلية الدرجة KittenComponent الداخلي منشئ ( مصدر البيانات : KittenDataSource ) {
المُنشئ () : هذا ( KittenDataSource ())
متعة onViewCreated ( view : KittenView ) { / * ... * / }
متعة onStart () { / * ... * / }
متعة onStop () { / * ... * / }
متعة onViewDestroyed () { / * ... * / }
متعة onDestroy () { / * ... * / }
}
عرض الخام KittenComponent.kt مستضاف مع ❤ بواسطة GitHub


هناك نوعان من التبعيات ، KittenDataSource و KittenView. سنحتاج إلى تطبيقات تجريبية لهذه قبل أن نتمكن من بدء الاختبار.



للتأكد من اكتمالها ، يوضح هذا الرسم البياني تدفق البيانات داخل الوحدة:







تطبيق اختبار KittenDataSource



هذا المكون مسؤول عن طلبات الشبكة. لديها تطبيقات منفصلة لكل منصة ، ونحن بحاجة إلى تطبيق آخر للاختبارات. هذا ما تبدو عليه واجهة KittenDataSource:



الواجهة الداخلية KittenDataSource {
تحميل المرح ( limit : Int ، offset : Int ) : ربما < String >
}
عرض الخام MviKmpKittenDataSourceInterface.kt مستضاف مع ❤ بواسطة GitHub


يدعم TheCatAPI ترقيم الصفحات ، لذلك أضفت الوسائط المناسبة على الفور. بخلاف ذلك ، فهو مشابه جدًا لـ KittenStore.Network ، الذي قمنا بتطبيقه مسبقًا. الاختلاف الوحيد هو أنه يجب علينا استخدام تنسيق JSON لأننا نختبر رمزًا حقيقيًا في التكامل. لذلك نحن فقط نستعير فكرة التنفيذ:



فئة داخلية TestKittenDataSource (
خاصة فال جدولة : TestScheduler
) : KittenDataSource {
private var images by AtomicReference<List<String>?>(null)
private var seed by AtomicInt()
override fun load(limit: Int, page: Int): Maybe<String> =
singleFromFunction { images }
.notNull()
.map {
val offset = page * limit
it.subList(fromIndex = offset, toIndex = offset + limit)
}
.mapIterable { it.toJsonObject() }
.map { JsonArray(it).toString() }
.onErrorComplete()
.observeOn(scheduler)
private fun String.toJsonObject(): JsonObject =
JsonObject(mapOf("url" to JsonPrimitive(this)))
fun generateImages(): List<String> {
val images = List(MAX_IMAGES) { "Img${seed + it}" }
this.images = images
seed += MAX_IMAGES
return images
}
private companion object {
private const val MAX_IMAGES = 50
}
}


كما في السابق ، نقوم بإنشاء قوائم مختلفة من السلاسل المشفرة في مصفوفة JSON عند كل طلب. إذا لم يتم إنشاء أي صور ، أو إذا كانت وسيطات الطلب خاطئة ، فربما ينتهي الأمر بدون رد. تُستخدم



مكتبة kotlinx.serialization لتشكيل مصفوفة JSON . بالمناسبة ، فإن KittenStoreParser الذي تم اختباره يستخدمه لفك التشفير.



تنفيذ اختبار KittenView



هذا هو المكون الأخير الذي نحتاج إلى تنفيذ تجريبي له قبل أن نبدأ في الاختبار. ها هي واجهته:



واجهة KittenView : MviView < نموذج ، حدث > {
نموذج فئة البيانات (
val isLoading : منطقية ،
val هو الخطأ : منطقي ،
val imageUrls : قائمة < سلسلة >
)
حدث الفصل المختوم {
تنشيط الكائن : الحدث ()
}
}
عرض الخام MviKmpKittenViewInterface.kt مستضاف مع ❤ بواسطة GitHub


إنها وجهة نظر تأخذ النماذج وتطلق الأحداث فقط ، لذا فإن تنفيذها التجريبي بسيط للغاية:



class TestKittenView : AbstractMviView < Model ، Event > () ، KittenView {
نموذج var lateinit : نموذج
تجاوز المرح تجعل ( نموذج : نموذج ) {
هذا .model = نموذج
}
}
عرض الخام تمت استضافة TestKittenView.kt مع ❤ بواسطة GitHub


نحتاج فقط إلى تذكر آخر نموذج تم قبوله - سيتيح لنا ذلك التحقق من صحة النموذج المعروض. يمكننا أيضًا إرسال الأحداث نيابة عن KittenView باستخدام طريقة الإرسال (Event) ، والتي تم الإعلان عنها في فئة AbstractMviView الموروثة.



اختبارات الاندماج لـ KittenComponent



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



كما في السابق ، لنبدأ بإنشاء مثيل للتبعيات والتهيئة:



فئة KittenComponentTest {
خاص فال dataSourceScheduler = TestScheduler ()
خاص فال مصدر بيانات = TestKittenDataSource (dataSourceScheduler)
عرض val الخاص = TestKittenView ()
خاصة متعة startComponent () : KittenComponent =
KittenComponent (مصدر البيانات). تطبيق {
onViewCreated (عرض)
onStart ()
}
// ...
}
عرض الخام KittenComponentTest.kt مستضاف مع ❤ بواسطة GitHub


يوجد حاليًا جدولين مستخدمين للوحدة النمطية: mainScheduler و computationScheduler. نحن بحاجة إلى تجاوزها:



فئة KittenComponentTest {
خاص فال dataSourceScheduler = TestScheduler ()
خاص فال مصدر بيانات = TestKittenDataSource (dataSourceScheduler)
عرض val الخاص = TestKittenView ()
خاصة متعة startComponent () : KittenComponent =
KittenComponent (مصدر البيانات). تطبيق {
onViewCreated (عرض)
onStart ()
}
// ...
@قبل الاختبار
متعة من قبل () {
overrideSchedulers (main = { TestScheduler ()} ، الحساب = { TestScheduler ()})
}
تضمين التغريدة
متعة بعد () {
overrideSchedulers ()
}
}
عرض الخام KittenComponentTest.kt مستضاف مع ❤ بواسطة GitHub


يمكننا الآن كتابة بعض الاختبارات. دعنا نتحقق من البرنامج النصي الرئيسي أولاً للتأكد من تحميل الصور وعرضها عند بدء التشغيل:



@اختبار
متعة loads_and_shows_images_WHEN_created () {
صور val = dataSource.generateImages ()
startComponent ()
assertEquals (images، view.model.imageUrls)
}
عرض الخام KittenComponentTest.kt مستضاف مع ❤ بواسطة GitHub


هذا الاختبار مشابه جدًا للاختبار الذي كتبناه عندما نظرنا إلى اختبارات الوحدة لـ KittenStore. الآن فقط يتم تضمين الوحدة بأكملها.



خطوات الاختبار:



  • إنشاء روابط للصور في TestKittenDataSource ؛

  • إنشاء وتشغيل KittenComponent ؛

  • تأكد من وصول الروابط إلى TestKittenView.



سيناريو آخر مثير للاهتمام: يجب إعادة تحميل الصور عندما يطلق KittenView حدث RefreshTriggered.



@اختبار
متعة reloads_images_WHEN_Event_RefreshTriggered () {
dataSource.generateImages ()
startComponent ()
val newImages = dataSource.generateImages ()
view.dispatch ( Event.RefreshTriggered )
assertEquals (newImages، view.model.imageUrls)
}
عرض الخام KittenComponentTest4.kt مستضاف مع ❤ بواسطة GitHub


مراحل:



  • إنشاء روابط مصدر للصور ؛

  • إنشاء وتشغيل KittenComponent ؛

  • إنشاء روابط جديدة ؛

  • إرسال Event.RefreshTriggered نيابة عن KittenView ؛

  • تأكد من وصول الروابط الجديدة إلى TestKittenView.





اختبارات التشغيل



لإجراء جميع الاختبارات ، نحتاج إلى إجراء مهمة Gradle التالية:



./gradlew :shared:kittens:build


سيؤدي ذلك إلى تجميع الوحدة وتشغيل جميع الاختبارات على جميع الأنظمة الأساسية المدعومة: Android و iosx64.



وهنا تقرير تغطية JaCoCo:







خاتمة



في هذه المقالة ، قمنا بتغطية وحدة Kittens باختبارات الوحدة والتكامل. سمح لنا تصميم الوحدة المقترحة بتغطية الأجزاء التالية:



  • KittenStoreImpl - يحتوي على معظم منطق الأعمال ؛

  • KittenStoreNetwork - مسؤول عن طلبات الشبكة عالية المستوى ؛

  • KittenStoreParser - مسؤول عن تحليل استجابات الشبكة ؛

  • كل التحولات والوصلات.



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



هذه الاختبارات لها المزايا التالية:



  • لا تستخدم واجهات برمجة تطبيقات النظام الأساسي ؛

  • أداء سريع جدا

  • موثوقة (لا ترمش) ؛

  • تعمل على جميع المنصات المدعومة.



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



أتمنى أن يساعدك هذا في مشاريعك. شكرا لقراءة مقالاتي! ولا تنسوا متابعتي على تويتر .



...





تمرين إضافي



إذا كنت ترغب في العمل مع تطبيقات الاختبار أو اللعب باستخدام MVI ، فإليك بعض التمارين العملية.



إعادة هيكلة مصدر KittenDataSource



يوجد تطبيقان لواجهة KittenDataSource في الوحدة: أحدهما لنظام Android والآخر لنظام iOS. لقد ذكرت بالفعل أنهم مسؤولون عن الوصول إلى الشبكة. لكن لديهم في الواقع وظيفة أخرى: يقومون بإنشاء عنوان URL للطلب بناءً على وسيطات الإدخال "limit" و "الصفحة". في الوقت نفسه ، لدينا فئة KittenStoreNetwork لا تفعل شيئًا سوى تفويض المكالمة إلى KittenDataSource.



التعيين: انقل منطق إنشاء طلب URL من KittenDataSourceImpl (على Android و iOS) إلى KittenStoreNetwork. تحتاج إلى تغيير واجهة KittenDataSource على النحو التالي:







بمجرد الانتهاء من ذلك ، ستحتاج إلى تحديث اختباراتك. الفصل الوحيد الذي تحتاج إلى لمسه هو TestKittenDataSource.



مضيفا تحميل الصفحة



يدعم TheCatAPI ترقيم الصفحات ، لذا يمكننا إضافة هذه الوظيفة لتجربة مستخدم أفضل. يمكنك البدء بإضافة حدث Event.EndReached جديد لـ KittenView ، وبعد ذلك سيتوقف الرمز عن التجميع. بعد ذلك ستحتاج إلى إضافة Intent.LoadMore المناسب ، وتحويل الحدث الجديد إلى Intent ، ومعالجة الأخير في KittenStoreImpl. ستحتاج أيضًا إلى تعديل واجهة KittenStoreImpl.Network على النحو التالي:







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






All Articles