Ana Sayfa / Blog / Test piramidi: unit vs integration vs E2E gerçek dağılımı

Test piramidi: unit vs integration vs E2E gerçek dağılımı

Klasik test piramidi tavsiyesi 70% unit, 20% integration, 10% E2E. Gerçek projelerde bu oran her zaman işe yaramıyor. 3 projeden pratik dağılımlar.

“Test pyramid” tavsiyesi 2009’dan beri dolaşıyor: %70 unit, %20 integration, %10 E2E. Ama pratikte bu oranı rigorously uygulayan ekiplerde bug yakalanma oranı düşük.

3 farklı projede test stratejisi tuttum, her birinde farklı oran ortaya çıktı. Bu yazıda klasik piramit tavsiyesinin ne zaman işe yaradığını, ne zaman yaramadığını anlatıyorum.

Klasik piramidin mantığı

Mike Cohn 2009’da şunu önerdi:

  • Unit test (en alt, en fazla): tek fonksiyon/class test, milisaniye hızında, isolation
  • Integration test (orta): birkaç component’in birlikte çalışması, saniye hızında
  • E2E test (en üst, en az): tüm sistemi kullanıcı gibi, dakika hızında

Mantık: unit test ucuz, E2E pahalı. Çoğunluk unit’te olmalı.

Piramit nerelerde işliyor

Pure logic-heavy code. Algorithm, business rule calculation, utility function. Unit test burada 80-90% coverage ile bug yakalıyor.

def calculate_discount(price, coupon, user_tier):
    # 20 farklı logic branch
    ...

# 40 unit test bu branches'ları cover ediyor

Framework-independent logic. Domain layer, model validation, formatting. Hızlı iterate, deterministic.

Refactoring-heavy phase. Refactor sırasında unit test’ler regression detect ediyor, anında feedback.

Piramit nerede kırılıyor

Integration-heavy system. Microservice’ler, multiple external API, complex database operations. Unit test isolation’da olmadığı için bug yakalamıyor. Mock’lar aldatıcı oluyor.

Örnek: 20 microservice’in birbiriyle konuştuğu sistem. Her servisin unit testi geçiyor ama entegrasyonda crash. Bug’ı yakalayan integration test.

UI-heavy frontend. React/Vue component’leri. Unit test DOM’u mock’luyor, gerçek browser behavior’ı yok. Event handler unit’te geçer, real click’te kırılabilir.

Data transformation pipeline. ETL, data migration. Her step’in unit testi önemsiz, asıl end-to-end pipeline’ın doğru output vermesi.

Gerçek proje 1: fintech backend

Backend, 15 microservice, complex integration. Unit/integration/E2E oranı:

  • Unit: %45
  • Integration: %40
  • E2E: %15

Integration test ağır olduğu için “trapezoid” pattern. Her servisin kendi içinde unit yeterli, servisler arası integration kritik.

Contract test (Pact framework) integration subset olarak özel yer buldu.

Gerçek proje 2: React SPA

React frontend, Storybook ile component development. Oran:

  • Unit (component logic, hooks): %30
  • Component test (React Testing Library): %45
  • E2E (Playwright): %25

Component testler integration’ın frontend versiyonu. Render + event + assertion. Real user interaction simüle ediliyor. Çok daha güvenilir.

E2E artık “ağır” değil Playwright sayesinde. 100 test 5 dakikada paralel.

Gerçek proje 3: data pipeline

Python ETL, airflow ile orchestrate. Oran:

  • Unit: %50 (transformation functions)
  • Integration: %30 (source/sink interactions)
  • E2E / pipeline test: %20 (full DAG end-to-end)

Pipeline test’ler critical: input data’dan output data’ya dönüşüm doğru mu? Mock’larla zor, gerçek mini dataset ile.

“Honeycomb” alternative

Google ve Spotify “testing honeycomb” önerisiyle karşı çıktı klasik piramide:

  • Az sayıda unit (implementation detail’e bağlı, brittle)
  • Çok integration (asıl business logic test)
  • Az sayıda E2E

Specifically mid-size teams, service-oriented architecture için.

Microservice ekiplerinde honeycomb pattern pratik çıkıyor.

Test strategy selection

Projeye bakıp karar verirken şunları değerlendiriyorum:

Business logic karmaşıklığı. Çok → unit test ağırlık

Integration karmaşıklığı. Çok → integration test ağırlık

UI karmaşıklığı. Çok → component/E2E ağırlık

Refactoring frequency. Çok → unit (fast feedback) ağırlık

Team maturity. Junior ekip → E2E daha forgiving (implementation detail’i değil user flow’u test)

Deployment risk. Yüksek → E2E ağırlık (release gate)

Mock vs real

Unit/integration arasındaki sınırı bulanıklaştıran: mock kullanımı.

Aggressive mocking ile “unit test” yazıyorsunuz ama production bug’larını yakalamıyorsunuz çünkü mock’lar aldatıcı.

Minimal mocking yaklaşımı: sadece external system (DB, 3rd party API, filesystem) mock. Internal dependency’ler real.

# Aggressive mock (bug miss riski)
def test_process_order():
    mock_db = Mock()
    mock_payment = Mock()
    mock_email = Mock()
    # Her şey mock
    service.process_order(order)
    mock_db.save.assert_called()

# Minimal mock (daha güvenilir)
def test_process_order(real_db):  # real test DB
    mock_payment_api = Mock(return_value={'status': 'success'})
    service = OrderService(real_db, mock_payment_api)
    service.process_order(order)
    saved_order = real_db.query(Order).first()
    assert saved_order.status == 'paid'

Minimal mock integration gibi hissettiriyor ama değerli.

Test speed vs value

Speed-value trade-off:

  • Unit: hızlı, düşük value (isolated)
  • Integration: orta hızlı, yüksek value (real interaction)
  • E2E: yavaş, en yüksek value (full scenario)

Modern tool’larla speed gap kapanıyor. Playwright 100 E2E 5 dakikada, integration test Testcontainers ile 10 dakikada 500 test.

Speed artık tek kriter değil. Value daha önemli.

Flakiness: test güvenilirliği

Flaky test = bazen geçiyor bazen geçmiyor. Ekip güveninin düşmanı.

Flakiness sebepleri:
– Race condition
– Test order dependency
– External service flakiness
– Time-sensitive assertion
– Shared state

Flaky test’i skip etmek yerine fix etmek şart. Flaky → disable → bug kaçtı = klasik senaryo.

CI’da flaky test detection: 3 kere çalıştırınca 1’i fail = flagged, investigate.

Coverage yanılgısı

“80% coverage” hedefi aldatıcı. 80% coverage ile bug’ları yakalamayan test’ler yazmak mümkün.

  • Assertion olmayan “smoke test” coverage’ı çıkarıyor ama bug yakalamıyor
  • Happy path only test
  • Mock-heavy test production behavior’ını test etmiyor

Coverage metrik değil hedef. Gerçek hedef “production’a kaçan bug sayısı”.

Mutation testing coverage’ın kalitesini ölçüyor: coverage yüksek ama mutation testing düşükse test’ler sathi.

Test pyramid’ı güncel tutma

Proje ilerledikçe test dağılımı değişiyor:

  • Erken aşama: E2E ağırlıklı (feature validation)
  • Olgunlaşma: unit + integration artıyor
  • Refactoring phase: unit ağırlık
  • Maintenance: integration/E2E preservation

Her 6 ayda test stratejisi review: ne zaman hangi test bug yakaladı, hangi test boşa çıktı, nereye yatırım yapmalıyız.

Son tavsiye

Klasik test pyramid rehber, dogma değil. Projenin doğasına göre honeycomb, trapezoid, reverse pyramid de olabilir.

Tavsiyem:

  1. Başlangıçta ne karar vereceğini projenin mimarisine göre düşün
  2. Her test seviyesinin farklı bug tip’i yakaladığını kabul et
  3. Coverage hedefi yerine “production’a kaçan bug” metric’i kullan
  4. Flaky test’leri hızlıca fix et veya atla
  5. 6 ayda bir test stratejisi review

Bu disiplin test yatırımını doğru yerlere yönlendiriyor. Production stability en iyi test yazımı yerlerine yatırımdan geliyor, hedefe göre test yazımından değil.

Bu konuda bir projeniz mi var?

Kısa bir özet bırakın, 24 saat içinde size dönüş yapayım.

İletişime Geç