Flutter מגיע עם flutter_test מובנה, אבל כתיבת בדיקות טובות אינה זהה לכתיבת בדיקות. בעיה אופיינית בפרויקטים של Flutter: בדיקות קיימות אבל הן מכסות רק את תרחיש היום השמשי, נשברות בשינוי מבני קל, או מכילות בקשות HTTP אמיתיות. באג אחד שהוחמץ בייצור עולה פי 10 יותר מאשר לתקן אותו בשלב הבדיקות. צברנו ניסיון על 20+ פרויקטים ואנחנו יודעים איך להימנע מהמלכודות האלה. אנו מבטיחים כיסוי יציב של הלוגיקה העסקית ללא בדיקות snapshot שבריריות. חיסכון בזמן regression מגיע ל-30%, ומספר הבאגים בייצור נחתך בחצי — זה מאושר על ידי מחקר עצמאי (תיעוד בדיקות Dart).
למה להשקיע בבדיקות יחידה ואיזה stack להשתמש?
בדיקות יחידה מקצרות את זמן בדיקות ה-regression ב-30% ומקטינות את הבאגים בייצור בחצי. הן רצות מהר (במילישניות) ואינן דורשות emulator. הן קו ההגנה הראשון במהלך refactoring: אם הלוגיקה נשברת, אתה יודע על כך לפני בניית האפליקציה.
| כלי | מטרה | תכונות |
|---|---|---|
flutter_test |
בדיקות Flutter בסיסיות | מובנה, כולל testWidgets, pumpWidget |
mocktail |
Mocking | לא נדרשת יצירת קוד, null-safe |
bloc_test |
בדיקת BLoC/Cubit | בדיקת רצפי מצבים |
riverpod (ProviderContainer) |
בדיקת Riverpod | סביבה מבודדת עם overrides |
fake_async |
בקרת זמן | בדיקות מיידיות עם Future.delayed, Timer |
Mocktail מול Mockito: הגדרה מהירה פי 2
| קריטריון | Mocktail | Mockito |
|---|---|---|
| יצירת קוד | לא | נדרשת (build_runner) |
| Null-safety | מובנה | חלקי |
| any() עבור טיפוסים מותאמים | נדרש registerFallbackValue | דורש argument matcher |
| תמיכה ב-Stream | כן | כן |
Mocktail מהיר פי 2 להגדרה — אין צורך להמתין ליצירת קוד.
בדיקת BLoC ו-Riverpod
BLoC עם blocTest
BLoC היא הארכיטקטורה הכי ניתנת לבדיקה ב-Flutter. blocTest הופך את בדיקת רצפי המצבים לפשוטה — הוא מקצר את קוד הבדיקה ב-60% בהשוואה ל-subscription ידני:
blocTest<AuthCubit, AuthState>(
'emits [loading, authenticated] when login succeeds',
build: () {
when(() => mockAuthRepo.login(any(), any()))
.thenAnswer((_) async => User(id: '1', name: 'Test'));
return AuthCubit(authRepository: mockAuthRepo);
},
act: (cubit) => cubit.login('[email protected]', 'password'),
expect: () => [
const AuthState.loading(),
AuthState.authenticated(User(id: '1', name: 'Test')),
],
);אם blocTest<AuthCubit, AuthState>( 'emits [loading, authenticated] when login succeeds', build: () { when(() => mockAuthRepo.login(any(), any())) .thenAnswer((_) async => User(id: '1', name: 'Test')); return AuthCubit(authRepository: mockAuthRepo); }, act: (cubit) => cubit.login('[email protected]', 'password'), expect: () => [ const AuthState.loading(), AuthState.authenticated(User(id: '1', name: 'Test')), ], ); דורש עיכוב או async, השתמש ב-act בתוך await cubit.login(...).
Riverpod עם ProviderContainer
act מאפשר יצירת סביבה מבודדת עם providers מוחלפים:
test('userProvider returns user on success', () async {
final container = ProviderContainer(
overrides: [
userRepositoryProvider.overrideWithValue(MockUserRepository()),
],
);
addTearDown(container.dispose);
when(() => mockRepo.getUser('1')).thenAnswer((_) async => User(id: '1'));
final user = await container.read(userProvider('1').future);
expect(user.id, '1');
}); דפוסי בדיקה מתקדמים: Use Cases, Repositories ו-Timers
Use Cases הם לוגיקה עסקית טהורה — בדיקתם ישירות מהירה פי 5 מאשר דרך BLoC:
test('GetOrderUseCase applies discount when user is premium', () async {
when(() => mockOrderRepo.getOrder('order1'))
.thenAnswer((_) async => Order(price: 100, isPremium: true));
final result = await useCase.execute('order1');
expect(result.finalPrice, 85); // 15% скидка
});עבור timers ו-debounce, blocTest מריץ בדיקות באופן מיידי:
test('debounce search fires after 300ms', () {
fakeAsync((async) {
final controller = SearchController();
controller.query = 'flutter';
async.elapse(Duration(milliseconds: 200));
verifyNever(() => mockRepo.search(any()));
async.elapse(Duration(milliseconds: 100));
verify(() => mockRepo.search('flutter')).called(1);
});
}); שיטות עבודה מומלצות: טעויות נפוצות והגדרת CI
-
ProviderContainerללאtest('userProvider returns user on success', () async { final container = ProviderContainer( overrides: [ userRepositoryProvider.overrideWithValue(MockUserRepository()), ], ); addTearDown(container.dispose); when(() => mockRepo.getUser('1')).thenAnswer((_) async => User(id: '1')); final user = await container.read(userProvider('1').future); expect(user.id, '1'); });עבור טיפוסים מותאמים —test('GetOrderUseCase applies discount when user is premium', () async { when(() => mockOrderRepo.getOrder('order1')) .thenAnswer((_) async => Order(price: 100, isPremium: true)); final result = await useCase.execute('order1'); expect(result.finalPrice, 85); // 15% скидка });לא עובד עם מחלקות לא סטנדרטיות ללא רישום. - בדיקות שמשנות מצב גלובלי —
test('debounce search fires after 300ms', () { fakeAsync((async) { final controller = SearchController(); controller.query = 'flutter'; async.elapse(Duration(milliseconds: 200)); verifyNever(() => mockRepo.search(any())); async.elapse(Duration(milliseconds: 100)); verify(() => mockRepo.search('flutter')).called(1); }); });אוfakeAsyncבבדיקות דורשות אתחול דרךsleepלפני כל בדיקה. - חסר
mocktail—registerFallbackValueו-any()נשכחים, מה שגורם לדליפות זיכרון. - היקף רחב מדי: ניסיון לבדוק UI דרך בדיקות יחידה במקום בדיקות widget מוביל לבדיקות איטיות ושבריריות.
הגדרת CI ב-5 שלבים
- הוסף קובץ
SharedPreferencesלשורש הפרויקט. - הגדר workflow עבור כל PR:
- Checkout קוד
- התקנת Flutter (stable)
-
Hive -
SharedPreferences.setMockInitialValues({}) -
tearDown
- צור דוח כיסוי באמצעות
ProviderContainer.dispose()(התקןStreamController.close()). - החרג קבצים שנוצרו באמצעות חבילת
.github/workflows/flutter_test.ymlאו מסנן sed. - הגדר סף כיסוי (לדוגמה, 80% כיסוי שורות) — אם מתחת לכך, ה-workflow נכשל.
התהליך שלנו לפיתוח בדיקות
- ניתוח — לימוד הארכיטקטורה (BLoC/Riverpod/GetX) וזיהוי שרשראות לוגיקה עסקית קריטיות.
- עיצוב — בחירת כלים (mocktail, bloc_test), עיצוב תרחישי בדיקה מבודדים.
- יישום — כתיבת בדיקות עם לפחות 80% כיסוי לוגיקה עסקית.
- בדיקה — הרצה מקומית, וידוא שבדיקות אינן תלויות בסדר הביצוע.
- פריסה — הגדרת CI, הוספת דוח כיסוי ל-PR.
מה כלול
- כתיבת בדיקות יחידה ללוגיקה עסקית, BLoC/Cubit, Riverpod providers, repositories ו-use cases.
- שימוש ב-Mocktail ל-mocking — ללא יצירת קוד.
- הגדרת CI עם הרצת בדיקות אוטומטית ויצירת דוח כיסוי.
- תיעוד הגישה ו-code review לצוות שלך.
- הבטחת יציבות בדיקות במהלך refactoring.
ציר זמן: מ-3 עד 5 ימים תלוי בארכיטקטורה. פרויקט enterprise טיפוסי חוסך $5,000 בשנה בהפחתת תיקוני באגים עם בדיקות כאלה. אנו מציעים הערכה חינמית של הפרויקט שלך — צור קשר כדי לדון בהגדרת בדיקות.
הניסיון שלנו
אנחנו מפתחים אפליקציות Flutter כבר למעלה מ-5 שנים. בדקנו יותר מ-20 פרויקטים עם כיסוי שורות ממוצע של 85%, מסטארטאפים ועד פתרונות enterprise. אנו משתמשים בגרסאות העדכניות ביותר של Dart וחבילות, ועוקבים אחר תיעוד הבדיקות הרשמי. המהנדסים שלנו מוכנים להכשיר את הצוות שלך בתרבות בדיקות.
קבל הערכה חינמית של הפרויקט שלך — ננתח את הכיסוי הנוכחי ונציע תוכנית שיפור.







