a.
mcp.altay.socialMCP & Prompt Katalogu
Skill
Öne çıkan·v1.0.0·2026-04-22

Test Yazma Disiplini

Değer üreten, yalancı yeşil görmeyen testler yazmayı öğreten skill — test piramidi, mock stratejisi, arrange-act-assert, flakiness önleme, hangi testi ne zaman yazacağının karar ağacı.

testingunitintegratione2eskillquality

İçerik

Test Yazma Disiplini

Üç çeşit test kodu var: değer üreten, gürültü çıkaran ve güvence vermeden yeşil görünen. Bu skill üçüncü ikisine düşmeden birinciyi yazmayı öğretir.

Ne zaman aktif olur

  • Yeni bir feature yazılırken (test-first veya beraber)
  • Bug fix için regression testi eklerken
  • Mevcut testi refactor ederken
  • CI yavaş / flaky olduğunda kurtarma

Çekirdek prensipler

1. Test davranışı test eder, implementasyonu değil

Implementasyon testi: "getUserById fonksiyonu UserRepo.findOne çağırıyor mu?" → İç yapıyı değiştirirsen test kırılır, davranış değişmese bile. Kötü.

Davranış testi: "User ID'si ile aradığımda doğru kullanıcıyı alıyor muyum?" → İç değişse de test ayakta, davranış değişirse kırılır. İyi.

2. Test piramidi — ama mantıklı şekilde

        /\
       /E2E\          az (yavaş, flaky potansiyel)
      /______\
     /  INT   \       orta (DB + gerçek servis)
    /__________\
   /    UNIT    \     çok (hızlı, deterministic)
  /______________\

Ama: Piramidin "kuralı" değil "yön tavsiyesi". Bazı takımlar trophy modelini (az unit, çok integration) tercih eder çünkü unit test çoğu zaman implementation-coupled oluyor. Uyarlama kritik.

3. Arrange - Act - Assert

Her test üç bölüm:

test("expired token reddedilmeli", () => {
  // Arrange
  const token = makeExpiredToken();
  const authService = new AuthService(fakeClock);

  // Act
  const result = authService.verify(token);

  // Assert
  expect(result.ok).toBe(false);
  expect(result.error).toBe("expired");
});

Bu sırayı bozma — Assert → Act → Arrange okunmaz.

4. Tek test, tek davranış

Tek test() içinde 5 farklı davranışı test etme. Bölerse okunur ve hangi case'in fail ettiği bir bakışta belli olur.

5. Mock gerçeği yalanlar

Her mock bir varsayım. Bu varsayım gerçekle uyumsuzsa test yeşil + prod kırmızı.

  • İç kod için: mockla (DB, HTTP, clock, random)
  • Kendi kütüphanen / bağımsız fonksiyon: mocklama, gerçeğini kullan
  • Kritik yol boundary'leri: contract test ile mock'u gerçeğe eşitle

Hangi testi ne zaman yazarım

Unit test

Kullan:

  • Saf hesaplama fonksiyonları (formatter, validator, calculator)
  • Business rule / state machine
  • Utility / pure logic

Kullanma:

  • Her sınıf/dosya için "kaplama çıksın" diye
  • 3 satır DI glue kodu için
  • UI render detayı için (görünüm değişince test kırılmasın)

Integration test

Kullan:

  • API endpoint (route → service → DB)
  • Veritabanı ile iş kuralı
  • Queue producer/consumer
  • Auth/authorization flow

Kullanma:

  • Her iç helper için (unit yeter)

E2E test

Kullan:

  • Kritik yol: login → core action → logout
  • Cross-page akış (signup → onboarding → first action)
  • Browser-spesifik davranış (focus, keyboard nav)

Kullanma:

  • Her buton click için (flaky + yavaş)
  • UI'nın rengi / spacing'i için (visual regression ayrı araç)

Test adı formatı

Eski: test_1, should work, test login

İyi: Bir cümle — koşul + beklenti

test("süresi geçmiş token verify çağrısında 'expired' hatasıyla reddedilir")
test("valid idempotency key ile gönderilen tekrarlı istek cached response döner")
test("admin olmayan kullanıcı /admin/users erişiminde 403 alır")

İyi test adı = test kırıldığında ne olduğunu adından anlarsın.

Arrange — fixture yönetimi

Repeat yourself'ten kaçınma

Her test aynı createUser, seedOrders rutinini tekrarlıyorsa factory yaz:

// test/factories/user.ts
export function userFactory(overrides?: Partial<User>): User {
  return {
    id: crypto.randomUUID(),
    email: "test@example.com",
    role: "user",
    createdAt: new Date("2026-01-01"),
    ...overrides,
  };
}

// test
const admin = userFactory({ role: "admin" });

Fixed values

Random data yerine deterministik. Random kullanacaksan seed'li.

// ❌ flaky
const user = { id: Math.random(), name: faker.name.firstName() };

// ✅ stable
const user = userFactory({ id: "usr_test_001", name: "Alice" });

Act — doğru sınırı aramak

Test ettiğin birimi düzgün çiz. Küçükse implementation'a yaklaşır, büyükse setup patlar.

Durum Uygun sınır
Saf fonksiyon Fonksiyonu çağır
HTTP endpoint Supertest / fetch ile gerçek HTTP
React component render() + user interaction (user-event)
Queue consumer Mesajı queue'ya publish et, consumer'ın yazdığı DB'yi oku

Assert — neyi iddia ediyorsun?

Gereksiz assertion'dan kaç:

// ❌ kırılgan
expect(user).toEqual({
  id: "usr_001",
  email: "a@b.com",
  role: "user",
  createdAt: expect.any(Date),  // 3 gereksiz
  updatedAt: expect.any(Date),
});

// ✅ odaklı
expect(user.email).toBe("a@b.com");
expect(user.role).toBe("user");

Ama eksik de etme:

// ❌ false positive riski
const result = await createOrder(...);
expect(result).toBeDefined();  // ne?

// ✅
expect(result.ok).toBe(true);
expect(result.value.status).toBe("pending");

Hata durumları test edilmez, test edilir

Mutlu yol yeterli değil. Her endpoint / fonksiyon için:

  • Invalid input — 400 / error
  • Not found — 404 / null
  • Authorization fail — 403
  • Conflict — 409 (duplicate, race)
  • Upstream fail — 502/503 (mock ile simüle)
  • Empty / zero / negative edge cases
  • Unicode, emoji, very long strings
  • Concurrent — race condition varsa

Flakiness — test yalan söylüyor

Nedenleri

  • Shared state: test A, test B'nin bıraktığı DB kaydını okur → Her test kendi fixture'ını oluştursun, cleanup etsin (transaction rollback)

  • Zaman: new Date() test çalıştığı an farklı → Clock'u inject et (fakeClock)

  • Random: UUID, faker seed'li olmadan → Seed ver veya deterministik değer

  • Network: gerçek external API çağrısı → Mock veya VCR / nock kaydı

  • Async race: setTimeout, setInterval olmadan test bitmiş ama event hala patlamadı → await, waitFor, proper flush

  • Order dependent: test A fail olunca B de fail → Test'ler bağımsız olmalı

Diagnose

Flaky test'i sil değil, izole et + debug et. 100 kere döndür:

for i in {1..100}; do pnpm test foo || break; done

İlk fail'de stack trace al, sebebi bul.

Coverage — hedef mi, yan ürün mü?

  • %80+ kod coverage istatistikten anlamlı değildir (trivial getter dahil)
  • %100 branch coverage kritik business rule'da anlamlı
  • Kaplama % değil, kritik yolun kaplandığı önemli

Coverage tool'u guard, hedef değil.

Test ortamı

DB

  • Memory DB (SQLite in-memory) — hızlı ama dialect farkı
  • Real DB (docker-compose) — yavaş ama gerçeği simüle eder, tercih et
  • Her test transaction içinde, sonunda rollback

Network

  • Mock (msw, nock) — hızlı, kontrollü
  • Recorded (VCR, Polly.js) — gerçek API kaydı, yavaş değişen için
  • Real — dış SLA'ya bağımlı, CI flaky, kaçın

Somut örnekler

❌ Kötü test

test("user works", async () => {
  const u = await createUser({ email: "a@b.com" });
  expect(u).toBeTruthy();
  const f = await findUser(u.id);
  expect(f).toBeTruthy();
  await deleteUser(u.id);
  const g = await findUser(u.id);
  expect(g).toBeFalsy();
});

Problemler: tek test 3 davranış, assertion belirsiz, cleanup sonda (fail durumunda orphan), isim hiçbir şey söylemiyor.

✅ İyi testler

describe("UserRepository", () => {
  test("createUser, verilen e-posta ile yeni kullanıcı döner", async () => {
    const user = await userRepo.create({ email: "a@b.com" });
    expect(user.id).toBeDefined();
    expect(user.email).toBe("a@b.com");
  });

  test("findById, var olan kullanıcıyı döner", async () => {
    const created = await userRepo.create({ email: "a@b.com" });
    const found = await userRepo.findById(created.id);
    expect(found).not.toBeNull();
    expect(found!.email).toBe("a@b.com");
  });

  test("findById, var olmayan ID için null döner", async () => {
    const found = await userRepo.findById("non-existent");
    expect(found).toBeNull();
  });

  test("delete, kullanıcıyı silip findById ile null döndürür", async () => {
    const created = await userRepo.create({ email: "a@b.com" });
    await userRepo.delete(created.id);
    const found = await userRepo.findById(created.id);
    expect(found).toBeNull();
  });
});

Checklist — test commit'i öncesi

  • Her test() tek davranışı test ediyor
  • Test adları cümle — koşul + beklenti
  • AAA sırası korundu
  • Random / time / network mocklı (veya deterministik)
  • Cleanup garantili (afterEach veya transaction rollback)
  • Hata / sad path case'leri var
  • Coverage % hedef değil, kritik yol kaplı
  • Aynı test 50 kere arka arkaya çalıştırıldığında yeşil

Anti-pattern'ler

  • expect(true).toBe(true) — placeholder test
  • test.skip(...) kalıcı olmuş — ya düzelt ya sil
  • ❌ "Kaplama çıksın" diye getter/setter testi
  • ❌ Assert hiç yok — sadece çağırıyor, exception olmuyorsa yeşil
  • try { await doSomething(); fail("should throw"); } catch(e) {}expect().rejects.toThrow() kullan
  • ❌ Test dosyasında production logic (helper fonksiyonu production'dan ayrı yazma)
  • sleep(1000) ile timing düzeltme — gerçek wait kullan (waitFor, polling)
  • ❌ Test içinde console.log kalıcı olarak
  • ❌ Snapshot test her yerde — sadece karmaşık stable output için
  • ❌ Shared mutable global state test'ler arası