%90 Test Coverage, %0 Güvence
Merhaba Arkadaşlar, test dünyasında çoğu kişinin doğru sandığı bir şey var. Ben de öyle sanıyordum. Ta ki production’da patlayana kadar.
Test yazıyoruz.
Coverage’ye bakıyoruz.
Rapor yeşil, CI geçiyor, herkes mutlu, özgüven tavan.
Ama gerçek şu: Coverage çoğu zaman hiçbir şey ifade etmiyor.
Bunu production’da canın yanınca anlıyorsun.
Bir gün premium hesaplamasında yanlış rakam dönmeye başladı. Test var. Coverage var. Her şey yeşil.
Test ne yapıyor?
var result = premiumService.calculate(policyId);
assertThat(result).isNotNull();
Yani?
Hiçbir şey.
Yanlış hesapla, yine geç.
0 dön, yine geç.
Çöp dön, yine geç.
Ama coverage %90 👍
Mutation Testing Nedir?
Kısaca:
Kodunu bozuyor.
Testine bakıyor.
Eğer testin bunu fark etmiyorsa -> testin işe yaramıyor.
Pitest Ne Yapıyor?
// Orijinal
if (amount > 0) {
premium.setValid(true);
}
Bunu alıyor, bozuyor:
if (amount >= 0) { ... } // boundary değiştirdi
if (amount < 0) { ... } // koşulu tersine çevirdi
// premium.setValid(true); // setter'ı sildi
Sonra testleri çalıştırıyor.
- Test fail -> iyi (killed)
- Test geçti -> geçmiş olsun (survived)
Kurulum
Spring Boot + Maven + JUnit 5:
<properties>
<pitest.version>1.23.0</pitest.version>
<pitest-junit5.version>1.2.3</pitest-junit5.version>
</properties>
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>${pitest.version}</version>
<dependencies>
<dependency>
<groupId>org.pitest</groupId>
<artifactId>pitest-junit5-plugin</artifactId>
<version>${pitest-junit5.version}</version>
</dependency>
</dependencies>
<configuration>
<targetClasses>
<param>com.example.myapp.modules.*</param>
</targetClasses>
<excludedClasses>
<param>*.dto.*</param>
<param>*.entity.*</param>
<param>*.config.*</param>
<param>*.controller.*</param>
</excludedClasses>
<targetTests>
<param>com.example.myapp.unit.**</param>
</targetTests>
<mutationThreshold>50</mutationThreshold>
<outputFormats>
<param>HTML</param>
</outputFormats>
<timestampedReports>false</timestampedReports>
</configuration>
</plugin>
mvn test pitest:mutationCoverage
# rapor: target/pit-reports/index.html
targetClasses, hangi production kodu mutasyona uğrayacak. excludedClasses, dto, entity gibi test edilmesi anlamsız şeyler. mutationThreshold, bu oranın altında build fail eder.
Bizde Ne Oldu?
641 test vardı.
JaCoCo: %66.
Güzel görünüyor.
Pitest çalıştırdık:
1704 mutation
1061 killed (%62)
429 no coverage
Test strength: %83
Demek ki test yazdığımız yerlerin bir kısmı aslında hiçbir şeyi doğrulamıyormuş.
1704 mutant oluştu. 1061’ini testlerimiz yakaladı.
429’u zaten test yazmadığımız satırlarda, onları yakalamamız mümkün değildi.
Geriye kalan 214 mutant? Test var ama yakalayamamış. Testin zayıf olduğu yerler.
Yani: test yazdığımız yerlerin %83’ü güçlü. %17’si işe yaramıyor.
| Metrik | Bizde | Ne Söylüyor |
|---|---|---|
| Mutation Coverage | %62 | Toplam mutant kill oranı |
| Test Strength | %83 | Test olan yerlerde kill oranı (asıl önemli olan bu) |
| No Coverage | 429 | Hiç test yok, coverage sorunu |
Gerçek Problemler
1. Neyi kaydettiğini doğrulamıyorsun
// Zayıf
verify(repository).save(any());
Ne kaydedildi? Bilmiyorsun.
// Güçlü
var captor = ArgumentCaptor.forClass(PolicyPremium.class);
verify(repository).save(captor.capture());
assertThat(captor.getValue().getApplicationNo()).isEqualTo("APP-001");
assertThat(captor.getValue().getPolicy()).isEqualTo(policy);
Artık setter silinirse test fail eder.
2. Boundary test yok
if (nationalId.length() < 5) {
return "***";
}
Peki length == 5? Test yok. Pitest < yerine <= koyuyor, test hala geçiyor.
@Test
void shouldHandleExactly5Chars() {
assertThat(maskTckn("12345")).isEqualTo("123**45");
}
3. “Crash olmadı” testi
assertThat(result).isNotNull();
Bu test sadece “null dönmedi” diyor. Yanlış rakam dönse de geçer. Neyi test ettiği belirsiz.
// Bunun yerine
assertThat(result.getAmount()).isEqualByComparingTo(BigDecimal.valueOf(6000);
assertThat(result.getCurrency()).isEqualTo("TRY");
JaCoCo vs Pitest
| JaCoCo | Pitest | |
|---|---|---|
| Soru | “Kod çalıştırıldı mı?” | “Doğru çalıştığı kontrol ediliyor mu?” |
| Ölçtüğü | Satır/branch coverage | Test assertion kalitesi |
| Ne zaman | Her build | Test yazdıktan sonra, kritik modüllerde |
Sonuç
Coverage seni kandırır.
Mutation testing yakalar.
Görüşmek üzere