Bölüm 10 — Konu 45: Test Yazımı (Unit, Widget, Integration, Golden)
Dizi · 45/61 Flutter Türkçe Tutorial
- Bölüm 1 — Konu 1: Flutter Nedir, Mimarisi (Widget → Element → RenderObject, Skia/Impeller)
- Bölüm 1 — Konu 2: Ortam Kurulumu (Flutter SDK, Android Studio/VS Code, Emulator/Simulator, DevTools'a İlk Bakış)
- Bölüm 1 — Konu 3: İlk Proje Yapısı (`pubspec.yaml`, `lib/`, Klasör Mimarisi, `flutter create` Anatomisi)
- Bölüm 1 — Konu 4: Hot Reload / Hot Restart Ne Yapıyor, Neden Önemli
- Bölüm 2 — Konu 5: "Her Şey Widget'tır" Felsefesi, Widget Ağacı
- Bölüm 2 — Konu 6: StatelessWidget vs StatefulWidget + `setState()` Derinlemesine (Rebuild Mekanizması)
- Bölüm 2 — Konu 7: Temel Layout — Container, Row, Column, Stack, Padding, Align, Center
- Bölüm 2 — Konu 8: Constraint Sistemi — "Constraints Go Down, Sizes Go Up, Parent Sets Position"
- Bölüm 2 — Konu 9: `Expanded`, `Flexible`, `Spacer`, Intrinsic Widget'lar
- Bölüm 3 — Konu 10: MaterialApp/CupertinoApp, Scaffold, AppBar
- Bölüm 3 — Konu 11: `ListView`, `GridView`, `SingleChildScrollView` (+ Builder Pattern)
- Bölüm 3 — Konu 12: Text, TextStyle, Icon, Image, Asset Yönetimi
- Bölüm 3 — Konu 13: Custom Widget Yazma Prensipleri (Composition Over Inheritance)
- Bölüm 3 — Konu 14: Navigator 1.0 — Push/Pop, Named Routes
- Bölüm 4 — Konu 15: Form, TextField/TextFormField, GlobalKey<FormState>, Validasyon
- Bölüm 4 — Konu 16: Theme Sistemi (ThemeData, ColorScheme)
- Bölüm 4 — Konu 17: Responsive & Adaptive Tasarım (MediaQuery, LayoutBuilder, OrientationBuilder, Breakpoint Stratejileri)
- Bölüm 5 — Konu 18: Future/Async-Await Flutter Bağlamında, FutureBuilder
- Bölüm 5 — Konu 19: `Stream`, `StreamBuilder`
- Bölüm 5 — Konu 20: HTTP İstekleri (`http` / `dio` Paketleri)
- Bölüm 5 — Konu 21: JSON Serialization (Manuel → `json_serializable`/`freezed`)
- Bölüm 5 — Konu 22: Local Storage — `shared_preferences` → `Hive` → `sqflite`/Drift
- Bölüm 6 — Konu 23: InheritedWidget ve InheritedModel — "Neden setState Yetmiyor" Sorusunun Cevabı
- Bölüm 6 — Konu 24: Provider Paketi
- Bölüm 6 — Konu 25: Riverpod (Modern Yaklaşım, Provider'ın Halefi)
- Bölüm 6 — Konu 26: BLoC/Cubit Pattern (`flutter_bloc`)
- Bölüm 6 — Konu 27: GetX (Tartışmalı Ama Yaygın)
- Bölüm 6 — Konu 28: Karşılaştırma — Hangi Projede Hangisi?
- Bölüm 7 — Konu 29: Navigator 2.0 (Router, RouteInformationParser, RouterDelegate)
- Bölüm 7 — Konu 30: `go_router` Paketi (Pratik ve Modern Çözüm)
- Bölüm 7 — Konu 31: Deep Linking
- Bölüm 7 — Konu 32: Repository Pattern, Katmanlı Mimari (Data/Domain/Presentation)
- Bölüm 7 — Konu 33: Dependency Injection (`get_it`, `injectable`)
- Bölüm 7 — Konu 34: Clean Architecture Uyarlaması, SOLID Prensipleri
- Bölüm 8 — Konu 35: Implicit Animasyonlar
- Bölüm 8 — Konu 36: Explicit Animasyonlar (`AnimationController`, `Tween`, `Curve`)
- Bölüm 8 — Konu 37: Hero Animasyonları
- Bölüm 8 — Konu 38: `CustomPainter` / `Canvas`
- Bölüm 8 — Konu 39: Rive / Lottie Entegrasyonu
- Bölüm 9 — Konu 40: Platform Channels
- Bölüm 9 — Konu 41: Permission Yönetimi (`permission_handler`)
- Bölüm 9 — Konu 42: Kamera, Konum, Sensörler
- Bölüm 9 — Konu 43: Push Notification (Firebase Cloud Messaging)
- Bölüm 9 — Konu 44: Android/iOS Build Sistemleri
- Bölüm 10 — Konu 45: Test Yazımı (Unit, Widget, Integration, Golden)
- Bölüm 10 — Konu 46: CI/CD
- Bölüm 10 — Konu 47: Rebuild Optimizasyonu (`const`, `key` Kullanımı)
- Bölüm 10 — Konu 48: DevTools Profiling
- Bölüm 10 — Konu 49: Lazy Loading, Pagination, Büyük Liste Performansı
- Bölüm 11 — Konu 50: Firebase Ekosistemi (Auth, Firestore, Storage, Functions)
- Bölüm 11 — Konu 51: Supabase Alternatifi
- Bölüm 11 — Konu 52: GraphQL (Opsiyonel)
- Bölüm 11 — Konu 53: Store Yayınlama Süreci (İmzalama, Listing, Versiyonlama)
- Bölüm 11 — Konu 54: App Size, Obfuscation, Flavor Yönetimi
- Bölüm 12 — Konu 55: Flutter Web / Desktop
- Bölüm 12 — Konu 56: Custom `RenderObject` Yazımı
- Bölüm 12 — Konu 57: Engine Mimarisi Derinlemesine (Impeller vs Skia)
- Bölüm 12 — Konu 58: Plugin Geliştirme, pub.dev'e Paket Yayınlama
- Bölüm 12 — Konu 59: Monorepo Mimarisi (Melos)
- Bölüm 12 — Konu 60: Erişilebilirlik (Accessibility) Derinlemesine
- Bölüm 12 — Konu 61: Yerelleştirme (Localization / i18n)
İçindekiler 9 başlık
- Neden Test Yazarsın?
- Dört Test Türü — Piramitteki Yerleri
- Unit Test — Saf Mantığı Test Etme
- Widget Test — Bir Widget'ın Davranışını Test Etme
- Integration Test — Uçtan Uca Gerçek Cihazda
- Golden Test — Piksel Piksel Görsel Doğrulama
- Hangi Test Türünü Ne Zaman Yazarsın?
- 🎯 Bu Dersten Çıkarılması Gerekenler
- 📝 Ödevler
Bölüm 10'a ("Kalite & Performans") hoş geldin. Şimdiye kadar kodumuzu elle çalıştırıp gözle doğruladık — bu, küçük bir uygulamada işe yarar ama proje büyüdükçe her değişiklikte her şeyi elle test etmek imkansızlaşır. Bu ders, Flutter'ın dört test türünü, ne zaman hangisini yazacağını öğretiyor.
Neden Test Yazarsın?
- Regresyon önleme: Bir özelliği düzeltirken, başka bir özelliği bozmadığını otomatik olarak doğrularsın.
- Güvenle refactor: Bölüm 7'de öğrendiğimiz Clean Architecture'ı hatırlarsan —
domainkatmanının Flutter'dan habersiz olması, testlerin hızlı ve basit olmasını sağlıyordu. Testler olmadan, o mimarinin en büyük faydası kaybolur. - Dokümantasyon: İyi yazılmış bir test, "bu fonksiyon şu girdide şunu yapmalı" diyen çalışan bir örnektir.
Dört Test Türü — Piramitteki Yerleri
△ Integration (az sayıda, yavaş, gerçekçi)
△△△ Widget (orta sayıda)
△△△△△△△ Unit (çok sayıda, hızlı, ucuz)Bu "test piramidi", hangi türden ne kadar yazman gerektiğine dair yaygın bir kural: unit testler hızlı ve ucuz olduğu için çok sayıda, integration testler yavaş ve pahalı olduğu için az sayıda yazılır.
Unit Test — Saf Mantığı Test Etme
flutter pub add dev:test # zaten flutter test SDK'sının parçası, genelde ekstra gerekmez// lib/domain/usecases/toplam_hesapla.dart
double toplamHesapla(List<double> fiyatlar) {
return fiyatlar.fold(0, (toplam, fiyat) => toplam + fiyat); // Dart Bölüm 5'i hatırla
}// test/toplam_hesapla_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:senin_projen/domain/usecases/toplam_hesapla.dart';
void main() {
group('toplamHesapla', () {
test('boş liste için 0 döner', () {
expect(toplamHesapla([]), 0);
});
test('birden fazla fiyatı doğru toplar', () {
expect(toplamHesapla([10.0, 20.0, 5.5]), 35.5);
});
});
}Parça parça inceleyelim:
group(...) — Dart Bölüm 5'te öğrendiğimiz anonim fonksiyon parametresi alan bir yapı; ilgili testleri mantıksal olarak bir araya toplar (test çıktısında iç içe görünürler).
test('açıklama', () { ... }) — her test() çağrısı, tek bir senaryoyu doğrular. İsim, ne test edildiğini açıkça anlatmalı (bu, kod okunabilirliği açısından Bölüm 7'deki Single Responsibility'ye benzer bir disiplin).
expect(gercekDeger, beklenenDeger) — testin kalbi. Gerçek değer, beklenenle eşleşmezse, test başarısız olur ve hangi test olduğunu, beklenen/gerçek değerleri açıkça raporlar.
Neden bu kadar kolay? Çünkü toplamHesapla, saf bir fonksiyon — widget yok, BuildContext yok, sadece girdi alıp çıktı üreten bir Dart kodu. Bölüm 7'de domain katmanının neden Flutter'ı bilmemesi gerektiğini anlatırken bahsettiğimiz test edilebilirlik faydası, tam olarak burada somutlaşıyor.
flutter test test/toplam_hesapla_test.dart # tek dosya
flutter test # tüm testlerWidget Test — Bir Widget'ın Davranışını Test Etme
// test/sayac_widget_test.dart
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:senin_projen/sayac_ekrani.dart';
void main() {
testWidgets('artı butonuna basınca sayaç artar', (WidgetTester tester) async {
// 1. Widget'ı "sahte" bir ekrana yerleştir
await tester.pumpWidget(const MaterialApp(home: SayacEkrani()));
// 2. Başlangıç durumunu doğrula
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// 3. Kullanıcı etkileşimini simüle et
await tester.tap(find.byIcon(Icons.add));
await tester.pump(); // Bölüm 2 Konu 6'daki rebuild'i TETİKLE
// 4. Yeni durumu doğrula
expect(find.text('1'), findsOneWidget);
});
}testWidgets(...), normal test()'ten farklı olarak, bir WidgetTester sağlar — bu, gerçek bir cihaz/emülatör olmadan, widget ağacını bellekte simüle eden bir araçtır.
tester.pumpWidget(...) — widget'ı, runApp()'ın test ortamındaki karşılığı olarak "ekrana" yerleştirir.
find.text('0') / find.byIcon(...) — Bölüm 1'de öğrendiğimiz widget ağacı kavramını hatırla; find, bu ağaçta belirli widget'ları arayan bir "finder" API'sidir.
tester.tap(...) — bir dokunma olayını simüle eder (gerçek bir parmak dokunuşu gibi).
await tester.pump() — çok kritik bir satır. Gerçek bir uygulamada setState() çağrıldığında Flutter otomatik olarak yeniden çizer, ama test ortamında, bir sonraki "frame"in ne zaman çizileceğini sen elle tetiklemelisin — pump(), "bir kare ileri git" demektir. Bunu unutursan, tap() sonrası state değişmiş olsa bile, eski görünüm hâlâ test edilir ve testin yanlış sonuç vermesine yol açar.
expect(find.text('1'), findsOneWidget) — findsOneWidget/findsNothing/findsNWidgets(n), Dart Bölüm 7'de öğrendiğimiz enum benzeri sabitler; bulunan widget sayısını doğrular.
Integration Test — Uçtan Uca Gerçek Cihazda
// integration_test/uygulama_akisi_test.dart
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:senin_projen/main.dart' as app;
void main() {
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('giriş yap ve ana sayfaya git', (WidgetTester tester) async {
app.main(); // GERÇEK uygulamayı başlat
await tester.pumpAndSettle(); // tüm animasyonlar/asenkron işler bitene kadar bekle
await tester.enterText(find.byKey(const Key('email_alani')), 'test@ornek.com');
await tester.enterText(find.byKey(const Key('sifre_alani')), '123456');
await tester.tap(find.text('Giriş Yap'));
await tester.pumpAndSettle();
expect(find.text('Hoş Geldin'), findsOneWidget);
});
}Widget test ile temel fark: Widget test, tek bir widget'ı izole ederek (sahte bir MaterialApp içinde) test eder. Integration test, gerçek uygulamayı, gerçek bir cihaz veya emülatörde, Navigator (Bölüm 3 Konu 14), gerçek HTTP çağrıları (Bölüm 5 Konu 20) dahil her şeyiyle çalıştırır — bu yüzden çok daha yavaş ama çok daha gerçekçidir.
find.byKey(const Key('email_alani')) — metne göre bulmak (find.text) yerine, widget'a açıkça bir kimlik (Key) vermek daha güvenilirdir — metin değişebilir (çeviri, A/B test), Key değişmez. Bu, Bölüm 8 Konu 35'te AnimatedSwitcher için öğrendiğimiz Key kavramının başka bir kullanım alanı.
pumpAndSettle() — pump()'ın "tek kare ilerlet" davranışından farklı olarak, tüm animasyonlar ve bekleyen Future'lar tamamlanana kadar otomatik olarak kareleri ilerletir — bir giriş isteğinin API'den cevap beklemesi gibi asenkron işlemler için idealdir.
Golden Test — Piksel Piksel Görsel Doğrulama
testWidgets('profil kartı görsel olarak doğru', (WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(home: ProfilKarti(isim: 'Ahmet', yas: 30)),
);
await expectLater(
find.byType(ProfilKarti),
matchesGoldenFile('goldens/profil_karti.png'),
);
});Golden test, widget'ın render edilmiş görüntüsünü, önceden kaydedilmiş bir referans resimle (golden file) piksel piksel karşılaştırır. İlk çalıştırmada --update-goldens bayrağıyla referans resmi oluşturursun:
flutter test --update-goldensSonraki her testte, mevcut render ile bu referans karşılaştırılır — biri piksel bazında farklıysa, test başarısız olur. Bu, shouldRepaint (Bölüm 8 Konu 38) gibi "görsel çıktının doğruluğunu" doğrulayan, diğer üç test türünün yakalayamadığı bir hata sınıfını (yanlışlıkla bozulan bir layout, kaymış bir ikon) yakalar.
Pratik uyarı: Golden testler, farklı işletim sistemleri/font render motorları arasında hafif farklılıklar gösterebilir — bu yüzden genelde CI ortamında tek bir standart platformda çalıştırılır (Bölüm 10 Konu 46'da CI/CD'yi göreceğiz).
Hangi Test Türünü Ne Zaman Yazarsın?
| Test Türü | Test Ettiği Şey | Hız | Örnek |
|---|---|---|---|
| Unit | Saf mantık (domain katmanı, yardımcı fonksiyonlar) |
Çok hızlı | toplamHesapla(), bir UseCase |
| Widget | Tek bir widget'ın davranışı (izole) | Hızlı | Bir formun validasyon mesajı gösterip göstermediği |
| Integration | Uçtan uca kullanıcı akışı (gerçek cihaz) | Yavaş | "Giriş yap → ürün ekle → ödeme yap" akışının tamamı |
| Golden | Görsel doğruluk (piksel eşleşmesi) | Orta | Bir tasarım sisteminin bileşenlerinin tam istenen görünümde olduğu |
Genel tavsiye: domain/iş mantığı için bol bol unit test yaz (ucuzlar); kritik kullanıcı akışları için birkaç integration test yaz (pahalı ama değerli); UI davranışı için orta miktarda widget test; görsel tutarlılığın kritik olduğu (bir tasarım sistemi, marka bileşenleri) yerlerde golden test.
🎯 Bu Dersten Çıkarılması Gerekenler
- Unit test, saf Dart mantığını test eder — Bölüm 7'deki Clean Architecture'ın
domainkatmanı, bu testleri kolay ve hızlı yapar. - Widget test (
testWidgets+WidgetTester), tek bir widget'ı izole test eder;pump()ile bir sonraki kareyi elle tetiklemen gerekir. - Integration test, gerçek uygulamayı uçtan uca (gerçek Navigator, HTTP çağrıları dahil) test eder;
pumpAndSettle()asenkron işlemlerin bitmesini bekler. - Golden test, widget'ın render edilmiş görüntüsünü referans bir resimle piksel piksel karşılaştırır.
- Test piramidi: çok sayıda unit test, orta sayıda widget test, az sayıda integration test.
find.byKey(), metne göre bulmaktan (find.text()) daha güvenilir bir widget bulma yöntemidir.
📝 Ödevler
- [ ] Bölüm 7'de yazdığın (ya da benzer) bir
UseCasesınıfı için en az 3 senaryoyu kapsayan unit testler yaz. - [ ] Bir form widget'ı için, boş alan gönderildiğinde hata mesajının göründüğünü doğrulayan bir widget test yaz.
- [ ]
integration_testpaketini kur, uygulamanın 2 ekranlı basit bir akışını (örn. liste → detay) uçtan uca test et. - [ ] Bir widget için golden test yaz,
--update-goldensile referans resmi oluştur, sonra widget'ı bilerek bozup testin başarısız olduğunu gözlemle. - [ ] Kendi cümlelerinle, "test piramidinin neden çok integration test yerine çok unit test önerdiğini" açıkla.
Sıradaki konu: Bölüm 10 — Konu 46: CI/CD