أساسيات اختبار الودجت باستخدام pumpWidget والمحددات
أساسيات اختبار الودجت باستخدام pumpWidget والمحددات
تتيح لك اختبارات الودجت التحقق من أن واجهة مستخدم Flutter تُعرض بشكل صحيح وتستجيب للتفاعلات — كل ذلك دون تشغيل جهاز حقيقي أو محاكٍ. تأتي حزمة flutter_test مع كل مشروع Flutter وتوفر الفئة WidgetTester، الأداة الرئيسية لاختبار الودجات. في هذا الدرس ستتعلم كيفية عرض الودجات في عزل تام، وتحديد العناصر داخل الشجرة المُعرَضة، وكتابة تأكيدات دقيقة.
لماذا اختبارات الودجت؟
اختبارات الوحدة تتحقق من المنطق؛ اختبارات التكامل تتحقق من تدفقات التطبيق الكاملة. اختبارات الودجت تحتل المنطقة الوسطى: تُعرض ودجت واحداً (أو شجرة فرعية صغيرة) في بيئة محاكاة وتتيح لك التفاعل معها برمجياً. هي أسرع من اختبارات التكامل، وأكثر واقعية من اختبارات الوحدة، وتكشف الانحدارات في واجهة المستخدم قبل أن يراها المستخدمون.
- تعمل كلياً على الجهاز المضيف — لا حاجة لمحاكٍ Android أو iOS
- دورة حياة الودجت الكاملة تُمارَس (
initState،build،dispose) - يمكنها محاكاة النقرات والتمرير وإدخال النص والعمليات غير المتزامنة
- متكاملة مع أنبوب CI لـ
flutter test
إعداد ملف اختبار الودجت
ملفات اختبار الودجت تقع في مجلد test/ وتستورد package:flutter_test/flutter_test.dart. كل اختبار يستقبل وسيطة WidgetTester من خلال دالة testWidgets. المختبر هو مقبضك على محرك Flutter المحاكَى.
هيكل اختبار ودجت بسيط
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/widgets/greeting_card.dart';
void main() {
testWidgets('GreetingCard displays the given name', (WidgetTester tester) async {
// 1. عرض الودجت تحت الاختبار
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(
body: GreetingCard(name: 'Edrees'),
),
),
);
// 2. تحديد عنصر النص
final nameFinder = find.text('Hello, Edrees!');
// 3. التأكيد أنه موجود مرة واحدة بالضبط
expect(nameFinder, findsOneWidget);
});
}
MaterialApp (وعادةً Scaffold). كثير من ودجات Flutter تتطلب Directionality وMediaQuery وTheme من أسلاف — التغليف في MaterialApp يوفر كل هذا مجاناً.pumpWidget — عرض ودجت في عزل تام
WidgetTester.pumpWidget(widget) ينشئ شجرة الودجت المعطاة في بيئة الاختبار. تحت الغطاء يستدعي runApp() في محرك Flutter معزول، يبني شجرة الودجت، ينفذ التخطيط، ويرسم الإطار الأول. لأنه يُرجع Future<void>، يجب عليك استخدام await معه.
بعد استدعاء pumpWidget، تكون شجرة الودجت مبنية بالكامل ويمكنك فوراً الاستعلام عنها بالمحددات. إذا كان ودجتك يُطلق عملاً غير متزامن (مثل FutureBuilder أو رسوم متحركة)، ستحتاج أيضاً إلى tester.pump() أو tester.pumpAndSettle() لتقديم الساعة ومعالجة المهام الدقيقة المعلقة.
pumpWidget مقابل pump مقابل pumpAndSettle
// pumpWidget — العرض الأولي (استدعه مرة واحدة لكل اختبار)
await tester.pumpWidget(const MyWidget());
// pump() — تقديم إطار واحد (استخدمه بعد setState أو الأحداث غير المتزامنة)
await tester.tap(find.byType(ElevatedButton));
await tester.pump(); // يُطلق إعادة البناء
// pumpAndSettle() — ضخ حتى لا تبقى إطارات معلقة
// مفيد بعد الرسوم المتحركة أو اكتمال Future
await tester.pumpAndSettle();
// pump(Duration) — التقدم بمدة محددة
await tester.pump(const Duration(milliseconds: 300));
المحددات (Finders) — تحديد العناصر في شجرة الودجت
بمجرد عرض شجرة الودجت، تستخدم المحددات لتحديد عناصر محددة قبل إجراء التأكيدات أو التفاعلات. الكائن العلوي find يكشف مجموعة غنية من منشئي المحددات.
find.text()
يبحث في شجرة الودجت عن ودجت Text (أو RichText) الذي تطابق سلسلته المعروضة الوسيطة تماماً.
أمثلة على find.text()
// إيجاد ودجت يعرض السلسلة الدقيقة 'Submit'
final submitFinder = find.text('Submit');
expect(submitFinder, findsOneWidget);
// إيجاد ودجت بسلسلة مختلفة — مفيد للمحتوى الديناميكي
expect(find.text('Score: 42'), findsOneWidget);
// التأكيد على الغياب — لا شيء بهذا التسمية مُعرَض
expect(find.text('Error: invalid input'), findsNothing);
find.byType()
يجد جميع الودجات من نوع Dart معين في الشجرة الحالية. هذا هو المحدد الأكثر شيوعاً لأنه لا يعتمد على النص المعروض أو المفاتيح، مما يجعل الاختبارات قوية ضد تغييرات المحتوى.
أمثلة على find.byType()
// التحقق من وجود CircularProgressIndicator (حالة التحميل)
expect(find.byType(CircularProgressIndicator), findsOneWidget);
// التحقق من عرض اثنين بالضبط من ElevatedButton
expect(find.byType(ElevatedButton), findsNWidgets(2));
// التأكيد على وجود ListView في مكان ما في الشجرة
expect(find.byType(ListView), findsOneWidget);
// النقر على FloatingActionButton الوحيد في الشجرة
await tester.tap(find.byType(FloatingActionButton));
await tester.pump();
find.byKey()
يحدد ودجت أُعطي Key محدداً. المفاتيح هي الطريقة الأكثر دقة واستقراراً لاستهداف الودجات في الاختبارات، خاصة عندما تحتوي الشجرة على ودجات متعددة من نفس النوع أو بنص متطابق. قم بتعيين المفاتيح في كود الإنتاج بعناية من أجل قابلية الاختبار.
find.byKey() — ودجت الإنتاج والاختبار
// في كود الودجت الخاص بك، قم بتعيين Key:
class LoginForm extends StatelessWidget {
const LoginForm({super.key});
@override
Widget build(BuildContext context) {
return Column(
children: [
TextFormField(key: const Key('emailField')),
TextFormField(key: const Key('passwordField')),
ElevatedButton(
key: const Key('loginButton'),
onPressed: () {},
child: const Text('Log In'),
),
],
);
}
}
// في اختبارك، استخدم find.byKey():
await tester.pumpWidget(const MaterialApp(home: Scaffold(body: LoginForm())));
expect(find.byKey(const Key('emailField')), findsOneWidget);
expect(find.byKey(const Key('passwordField')), findsOneWidget);
await tester.tap(find.byKey(const Key('loginButton')));
await tester.pump();
const Key('...') للمفاتيح النصية البسيطة وValueKey<T> عندما يكون المفتاح مشتقاً من البيانات (مثل ValueKey<int>(item.id)). استخدام GlobalKey في الاختبارات نادراً ما يكون ضرورياً ويضيف اقتراناً.مطابقات التأكيد الشائعة
بمجرد حصولك على محدد، اقرنه بمطابق داخل expect():
findsOneWidget— تطابق واحد بالضبطfindsNothing— صفر تطابقاتfindsNWidgets(n)— n تطابقات بالضبطfindsAtLeastNWidgets(n)— على الأقل n تطابقاتfindsWidgets— تطابق واحد أو أكثر
find.text() يُجري تطابقاً دقيقاً بشكل افتراضي. إذا كان ودجتك يعرض 'Hello, Edrees!' وبحثت عن 'Hello'، فالمحدد لا يُرجع شيئاً. مرر findRichText: true للبحث أيضاً داخل أجزاء RichText، واستخدم find.textContaining() للتطابق الجزئي.الخلاصة
تمنحك اختبارات الودجت طريقة سريعة وموثوقة للتحقق من صحة واجهة المستخدم دون جهاز مادي. نمط الخطوات الثلاث — العرض بـ pumpWidget، التحديد بالمحددات، التأكيد بالمطابقات — ينطبق على كل اختبار ودجت ستكتبه تقريباً. أتقن find.text() وfind.byType() وfind.byKey() ولديك الأدوات لتغطية غالبية سيناريوهات ودجت Flutter.
MaterialApp؛ قم دائماً بـ await pumpWidget؛ اختر المحدد الذي يجعل اختبارك أكثر قابلية للقراءة وأقل هشاشة — byKey للاستقرار، byType للتأكيدات الهيكلية، وtext لتأكيدات المحتوى.