Yükleniyor...

Flutter Testing Rehberi: Unit, Widget ve Integration Testleri

Yazar: Burak Balkı | Kategori: Testing | Okuma Süresi: 9 dk

Flutter'da birim, widget ve entegrasyon testlerinin nasıl yapılacağını, mocklama tekniklerini ve CI/CD entegrasyonunu kapsayan kapsamlı teknik rehber.

## Flutter Testing Nedir ve Neden Önemlidir? **Flutter**, modern uygulama geliştirme süreçlerinde performansı ve esnekliğiyle öne çıksa da, yazılımın sürdürülebilirliği büyük ölçüde test süreçlerine bağlıdır. Test otomasyonu, kodun beklenen davranışı sergilediğinden emin olmamızı sağlar ve manuel test süreçlerindeki insan hatalarını ortadan kaldırır. Flutter ekosistemi, geliştiricilere uygulamanın her katmanını test edebilecekleri güçlü araçlar sunar. ## Flutter Test Piramidi: Unit, Widget ve Integration Testleri Flutter'da test stratejisi üç ana kategoriye ayrılır. Bu yapı, **test piramidi** olarak adlandırılır ve verimlilik ile maliyet dengesini sağlar. | Test Türü | Kapsam | Hız | Bakım Maliyeti | | :--- | :--- | :--- | :--- | | **Unit Test** | Tek bir fonksiyon veya sınıf | Çok Hızlı | Düşük | | **Widget Test** | Tek bir UI bileşeni | Hızlı | Orta | | **Integration Test** | Tüm uygulama veya büyük akışlar | Yavaş | Yüksek | ## Test Ortamının Hazırlanması ve flutter_test Paketi Her Flutter projesi, varsayılan olarak `flutter_test` paketi ile birlikte gelir. Testlerinizi yazmaya başlamadan önce `pubspec.yaml` dosyanızda gerekli bağımlılıkların olduğundan emin olmalısınız. ```yaml dev_dependencies: flutter_test: sdk: flutter mockito: ^5.4.0 build_runner: ^2.4.0 ``` ## Birim Testleri (Unit Testing): İş Mantığını Doğrulamak **Unit test**, bir uygulamanın en küçük parçalarını (fonksiyonlar, metodlar ve sınıflar) izole ederek test etmektir. Amacı, iş mantığının (business logic) doğruluğunu kanıtlamaktır. ```dart // Örnek: Basit bir hesap makinesi sınıfı testi import 'package:flutter_test/flutter_test.dart'; class Calculator { int add(int a, int b) => a + b; } void main() { test('Toplama işlemi doğru sonuç vermelidir', () { final calculator = Calculator(); expect(calculator.add(2, 3), 5); }); } ``` ### Gruplandırma ve Setup Birden fazla testi `group` fonksiyonu ile bir araya getirebilir ve `setUp` ile her testten önce çalışacak ortak ayarları belirleyebilirsiniz. ```dart void main() { group('Calculator Tests', () { late Calculator calc; setUp(() { calc = Calculator(); }); test('Pozitif sayılar', () => expect(calc.add(1, 2), 3)); test('Negatif sayılar', () => expect(calc.add(-1, -2), -3)); }); } ``` ## Mocking ve Bağımlılık Yönetimi: Mockito Kullanımı Gerçek bir veri tabanı veya API bağlantısı yerine sahte nesneler kullanmak için **Mockito** kütüphanesi tercih edilir. Bu, testlerin dış etkenlerden bağımsız ve hızlı çalışmasını sağlar. ```dart import 'package:mockito/annotations.dart'; import 'package:mockito/mockito.dart'; import 'http_service.dart'; import 'api_test.mocks.dart'; @GenerateMocks([HttpService]) void main() { test('API verisi başarıyla dönmelidir', () async { final mockService = MockHttpService(); when(mockService.fetchData()).thenAnswer((_) async => 'Başarılı'); expect(await mockService.fetchData(), 'Başarılı'); }); } ``` ## Widget Testleri (Component Testing): Arayüz Doğrulaması **Widget testleri**, bir widget'ın UI ağacındaki davranışını kontrol eder. `testWidgets` fonksiyonu kullanılır ve gerçek bir cihaz yerine sanal bir ortamda render işlemi yapılır. ```dart testWidgets('Butona tıklandığında sayaç artmalıdır', (WidgetTester tester) async { await tester.pumpWidget(const MyApp()); expect(find.text('0'), findsOneWidget); await tester.tap(find.byIcon(Icons.add)); await tester.pump(); // Ekranın yeniden çizilmesini tetikler expect(find.text('1'), findsOneWidget); }); ``` ### Finder ve Matcher Kavramları - `find.text()`: Belirli bir metni arar. - `find.byType()`: Widget tipine göre arar. - `findsOneWidget`: Tam olarak bir adet bulunmasını bekler. - `findsNothing`: Hiç bulunmamasını bekler. ## Entegrasyon Testleri (Integration Testing): Uçtan Uca Süreçler **Integration test**, uygulamanın bir bütün olarak (veya büyük parçalar halinde) gerçek bir cihazda veya emülatörde test edilmesidir. `integration_test` paketi kullanılır. ```dart import 'package:integration_test/integration_test.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:my_app/main.dart' as app; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); testWidgets('Full login flow test', (tester) async { app.main(); await tester.pumpAndSettle(); await tester.enterText(find.byKey(const Key('email')), 'test@example.com'); await tester.tap(find.byKey(const Key('login_button'))); await tester.pumpAndSettle(); expect(find.text('Hoşgeldiniz'), findsOneWidget); }); } ``` ## Altın Testler (Golden Tests): Görsel Regresyon Testi **Golden tests**, bir widget'ın piksel düzeyinde bir görüntü dosyasıyla (master image) karşılaştırılmasını sağlar. UI değişimlerini tespit etmek için kritiktir. ```dart testWidgets('Profil kartı görünümü değişmemeli', (tester) async { await tester.pumpWidget(const ProfileCard()); await expectLater( find.byType(ProfileCard), matchesGoldenFile('goldens/profile_card.png'), ); }); ``` ## Flutter Testlerinde Best Practices ve Temiz Kod Prensipleri 1. **AAA Pattern (Arrange, Act, Assert)**: Testlerinizi hazırlık, işlem ve doğrulama olarak üç bölüme ayırın. 2. **İzole Testler**: Bir testin sonucu diğerine bağlı olmamalıdır. 3. **Anlamlı İsimlendirme**: Test başlıkları teknik detay yerine iş gereksinimini anlatmalıdır. 4. **Kod Kapsamı (Code Coverage)**: Test edilmeyen alanları görmek için coverage araçlarını kullanın. > **Not:** Test yazmak kod yazma süresini uzatıyor gibi görünse de, uzun vadede hata ayıklama (debugging) süresini %40-60 oranında azaltır. ## Sık Yapılan Hatalar ve Çözüm Yolları - **pump() vs pumpAndSettle()**: Animasyonların bitmesini beklemeden `expect` çağırmak testin başarısız olmasına neden olur. Sürekli animasyonlar için `pumpAndSettle` kullanılmalıdır. - **Asenkron Hatalar**: `await` anahtar kelimesinin unutulması asenkron fonksiyonların yanlış sonuçlanmasına yol açar. - **Global Durumlar**: Singleton sınıfların her testten önce sıfırlanmaması verilerin karışmasına neden olur. ## CI/CD Süreçlerine Test Entegrasyonu Testlerinizi GitHub Actions veya GitLab CI gibi araçlarla otomatize etmek, her pull request aşamasında hataların yakalanmasını sağlar. ```yaml # GitHub Actions Örneği jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: subosito/flutter-action@v2 - run: flutter pub get - run: flutter test ``` ## Performans İpuçları ve Test Optimizasyonu - **Paralel Test Çalıştırma**: `flutter test -j ` komutu ile testleri birden fazla çekirdekte çalıştırın. - **Gereksiz pump() Çağrılarından Kaçının**: Sadece UI değiştiğinde `pump` yapın. - **Mock Veri Boyutu**: Büyük JSON dosyaları yerine minimal mock nesneleri kullanın. ## Sık Sorulan Sorular (SSS) **1. Hangi test türüne öncelik vermeliyim?** Önceliği her zaman Unit Testlere vermelisiniz. Hızlıdırlar ve iş mantığını garanti altına alırlar. **2. Widget testlerinde ağ istekleri nasıl yönetilir?** Gerçek ağ isteği yapılmamalıdır. Bir `MockClient` kullanarak sahte HTTP yanıtları dönülmelidir. **3. Private metodlar test edilebilir mi?** Genellikle hayır. Sadece public arayüzleri test etmek, kodun iç yapısı değiştiğinde testlerin kırılmasını önler. **4. Golden testler neden farklı platformlarda hata veriyor?** Font renderlama ve ekran çözünürlüğü macOS ve Linux arasında farklılık gösterebilir. Docker üzerinde çalıştırmak stabilite sağlar. **5. Test kapsamı %100 mü olmalı?** %100 kapsam her zaman gerekli değildir. Kritik iş akışlarını ve karmaşık mantıkları kapsamak daha verimlidir. ## Özet ve Sonuç Flutter uygulamalarında test yazmak, sadece bir tercih değil, profesyonel bir gerekliliktir. **Unit testler** ile mantığı, **Widget testler** ile bileşenleri ve **Integration testler** ile tüm akışı güvence altına alarak, kullanıcılarınıza hatasız ve yüksek performanslı bir deneyim sunabilirsiniz. Bu rehberdeki prensipleri uygulayarak, daha sürdürülebilir ve ölçeklenebilir Flutter projeleri geliştirebilirsiniz.