← Kursa Dön
📄 Text · 30 min

Test Temelleri

Neden Test Yazarız?

Bir köprü inşa ettiğini düşün. Köprü bitti, güzel görünüyor. Peki üzerinden kamyon geçtiğinde çökmeyeceğinden emin misin? "Bence sağlam" demek yeterli mi? Tabii ki hayır — mühendisler köprüyü test eder. Yük testleri, rüzgar testleri, deprem simülasyonları yapar. Çünkü "bence çalışıyor" ile "test ettim, çalışıyor" arasında hayat kurtaran bir fark var.

Yazılımda da durum aynı. Kodu yazarsın, tarayıcıda açarsın, birkaç butona tıklarsın, "çalışıyor" dersin. Ama edge case'leri test ettin mi? Kullanıcı boş form gönderirse? Sunucu hata dönerse? İnternet kesilirse? 3 ay sonra bir arkadaşın kodu değiştirdiğinde hâlâ çalışıyor mu?

Test yazmak, kodunun doğru çalıştığının kanıtıdır. "Bence çalışıyor" yerine "ispatladım, çalışıyor" dersin.


Test Yazmanın Gerçek Faydaları

Test yazmak ilk bakışta "ekstra iş" gibi görünür. Ama aslında toplam iş miktarını azaltır:

  1. Bug'ları erken yakalar — Production'da bug bulmak, geliştirme sırasında bulmaktan 100x daha pahalı

  2. Refactoring cesareti verir — "Kodu değiştirdim, hiçbir test kırılmadı" = güven

  3. Canlı dokümantasyon — Testler, kodun nasıl kullanılacağını gösterir

  4. Tasarımı iyileştirir — Test yazması zor kod = kötü tasarım sinyali

  5. Regression önler — Eski bug'lar geri dönmez

Test yazmayan geliştirici:
  Kod yaz → Manuel test → "Çalışıyor" → Deploy → 🐛 Bug! → Panic fix → 🐛 Yeni bug

Test yazan geliştirici:
  Kod yaz → Test yaz → Otomatik test → Güvenle deploy → ✅ Çalışıyor → 😌 Huzur

💡 İpucu: "Test yazmaya vaktim yok" aslında "Bug düzeltmeye vaktim var" demektir. Tecrübeli geliştiriciler bilir: test yazmak zamanı tasarruf eder, harcamaz.

Test Yazmanın Maliyeti vs Getirisi

Proje Büyüklüğü   Test Yok         Test Var
──────────────────────────────────────────────
Küçük proje        Hızlı ✅         Biraz yavaş ⚠️
(< 1000 satır)     Manuel test OK    Overkill olabilir

Orta proje         Riskli ⚠️        Güvenli ✅
(1000-10000)       Regression başlar  Refactor cesareti

Büyük proje        Tehlikeli ❌      Zorunlu ✅
(> 10000)          Dokunmaya kork!   Değişim güvenli

Ekip projesi       Kabus ❌          Olmazsa olmaz ✅
(2+ geliştirici)   "Kim bozdu?"      CI/CD güven

Test Türleri: Piramit Modeli

Tüm testler aynı değildir. Farklı seviyelerde, farklı şeyleri test eden üç ana tür var:

           ╱╲
          ╱  ╲         E2E Tests (az, yavaş, pahalı)
         ╱ E2E╲        Gerçek tarayıcı, gerçek API
        ╱──────╲
       ╱        ╲      Integration Tests (orta)
      ╱Integration╲    Bileşenler arası etkileşim
     ╱──────────────╲
    ╱                ╲  Unit Tests (çok, hızlı, ucuz)
   ╱   Unit Tests     ╲ Tek fonksiyon/modül
  ╱────────────────────╲

Bu Test Piramidi — Martin Fowler tarafından popülerleştirildi. Alt kısım geniş (çok test), üst kısım dar (az test).

Unit Test: Tek Parçayı Test Et

Bir fonksiyon, bir class, bir modül — izole bir parçanın doğru çalıştığını kontrol eder. Dış bağımlılıklar (veritabanı, API, dosya sistemi) mock edilir.

Analoji: Bir arabanın motor kontrolü gibi. Motoru araçtan çıkarıp, tek başına test edersin. Benzin veriyor musun? Ateşleme çalışıyor mu? Piston hareket ediyor mu? Direksiyon, tekerlek, fren — hepsi ayrı test edilir.

// Fonksiyon
function calculateDiscount(price, discountPercent) {
  if (price < 0) throw new Error("Fiyat negatif olamaz");
  if (discountPercent < 0 || discountPercent > 100) {
    throw new Error("İndirim yüzdesi 0-100 arasında olmalı");
  }
  return price - (price * discountPercent / 100);
}

// Unit Test
test("calculateDiscount: %20 indirim", () => {
  expect(calculateDiscount(100, 20)).toBe(80);
});

test("calculateDiscount: negatif fiyat hata fırlatır", () => {
  expect(() => calculateDiscount(-10, 20)).toThrow("Fiyat negatif olamaz");
});

test("calculateDiscount: %0 indirim fiyatı değiştirmez", () => {
  expect(calculateDiscount(50, 0)).toBe(50);
});

test("calculateDiscount: %100 indirim bedava yapar", () => {
  expect(calculateDiscount(50, 100)).toBe(0);
});

test("calculateDiscount: geçersiz indirim yüzdesi hata fırlatır", () => {
  expect(() => calculateDiscount(100, 150)).toThrow("İndirim yüzdesi 0-100 arasında olmalı");
  expect(() => calculateDiscount(100, -10)).toThrow("İndirim yüzdesi 0-100 arasında olmalı");
});

Özellikleri:

  • ⚡ Çok hızlı (milisaniye)

  • 🔧 Kolay yazılır ve debug edilir

  • 🎯 Hatanın tam yerini gösterir

  • 📦 İzole — dış bağımlılık yok

Integration Test: Parçaların Birlikte Çalışması

Birden fazla modülün birlikte doğru çalıştığını kontrol eder. "Her parça tek başına çalışıyor ama birlikte de çalışıyor mu?"

Analoji: Motor, vites kutusu ve tekerleği birlikte test et. Motor çalışıyor, vites de çalışıyor — ama birbirine bağladığında güç aktarımı doğru mu? Vites 3'e geçtiğinde teker doğru hızda dönüyor mu?

// UserService + Database + EmailService birlikte
test("kullanıcı oluşturma: veritabanına kaydeder ve email gönderir", async () => {
  const db = new TestDatabase(); // Gerçek veritabanı (test DB)
  const emailService = new EmailService();
  const userService = new UserService(db, emailService);

  const user = await userService.createUser({
    name: "Ali",
    email: "ali@test.com",
  });

  // Veritabanında var mı?
  const saved = await db.findById(user.id);
  expect(saved).not.toBeNull();
  expect(saved.name).toBe("Ali");

  // Email gönderildi mi?
  expect(emailService.sentEmails).toHaveLength(1);
  expect(emailService.sentEmails[0].to).toBe("ali@test.com");
});

Özellikleri:

  • 🔗 Modüller arası etkileşimi test eder

  • 🕐 Unit'e göre yavaş (veritabanı, dosya sistemi)

  • 🧩 Gerçek bağımlılıklar kullanılabilir (test DB, test server)

  • 🐛 "Integration bug'larını" yakalar — "her parça çalışıyor ama birlikte bozuluyor"

E2E (End-to-End) Test: Gerçek Kullanıcı Senaryosu

Uygulamayı gerçek bir kullanıcı gibi test eder. Gerçek tarayıcı açılır, butonlara tıklanır, formlar doldurulur, sonuçlar kontrol edilir.

Analoji: Arabayı yolda sürmek. Motor, vites, direksiyon, fren — her şey birlikte, gerçek koşullarda. Yokuşta kalkış, viraj, acil fren. Gerçek kullanıcı deneyimini simüle edersin.

// Cypress veya Playwright ile
test("kullanıcı giriş yapabilir", async ({ page }) => {
  // Gerçek tarayıcıda gerçek uygulama
  await page.goto("https://myapp.com/login");

  // Form doldur
  await page.fill("#email", "ali@test.com");
  await page.fill("#password", "güçlüşifre123");
  await page.click("#login-button");

  // Dashboard'a yönlendirildi mi?
  await expect(page).toHaveURL("/dashboard");
  await expect(page.locator("h1")).toHaveText("Hoş Geldiniz, Ali");
});

test("yanlış şifre ile giriş yapılamaz", async ({ page }) => {
  await page.goto("https://myapp.com/login");

  await page.fill("#email", "ali@test.com");
  await page.fill("#password", "yanlisşifre");
  await page.click("#login-button");

  // Hata mesajı gösteriliyor mu?
  await expect(page.locator(".error-message")).toHaveText("Geçersiz email veya şifre");
  await expect(page).toHaveURL("/login"); // Hâlâ login sayfasında
});

Özellikleri:

  • 🌐 Gerçek tarayıcı, gerçek sunucu

  • 🐢 En yavaş test türü (saniye-dakika)

  • 💰 En pahalı (kurulum, bakım, CI süresi)

  • ✅ En yüksek güven seviyesi — "Gerçekten çalışıyor"

  • 🔧 Bakımı zor — UI değiştiğinde kırılabilir

Hangi Türden Ne Kadar?

Piramit kuralı:
├── Unit Tests:        ~70% (yüzlerce/binlerce)
├── Integration Tests: ~20% (onlarca)
└── E2E Tests:         ~10% (kritik akışlar)

Neden bu oran?

  • Unit testler hızlı ve ucuz — çok yaz

  • Integration testler orta — önemli etkileşimleri test et

  • E2E testler yavaş ve kırılgan — sadece kritik kullanıcı akışlarını test et

E2E testi yazılması gereken kritik akışlar:
├── Kullanıcı kaydı ve giriş
├── Ödeme akışı (sepet → ödeme → onay)
├── Temel navigasyon (sayfa geçişleri)
└── Veri kaybına yol açabilecek akışlar

⚠️ Dikkat: "Ice cream cone" anti-pattern'ından kaçın — çok E2E, az unit. Bu durumda test suite'in yavaş, kırılgan ve bakımı zor olur. Piramidin tabanını güçlü tut.


TDD vs BDD

TDD: Test-Driven Development

TDD'nin mantığı şu: önce test yaz, sonra kodu yaz. Kulağa garip gelebilir — henüz kod yok, neyi test edeceksin? Ama bu yaklaşım seni düşünmeye zorlar: "Bu fonksiyon ne yapmalı? Hangi girdilere hangi çıktıyı vermeli?"

TDD Döngüsü (Red-Green-Refactor):

1. 🔴 RED    → Test yaz → Test başarısız olur (kod yok çünkü)
2. 🟢 GREEN  → Testi geçecek minimum kodu yaz
3. 🔵 REFACTOR → Kodu temizle, test hâlâ geçiyor mu kontrol et
4. → 1'e dön

Bunu pratikte görelim:

// 1. 🔴 RED — Önce test yaz (fonksiyon henüz YOK)
test("palindrome kontrolü: 'kayak' palindrome olmalı", () => {
  expect(isPalindrome("kayak")).toBe(true);
});

test("palindrome kontrolü: 'merhaba' palindrome olmamalı", () => {
  expect(isPalindrome("merhaba")).toBe(false);
});

test("palindrome kontrolü: boş string palindrome olmalı", () => {
  expect(isPalindrome("")).toBe(true);
});

test("palindrome kontrolü: büyük-küçük harf duyarsız", () => {
  expect(isPalindrome("Kayak")).toBe(true);
});

// Testleri çalıştır → HEPSİ KIRMIZI ❌ (isPalindrome tanımlı değil)

// 2. 🟢 GREEN — Testi geçecek EN BASİT kodu yaz
function isPalindrome(str) {
  const normalized = str.toLowerCase();
  return normalized === normalized.split("").reverse().join("");
}

// Testleri çalıştır → HEPSİ YEŞİL ✅

// 3. 🔵 REFACTOR — Kodu iyileştir (testler hâlâ geçmeli)
function isPalindrome(str) {
  const s = str.toLowerCase();
  let left = 0, right = s.length - 1;
  while (left < right) {
    if (s[left] !== s[right]) return false;
    left++;
    right--;
  }
  return true;
}
// Testleri çalıştır → HEPSİ YEŞİL ✅ (refactor başarılı)

TDD'nin faydaları:

  • Testler her zaman yazılır (sonraya kalmaz)

  • Daha iyi API tasarımı — "kullanıcı perspektifinden" düşünürsün

  • Minimal kod — sadece gerekli olanı yazarsın (YAGNI prensibi)

  • Güvenli refactoring — her değişiklik anında test edilir

TDD ne zaman kullanmalı:

  • Yeni bir fonksiyon/modül yazarken

  • Bir bug fix yaparken (önce bug'ı reproduce eden test yaz)

  • Algoritma/hesaplama kodu yazarken

TDD ne zaman zor/gereksiz:

  • UI prototipleme — hızlı deney yaparken

  • Bir kerelik scriptler

  • API tasarımı henüz net değilken

BDD: Behavior-Driven Development

BDD, TDD'nin bir uzantısı — teknik olmayan kişilerin de anlayabileceği bir dille test yazarsın. "Given-When-Then" formatını kullanır:

// BDD stili — describe/it ile okunabilir yapı
describe("Alışveriş Sepeti", () => {
  describe("ürün eklendiğinde", () => {
    it("sepetteki ürün sayısı artmalı", () => {
      // Given: Boş sepet
      const cart = new ShoppingCart();

      // When: Ürün eklenir
      cart.addItem({ id: 1, name: "Laptop", price: 5000 });

      // Then: Sayı 1 olur
      expect(cart.itemCount).toBe(1);
    });

    it("toplam fiyat güncellenmeli", () => {
      const cart = new ShoppingCart();

      cart.addItem({ id: 1, name: "Laptop", price: 5000 });
      cart.addItem({ id: 2, name: "Mouse", price: 200 });

      expect(cart.totalPrice).toBe(5200);
    });
  });

  describe("ürün silindiğinde", () => {
    it("sepetten kalkmalı", () => {
      const cart = new ShoppingCart();
      cart.addItem({ id: 1, name: "Laptop", price: 5000 });

      cart.removeItem(1);

      expect(cart.itemCount).toBe(0);
    });

    it("boş sepetten silme hata vermemeli", () => {
      const cart = new ShoppingCart();

      expect(() => cart.removeItem(999)).not.toThrow();
    });
  });
});

TDD vs BDD:

ÖzellikTDDBDD
OdakFonksiyon/modül doğruluğuKullanıcı davranışı
DilTeknikİş dili (business language)
Formattest/expectdescribe/it (Given-When-Then)
Kimin içinGeliştiricilerGeliştirici + Ürün ekibi
GranülarityBirim seviyesiÖzellik/senaryo seviyesi

Pratikte çoğu ekip ikisini birlikte kullanır: BDD formatında yazılmış, TDD döngüsüyle geliştirilmiş testler.


Test Araçları Ekosistemi

JavaScript/TypeScript dünyasında birçok test aracı var. Hangisini ne zaman kullanacağını bilmek önemli:

Unit / Integration Test Araçları

AraçAçıklamaKullanım
JestMeta'nın (Facebook) test framework'üReact projeleri, genel amaçlı
VitestVite tabanlı, Jest uyumlu, hızlıVite projeler, modern ✅
Mocha + ChaiEsnek, modüler yapıEski projeler, Node.js
Node Test RunnerNode.js yerleşik (v18+)Harici bağımlılık istemiyorsan
// Jest vs Vitest — neredeyse aynı API
// Jest
const { describe, test, expect } = require("@jest/globals");

// Vitest — Vite projelerinde önerilir
import { describe, test, expect } from "vitest";

// İkisinde de aynı şekilde test yazarsın:
test("1 + 1 = 2", () => {
  expect(1 + 1).toBe(2);
});

E2E Test Araçları

AraçAçıklamaKullanım
PlaywrightMicrosoft, cross-browserModern projeler, CI/CD ✅
CypressDeveloper-friendlyFrontend odaklı projeler
PuppeteerGoogle, Chrome odaklıChrome otomasyon, scraping
// Playwright — cross-browser (Chromium, Firefox, WebKit)
import { test, expect } from "@playwright/test";

test("ana sayfa başlığı doğru", async ({ page }) => {
  await page.goto("https://myapp.com");
  await expect(page).toHaveTitle(/My App/);
});

// Cypress — component testing de destekler
describe("Login", () => {
  it("should login successfully", () => {
    cy.visit("/login");
    cy.get("#email").type("ali@test.com");
    cy.get("#password").type("password123");
    cy.get("button[type=submit]").click();
    cy.url().should("include", "/dashboard");
  });
});

Assertion Kütüphaneleri

// Jest/Vitest — yerleşik expect
expect(value).toBe(expected);           // Strict equality (===)
expect(value).toEqual(expected);        // Deep equality
expect(value).toBeTruthy();             // Truthy check
expect(value).toBeNull();               // null check
expect(value).toBeDefined();            // !== undefined
expect(value).toContain(item);          // Array/String contains
expect(fn).toThrow();                   // Hata fırlatma
expect(fn).toThrow("mesaj");            // Belirli hata mesajı
expect(value).toBeGreaterThan(3);       // > 3
expect(value).toBeCloseTo(0.3, 5);     // Float karşılaştırma
expect(value).toMatch(/regex/);         // RegExp
expect(value).toHaveProperty("name");   // Nesne property

// Negation — .not ile
expect(value).not.toBe(other);
expect(fn).not.toThrow();

İlk Test: Adım Adım

Test yazmayı hiç bilmeyen biri için sıfırdan:

# 1. Proje oluştur
mkdir my-project && cd my-project
npm init -y

# 2. Jest yükle
npm install -D jest

# 3. Jest yapılandır (package.json)
{
  "scripts": {
    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage"
  }
}
// src/math.js — Test edilecek kod
function add(a, b) {
  return a + b;
}

function multiply(a, b) {
  return a * b;
}

function divide(a, b) {
  if (b === 0) throw new Error("Sıfıra bölünemez");
  return a / b;
}

module.exports = { add, multiply, divide };
// src/math.test.js — Test dosyası (.test.js uzantısı)
const { add, multiply, divide } = require("./math");

describe("Math modülü", () => {
  describe("add", () => {
    test("iki pozitif sayıyı toplar", () => {
      expect(add(2, 3)).toBe(5);
    });

    test("negatif sayılarla çalışır", () => {
      expect(add(-1, -2)).toBe(-3);
    });

    test("sıfır ile toplamak değeri değiştirmez", () => {
      expect(add(5, 0)).toBe(5);
    });
  });

  describe("multiply", () => {
    test("iki sayıyı çarpar", () => {
      expect(multiply(3, 4)).toBe(12);
    });

    test("sıfır ile çarpmak sıfır verir", () => {
      expect(multiply(5, 0)).toBe(0);
    });

    test("negatif sayı ile çarpmak sonucu negatif yapar", () => {
      expect(multiply(3, -2)).toBe(-6);
    });
  });

  describe("divide", () => {
    test("iki sayıyı böler", () => {
      expect(divide(10, 2)).toBe(5);
    });

    test("sıfıra bölme hata fırlatır", () => {
      expect(() => divide(10, 0)).toThrow("Sıfıra bölünemez");
    });

    test("ondalıklı sonuç döndürebilir", () => {
      expect(divide(7, 2)).toBeCloseTo(3.5);
    });
  });
});
# 4. Testleri çalıştır
npm test

# Çıktı:
# PASS  src/math.test.js
#   Math modülü
#     add
#       ✓ iki pozitif sayıyı toplar (2ms)
#       ✓ negatif sayılarla çalışır
#       ✓ sıfır ile toplamak değeri değiştirmez
#     multiply
#       ✓ iki sayıyı çarpar (1ms)
#       ✓ sıfır ile çarpmak sıfır verir
#       ✓ negatif sayı ile çarpmak sonucu negatif yapar
#     divide
#       ✓ iki sayıyı böler (1ms)
#       ✓ sıfıra bölme hata fırlatır
#       ✓ ondalıklı sonuç döndürebilir
#
# Test Suites: 1 passed, 1 total
# Tests:       9 passed, 9 total
# Time:        0.5s

Test İsimlendirme

İyi test ismi, testin ne kontrol ettiğini okumadan anlatır:

// ❌ Kötü isimler — ne test edildiği belirsiz
test("test1", () => { /* ... */ });
test("çalışıyor", () => { /* ... */ });
test("add fonksiyonu", () => { /* ... */ });
test("hata testi", () => { /* ... */ });

// ✅ İyi isimler — ne yaptığını ve ne beklediğini söyler
test("add: iki pozitif sayıyı topladığında doğru sonuç döndürür", () => {});
test("login: yanlış şifre ile giriş yapıldığında hata fırlatır", () => {});
test("sepet: aynı ürün tekrar eklendiğinde miktarı artırır", () => {});
test("formatDate: null tarih verildiğinde 'N/A' döndürür", () => {});

// Pattern: [birim]: [senaryo] → [beklenen sonuç]
test("calculateTax: %18 KDV uygulandığında fiyatın %18'ini ekler", () => {
  expect(calculateTax(100, 18)).toBe(118);
});

💡 İpucu: Testi oku — eğer test isminden ne test edildiğini anlayamıyorsan, ismini değiştir. Test isimleri aynı zamanda dokümantasyon işlevi görür. jest --verbose ile test isimleri listesine bak — bir specification gibi okunmalı.


Neyi Test Etmeli, Neyi Etmemeli?

Test ET ✅

  • İş mantığı: Hesaplamalar, validasyonlar, dönüşümler

  • Edge case'ler: Boş input, sınır değerler, null/undefined

  • Hata senaryoları: Hatalı input'ta doğru hata mesajı dönüyor mu?

  • Entegrasyon noktaları: API çağrıları, veritabanı sorguları (mock ile)

  • Kritik kullanıcı akışları: Giriş, kayıt, ödeme

  • Regresyon: Düzeltilen bug'lar için test ekle (tekrar oluşmasın)

Test ETME ❌

  • Framework/kütüphane kodu: React'ın useState'ini test etme — React ekibi zaten etti

  • Trivial getter/setter: getName() { return this.name; } — test etmeye değmez

  • Implementasyon detayları: Kodun "nasıl" yaptığını değil, "ne" yaptığını test et

  • Dış servisler: Gerçek API'leri unit testte test etme — mock kullan

  • Sabit değerler: const PI = 3.14159 — test gereksiz

// ❌ İmplementasyon detayını test etme
test("sort fonksiyonu quicksort algoritması kullanır", () => {
  // Bu kötü — algoritma değişse test kırılır ama sonuç aynı kalır
});

// ✅ Davranışı test et
test("sort fonksiyonu diziyi küçükten büyüğe sıralar", () => {
  expect(sort([3, 1, 2])).toEqual([1, 2, 3]);
});

// ❌ Trivial — test etmeye değmez
test("user.getName() ismi döndürür", () => {
  const user = new User("Ali");
  expect(user.getName()).toBe("Ali"); // Getter'ı test etmenin anlamı yok
});

// ✅ Anlamlı — iş mantığı test ediliyor
test("user.getDisplayName() isim ve soyismi birleştirir", () => {
  const user = new User("Ali", "Yılmaz");
  expect(user.getDisplayName()).toBe("Ali Yılmaz");
});

Test Yaşam Döngüsü: Setup ve Teardown

describe("Database tests", () => {
  let db;

  // Tüm testlerden ÖNCE bir kez çalışır
  beforeAll(async () => {
    db = await Database.connect("test-db");
    console.log("DB bağlantısı kuruldu");
  });

  // Her testten ÖNCE çalışır
  beforeEach(async () => {
    await db.clear(); // Her test temiz başlasın
  });

  // Her testten SONRA çalışır
  afterEach(() => {
    jest.restoreAllMocks(); // Mock'ları temizle
  });

  // Tüm testlerden SONRA bir kez çalışır
  afterAll(async () => {
    await db.disconnect();
    console.log("DB bağlantısı kapatıldı");
  });

  test("kullanıcı oluşturur", async () => {
    await db.createUser({ name: "Ali" });
    const users = await db.getAllUsers();
    expect(users).toHaveLength(1);
  });

  test("kullanıcı siler", async () => {
    // beforeEach sayesinde DB temiz — önceki testten etkilenmez
    await db.createUser({ name: "Ayşe" });
    await db.deleteUser("Ayşe");
    const users = await db.getAllUsers();
    expect(users).toHaveLength(0);
  });
});
Çalışma sırası:
beforeAll       → DB bağlan (1 kez)
  beforeEach    → DB temizle
  test 1        → kullanıcı oluştur
  afterEach     → mock temizle
  beforeEach    → DB temizle
  test 2        → kullanıcı sil
  afterEach     → mock temizle
afterAll        → DB kapat (1 kez)

Yaygın Hatalar

1. Asenkron Testi Yanlış Yazmak

// ❌ async test'te await unutmak — test her zaman geçer!
test("veri yüklenir", () => {
  // Bu promise resolve olmadan test biter — assert çalışmaz
  fetchData().then(data => {
    expect(data).toHaveLength(5); // ASLA ÇALIŞMAZ ama test geçer!
  });
});

// ✅ Doğru: async/await kullan
test("veri yüklenir", async () => {
  const data = await fetchData();
  expect(data).toHaveLength(5);
});

// ✅ Veya: return promise
test("veri yüklenir", () => {
  return fetchData().then(data => {
    expect(data).toHaveLength(5);
  });
});

2. Testler Arası State Paylaşımı

// ❌ Shared state — testler birbirini etkiler
const users = [];

test("kullanıcı ekler", () => {
  users.push("Ali");
  expect(users).toHaveLength(1);
});

test("kullanıcı listesi boş olmalı", () => {
  expect(users).toHaveLength(0); // ❌ FAIL — "Ali" hâlâ var!
});

// ✅ Her test kendi state'ini oluştursun
test("kullanıcı ekler", () => {
  const users = [];
  users.push("Ali");
  expect(users).toHaveLength(1);
});

3. Assert Yazmmamak

// ❌ "Patlamazsa yeter" — hiçbir şeyi doğrulamıyor
test("processOrder çalışır", () => {
  processOrder({ id: 1 }); // Hata fırlatmazsa "geçer"
  // Assert YOK — çıktıyı kontrol etmiyorsun!
});

// ✅ Sonucu doğrula
test("processOrder sipariş ID döndürür", () => {
  const result = processOrder({ id: 1 });
  expect(result.orderId).toBeDefined();
  expect(result.status).toBe("confirmed");
});

Özet

  • Test yazmak "ekstra iş" değil, toplam iş miktarını azaltan bir yatırımdır. Bug'ları erken yakalar, refactoring cesareti verir, canlı dokümantasyon oluşturur.

  • Unit Test: Tek parçayı izole test eder. Hızlı, ucuz, çok yaz (~70%).

  • Integration Test: Parçaların birlikte çalışmasını test eder. Orta hız, orta miktar (~20%).

  • E2E Test: Gerçek kullanıcı senaryosu. Yavaş, pahalı, az yaz (~10%). Sadece kritik akışlar.

  • Test Piramidi: Altı geniş (unit), üstü dar (E2E). Anti-pattern: Ice cream cone (çok E2E, az unit).

  • TDD: Önce test yaz → kodu yaz → refactor et. Red-Green-Refactor döngüsü. İş mantığında çok faydalı.

  • BDD: İş dilinde test yaz (describe/it). Given-When-Then formatı. Ekip iletişimini güçlendirir.

  • Davranışı test et, implementasyon detayını değil. Test isimleri dokümantasyon gibi okunmalı.