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

لقد بدأت دراسة Flutter منذ أكثر من عام ، قبل إصدارها رسميًا ، أثناء الدراسة ، لم تكن مشكلة في العثور على أي معلومات تطوير. وعندما أردت تجربة TDD ، اتضح أن المعلومات حول الاختبار كانت صغيرة بشكل كارثي. باللغة الروسية ، وبشكل عام ، لا شيء تقريبا. كان لا بد من دراسة قضايا الاختبار بشكل مستقل ، وفقًا للشفرة المصدرية لاختبارات Flutter والمقالات النادرة باللغة الإنجليزية. كل ما درسته على اختبار العناصر البصرية ، وصفت في مقال لمساعدة أولئك الذين بدأوا للتو في الخوض في الموضوع.
معلومات عامة
اختبار القطعة يختبر عنصر واجهة مستخدم واحد. ويمكن أيضا أن يسمى اختبار المكون. الغرض من الاختبار هو إثبات أن واجهة مستخدم التطبيق المصغر تبدو وتتفاعل كما هو مخطط لها. يتطلب اختبار عنصر واجهة مستخدم بيئة اختبار توفر السياق المناسب لدورة حياة عنصر واجهة المستخدم.
يحتوي عنصر واجهة المستخدم الذي تم اختباره على القدرة على تلقي إجراءات المستخدم والأحداث ، والرد عليها ، وإنشاء شجرة من عناصر واجهة المستخدم الفرعية. لذلك ، اختبارات القطعة أكثر تعقيدًا من اختبارات الوحدة. ومع ذلك ، مثل اختبار الوحدة ، تعد بيئة اختبار التطبيقات المصغرة بمثابة محاكاة بسيطة ، أبسط بكثير من نظام واجهة مستخدم كامل.
يتيح لك اختبار الأداة المساعدة عزل واختبار سلوك عنصر واحد من الواجهة المرئية. والأمر الملحوظ هو إجراء جميع عمليات الفحص في وحدة التحكم ، والتي تعد مثالية للاختبارات التي تعمل كجزء من عملية CI / CD.
عادةً ما توجد الملفات التي تحتوي على اختبارات في الدليل الفرعي للاختبار للمشروع.
يمكن تشغيل الاختبارات إما من IDE أو من وحدة التحكم باستخدام الأمر:
$ flutter test
في هذه الحالة ، سيتم تنفيذ جميع الاختبارات باستخدام قناع * _test.dart من الدليل الفرعي للاختبار .
يمكنك إجراء اختبار منفصل عن طريق تحديد اسم الملف:
$ flutter test test/phone_screen_test.dart
يتم إنشاء الاختبار بواسطة وظيفة testWidgets ، التي تتلقى أداة كمعلمة اختبار ، والتي يتفاعل رمز الاختبار مع عنصر واجهة المستخدم تحت الاختبار:
testWidgets(' ', (WidgetTester tester) async {
لدمج الاختبارات في كتل منطقية ، يمكن دمج وظائف الاختبار في مجموعات ، داخل وظيفة المجموعة :
group(' ', (){ testWidgets(' ', (WidgetTester tester) async {
تسمح لك وظائف setUp و tearDown بتنفيذ بعض التعليمات البرمجية "قبل" و "بعد" كل اختبار. وفقًا لذلك ، تتيح لك وظائف setUpAll و tearDownAll تشغيل التعليمات البرمجية "قبل" و "بعد" جميع الاختبارات ، وإذا تم استدعاء هذه الوظائف داخل المجموعة ، فسيتم استدعاؤها "قبل" و "بعد" تنفيذ جميع الاختبارات في المجموعة:
setUp(() {
بحث القطعة
لتنفيذ بعض الإجراءات على عنصر واجهة متداخلة ، تحتاج إلى العثور عليها في شجرة عنصر واجهة المستخدم. للقيام بذلك ، هناك كائن بحث عمومي يسمح لك بالعثور على عناصر واجهة تعامل المستخدم:
- في الشجرة حسب النص - find.text ، find.widgetWithText ؛
- حسب المفتاح - find.byKey ؛
- بواسطة أيقونة - find.byIcon ، find.widgetWithIcon ؛
- حسب النوع - find.byType ؛
- حسب الموقف في الشجرة - find.descendant و find.ancestor ؛
- باستخدام وظيفة لتحليل الحاجيات في قائمة - find.byWidgetPredicate .
اختبار التفاعل القطعة
توفر فئة WidgetTester وظائف لإنشاء عنصر واجهة مستخدم للاختبار ، في انتظار تغيير حالته ، ولتنفيذ بعض الإجراءات على هذه الأدوات المصغرة.
أي تغيير في عنصر واجهة المستخدم يتسبب في تغيير حالته. لكن بيئة الاختبار لا تعيد بناء عنصر واجهة المستخدم في نفس الوقت. يجب أن تشير بشكل مستقل إلى بيئة الاختبار التي تريد إعادة إنشاء عنصر واجهة المستخدم من خلال استدعاء وظائف المضخة أو pumpAndSettle .
- pumpWidget - إنشاء أداة اختبار ؛
- pump - يبدأ في معالجة انتقال حالة القطعة وينتظر اكتماله خلال المهلة المحددة (100 مللي ثانية افتراضيًا) ؛
- pumpAndSettle - تستدعي المضخات في دورة لتغيير الحالات أثناء مهلة معينة (100 مللي افتراضيًا) ، هذا هو انتظار اكتمال جميع الرسوم المتحركة ؛
- اضغط - إرسال نقرة إلى القطعة.
- longPress - الضغط لفترة طويلة.
- قذف - انتقاد / انتقاد.
- سحب السحب
- إدخال النص - إدخال النص.
يمكن للاختبارات تنفيذ كل من السيناريوهات الإيجابية ، والتحقق من الفرص المخطط لها ، والسلبية للتأكد من أنها لا تؤدي إلى عواقب وخيمة ، على سبيل المثال ، عندما ينقر المستخدم في الاتجاه الخاطئ ولا يدخل ما هو مطلوب:
await tester.enterText(find.byKey(Key('phoneField')), 'bla-bla-bla');
بعد أي إجراءات مع الحاجيات ، تحتاج إلى استدعاء tester.pumpAndSettle () لتغيير الحالات.
Moki
كثيرون على دراية بمكتبة موكيتو . تحولت هذه المكتبة من عالم جافا إلى نجاح كبير لدرجة أن هناك تطبيقات لهذه المكتبة في العديد من لغات البرمجة ، بما في ذلك Dart.
للاتصال ، يجب إضافة التبعية إلى المشروع. أضف الأسطر التالية إلى ملف pubspec.yaml :
dependencies: mockito: any
والاتصال في ملف الاختبار:
import 'package:mockito/mockito.dart';
تتيح لك هذه المكتبة إنشاء فئات moque ، والتي تعتمد عليها عنصر واجهة المستخدم ، بحيث يصبح الاختبار أكثر بساطة ولا يغطي سوى الرمز الذي نختبره.
على سبيل المثال ، إذا قمنا باختبار عنصر واجهة المستخدم PhoneInputScreen ، والذي عند النقر عليه ، باستخدام خدمة AuthInteractor ، ينفذ طلبًا للجهة الخلفية authInteractor.checkAccess () ، ثم نستبدل الصورة الوهمية بدلاً من الخدمة ، يمكننا التحقق من أهم شيء - حقيقة الوصول إلى هذه الخدمة.
يتم إنشاء الغوغاء التبعية كأحفاد فئة Mock وتطبيق واجهة التبعية:
class AuthInteractorMock extends Mock implements AuthInteractor {}
الفئة في Dart هي أيضًا واجهة ، لذلك ليست هناك حاجة إلى إعلان الواجهة بشكل منفصل ، كما هو الحال في بعض لغات البرمجة الأخرى.
لتحديد وظيفة الموك ، عند استخدام الوظيفة ، والتي تتيح لك تحديد استجابة الموك لمكالمة لوظيفة معينة:
when( authInteractor.checkAccess(any), ).thenAnswer((_) => Future.value(true));
يمكن لـ Moki إرجاع الأخطاء أو البيانات الخاطئة:
when( authInteractor.checkAccess(any), ).thenAnswer((_) => Future.error(UnknownHttpStatusCode(null)));
الشيكات
أثناء الاختبار ، يمكنك التحقق من الحاجيات على الشاشة. يتيح لك ذلك التأكد من صحة الحالة الجديدة للشاشة من حيث رؤية الأدوات المصغّرة المطلوبة:
expect(find.text(' '), findsOneWidget); expect(find.text(' '), findsNothing);
بعد الانتهاء من الاختبار ، يمكنك أيضًا التحقق من طرق فصل الغوغاء التي تم استدعاؤها أثناء الاختبار ، وعدد المرات. يعد هذا ضروريًا ، على سبيل المثال ، لفهم ما إذا كانت هذه البيانات أو تلك مطلوبة كثيرًا ، ما إذا كانت هناك أي تغييرات غير ضرورية في حالة التطبيق:
verify(appComponent.authInteractor).called(1); verify(authInteractor.checkAccess(any)).called(1); verifyNever(appComponent.profileInteractor);
التصحيح
يتم إجراء الاختبارات في وحدة التحكم دون أي رسومات. يمكنك تشغيل الاختبارات في وضع التصحيح وتعيين نقاط التوقف في رمز عنصر واجهة المستخدم.
للحصول على فكرة عما يحدث في شجرة عنصر واجهة المستخدم ، يمكنك استخدام وظيفة debugDumpApp () التي ، عند استدعاؤها في رمز الاختبار ، تعرض التمثيل النصي للتسلسل الهرمي لشجرة عنصر واجهة المستخدم بالكامل في وقت معين في وحدة التحكم.
لفهم كيفية استخدام عنصر واجهة المستخدم moki ، توجد وظيفة logInvocations () . يستغرق كمعلمة قائمة moxas والمشاكل إلى وحدة التحكم سلسلة من استدعاء الأسلوب لهذه moxas التي تم تنفيذها في الاختبار.
مثال على هذا الاستنتاج أدناه. علامة التحقق على المكالمات التي تم فحصها في الاختبار باستخدام وظيفة التحقق :
AppComponentMock.sessionChangedInteractor [VERIFIED] AppComponentMock.authInteractor [VERIFIED] AuthInteractorMock.checkAccess(71111111111)
تدريب
يجب تقديم جميع التبعيات إلى القطعة التي تم اختبارها في شكل mok:
class SomeComponentMock extends Mock implements SomeComponent {} class AuthInteractorMock extends Mock implements AuthInteractor {}
يجب أن يتم نقل التبعيات إلى المكون الذي تم اختباره بطريقة مقبولة في التطبيق الخاص بك. لبساطة رواية القصص ، فكر في مثال يتم فيه تمرير التبعيات عبر المنشئ.
في مثال التعليمات البرمجية ، PhoneInputScreen عبارة عن عنصر واجهة مستخدم للاختبار يستند إلى StatefulWidget ملفوف في سقالة . يتم إنشاؤه في بيئة اختبار باستخدام دالة pumpWidget () :
await tester.pumpWidget(PhoneInputScreen(mock));
ومع ذلك ، يمكن لعنصر واجهة المستخدم الحقيقي استخدام المحاذاة لعناصر واجهة المستخدم المتداخلة ، الأمر الذي يتطلب MediaQuery في شجرة عنصر واجهة المستخدم ، ومن المحتمل أن يحصل على Navigator.of (سياق) للتنقل ، لذلك من العملي أن يتم التفاف عنصر واجهة المستخدم تحت الاختبار في MaterialApp أو CupertinoApp :
await tester.pumpWidget( MaterialApp( home: PhoneInputScreen(mock), ), );
بعد إنشاء عنصر واجهة مستخدم للاختبار وبعد أي إجراءات باستخدامه ، تحتاج إلى استدعاء tester.pumpAndSettle () حتى تتمكن بيئة الاختبار من معالجة جميع التغييرات في حالة عنصر واجهة المستخدم.
اختبارات التكامل
معلومات عامة
على عكس اختبارات التطبيق المصغر ، يتحقق اختبار التكامل من التطبيق بالكامل أو جزءًا كبيرًا منه. الهدف من اختبار التكامل هو التأكد من أن جميع الحاجيات والخدمات تعمل معًا كما هو متوقع. يمكن ملاحظة تشغيل اختبار التكامل في جهاز محاكاة أو على شاشة الجهاز. هذه الطريقة هي بديل جيد للاختبار اليدوي. بالإضافة إلى ذلك ، يمكن استخدام اختبارات التكامل لاختبار أداء التطبيق.
عادة ما يتم إجراء اختبار التكامل على جهاز حقيقي أو محاكي ، مثل iOS Simulator أو Android Emulator.
عادةً ما توجد الملفات التي تحتوي على اختبارات التكامل في الدليل الفرعي test_driver للمشروع.
التطبيق معزول عن كود برنامج التشغيل الاختباري ويبدأ بعده. يسمح لك برنامج تشغيل الاختبار بالتحكم في التطبيق أثناء الاختبار. يبدو مثل هذا:
import 'package:flutter_driver/driver_extension.dart'; import 'package:app_package_name/main.dart' as app; void main() { enableFlutterDriverExtension(); app.main(); }
يتم تشغيل الاختبارات من سطر الأوامر. إذا تم وصف تشغيل التطبيق الهدف في ملف app.dart ، وكان يسمى البرنامج النصي للاختبار app_test.dart ، فسيكون الأمر التالي كافيًا:
$ flutter drive --target=test_driver/app.dart
إذا كان البرنامج النصي للاختبار له اسم مختلف ، فأنت بحاجة إلى تحديده بشكل صريح:
$ flutter drive --target=test_driver/app.dart --driver=test_driver/home_test.dart
يتم إنشاء اختبار بواسطة وظيفة الاختبار ، ويتم تجميعه حسب وظيفة المجموعة .
group('park-flutter app', () {
يوضح هذا المثال رمز إنشاء برنامج تشغيل اختبار تتفاعل الاختبارات من خلاله مع التطبيق قيد الاختبار.
التفاعل مع التطبيق المختبر
تتفاعل أداة FlutterDriver مع تطبيق الاختبار من خلال الطرق التالية:
- اضغط - إرسال نقرة إلى القطعة.
- waitFor - انتظر القطعة لتظهر على الشاشة.
- waitForAbsent - انتظر اختفاء عنصر واجهة المستخدم ؛
- قم بالتمرير والتمريرفي عرض ، scrollUntilVisible - قم بتمرير الشاشة إلى الإزاحة المحددة أو إلى الأداة المرغوبة ؛
- enterText ، getText - إدخال نص أو أخذ نص عنصر واجهة المستخدم ؛
- لقطة شاشة - الحصول على لقطة شاشة ؛
- requestData - تفاعل أكثر تعقيدًا من خلال استدعاء دالة داخل التطبيق قيد الاختبار.
قد يكون هناك موقف عندما تحتاج إلى التأثير على الحالة العامة للتطبيق من رمز الاختبار. على سبيل المثال ، لتبسيط اختبار التكامل عن طريق استبدال جزء من الخدمات داخل التطبيق مع moki. في التطبيق ، يمكنك تحديد معالج طلب ، والذي يمكن الوصول إليه من خلال مكالمة إلى driver.requestData ("بعض المعلمات") في رمز الاختبار:
void main() { Future<String> dataHandler(String msg) async { if (msg == "some param") {
بحث القطعة
يختلف البحث عن عناصر واجهة تعامل المستخدم أثناء اختبار التكامل مع عنصر العثور العام في تكوين الأساليب عن وظيفة مماثلة في اختبار عناصر واجهة التعامل. ومع ذلك ، فإن المعنى العام عمليا لا يتغير:
- في الشجرة حسب النص - find.text ، find.widgetWithText ؛
- حسب المفتاح - find.byValueKey ؛
- حسب النوع - find.byType ؛
- في الموجه - find.byTooltip ؛
- بواسطة التسمية الدلالية - find.bySemanticsLabel ؛
- حسب الموقف في الشجرة find.descendant و find.ancestor .
استنتاج
نظرنا في طرق لتنظيم اختبار واجهة التطبيق المكتوبة باستخدام رفرفة. يمكننا كلاهما تنفيذ اختبارات للتحقق من أن الكود يفي بمتطلبات المواصفات الفنية ، ونجري اختبارات بهذه المهمة بالذات. من أوجه القصور الملحوظة في اختبار التكامل - لا توجد وسيلة للتفاعل مع مربعات حوار النظام في النظام الأساسي. ولكن ، على سبيل المثال ، يمكن تجنب طلبات الأذونات بإصدار أذونات من سطر الأوامر في مرحلة تثبيت التطبيق ، كما هو موضح في هذه البطاقة .
هذه المقالة هي نقطة البداية لاستكشاف موضوع اختبار يقدم القارئ باختصار كيفية عمل اختبار واجهة المستخدم. لا يحفظ وثائق القراءة ، والتي يسهل منها معرفة كيفية عمل فئة أو طريقة معينة. بعد كل شيء ، تتطلب دراسة موضوع جديد لنفسك ، أولاً وقبل كل شيء ، فهمًا لجميع العمليات الجارية ككل ، دون تفاصيل مفرطة.