← Kursa Dön
📄 Text · 25 min

Test Piramidi

Yazılım testi, profesyonel geliştirmenin olmazsa olmaz parçasıdır. Kod yazmak kadar, yazdığınız kodun doğru çalıştığını kanıtlamak da sizin sorumluluğunuzdadır. Bu derste test dünyasının temel kavramlarını, Test Piramidi modelini, test coverage stratejilerini ve TDD (Test-Driven Development) yaklaşımını derinlemesine inceleyeceğiz.

Neden Test Yazıyoruz?

Test yazmak, ilk bakışta zaman kaybı gibi görünebilir — ama tam tersi doğrudur. Test yazmamanın maliyeti, yazma maliyetinin katbekat üzerindedir:

  1. Güvenlik ağı: Kod değişikliklerinde mevcut işlevselliğin bozulmadığını garanti eder

  2. Dokümantasyon: Testler, kodun nasıl kullanılması gerektiğini gösteren canlı dokümantasyondur

  3. Refactoring cesareti: İyi test coverage ile kodu cesaretle yeniden yapılandırabilirsiniz

  4. Hata maliyetini azaltma: Production'da bulunan bir hata, geliştirme sırasında bulunandan 10-100x daha maliyetlidir

  5. Tasarım kalitesi: Test yazılabilir kod yazmak, doğal olarak daha iyi tasarıma yönlendirir (loose coupling, single responsibility)

Test Piramidi Nedir?

Test Piramidi, Mike Cohn tarafından tanımlanan ve test stratejisini görselleştiren bir modeldir. Piramit şeklinde çizilir çünkü alt katmandaki testlerden çok, üst katmandakilerin az olması gerekir:

        ╱  E2E  ╲         ← Az sayıda, yavaş, pahalı
       ╱──────────╲
      ╱ Integration ╲     ← Orta sayıda, orta hız
     ╱────────────────╲
    ╱   Unit Tests     ╲  ← Çok sayıda, hızlı, ucuz
   ╱────────────────────╲

Alt katman — Unit Tests (Birim Testleri):

  • Tek bir sınıf veya metodu izole olarak test eder

  • Dış bağımlılıklar (veritabanı, network, dosya sistemi) mock edilir

  • Milisaniyeler içinde çalışır — binlerce test saniyeler sürer

  • Tüm testlerin %70-80'ini oluşturmalıdır

Orta katman — Integration Tests (Entegrasyon Testleri):

  • Birden fazla bileşenin birlikte çalışmasını test eder

  • Spring context ayağa kaldırılabilir, veritabanı bağlantısı kurulabilir

  • Saniyeler-dakikalar sürebilir

  • Tüm testlerin %15-25'ini oluşturmalıdır

Üst katman — E2E Tests (Uçtan Uca Testler):

  • Tüm sistemi bir kullanıcı gibi test eder (UI → API → DB → Response)

  • Selenium, Cypress, Playwright gibi araçlarla browser otomasyonu

  • Dakikalar sürer, kırılgandır (UI değişikliklerinden etkilenir)

  • Tüm testlerin %5-10'unu oluşturmalıdır

Spring Boot'ta Test Katmanları

Spring Boot, her test katmanı için özel araçlar sunar:

KatmanAraçAnotasyonHız
UnitJUnit 5 + Mockito@ExtendWith(MockitoExtension.class)Çok hızlı
SliceSpring Test Slices@WebMvcTest, @DataJpaTestHızlı
IntegrationFull Spring Context@SpringBootTestYavaş
E2ETestRestTemplate / WebTestClient@SpringBootTest(webEnvironment=RANDOM_PORT)En yavaş

Slice Test kavramı Spring Boot'a özgüdür: tam application context yerine sadece ilgili katmanı (web, JPA, vb.) ayağa kaldırır. Bu sayede integration test hızında ama daha odaklı testler yazabilirsiniz.

Test Coverage (Test Kapsama Oranı)

Test coverage, kodunuzun ne kadarının testlerle kapsandığını ölçen bir metriktir:

<!-- pom.xml — JaCoCo coverage plugin -->
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.11</version>
    <executions>
        <execution>
            <goals><goal>prepare-agent</goal></goals>
        </execution>
        <execution>
            <id>report</id>
            <phase>test</phase>
            <goals><goal>report</goal></goals>
        </execution>
    </executions>
</plugin>

Coverage metrikleri:

  • Line coverage: Çalıştırılan satır sayısı / toplam satır sayısı

  • Branch coverage: Test edilen karar dalları (if/else, switch) / toplam dal sayısı

  • Method coverage: Çağrılan metot sayısı / toplam metot sayısı

  • Class coverage: En az bir metodu test edilen sınıf sayısı

Hedef coverage oranları:

  • %80+ line coverage çoğu proje için yeterlidir

  • %100 hedefi genellikle gerçekçi değildir ve getter/setter gibi trivial kodun test edilmesini zorunlu kılar

  • Branch coverage, line coverage'dan daha değerlidir — edge case'leri yakalar

⚠️ Uyarı: Yüksek coverage, kaliteli test anlamına gelmez! Bir test assertion içermeden sadece metotları çağırabilir — coverage yükselir ama hiçbir şey doğrulanmaz. Bu duruma "false confidence" (sahte güven) denir. Her testin anlamlı assertion'ları olmalıdır.

TDD — Test-Driven Development

TDD, testleri koddan önce yazma disiplinidir. Kent Beck tarafından popülerleştirilen bu yaklaşım üç adımdan oluşur:

┌──────────┐     ┌──────────┐     ┌──────────┐
│  1. RED  │ ──→ │ 2. GREEN │ ──→ │3. REFACTOR│
│  Test yaz│     │ Kodu yaz │     │ Temizle  │
│ (başarısız)    │(geçecek  │     │(testler  │
│          │     │ kadar)   │     │ yeşil)   │
└──────────┘     └──────────┘     └──────────┘
      ↑                                │
      └────────────────────────────────┘
  1. RED: Henüz implementasyon olmadan bir test yazın — test başarısız olacak (kırmızı)

  2. GREEN: Testi geçirecek minimum kodu yazın — şık olması gerekmez, çalışması yeter

  3. REFACTOR: Kodu temizleyin, iyileştirin — testler hâlâ yeşil kalmalı

TDD örneği — bir Calculator servisi:

// STEP 1: RED — Test yaz (implementasyon yok)
@Test
@DisplayName("İki pozitif sayıyı toplar")
void shouldAddTwoPositiveNumbers() {
    Calculator calc = new Calculator();
    assertEquals(5, calc.add(2, 3));
}

// STEP 2: GREEN — Minimum implementasyon
public class Calculator {
    public int add(int a, int b) {
        return a + b;
    }
}

// STEP 3: REFACTOR — İhtiyaç varsa iyileştir
// Bu durumda kod zaten temiz, refactor gereksiz

TDD'nin faydaları:

  • Tasarımı yönlendirir: Test yazılabilir kod doğal olarak daha modülerdir

  • Over-engineering'i önler: Sadece testi geçirecek kadar kod yazarsınız

  • Regresyon güvencesi: Her yeni özellik otomatik olarak test edilmiş olur

  • Dokümantasyon: Testler, kodun beklenen davranışını açıkça tanımlar

Test Best Practices

  1. AAA Pattern (Arrange-Act-Assert): Her test üç bölümden oluşmalı

  2. Tek sorumluluk: Her test tek bir davranışı doğrulamalı

  3. Bağımsızlık: Testler birbirine bağımlı olmamalı, herhangi bir sırada çalışabilmeli

  4. Tekrarlanabilirlik: Her çalıştırıldığında aynı sonucu vermeli

  5. Hızlılık: Unit testler milisaniyeler içinde çalışmalı

  6. İsimlendirme: Test adı, neyin test edildiğini açıkça belirtmeli

@Test
@DisplayName("Stokta olmayan ürün siparişinde exception fırlatılmalı")
void shouldThrowException_WhenProductOutOfStock() {
    // Arrange
    Product product = new Product("Laptop", 0);
    OrderService service = new OrderService();

    // Act & Assert
    assertThrows(OutOfStockException.class,
        () -> service.placeOrder(product, 1));
}

Test piramidi, coverage metrikleri ve TDD yaklaşımı birlikte kullanıldığında, güvenilir, bakımı kolay ve değişime açık yazılımlar üretmenin temelidir.