كيفية جعل التطبيق أكثر استقرارًا مع وجود نوعين من اختبارات الوحدة

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

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

اختبار الامتثال


يتحقق اختبار المطابقة من أن النتيجة الفعلية لتنفيذ بعض الوظائف تتطابق مع النتيجة المتوقعة أم لا. اسمح لي بعرض مثال لك - تخيل أن هناك تطبيقًا يعرض قائمة الأخبار لهذا اليوم:



تؤخذ البيانات المتعلقة بالأخبار من مصادر مختلفة ، وعند الخروج من طبقة الأعمال يتم تحويلها إلى النموذج التالي:

data class News( val text: String, val date: Long ) 

وفقًا لمنطق التطبيق ، مطلوب نموذج من النموذج التالي لكل عنصر من عناصر القائمة:

 data class NewsViewData( val id: String, val title: String, val description: String, val date: String ) 

ستكون الفئة التالية مسؤولة عن تحويل نموذج مجال إلى نموذج عرض :

 class NewsMapper { fun mapToNewsViewData(news: List<News>): List<NewsViewData> { return mutableListOf<NewsViewData>().apply{ news.forEach { val textSplits = it.text.split("\\.".toRegex()) val dateFormat = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale("ru")) add( NewsViewData( id = it.date.toString(), title = textSplits[0], description = textSplits[1].trim(), date = dateFormat.format(it.date) ) ) } } } } 

وهكذا ، نحن نعرف أن بعض الشيء

 News( "Super News. Some description and bla bla bla", 1551637424401 ) 

سيتم تحويلها إلى بعض الكائنات

 NewsViewData( "1551637424401", "Super News", "Some description and bla bla bla", "2019-03-03 21:23" ) 

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

للقيام بذلك ، في مجلد التطبيق / src / test / ... ، قم بإنشاء فئة NewsMapperTest بالمحتويات التالية:

 class NewsMapperTest { private val mapper = NewsMapper() @Test fun mapToNewsViewData() { val inputData = listOf( News("Super News. Some description and bla bla bla", 1551637424401) ) val outputData = mapper.mapToNewsViewData(inputData) Assert.assertEquals(outputData.size, inputData.size) outputData.forEach { Assert.assertEquals(it.id, "1551637424401") Assert.assertEquals(it.title, "Super News") Assert.assertEquals(it.description, "Some description and bla bla bla") Assert.assertEquals(it.date, "2019-03-03 21:23") } } } 

تتم مقارنة النتيجة التي تم الحصول عليها مع التوقعات باستخدام طرق من حزمة org.junit.Assert . إذا كانت أي قيمة لا تفي بالتوقعات ، فسيفشل الاختبار.

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

هناك إطار Mockito شعبية لخلق وهمية.
في Kotlin ، تكون جميع الفئات نهائية بشكل افتراضي ، لذلك لا يمكنك إنشاء كائنات وهمية على Mockito من البداية. للتغلب على هذا القيد ، يوصى بإضافة تبعية mockito-inline .

إذا كنت تستخدم kotlin dsl عند كتابة الاختبارات ، فيمكنك استخدام مكتبات متنوعة ، مثل Mockito-Kotlin .

افترض أن NewsMapper يأخذ في شكل تبعية NewsRepo معينة ، والتي تسجل المعلومات حول المستخدم عرض عنصر أخبار معين. ثم من المنطقي أن تسخر من NewsRepo وتحقق من القيم المرجعة لطريقة mapToNewsViewData اعتمادًا على نتيجة isNewsRead .

 class NewsMapperTest { private val newsRepo: NewsRepo = mock() private val mapper = NewsMapper(newsRepo) … @Test fun mapToNewsViewData_Read() { whenever(newsRepo.isNewsRead(anyLong())).doReturn(true) ... } @Test fun mapToNewsViewData_UnRead() { whenever(newsRepo.isNewsRead(anyLong())).doReturn(false) ... } … } 

وبالتالي ، فإن الكائن الصوري يسمح لك بمحاكاة خيارات متعددة لقيم الإرجاع لاختبار حالات الاختبار المختلفة.

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

اختبار الاتصال


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

 class MainPresenter( private val view: MainView, private val interactor: NewsInteractor, private val mapper: NewsMapper ) { var scope = CoroutineScope(Dispatchers.Main) fun onCreated() { view.setLoading(true) scope.launch { val news = interactor.getNews() val newsData = mapper.mapToNewsViewData(news) view.setLoading(false) view.setNewsItems(newsData) } } … } 

الشيء الأكثر أهمية هنا هو حقيقة استحضار طرق من Interactor و View . سيبدو الاختبار كالتالي:

 class MainPresenterTest { private val view: MainView = mock() private val mapper: NewsMapper = mock() private val interactor: NewsInteractor = mock() private val presenter = MainPresenter(view, interactor, mapper).apply { scope = CoroutineScope(Dispatchers.Unconfined) } @Test fun onCreated() = runBlocking { whenever(interactor.getNews()).doReturn(emptyList()) whenever(mapper.mapToNewsViewData(emptyList())).doReturn(emptyList()) presenter.onCreated() verify(view, times(1)).setLoading(true) verify(interactor).getNews() verify(mapper).mapToNewsViewData(emptyList()) verify(view).setLoading(false) verify(view).setNewsItems(emptyList()) } } 

قد تكون هناك حاجة لحلول مختلفة لاستبعاد تبعيات النظام الأساسي من الاختبارات ، كما كل هذا يتوقف على التكنولوجيا للعمل مع multithreading. يستخدم المثال أعلاه Kotlin Coroutines بنطاق مهيأ لإجراء الاختبارات ، مثل المستخدمة في رمز برنامج Dispatchers.Main يشير إلى مؤشر ترابط UI android ، وهو أمر غير مقبول في هذا النوع من الاختبارات. سيتطلب استخدام RxJava حلولًا أخرى ، على سبيل المثال ، إنشاء TestRule بتبديل تدفق تنفيذ التعليمات البرمجية.

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

*****


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

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

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

الكود المصدري للمثال متاح على جيثب .

Source: https://habr.com/ru/post/ar447588/


All Articles