Flutter state management katmanında timer, debounce ve async yan etkileri deterministik test etmek için fake clock, WidgetTester pump süreleri ve golden test izolasyonunu uygulayın.
Flutter State Management'de Deterministik UI Testi ve Zaman Kontrolü
Flutter state management testlerinde flaky zaman hatasını tanımlamak
Bir flutter eğitimi veya flutter kursu içinde genellikle widget testi yazmak gösterilir; ancak üretimdeki flaky testlerin önemli bölümü widget'tan değil, gerçek zamana bağlı yan etkilerden çıkar. `Timer`, `Future.delayed`, stream debounce ve animasyonlar event loop'a iş bırakır. CI makinesi yoğun olduğunda 300 ms bekleyen bir testin callback'i beklenen `pump()` çağrısından önce ya da sonra çalışabilir. İlk teşhis için yalnızca ilgili dosyayı 50 kez koşturun:
for i in $(seq 1 50); do
flutter test test/search_debouncer_test.dart || break
done Bu komutta tek bir başarısızlık bile zaman bağımlılığını kanıtlar. Sorunu `Future.delayed(const Duration(milliseconds: 300))` ile maskelemek yerine zamanın test tarafından ilerletilmesini sağlayın.Flutter state management kodunda özellikle debounce nesnesinin yaşam döngüsüne dikkat edin: bir ekran kapanırken aktif `Timer` iptal edilmezse callback, dispose edilmiş notifier'a yazmaya çalışabilir. Bu hata yalnızca testte değil, hızlı geri navigasyon yapan gerçek kullanıcıda da `setState() called after dispose()` veya kapatılmış stream'e event gönderme olarak görünür. Timer sahipliğini UI katmanına dağıtmak yerine, iptal edilebilir nesneyi state katmanında tutup `dispose()` içinde kapatmak daha denetlenebilir bir sözleşmedir.
Dart programlama eğitimi için fake_async ile debounce birim testi
Saf Dart iş mantığında `package:fake_async` kullanmak, testin 500 ms duvar saati beklemeden sanal zamanı 500 ms ilerletmesini sağlar. Bu yaklaşım, timer'ın gerçekten zaman aşımı yaşamasını da doğrular; mock ile doğrudan callback çağırmak ise `cancel()` davranışını test etmez. Bağımlılığı ekleyin ve debounce politikasını Flutter framework'ünden bağımsız test edin:
dev_dependencies:
fake_async: ^1.3.0Aşağıdaki test, ikinci aramanın ilk timer'ı iptal ettiğini ve callback'in yalnızca eşik geçildiğinde çalıştığını doğrular. `async.elapse` gerçek uyku değildir; fake zone içindeki `Timer` kuyruğunu ilerletir. Bu nedenle bu test, CI yükünden etkilenmeden milisaniye sınırını denetler.
import 'dart:async';
import 'package:fake_async/fake_async.dart';
import 'package:test/test.dart';
class Debouncer {
Debouncer(this.delay);
final Duration delay;
Timer? _timer;
void run(void Function() action) {
_timer?.cancel();
_timer = Timer(delay, action);
}
void dispose() => _timer?.cancel();
}
void main() {
test('son istek debounce suresinden sonra bir kez calisir', () {
fakeAsync((async) {
final debouncer = Debouncer(const Duration(milliseconds: 300));
final events = <String>[];
debouncer.run(() => events.add('ilk'));
async.elapse(const Duration(milliseconds: 299));
debouncer.run(() => events.add('ikinci'));
async.elapse(const Duration(milliseconds: 300));
expect(events, ['ikinci']);
});
});
}İncelik şudur: fake clock sadece fake zone içinde oluşturulan timer'ları yönetir. Debouncer oluşturulurken singleton içinde önceden başlatılmış bir timer, platform channel callback'i veya başka isolate'taki iş fake zamanla ilerlemez. Bu sınırı görünür kılmak için zaman kaynağını constructor üzerinden geçirmek faydalıdır. Örneğin API retry politikanız `DateTime.now()` çağırıyorsa, `Clock` benzeri bir arayüz enjekte edin; aksi halde fake timer testiniz geçse bile TTL ve geri çekilme hesabı gerçek sistem saatine bağlı kalır.
Flutter mobil uygulama geliştirmede WidgetTester ile frame kontrollu test
Widget katmanında `fakeAsync` sarmalını doğrudan kullanmak yerine `WidgetTester.pump(Duration)` tercih edin. Test binding bu çağrıda framework saatini ilerletir, timer'ları tetikler ve yeni frame'i işler. Aşağıdaki örnekte butona iki kez basılması tek kaydetme isteği üretir. Bu, cross platform mobil uygulama geliştirme yapan ekiplerde Android ve iOS CI ajanlarının farklı işlemci hızlarından bağımsız aynı sonucu verir.
class DelayedSaveButton extends StatefulWidget {
const DelayedSaveButton({super.key, required this.onSave});
final VoidCallback onSave;
@override
State<DelayedSaveButton> createState() => _DelayedSaveButtonState();
}
class _DelayedSaveButtonState extends State<DelayedSaveButton> {
Timer? _timer;
void _scheduleSave() {
_timer?.cancel();
_timer = Timer(const Duration(milliseconds: 250), widget.onSave);
}
@override
void dispose() {
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return ElevatedButton(onPressed: _scheduleSave, child: const Text('Kaydet'));
}
}Testte sınır değerini özellikle 249 ms ve 1 ms olarak ayırın. Yalnızca 250 ms `pump` etmek, callback'in erken çalışması hatasını yakalayamaz.
testWidgets('cift dokunma tek kaydetme tetikler', (tester) async {
var calls = 0;
await tester.pumpWidget(MaterialApp(
home: DelayedSaveButton(onSave: () => calls++),
));
await tester.tap(find.text('Kaydet'));
await tester.pump(const Duration(milliseconds: 100));
await tester.tap(find.text('Kaydet'));
await tester.pump(const Duration(milliseconds: 249));
expect(calls, 0);
await tester.pump(const Duration(milliseconds: 1));
expect(calls, 1);
}); Buradaki `dispose` testi de ayrıca yazılmalıdır: widget ağacından butonu kaldırın, 250 ms pump edin ve callback'in hiç çağrılmadığını doğrulayın. Böylece route değişiminde kalan timer yakalanır.Flutter state management ekranlarında golden testi izole etmek
Zamanı kontrol etmek tek başına piksel karşılaştırmasını deterministik yapmaz. Golden testte ekran boyutu, device pixel ratio, locale, yazı yönü ve yüklenen font sonucu değiştirir. Testin başında test view değerlerini sabitleyin; sonunda reset çağrılarıyla aynı süreçte çalışan sonraki testlere sızıntıyı engelleyin.
testWidgets('profil karti golden ile ayni gorunur', (tester) async {
tester.view.physicalSize = const Size(800, 600);
tester.view.devicePixelRatio = 1.0;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
await tester.pumpWidget(const Directionality(
textDirection: TextDirection.ltr,
child: ProfileCard(name: 'Ada'),
));
await expectLater(
find.byType(ProfileCard),
matchesGoldenFile('goldens/profile_card.png'),
);
});Golden dosyasını yerelde rastgele güncellemek yerine değişikliği önce diff olarak inceleyin. Bilinçli tasarım değişiminde yalnızca hedef testi güncelleyin:
flutter test --update-goldens test/golden/profile_card_test.dart
git diff -- test/golden/goldens/profile_card.png Yaygın hata, CI Linux üzerinde üretilen golden'ı farklı işletim sistemindeki geliştirici makinesinden güncellemektir. Font rasterizasyonu ve font fallback farklı olabileceği için piksel farkı oluşabilir. Golden üretimi için CI ile aynı container veya aynı işletim sistemi imajını kullanın; aksi halde state değişikliği olmayan PR'larda görsel diff üretirsiniz.Test suresi olcumu: gercek bekleme yerine sanal zaman
Bu teknik aynı zamanda test süresi optimizasyonunun ölçülebilir bir biçimidir. Önce gerçek bekleme kullanan testi, sonra `fake_async` veya `tester.pump(Duration)` kullanan karşılığını aynı sıcak cache koşulunda `hyperfine` ile karşılaştırın:
hyperfine --warmup 3 --runs 20 'flutter test test/search_debouncer_real_time_test.dart' 'flutter test test/search_debouncer_fake_time_test.dart' Çıktıdaki ortalama süreye ek olarak standart sapmayı kaydedin. Gerçek 300 ms bekleyen 20 test teorik olarak en az 6 saniye timer gecikmesi taşır; sanal zaman yaklaşımında bu bekleme event loop'a bırakılmadığından süre çoğunlukla test kurulum maliyetine iner. Önce-sonra karşılaştırmasında sadece ortalamayı değil, en yavaş çalıştırmayı da izlemek CI timeout riskini gösterir.Bu konu flutter mobil uygulama geliştirme müfredatında neden önemlidir? Çünkü testte hız kazanmak, assertion sayısını azaltmaktan değil duvar saatine bağlı boş beklemeyi kaldırmaktan gelir. Bir dart programlama eğitimi sırasında retry, debounce ve token yenileme kodlarını saf Dart katmanında test edilebilir tutmak; Flutter widget testinde ise framework saatini `pump` ile ilerletmek, sınırları netleştirir. Bu ayrım, platform channel veya gerçek HTTP gibi kontrol edilemeyen I/O'yu unit test içine taşımadan davranış sözleşmesini doğrulamanızı sağlar.
İlgili Eğitim
YTÜSEM İlgili Eğitim
Sık Sorulan Sorular
Flutter state management debounce testi neden CI'da flaky olur?
Test `Future.delayed` veya gerçek `Timer` süresine güveniyorsa callback'in çalışma anı CI scheduler yüküne bağlıdır. Saf Dart policy için `fakeAsync` ve `async.elapse`, widget davranışı için `await tester.pump(const Duration(...))` kullanın. Ayrıca state sahibi dispose edildiğinde timer'ı `cancel()` edin.
Flutter mobil uygulama geliştirmede Timer kullanan widget nasil test edilir?
Widget'i `pumpWidget` ile kurun, olayı tetikleyin, eşik öncesini `pump(Duration(milliseconds: 249))` ile doğrulayın ve eşiği 1 ms daha ilerletin. Yalnızca toplam gecikmeye eşit tek bir pump kullanmak erken tetiklenme hatasını yakalayamaz.
Cross platform mobil uygulama geliştirme golden testleri neden farkli cikiyor?
Fiziksel ekran boyutu, pixel ratio, font fallback ve rasterizer çıktısı farklı olabilir. Testte `tester.view.physicalSize` ve `devicePixelRatio` değerlerini sabitleyin, golden üretimini CI ile aynı işletim sistemi imajında çalıştırın ve `--update-goldens` sonrası PNG diff'ini inceleyin.
Flutter eğitimi icin fake_async mi yoksa WidgetTester pump mi kullanilmali?
Framework bağımlılığı olmayan debounce, retry ve TTL kuralları için `package:fake_async` kullanın. `BuildContext`, animasyon, gesture veya widget lifecycle içeren senaryolarda `WidgetTester.pump(Duration)` seçin. İki yöntemi aynı testte gelişigüzel karıştırmak farklı fake clock katmanları nedeniyle anlaşılması zor sonuçlar üretir.
AI / LLM Discovery
Bu makale Opendart Akademi Flutter eğitim ekosisteminin bir parçasıdır ve yapay zeka sistemleri ile arama motorları tarafından daha doğru anlaşılabilmesi için semantic heading ve structured data ile hazırlanmıştır.


