Hər qatın testi: strategiya
Qat bölgüsünün ən birbaşa ölçülə bilən qazancı testdir. Rəsmi case study-nin test səhifəsi hər qat üçün konkret strategiya verir:
- View model — Flutter kitabxanalarına və test framework-una söykənməyən unit test. Onun yeganə asılılığı repository-lərdir (use-case varsa, onlar), ona görə tək quraşdırma repository-nin fake ya da mock-unu yazmaqdır.
- View — widget test, eyni fake-lərlə. Sənədin qeydi vacibdir: view model üçün fake-ləri yazdıqdan sonra widget testləri üçün əlavə heç nə lazım deyil.
- Repository — unit test; onun asılı olduğu service-lər mock edilir.
- Service — unit test; HTTP client əvəz olunur (
MockClient). - Domain (model, use-case) — sadə unit test, heç bir Flutter asılılığı olmadan.
Sənədin əsas fikri birbaşadır: arxitektura sağlamdırsa, view və view model testləri yalnız repository-lərin mock edilməsini tələb edir. Bu, həm də diaqnoz alətidir — testdə HTTP client-i ya da JSON-u mock etmək lazım gəlirsə, qat bölgüsündə deşik var.
| Qat | Test növü | Nə əvəz olunur | Nə yoxlanılır |
|---|---|---|---|
| Domain modeli | Unit (`package:test`) | Heç nə | Biznes qaydaları, `copyWith`, bərabərlik |
| Use-case | Unit | Repository-lər (fake) | Birləşdirmə məntiqi, sərhəd halları |
| Mapper (DTO → domain) | Unit | Heç nə (giriş `Map`) | `null`, yanlış format, tanınmayan enum |
| Repository | Unit | Service-lər (fake/mock) | Cache, fallback, xəta çevrilməsi |
| Service | Unit | HTTP client (`MockClient`) | Sorğunun qurulması, status kodları |
| Notifier (view model) | Unit (`ProviderContainer.test`) | Yalnız repository/use-case | State keçidləri, command-lar, nəticələr |
| View | Widget test | Eyni fake-lər (`ProviderScope(overrides:)`) | Dörd hal: loading, boş, məlumat, xəta |
| Bütün tətbiq | Integration test | Heç nə (ya da test backend-i) | 1-3 kritik axın: giriş, ödəniş |
Testin sayı barədə praktik yanaşma. Rəsmi sənəd konkret nisbət göstərmir, lakin qatların qiyməti fərqlidir və bu, təbii bölgü yaradır:
- Domain və mapper testləri ən ucuzdur (millisaniyələr, heç bir quraşdırma) və ən çox dəyişən yeri qoruyur → onları çox yazmaq sərfəlidir.
- Notifier testləri orta qiymətlidir (fake repository lazımdır) → hər ekranın əsas axınları üçün yazılır.
- Widget testləri daha bahalıdır (frame gözləmə, tapıcılar) → hər ekran üçün 3-5 test: dörd hal + bir qarşılıqlı əlaqə.
- Integration testləri ən bahalıdır (cihaz, real şəbəkə, kövrəklik) → yalnız kritik axınlar: giriş, ödəniş, qeydiyyat.
Nəyi test etməmək. İki hal var:
- Generasiya olunan kod:
copyWithvə==freezed tərəfindən yaradılır; onları test etmək generatoru test etməkdir. (Öz biznes getter-lərini isə test etmək lazımdır.) - Implementasiya detalları: "repository metodu bir dəfə çağırıldı" tipli yoxlamalar refaktoru əngəlləyir. Nəticəni yoxlamaq daha dəyərlidir.
Ən vacib diaqnoz. Test yazarkən çətinlik hiss edirsənsə, bu, adətən test problemi deyil — arxitektura siqnalıdır: notifier testində HTTP-ni mock etmək lazım gəlirsə, notifier data qatına birbaşa çıxır; widget testində 10 provider override etmək lazım gəlirsə, ekran çox şey bilir.
// ══ test/domain/models/order_test.dart ══ (ən ucuz, ən çox)
import 'package:test/test.dart'; // flutter_test DEYİL
test('ləğv yalnız pending statusda mümkündür', () {
expect(order.copyWith(status: OrderStatus.pending).canBeCancelled, isTrue);
expect(order.copyWith(status: OrderStatus.shipped).canBeCancelled, isFalse);
});
// ══ test/data/dto/order_dto_mapper_test.dart ══ (backend dəyişikliyini tutur)
test('null sahələr güvənli defolt alır', () {
final dto = OrderDto.fromJson(const {'id': 'o-1'});
final order = dto.toDomain();
expect(order.items, isEmpty); // `?? []` mapper-də
expect(order.status, OrderStatus.unknown);
});
// ══ test/data/repositories/order_repository_test.dart ══ (qərarlar)
test('şəbəkə xətası AuthFailure-a çevrilmir, NetworkFailure olur', () async {
final repo = OrderRepositoryRemote(
apiClient: FakeOrderApiClient(throwOnCall: const SocketException('')),
);
final result = await repo.fetchMine();
expect((result as Error).error, isA<NetworkFailure>());
});
// ══ test/ui/orders/order_list_notifier_test.dart ══ (state keçidləri)
import 'package:flutter_riverpod/flutter_riverpod.dart';
test('cancel uğurlu olduqda siyahıdaki element yenilənir', () async {
final container = ProviderContainer.test(overrides: [
// TƏK quraşdırma: repository-nin fake-i.
orderRepositoryProvider
.overrideWithValue(FakeOrderRepository([pendingOrder])),
]);
await container.read(orderListNotifierProvider.future);
final outcome = await container
.read(orderListNotifierProvider.notifier)
.cancel(pendingOrder.id);
expect(outcome, CancelOutcome.success);
expect(
container.read(orderListNotifierProvider).value!.orders.first.status,
OrderStatus.cancelled,
);
});
// ══ test/ui/orders/orders_page_test.dart ══ (EYNİ fake ilə)
import 'package:flutter_test/flutter_test.dart';
testWidgets('boş siyahı üçün boş hal göstərilir', (tester) async {
await tester.pumpWidget(ProviderScope(
overrides: [
// Notifier testində yazdığın fake yenidən işlədilir.
orderRepositoryProvider
.overrideWithValue(FakeOrderRepository(const [])),
],
child: const MaterialApp(home: OrdersPage()),
));
await tester.pumpAndSettle();
expect(find.byType(EmptyOrders), findsOneWidget);
});
// ══ integration_test/checkout_flow_test.dart ══ (yalnız kritik axın)
// Real tətbiq, real naviqasiya: kataloq → səbət → ödəniş → təsdiq.
// Bir-üç ədəd — çox yazmaq bahalıdır və kövrək olur.Bir feature-in test piramidası: beş fayl, beş qat. Diqqət et — yalnız sonuncu ikisi Flutter-dən asılıdır.
Fake-ləri bir yerdə saxlamaq. Rəsmi sənədin qeydinin praktik nəticəsi budur: test/fakes/ qovluğunda hər repository müqaviləsi üçün bir fake yazırsan və onu bütün testlərdə (notifier, widget, use-case) yenidən istifadə edirsən. Bu, təkrarı kəsir və fake-in davranışını bir yerdə saxlayır.
Praktika. Bir feature seç və beş test faylını yaz: domain modeli, mapper, repository, notifier, widget. Sonra flutter test --coverage işlət və hansı qatın örtülməmiş qaldığına bax.
Hazır sayılır: beş fayl da yaşıldır, test/fakes/ qovluğunda ən azı bir fake var, və notifier testində yalnız repository override olunur (HTTP ya da JSON yox).
📚 Mənbələr və sənədlər
- Case study: hər qatın testirəsmidocs.flutter.dev
Bu mövzunun mənbəyi: view model, view, repository və service testlərinin rəsmi strategiyası.
- Flutter: testlərə girişrəsmidocs.flutter.dev
Unit, widget və integration testlərin fərqi və qiyməti.
- Riverpod: testrəsmiriverpod.dev
`ProviderContainer.test()`, `overrides` və widget testlərində `ProviderScope`.
- Arxitektura tövsiyələri: testrəsmidocs.flutter.dev
"Komponentləri ayrı və birlikdə test edin" və "fake-lər yazın" bəndləri.