BUGRA.WORK
HomeAboutProjectsSkillsToolsBlogContact
Sitebugra.work
KonumBursa, Türkiye
YığınNext.js · Tailwind · Vercel
Sürümv2 · 2026

© 2026 Buğra Şıkel. All rights reserved.

GitHubLinkedInTelegramEmailPrivacy
Ask AI
Blog'a Dön

11 Ağustos 2026·Buğra Şıkel

Mutation Testing vs Branch Coverage: Birim Testlerdeki Kör Noktaları Tespit Karar Matrisi

Mutation Testing vs Branch Coverage: Birim Testlerdeki Kör Noktaları Tespit Karar Matrisi

Giriş

Mikroservis mimarisiyle tasarlanmış bir finansal mutabakat (reconciliation) sisteminde karşılaştığımız kritik bir vakayı ele alalım. SonarQube kalite kapılarımız (quality gates), ilgili domain modülü için Branch Coverage oranını %96.4 olarak raporluyordu. CI/CD pipeline’ındaki tüm birim testler yeşil yanıyor, statik kod analizi sıfır zafiyet gösteriyordu. Ancak, ay sonu mutabakatında, döviz kuru dönüşümlerindeki bir sınır değer (boundary value) hatası nedeniyle 1.2 milyon dolarlık bir işlem hacminin %0.04’lük kısmında kuruş hassasiyeti (precision) sapması yaşandı.

Buradaki mimari yanılgı, kodun çalıştırılmış olmasını (execution), kodun doğrulanmış olmasıyla (assertion) karıştırmaktır. Bir if-else bloğunun tüm dallarından test koşumu sırasında geçilmesi, o dallardaki iş mantığının doğru test edildiği anlamına gelmez. Sadece o satırların JVM veya V8 motoru üzerinde işletildiğini matematiksel olarak kanıtlar. 15 yıllık production tecrübem bana şunu net bir şekilde öğretti: Test süitinizdeki asıl tehlike test edilmemiş kodlar değil, test edildiği sanılan kodlardır.

İşte bu illüzyonu yıkmak ve birim testlerdeki kör noktaları (blind spots) tespit etmek için, kodu kasıtlı olarak sabote eden Mutation Testing stratejisini devreye almamız gerekir. Bu karar matrisi, geleneksel kapsama metrikleri ile mutasyon tabanlı analiz arasındaki yapısal farkları, trade-off’ları ve production ortamı için entegrasyon stratejilerini incelemektedir.

İçindekiler

  • Branch Coverage: İllüzyonun Anatomisi
  • Mutation Testing: Kodu Kasıtlı Sabote Etmek
  • Karşılaştırma Matrisi
  • Hangi Senaryoda Hangisi Seçilmeli?
  • Production Vakası: Ödeme Sisteminde %14’lük Kaçak
  • Pratik Öneriler / Production Notları
  • Sık Sorulan Sorular
  • Sonuç

Branch Coverage: İllüzyonun Anatomisi

Branch coverage (dal kapsamı), kontrol akış grafiğindeki (control flow graph) karar noktalarının (if, switch, while, for) test koşumu sırasında hangi oranda değerlendirildiğini ölçer. Matematiksel temeli, McCabe’in Siklomatik Karmaşıklık (Cyclomatic Complexity) algoritmasına dayanır.

Bu metrik, kodun üzerinden geçilip geçilmediğini byte-code veya kaynak kod enstrümantasyonu (instrumentation) ile takip eder. Java ekosisteminde JaCoCo, JavaScript/TypeScript ekosisteminde Istanbul (nyc) bu işi AST (Abstract Syntax Tree) seviyesine sayaçlar yerleştirerek yapar.

Kör Nokta Nerede Başlar?

Aşağıdaki Java kod bloğunu inceleyelim. Geleneksel metriklerin bizi nasıl yanılttığını açıkça görebiliriz:

public class LoyaltyCalculator {
    public BigDecimal calculateDiscount(BigDecimal cartTotal, int customerTenureYears) {
        // Sınır değer kontrolü
        if (cartTotal.compareTo(new BigDecimal("1000")) > 0 && customerTenureYears >= 5) {
            return cartTotal.multiply(new BigDecimal("0.85")); // %15 indirim
        }
        return cartTotal;
    }
}

// JUnit 5 Test Sınıfı
class LoyaltyCalculatorTest {
    @Test
    void testCalculateDiscount() {
        LoyaltyCalculator calculator = new LoyaltyCalculator();
        
        // Dal 1: İf koşulu sağlanır (TRUE)
        calculator.calculateDiscount(new BigDecimal("1500"), 6);
        
        // Dal 2: İf koşulu sağlanmaz (FALSE)
        calculator.calculateDiscount(new BigDecimal("500"), 2);
        
        // DİKKAT: Hiçbir assertion (doğrulama) yapılmadı!
        // Veya yetersiz bir assertion yapıldı: assertNotNull(calculator);
    }
}

Yukarıdaki test çalıştırıldığında JaCoCo raporu %100 Branch Coverage gösterecektir. Çünkü if bloğunun hem true hem de false yolları çalıştırılmıştır. Ancak test, metodun döndürdüğü indirimli tutarın doğruluğunu asla kontrol etmez. Bu duruma literatürde Assertion-Free Testing veya Assertion Roulette adı verilir. Eğer bir geliştirici 0.85 çarpanını yanlışlıkla 0.95 olarak değiştirirse, test yine başarıyla geçecek ancak business logic production’da çökecektir.

Mutation Testing: Kodu Kasıtlı Sabote Etmek

Mutation testing, “Testleriniz kodunuzu test ediyor, peki testlerinizi kim test ediyor?” sorusuna verilen donanımsal ve algoritmik bir cevaptır. Mantık basittir: Orijinal kaynak kodun belirli kısımlarını (operatörler, koşullar, dönüş değerleri) kasıtlı olarak bozarız (mutasyon). Oluşan bu yeni hatalı koda Mutant denir. Ardından mevcut test süitini bu mutant kod üzerinde çalıştırırız.

  • Killed (Öldürülmüş Mutant): Test süiti bir hata fırlatır ve başarısız olursa, testiniz bu hatayı yakalayacak kadar niteliklidir. Mutant öldürülmüştür.
  • Survived (Hayatta Kalan Mutant): Kodu bozmanıza rağmen test süiti hala “Pass” dönüyorsa (yeşil yanıyorsa), testinizde ciddi bir kör nokta vardır. Mutant hayatta kalmıştır.

Nasıl Çalışır? (AST ve Bytecode Manipülasyonu)

Java ekosistemindeki endüstri standardı olan PiTest (PIT), kaynak kodu yeniden derlemek yerine, bellekteki (in-memory) JVM bytecode’unu ASM kütüphanesi yardımıyla manipüle eder. Bu sayede I/O maliyetinden kaçınır. Uygulanan temel mutasyon operatörleri şunlardır:

  • Conditionals Boundary Mutator: < operatörünü <= yapar. Sınır değer (off-by-one) hatalarını bulmak için kritiktir.
  • Math Mutator: Toplama (+) işlemini çıkarma (-) ile değiştirir.
  • Void Method Call Mutator: Geri dönüş değeri olmayan metod çağrılarını (örneğin eventPublisher.publish(event)) koddan tamamen siler.
  • Return Values Mutator: true dönen bir metodu false dönecek veya null referans dönecek şekilde değiştirir.

Mutation Testing, kodu değil, testin kalitesini metrikleştirir. Hedefi test sayısını artırmak değil, mevcut testlerin fault-detection (hata yakalama) kapasitesini maksimize etmektir.

Karşılaştırma Matrisi

Mimari kararlar alırken, iki metrik arasındaki yapısal farklılıkları anlamak CI/CD süreçlerinin sağlığı için elzemdir.

Özellik / Kriter Branch Coverage (JaCoCo, Istanbul) Mutation Testing (PiTest, Stryker)
Ölçüm Odak Noktası Kod satırlarının işletilme (execution) oranı Testlerin hata yakalama (assertion) kalitesi
İşlem Süresi (CPU Cost) Milisaniyeler/Saniyeler seviyesinde (Düşük) Dakikalar/Saatler seviyesinde (Çok Yüksek)
CI/CD Entegrasyonu Her pull request’te (PR) senkron olarak koşulabilir Gece koşumları (Nightly) veya sadece değişen dosyalar bazında (Incremental) koşulmalıdır
Yanlış Pozitif (False Positive) Yok denecek kadar az Yüksek (Equivalent Mutants – Eşdeğer Mutantlar sorunu)
Altyapı İhtiyacı Standart CI runner’ları yeterlidir (1-2 CPU) Yüksek paralelizasyon gerektirir (Çok çekirdekli runner, örn: 8-16 vCPU)
Kör Nokta Tespiti Sınır değer hatalarını (boundary) kaçırır Off-by-one ve eksik assertion tespitinde %98+ başarı

Hangi Senaryoda Hangisi Seçilmeli?

Yazılım mimarisinde gümüş kurşun (silver bullet) yoktur. Trade-off analizi yaparken bağlamı (context) değerlendirmek zorundayız.

Branch Coverage Kullanmanız Gereken Senaryolar:

  • Legacy Code (Eski Sistemler): Yüzbinlerce satırlık ve test oranı %20’lerde olan bir monolit uygulamada mutation testing uygulamak, CPU kaynaklarını boşa harcamaktır. Önce branch coverage ile baz çizgiyi (baseline) %70 seviyelerine çekmek gerekir.
  • UI ve Presentation Katmanları: DOM manipülasyonları veya basit veri bağlama (data binding) işlemlerinde mutantların analizi, harcanan zamana değmez.
  • Hızlı Geri Bildirim Döngüsü (Fast Feedback): Geliştiricinin lokal bilgisayarında, her kaydetme (save) işleminde TDD yaparken milisaniyelik tepkilere ihtiyaç duyulduğunda.

Mutation Testing Kullanmanız Gereken Senaryolar:

  • Domain Driven Design (DDD) – Core Domain: Finansal hesaplamalar, yetkilendirme (authorization) matrisleri, şifreleme veya sağlık verisi işleyen kritik Core iş mantığı kısımları.
  • Yüksek Kapsamlı (High-Coverage) Modüller: SonarQube’de halihazırda %90 üzeri coverage olan modüller. Kapsam zaten yüksektir, artık kaliteyi artırma vakti gelmiştir.
  • Kütüphane ve SDK Geliştirme: Diğer ekiplerin veya dış dünyadaki kullanıcıların doğrudan projesine dahil ettiği temel API/SDK kodları.

Production Vakası: Ödeme Sisteminde %14’lük Kaçak

2023’ün 3. çeyreğinde, saniyede 400 işlem (TPS) kapasiteli bir sepet (cart) hesaplama mikroservisini yeniden faktör ediyorduk (refactoring). TypeScript tabanlı bu projenin Jest ile çalıştırılan branch coverage oranı %98.2 idi. Projeye Stryker Mutator entegre etmeye karar verdik.

İlk koşum sonuçları sarsıcıydı:

  • Oluşturulan Mutant Sayısı: 1,432
  • Öldürülen (Killed) Mutant: 1,068
  • Hayatta Kalan (Survived) Mutant: 364
  • Mutation Score: Sadece %74.5

Hayatta kalan mutantları incelediğimizde, kupon kullanım süresini doğrulayan şu satırda bir kör nokta tespit ettik:

// Orijinal Kod
if (coupon.expirationDate.getTime() >= currentRequestDate.getTime()) {
    applyDiscount();
}

Stryker, bu koddaki >= operatörünü > olarak (ConditionalsBoundary) mutasyona uğratmıştı. Ancak ilgili unit testlerde tam olarak son kullanma milisaniyesine denk gelen bir mock veri yazılmadığı için test yeşil yanmaya devam ediyordu. Eğer production’da tam sınır sürede gelen bir istek olsaydı, kupon geçerli olmasına rağmen reddedilecekti. Stryker bu eksik testi bize gösterdi.

Performans İyileştirmesi (Optimizasyon): Stryker’ın ilk koşumu CI pipeline’ında 22 dakika sürdü (Önceki Jest süresi 45 saniyeydi). CI süreçlerini tıkamamak için şu konfigürasyonları uyguladık:

// stryker.conf.json
{
  "mutate": [
    "src/domain/**/*.ts", // Sadece core domain mantığını hedefe aldık
    "!src/infrastructure/**/*.ts"
  ],
  "incremental": true, // Cache mekanizmasını aktif ettik
  "concurrency": 6, // CI runner'in 8 çekirdeğinden 6'sını Stryker'a atadık
  "coverageAnalysis": "perTest"
}

Bu konfigürasyonla mutation testing süresi PR bazında 22 dakikadan p99 2 dakika 15 saniyeye düştü. CPU kullanımında gereksiz yük engellendi.

Pratik Öneriler / Production Notları

Mimari bir standardizasyon oluştururken dikkat etmeniz gereken checklist:

  1. Bütün Projeye Uygulamayın: Mutation testing’i tüm monolith veya büyük mikroservis genelinde çalıştırmak CI/CD süreçlerinizi durma noktasına getirir. Sadece @Entity, @DomainService veya /domain klasörleriyle sınırlayın.
  2. Incremental Analysis (Artımlı Analiz) Kullanın: PiTest ve Stryker, önceki koşumların AST (soyut sözdizimi ağacı) durumunu diskte (veya S3 bucket’ta) saklayabilir. Sadece PR’da (git diff) değişen dosyaların mutantlarını analiz eden eklentiler (örneğin pitest-github-plugin) yapılandırın.
  3. Equivalent Mutants (Eşdeğer Mutantlar) İçin Tolerans Tanıyın: Bazen kod mutasyona uğrar, ancak iş mantığı teorik olarak aynı kalır (Örneğin, performans için eklenmiş bir short-circuit mantığının silinmesi sonucu değiştirmez ama performansı etkiler). Bu nedenle mutation score hedefini %100 değil, %80-85 bandında tutun.
  4. Pipeline’ı Bloke Etme (Non-Blocking): İlk 3 ay mutation testing’i pipeline’da uyarı modunda (warning) çalıştırın. Ekip mutation score konseptine alıştıktan sonra threshold değerini enforce edin (build breaker).

Sık Sorulan Sorular

Mutation testing ile Condition/Branch coverage aynı şey değil mi?

Kesinlikle hayır. Branch coverage bir kod dalından geçildiğini garanti eder. Condition/Edge coverage tüm lojik kombinasyonların (A && B) denendiğini gösterir. Ancak hiçbiri testinizin assertEquals veya expect fonksiyonlarıyla dönüş verisini doğru bir şekilde kontrol ettiğini garanti etmez. Mutation testing ise assert mantığının kalitesini ölçer.

Mutation testler CI pipeline’ımızı sürekli timeout’a düşürüyor. Ne yapmalıyız?

Bu beklenen bir durumdur. 100 sınıflık bir projede binlerce mutant oluşabilir ve test süitiniz binlerce kez baştan çalıştırılır. Çözüm: Testlerinizi CoverageAnalysis: perTest modunda yapılandırın. Bu sayede araç, sadece mutasyona uğrayan satırı test eden spesifik testleri (tüm süiti değil) çalıştırır. İşlem süresinde p99 oranında düşüş yaşarsınız.

“Equivalent Mutant” kavramı nedir? Neden yanlış pozitif oluşturur?

Kod sabote edilse bile, yazılımın dışarıdan gözlemlenebilir davranışının değişmediği durumlardır. Örneğin: int a = 5; int b = a * 1; kodunu int b = a / 1; olarak mutasyona uğratmak iş mantığını değiştirmez. Bu mutantı testin yakalayamaması normaldir ve yanlış pozitif olarak rapora yansır. Geliştiricinin bu mutantları rapor üzerinde manuel olarak “ignore” (göz ardı) etmesi gerekebilir.

Veritabanı veya API entegrasyon testlerinde (Integration Tests) mutation testing kullanılmalı mı?

Hayır. Mutation testing, yüksek hızlı, dış bağımlılığı mock’lanmış Unit (Birim) testler için tasarlanmıştır. Veritabanına bağlanan bir integration testi milisaniyeler değil saniyeler sürer. Bunu yüzlerce mutant ile çarparsanız pipeline süresi günler sürer. Kesinlikle Anti-Pattern’dir.

Sonuç

Branch coverage, projenizin test altyapısının temel hijyen standardıdır; size test edilmemiş alanların haritasını verir. Ancak testlerinizin iş mantığını gerçekten doğrulayıp doğrulamadığını kanıtlayamaz. Güvenlik veya finansal tutarlılık gerektiren kritik domain modüllerinizde, testlerinizi mutation testing süzgecinden geçirmedikçe gerçek bir kalite güvencesinden (quality assurance) söz edemezsiniz.

Action item olarak: Pazartesi günü en kritik mikroservisinizin sadece domain katmanına PiTest (Java), Stryker (JS/TS) veya Infection (PHP) entegre edin ve lokal bilgisayarınızda çalıştırın. Ortaya çıkan survived mutantların loglarını incelediğinizde, sisteminizde sessizce bekleyen potansiyel production kesintilerini kendi gözlerinizle göreceksiniz.

Bunları da beğenebilirsiniz

Mevcut C++ Görüntü İşleme Pipeline’larına Python AI Modellerini Sıfır Kopyalama ile Entegre Etmek

Bu blog yazısı, mevcut yüksek performanslı C++ görüntü işleme boru hatlarına Python tabanlı yapay zeka modellerini entegre etmek için sıfır kopyalama yaklaşımlarını inceler. Veri kopyalama maliyetlerini ortadan kaldırarak performansı artırma ve bellek kullanımını optimize etme yöntemlerini detaylandırır.

23 Şubat 2026·Devamını Oku

React Native JSI Modülü Geliştirme: C++ Host Object Pattern ile Sıfırdan Mimari İnşası

React Native Bridge’in 2-5ms’lik gecikme bariyerini aşın. C++ Host Object deseniyle 15 mikrosaniye tepki süreli, senkron ve belleği optimize eden JSI modüllerini production ortamı için sıfırdan inşa edin.

20 Temmuz 2026·Devamını Oku

Monolitik CRUD Mimariden Event Sourcing’e Geçiş: State Mutasyonlarını Immutable Loglara Kademeli Taşıma Rehberi

Production ortamında veri kaybı yaratan UPDATE komutlarından kurtulup, p99 gecikmesini 400ms’den 18ms’ye düşüren Event Sourcing mimarisine aşamalı geçiş.

26 Temmuz 2026·Devamını Oku

Tüm yazılara dön