↑↓ seç · Enter aç · Esc kapat

Mutlu Tekin
Mutlu Tekin
← Yazılar

Bölüm 12 — Konu 55: Dart'ta Test Yazımı (`test` Paketi, Unit Test Mantığı)

3 dk okuma #dart
Dizi · 54/64 Dart Türkçe Tutorial
  1. Bölüm 1 — Konu 1: Dart Nedir, Nerede Kullanılır, Neden Flutter Bu Dili Seçti
  2. Bölüm 1 — Konu 2: Ortam Kurulumu (Dart SDK, DartPad, Terminal ile Çalıştırma)
  3. Bölüm 1 — Konu 3: İlk Program (`main()`, `print()`, Dosya Yapısı)
  4. Bölüm 1 — Konu 4: Yorum Satırları, Temel Sözdizimi Kuralları
  5. Bölüm 2 — Konu 5: Değişken Tanımlama — `var`, `final`, `const` Farkı
  6. Bölüm 2 — Konu 6: Temel Tipler — `int`, `double`, `String`, `bool`
  7. Bölüm 2 — Konu 7: Tip Çıkarımı (Type Inference) ve Açık Tip Belirtme
  8. Bölüm 2 — Konu 8: `dynamic` ve `Object` — Ne Zaman Kullanılır, Ne Zaman Kaçınılır
  9. Bölüm 2 — Konu 9: Null Safety Temelleri (`?`, `!`, `late`'e Giriş)
  10. Bölüm 3 — Konu 10: Aritmetik, Atama, Karşılaştırma, Mantıksal Operatörler
  11. Bölüm 3 — Konu 11: `if / else if / else`
  12. Bölüm 3 — Konu 12: `switch` / `switch expression` (Modern Dart)
  13. Bölüm 3 — Konu 13: Ternary Operatör, `??`, `??=`, `?.`
  14. Bölüm 3 — Konu 14: `for`, `while`, `do-while` Döngüleri
  15. Bölüm 4 — Konu 16: `List` — Oluşturma, Erişim, Temel Metodlar
  16. Bölüm 4 — Konu 17: `Set` — Benzersiz Eleman Mantığı
  17. Bölüm 4 — Konu 18: `Map` — Key-Value Yapılar
  18. Bölüm 4 — Konu 19: Koleksiyon Üzerinde `for-in`, `forEach`
  19. Bölüm 4 — Konu 20: Spread Operatörü (`...`, `...?`) ve Collection If/For
  20. Bölüm 5 — Konu 21: Fonksiyon Tanımlama, Parametreler (Positional, Named, Optional)
  21. Bölüm 5 — Konu 22: Varsayılan Parametre Değerleri (Derinlemesine)
  22. Bölüm 5 — Konu 23: Arrow Function (`=>`) Sözdizimi
  23. Bölüm 5 — Konu 24: Fonksiyonlar Birinci Sınıf Vatandaş — Değişkene Atama, Parametre Olarak Geçme
  24. Bölüm 5 — Konu 25: Anonim Fonksiyonlar ve Closure Kavramı (Derinlemesine)
  25. Bölüm 5 — Konu 26: Recursion (Özyineleme)
  26. Bölüm 6 — Konu 27: Class Tanımlama, Constructor (Varsayılan, Named, Factory)
  27. Bölüm 6 — Konu 28: Alanlar (Fields), Metodlar, `this` Kullanımı (Derinlemesine)
  28. Bölüm 6 — Konu 29: Initializer List, Constructor Kısayolları (Derinlemesine)
  29. Bölüm 6 — Konu 30: Getter / Setter
  30. Bölüm 6 — Konu 31: Statik Üyeler (`static`)
  31. Bölüm 7 — Konu 32: Kalıtım (`extends`), `super` Kullanımı
  32. Bölüm 7 — Konu 33: Metod Override Etme, `@override`
  33. Bölüm 7 — Konu 34: Soyut Sınıflar (`abstract class`)
  34. Bölüm 7 — Konu 35: Interface Mantığı (`implements`)
  35. Bölüm 7 — Konu 36: Mixin (`with`)
  36. Bölüm 7 — Konu 37: `enum` — Basit ve Gelişmiş (Metotlu Enum'lar)
  37. Bölüm 8 — Konu 38: `try / catch / finally`, `throw`
  38. Bölüm 8 — Konu 39: Özel Exception Sınıfları Yazma
  39. Bölüm 8 — Konu 40: Sound Null Safety Derinlemesine — `late`, `required`, Non-Nullable Varsayılan Davranış
  40. Bölüm 8 — Konu 41: `assert` ile Geliştirme Zamanı Kontrolleri
  41. Bölüm 9 — Konu 42: Generic Sınıflar ve Fonksiyonlar
  42. Bölüm 9 — Konu 43: Generic Sınırlamalar (`<T extends ...>`)
  43. Bölüm 9 — Konu 44: Dart'ın Built-in Generic Koleksiyonları Nasıl Çalışır
  44. Bölüm 10 — Konu 45: `Future` Kavramı, `async`/`await`
  45. Bölüm 10 — Konu 46: `Future.then`, Hata Yönetimi (`catchError`)
  46. Bölüm 10 — Konu 47: `Stream` Temelleri, `async*`, `yield`
  47. Bölüm 10 — Konu 48: `StreamController`, Broadcast Stream
  48. Bölüm 11 — Konu 49: `map`, `where`, `reduce`, `fold`
  49. Bölüm 11 — Konu 50: `sort`, Custom Comparator ile Sıralama
  50. Bölüm 11 — Konu 51: Iterable Kavramı Derinlemesine (Lazy Evaluation)
  51. Bölüm 11 — Konu 52: Cascade Notasyonu (`..`)
  52. Bölüm 12 — Konu 53: `pubspec.yaml`, pub.dev'den Paket Ekleme
  53. Bölüm 12 — Konu 54: Kütüphane Oluşturma, `import`/`export`, `part`/`part of`
  54. Bölüm 12 — Konu 55: Dart'ta Test Yazımı (`test` Paketi, Unit Test Mantığı)
  55. Bölüm 12 — Konu 56: Extension Methods
  56. Bölüm 12 — Konu 57: Dart'ın Derleme Modelleri (JIT vs AOT) ve Bunun Flutter'a Etkisi
  57. Bölüm 13 — Konu 59: Dart 3 Pattern Matching — Records, Destructuring, Sealed Classes, Exhaustive Switch
  58. Bölüm 13 — Konu 60: `Never` Tipi, `covariant` ve Generic Variance Detayları
  59. Bölüm 13 — Konu 61: Event Loop Derinlemesine — Microtask Queue vs Event Queue
  60. Bölüm 13 — Konu 62: Memory Model & Performans — Garbage Collection, `const` Nesnelerin Bellek Avantajı
  61. Bölüm 13 — Konu 63: Isolate'ler Arası Mesajlaşmanın Maliyeti ve `compute()`'un İç İşleyişi
  62. Bölüm 13 — Konu 64: FFI (Foreign Function Interface) — C Koduna Erişim
  63. Bölüm 13 — Konu 65: Sunucu Tarafında Dart — `dart:io`, `shelf` Paketiyle Basit Bir Backend/CLI Aracı Yazma
  64. Bölüm 13 — Konu 66: Derleyici & Analiz Araçları — `dart analyze`, Custom Lint Kuralları, `build_runner` Mimarisi
Dizinin sayfası →
İçindekiler 11 başlık
  1. Neden Test Yazmalısın?
  2. test Paketini Ekleme
  3. İlk Testini Yazma
  4. Testleri Çalıştırma
  5. expect() ve Matcher'lar
  6. group() — Testleri Gruplama
  7. setUp() ve tearDown() — Her Testten Önce/Sonra Çalışan Kod
  8. Neden Test Yazmak Önemli? — Somut Faydalar
  9. Hata Durumlarını Test Etme
  10. 🎯 Bu Dersten Çıkarılması Gerekenler
  11. 📝 Ödevler

Neden Test Yazmalısın?

Şimdiye kadar kodunu test etmek için genelde print() kullanıp çıktıyı elle kontrol ettik. Bu, küçük örnekler için işe yarar ama gerçek bir projede sürdürülebilir değildir — her değişiklikten sonra onlarca print() çıktısını elle kontrol etmek hem yavaş hem hataya açıktır. Unit test (birim test), bu kontrolü otomatikleştirir.

test Paketini Ekleme

Bölüm 12 Konu 53'te öğrendiğimiz gibi, bu bir dev dependency'dir (sadece geliştirme sırasında gerekir, üretim kodunda değil):

bash
dart pub add --dev test

İlk Testini Yazma

Dart projelerinde, test dosyaları geleneksel olarak test/ klasöründe, _test.dart uzantısıyla biter:

metin
lib/
  matematik.dart
test/
  matematik_test.dart

lib/matematik.dart:

dart
int topla(int a, int b) => a + b;
int kare(int x) => x * x;

test/matematik_test.dart:

dart
import 'package:test/test.dart';
import 'package:benim_projem/matematik.dart';

void main() {
  test('topla fonksiyonu iki sayıyı doğru toplamalı', () {
    expect(topla(2, 3), equals(5));
  });

  test('kare fonksiyonu doğru hesaplamalı', () {
    expect(kare(4), equals(16));
  });
}

Yapıyı parçalayalım:

  • test('açıklama', () { ... }) — bir test tanımlar. İlk parametre, testin ne test ettiğini açıklayan bir string; ikinci parametre, testin gerçek kodunu içeren bir fonksiyon.
  • expect(gercekDeger, matcher) — testin kalbi. Elde ettiğin gerçek değeri (topla(2, 3)), beklediğin değerle (equals(5)) karşılaştırır.

Testleri Çalıştırma

bash
dart test

Bu komut, test/ klasöründeki tüm test dosyalarını bulur ve çalıştırır. Çıktı, her testin geçip geçmediğini gösterir:

metin
00:01 +2: All tests passed!

Eğer bir test başarısız olursa:

dart
test('kare fonksiyonu doğru hesaplamalı', () {
  expect(kare(4), equals(17)); // bilerek YANLIŞ beklenti
});
metin
00:01 +1 -1: kare fonksiyonu doğru hesaplamalı [E]
  Expected: <17>
    Actual: <16>

Test çıktısı, tam olarak neyin beklendiğini ve gerçekte neyin döndüğünü gösterir — bu, hatayı hızlıca anlamanı sağlar.

expect() ve Matcher'lar

dart
import 'package:test/test.dart';

void main() {
  test('çeşitli matcher örnekleri', () {
    expect(5, equals(5));           // eşitlik kontrolü
    expect(5, isNot(equals(6)));    // eşit OLMAMA kontrolü
    expect(5 > 3, isTrue);          // bool kontrolü
    expect(null, isNull);           // null kontrolü
    expect([1, 2, 3], hasLength(3)); // liste uzunluğu
    expect([1, 2, 3], contains(2)); // eleman içeriyor mu
    expect(() => throw Exception(), throwsException); // hata fırlatıyor mu
  });
}

Dart'ın test paketi, birçok hazır matcher (eşleştirici) sunar — bunlar, expect()'in ikinci parametresinde kullanılan, farklı türde kontroller yapmanı sağlayan yardımcı araçlardır. equals() en yaygın kullanılanı olsa da, duruma göre isTrue, contains(), throwsException gibi daha spesifik matcher'lar kullanmak, testin niyetini daha net ifade eder.

group() — Testleri Gruplama

dart
import 'package:test/test.dart';

void main() {
  group('Matematik fonksiyonları', () {
    test('topla doğru çalışmalı', () {
      expect(topla(2, 3), equals(5));
    });

    test('kare doğru çalışmalı', () {
      expect(kare(4), equals(16));
    });
  });

  group('String fonksiyonları', () {
    test('büyük harfe çevirme çalışmalı', () {
      expect("merhaba".toUpperCase(), equals("MERHABA"));
    });
  });
}

group(), ilgili testleri mantıksal olarak bir araya toplar — bu hem test çıktısını daha okunabilir yapar hem de büyük projelerde testleri organize etmeni sağlar (Bölüm 12 Konu 54'te öğrendiğimiz dosya organizasyonuna benzer bir mantık).

setUp() ve tearDown() — Her Testten Önce/Sonra Çalışan Kod

dart
import 'package:test/test.dart';

class SepetHesaplayici {
  List<double> urunler = [];
  void ekle(double fiyat) => urunler.add(fiyat);
  double toplam() => urunler.fold(0, (t, f) => t + f);
}

void main() {
  late SepetHesaplayici sepet;

  setUp(() {
    sepet = SepetHesaplayici(); // HER testten önce çalışır, temiz bir sepet garantiler
  });

  test('boş sepetin toplamı sıfır olmalı', () {
    expect(sepet.toplam(), equals(0));
  });

  test('ürün eklenince toplam artmalı', () {
    sepet.ekle(10);
    sepet.ekle(20);
    expect(sepet.toplam(), equals(30));
  });
}

setUp(), her testten önce çalışan bir fonksiyondur — burada her test için temiz, sıfırlanmış bir SepetHesaplayici oluşturuyoruz. Bu önemli çünkü testler birbirinden bağımsız olmalıdır — bir testin, bir öncekinin bıraktığı durumdan etkilenmemesi gerekir. late kullanımını Bölüm 8'de hatırlarsan — burada sepet, setUp() içinde atanacağı için late olarak tanımlandı.

tearDown(), benzer şekilde her testten sonra çalışır — genelde temizlik işlemleri için (örneğin bir test veritabanı bağlantısını kapatmak) kullanılır.

Neden Test Yazmak Önemli? — Somut Faydalar

1. Regresyon (geri kaçma) önleme: Kodunu değiştirdiğinde, testler otomatik olarak eskiden çalışan bir şeyi bozup bozmadığını kontrol eder.

2. Dokümantasyon işlevi: İyi yazılmış testler, bir fonksiyonun nasıl kullanılması gerektiğini ve hangi durumları ele aldığını gösterir — bazen kod yorumlarından bile daha güvenilir bir "dokümantasyon" olabilir.

3. Güvenle refactor etme: Bölüm 7'de öğrendiğimiz gibi, kodun iç yapısını değiştirmek (refactoring) isteyebilirsin — testler varsa, değişiklikten sonra hiçbir şeyi bozmadığından emin olabilirsin.

Hata Durumlarını Test Etme

Bölüm 8'de öğrendiğimiz exception'ları da test edebilirsin:

dart
void yasKontrolEt(int yas) {
  if (yas < 0) throw Exception("Yaş negatif olamaz");
}

void main() {
  test('negatif yaş exception fırlatmalı', () {
    expect(() => yasKontrolEt(-5), throwsException);
  });

  test('geçerli yaş hata fırlatmamalı', () {
    expect(() => yasKontrolEt(25), returnsNormally);
  });
}

() => yasKontrolEt(-5) — dikkat et, expect()'e fonksiyonu çağırarak değil, bir closure olarak (Bölüm 5'te öğrendiğimiz kavram) veriyoruz. Bunun nedeni, test paketinin fonksiyonu kendisinin çağırıp, hatanın gerçekten fırlatılıp fırlatılmadığını kontrol edebilmesi gerekmesidir — eğer yasKontrolEt(-5) yazsaydık, hata hemen, expect() çağrılmadan önce fırlatılır ve testin kendisi çökerdi.


🎯 Bu Dersten Çıkarılması Gerekenler

  • Unit test, kodunun doğru çalıştığını otomatik olarak doğrulamanı sağlar; print() ile elle kontrol etmenin yerini alır.
  • test('açıklama', () { ... }), bir testi tanımlar; expect(gercekDeger, matcher), testin kalbi olan karşılaştırmayı yapar.
  • group(), ilgili testleri mantıksal olarak bir araya toplar.
  • setUp()/tearDown(), her testten önce/sonra çalışan, testlerin birbirinden bağımsız kalmasını sağlayan kod bloklarıdır.
  • Hata fırlatan kodu test ederken, fonksiyonu çağırmadan, bir closure olarak expect()'e vermelisin (throwsException matcher'ı ile).
  • Testler, regresyon önleme, dokümantasyon ve güvenli refactoring için değerlidir.

📝 Ödevler

  • [ ] Basit bir fonksiyon için (örn. topla, kare) test paketiyle en az 3 unit test yaz, dart test ile çalıştır.
  • [ ] Bilerek başarısız olacak bir test yaz (yanlış bir expect beklentisiyle), hata çıktısını oku ve yorumla.
  • [ ] group() kullanarak, ilgili testleri (örn. tüm matematik fonksiyonları) bir arada grupla.
  • [ ] setUp() kullanarak, her testten önce sıfırlanan bir nesne (örn. bir sayaç veya sepet sınıfı) ile en az 2 bağımsız test yaz.
  • [ ] throwsException matcher'ını kullanarak, hata fırlatması beklenen bir fonksiyonu test et; returnsNormally ile de normal çalışan bir durumu test et.

Sıradaki konu: Bölüm 12 — Konu 56: Extension Methods