Sparround

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.

QatTest növüNə əvəz olunurNə yoxlanılır
Domain modeliUnit (`package:test`)Heç nəBiznes qaydaları, `copyWith`, bərabərlik
Use-caseUnitRepository-lər (fake)Birləşdirmə məntiqi, sərhəd halları
Mapper (DTO → domain)UnitHeç nə (giriş `Map`)`null`, yanlış format, tanınmayan enum
RepositoryUnitService-lər (fake/mock)Cache, fallback, xəta çevrilməsi
ServiceUnitHTTP client (`MockClient`)Sorğunun qurulması, status kodları
Notifier (view model)Unit (`ProviderContainer.test`)Yalnız repository/use-caseState keçidləri, command-lar, nəticələr
ViewWidget testEyni fake-lər (`ProviderScope(overrides:)`)Dörd hal: loading, boş, məlumat, xəta
Bütün tətbiqIntegration testHeç 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: copyWith== 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.

dart
// ══ 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.