פיתוח בדיקות Widget לאפליקציות Flutter
לעתים קרובות אנו נתקלים במצב הזה: אתה כותב widget, מריץ את הבדיקה — ומקבל No MaterialApp found או RenderFlex overflow. נשמע מוכר? בדיקות Widget ב-Flutter ממלאות את הפער בין בדיקות יחידה (קטנות מדי) לבדיקות אינטגרציה (איטיות מדי). בניגוד לבדיקות אינטגרציה שמריצות את האפליקציה על מכשיר, בדיקות widget מציגות widgets בסביבת WidgetTester הסינתטית תוך אלפיות שניות — פי 10–20 מהר יותר. הן מאפשרות אימות מהיר של תצוגה, אינטראקציות משתמש ומצבי UI ללא סימולטור.
בעיות טיפוסיות להתחיל איתן
הטעות הראשונה והנפוצה ביותר היא לנסות לכתוב בדיקת widget עבור widget שתלוי ב-BuildContext אמיתי. לדוגמה, widget משתמש ב-Theme.of(context) וקורס עם No MaterialApp found או No Directionality widget. הפתרון — תמיד לעטוף את ה-widget הנבדק ב-MaterialApp או ב-Directionality מינימלי:
await tester.pumpWidget( MaterialApp(home: MyWidget()), ); הבעיה הנפוצה השנייה — await tester.pumpWidget( MaterialApp(home: MyWidget()), ); בבדיקות שלא הופיעו בדיבאגר. זה קורה כי גודל RenderFlex overflowed ברירת המחדל (800×600) לא תואם למכשיר האמיתי. פתרון דרך WidgetTester או ב-API הנוכחי של Flutter 3.x: tester.binding.window.physicalSizeTestValue.
הבעיה השלישית — אסינכרוניות. בדיקות Widget הן סינכרוניות כברירת מחדל, אבל widgets לעתים קרובות מסתמכים על Futures, Streams או אנימציות. ללא tester.view.physicalSize = const Size(390, 844) מתאים, הבדיקה או נתקעת או עוברת בלי לחכות לתוצאה. בואו נצלול לזה.
איך להימנע מ-RenderFlex Overflow בבדיקות?
pump() ברירת המחדל הוא 800×600. למסכי מובייל זה לעתים קרובות מוביל ל-overflow. הניסיון שלנו מראה שעדיף להגדיר את הגודל למכשיר ספציפי. השתמש ב-SurfaceSize (נוסף ב-Flutter 3) או ב-setSurfaceSize הישן יותר. דוגמה ל-iPhone 13:
tester.view.physicalSize = const Size(390, 844); תמיד אפס ב-physicalSizeTestValue:
tearDown(() { tester.view.resetPhysicalSize(); }); ארכיטקטורה של בדיקות Widget
מבנה קובץ הבדיקה משקף את מבנה ה-widget: tester.view.physicalSize = const Size(390, 844); משקף את tearDown. כל קובץ בדיקה מכסה widget אחד או מסך אחד. זה מפשט ניווט ותחזוקה.
group('LoginScreen', () {
testWidgets('shows error when email is invalid', (tester) async {
await tester.pumpWidget(MaterialApp(home: LoginScreen()));
await tester.enterText(find.byKey(Key('email_field')), 'not-an-email');
await tester.tap(find.byKey(Key('submit_button')));
await tester.pump(); // синхронный кадр
expect(find.text('Введите корректный email'), findsOneWidget);
});
});
tearDown(() { tester.view.resetPhysicalSize(); }); לעומת test/widgets/ — הבדל קריטי. lib/widgets/ מצייר פריים אחד. group('LoginScreen', () { testWidgets('shows error when email is invalid', (tester) async { await tester.pumpWidget(MaterialApp(home: LoginScreen())); await tester.enterText(find.byKey(Key('email_field')), 'not-an-email'); await tester.tap(find.byKey(Key('submit_button'))); await tester.pump(); // синхронный кадр expect(find.text('Введите корректный email'), findsOneWidget); }); }); ממשיך לצייר פריימים עד שלא נשארות אנימציות ממתינות. על widgets עם אנימציות אינסופיות (pump() עם pumpAndSettle()) pump() נתקע לנצח — השתמש ב-pumpAndSettle() במקום.
איך לדמות Providers בבדיקות?
בדיקת widget בלי דימוי תלויות היא לא בדיקת widget, היא בדיקת אינטגרציה. אנו מבטיחים שכל בדיקה מבודדת. הגישות משתנות לפי מנהל המצב:
| מנהל מצב | מנגנון דימוי | דוגמה |
|---|---|---|
| Riverpod | AnimatedBuilder |
repeat: true |
| BLoC | pumpAndSettle() עם bloc מדומה |
pump(Duration(seconds: 2)) |
| GetIt | רישום mock לפני הבדיקה | ProviderScope.overrides |
חשוב: ב-userProvider.overrideWithValue(AsyncValue.data(mockUser)), תמיד בטל רישום ב-BlocProvider — אחרת ה-mock דולף לבדיקה הבאה. עבור BLoC, BlocProvider<AuthBloc>.value(value: mockAuthBloc) נוח; עבור Riverpod, getIt.registerSingleton<AuthService>(MockAuthService()) או GetIt.
בדיקות Golden: למה ואיך?
בדיקות Golden הן בדיקות רגרסיה חזותיות. ה-widget מוצג, וצילום מסך מושווה עם tearDown ייחוס ב-mocktail. בריצה הראשונה, הייחוס נוצר (mockito); בריצות הבאות, כל הבדל פיקסל שובר את הבדיקה. זה מבטיח שה-UI לא השתנה באופן בלתי צפוי. בדיקות Golden טובות יותר מבדיקות חזותיות רגילות כי הן מזהות אוטומטית הבדלי פיקסלים בלתי נראים לעין.
testWidgets('PrimaryButton golden', (tester) async {
await tester.pumpWidget(
MaterialApp(
home: Center(
child: PrimaryButton(label: 'Сохранить'),
),
),
);
await expectLater(
find.byType(PrimaryButton),
matchesGoldenFile('goldens/primary_button.png'),
);
});עם זאת, בדיקות Golden תלויות פלטפורמה. גופנים, אנטי-aliasing, תצוגת צללים — כל אלה שונים ב-macOS, Linux ו-Windows CI. פתרון: הרץ בדיקות golden על פלטפורמה אחת בלבד, באמצעות renderer riverpod_generator או תמונת Docker.
החבילה .png מוסיפה test/goldens/, ומבטלת מלבנים במקום טקסט בייחוסים. הניסיון שלנו מראה שבדיקות golden על ספריית רכיבים (10–20 widgets) מוקמות תוך 2–3 ימים.
Async ו-Future בבדיקות
אם widget מפעיל flutter test --update-goldens באתחול (לדוגמה, testWidgets('PrimaryButton golden', (tester) async { await tester.pumpWidget( MaterialApp( home: Center(child: PrimaryButton(label: 'Сохранить')), ), ); await expectLater( find.byType(PrimaryButton), matchesGoldenFile('goldens/primary_button.png'), ); }); + בקשת HTTP), הבדיקה צריכה לשלוט בהשלמת ה-future הזה. ללא דימוי, קריאת הרשת או נכשלת או נתקעת. השתמש ב-canvaskit:
when(() => mockApiService.getUser()).thenAnswer((_) async => mockUser);
await tester.pumpWidget(/* ... */);
await tester.pump(); // запускает FutureBuilder
await tester.pump(Duration.zero); // ждём завершения Futuregolden_toolkit במקום loadAppFonts() — כשההתנהגות מורכבת. יישם Future, דרוס את השיטות הנדרשות — נקי יותר מאשר לדמות כל קריאה.
מה כלול בעבודה
- כתיבת בדיקות widget לכל המסכים והרכיבים המרכזיים
- הקמת בדיקות Golden עם הפלטפורמה הנכונה ל-CI (מובטח שיעברו ב-CI שלך)
- דימוי ספקים (Riverpod, BLoC, Provider, GetIt)
- כיסוי מקרי קצה: מצבים ריקים, שגיאות, טעינה
- הגדרת הרצת CI עם דוח כיסוי (כיסוי >= 80% על widgets)
לוחות זמנים ועלות
3–5 ימים לפרויקט עם סט מסכים סטנדרטי (10–20 widgets). בדיקות Golden לכל ספריית רכיבי ה-UI מוערכות בנפרד. העלות מחושבת באופן אישי — צור קשר כדי להעריך את הפרויקט שלך.
למה להפקיד את הבדיקות בידי מקצוענים?
יש לנו עשרות פרויקטים של Flutter שהושקו מסחרית ב-App Store וב-Google Play. אנו מכירים את המלכודות הנפוצות: מ-FutureBuilder ועד אי-התאמות mocktail ב-CI. אנו מבטיחים שבדיקות ה-widget שלך יהיו יציבות, מהירות וניתנות לתחזוקה. צור קשר כדי לדון בפרטים ולקבל ייעוץ.







