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:
Güvenlik ağı: Kod değişikliklerinde mevcut işlevselliğin bozulmadığını garanti eder
Dokümantasyon: Testler, kodun nasıl kullanılması gerektiğini gösteren canlı dokümantasyondur
Refactoring cesareti: İyi test coverage ile kodu cesaretle yeniden yapılandırabilirsiniz
Hata maliyetini azaltma: Production'da bulunan bir hata, geliştirme sırasında bulunandan 10-100x daha maliyetlidir
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:
| Katman | Araç | Anotasyon | Hız |
|---|---|---|---|
| Unit | JUnit 5 + Mockito | @ExtendWith(MockitoExtension.class) | Çok hızlı |
| Slice | Spring Test Slices | @WebMvcTest, @DataJpaTest | Hızlı |
| Integration | Full Spring Context | @SpringBootTest | Yavaş |
| E2E | TestRestTemplate / 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) │
└──────────┘ └──────────┘ └──────────┘
↑ │
└────────────────────────────────┘RED: Henüz implementasyon olmadan bir test yazın — test başarısız olacak (kırmızı)
GREEN: Testi geçirecek minimum kodu yazın — şık olması gerekmez, çalışması yeter
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 gereksizTDD'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
AAA Pattern (Arrange-Act-Assert): Her test üç bölümden oluşmalı
Tek sorumluluk: Her test tek bir davranışı doğrulamalı
Bağımsızlık: Testler birbirine bağımlı olmamalı, herhangi bir sırada çalışabilmeli
Tekrarlanabilirlik: Her çalıştırıldığında aynı sonucu vermeli
Hızlılık: Unit testler milisaniyeler içinde çalışmalı
İ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.
AI Asistan
Sorularını yanıtlamaya hazır