# Yazılım Test Mühendisliği: Gereksinimden Üretime Uygulamalı Doğrulama ve Geçerleme

> Gereksinimin test edilebilir hâle getirilmesinden birim ve tümleştirme testlerine; TDD, property-based ve mutation testing, fuzzing, Testcontainers, performans, güvenlik, dayanıklılık, CI/CD ve üretim doğrulamasına uzanan uygulamalı yazılım test mühendisliği ders notu.

- Author: Muhammet Ali Köker
- Language: tr
- Canonical: https://alikoker.com.tr/yazilim-test-muhendisligi-uygulamali-dogrulama-gecerleme
- Translation: https://alikoker.com.tr/en/software-test-engineering-applied-verification-validation
- Published: 2021-02-15T12:00:00+03:00
- Modified: 2026-08-31T11:00:00+03:00
- Verified: 2026-08-31T11:00:00+03:00
- Type: article

> İyi bir test, yazılımın çalıştığını göstermekten önce hangi koşulda çalışmaması gerektiğini tarif eder. Test mühendisliğinin değeri yeşil bir raporda değil, yanlış bir varsayımı mümkün olduğunca erken ve mümkün olduğunca ucuz biçimde görünür kılmasındadır.

## Giriş: Test etmek, hata aramak değil kanıt üretmektir

Bir projede test çalışmasını sonradan devreye soktuğumuzda tablo genellikle tanıdıktır: özellik tamamlanır, uygulama ayağa kaldırılır, birkaç mutlu yol elle denenir ve ancak bundan sonra otomasyona sıra gelir. Böyle kurulan test paketi çoğu zaman kodun arkasından yürür; yüzlerce senaryo yeşile döner, kod kapsamı yükselir, fakat ilk beklenmeyen veri, eşzamanlı istek, ağ gecikmesi ya da yanlış yapılandırma üretimde bambaşka bir davranış ortaya çıkarabilir.

Burada eksik olan çoğu zaman test sayısı değildir. Daha erken cevaplanması gereken soru şudur: **Hangi davranış hakkında güven üretmeye çalışıyoruz ve bu güven hangi kanıta dayanacak?**

Bir testi, küçük ve tekrar üretilebilir bir deney olarak düşünmek daha kullanışlıdır. Başlangıç durumunu kurar, sisteme belirli bir girdi veya olay uygular, gözlenebilir sonucu toplar ve bunu bağımsız biçimde tanımlanmış beklentiyle karşılaştırırız:

```text
başlangıç durumu
       -> girdi / olay
       -> sistem davranışı
       -> gözlem
       -> beklenen sonuçla karşılaştırma
       -> karar
```

Zincirin bir halkası belirsiz olduğunda testin verdiği güven de belirsizleşir. Gereksinim açık değilse doğru sonucu tanımlayamayız; veri kontrolsüzse aynı koşulu yeniden kuramayız; ortam üretimden kopuksa yanlış bir temsil üzerinde doğru test yapmış oluruz. Gözlenebilirlik yetersizse hata oluşur ama test bunu göremez. Daha tehlikelisi, beklenen değeri üretim kodunun aynı hesabıyla türetmek test ile sistemin aynı yanlışı paylaşmasına yol açabilir.

Bu yüzden JUnit, Selenium, JMeter, Mockito veya herhangi bir CI aracını öğrenmek test mühendisliğinin ancak araç katmanını açıklar. Araç zamanla değişir; temel problem değişmez: **Bir yazılım davranışının doğru olduğuna ne kadar güvenebiliriz ve bu güveni hangi bağımsız kanıtla gerekçelendirebiliriz?**

Bu notta test faaliyetini gereksinimden üretime uzanan tek bir mühendislik problemi olarak ele alacağım. Test edilebilir gereksinimlerden başlayıp birim, tümleştirme, sistem ve kabul seviyelerine; TDD ve test doubles'dan property-based testing, mutation testing ve fuzzing'e; veritabanı, API, web, mobil, performans, güvenlik, dayanıklılık ve eşzamanlılık testlerine kadar ilerleyeceğiz. Son durak CI/CD değildir; üretimde gözlenen davranış yeniden test tasarımına dönecektir. Buradaki hedef bütün teknikleri projeye taşımak değil, **belirli bir riski en düşük maliyetle ve yeterli kanıt gücüyle sınayacak yöntemi seçebilmektir**.

Bu yaklaşım ISO/IEC/IEEE 29119 serisinin test kavramları, süreçleri, dokümantasyonu ve test tasarım teknikleriyle; ISO/IEC 25010:2023 kalite modeliyle; ISTQB CTFL 4.0.1'in risk ve test tasarımı yaklaşımıyla uyumludur. Kritik sistemlerde kalite güvencesini yaşam döngüsü boyunca ele alan IEEE 730 standardının 2026 revizyonu da aynı noktayı güçlendirir: test, son aşamadaki ayrı bir faaliyet değil, kaliteyi üretme sürecinin bir parçasıdır.

---

## 1. Kalite ile test aynı şey değildir

Bir yazılımın bütün testleri geçmesi onun kaliteli olduğunu garanti etmez. Test, kalite hakkında kanıt üretir; kaliteyi tek başına oluşturmaz.

Kalite kavramını yalnız “hatasızlık” olarak ele almak özellikle büyük sistemlerde yanıltıcıdır. Bir servis fonksiyonel olarak doğru cevap verebilir fakat 30 saniyede yanıt veriyorsa kullanılabilir değildir. Doğru sonucu üretebilir fakat belleği sürekli büyüyorsa sürdürülebilir değildir. Doğru kullanıcıya doğru veriyi gösterebilir fakat başka kullanıcının nesnesine erişim denetimi yapmıyorsa güvenli değildir. Bir masaüstü uygulaması bütün fonksiyonlarını yerine getirebilir fakat yükseltme sırasında kullanıcı verisini bozuyorsa güvenilir değildir.

ISO/IEC 25010:2023 ürün kalitesini dokuz ana karakteristik altında değerlendirir. Bir test stratejisi hazırlanırken bunların tümü aynı ağırlıkta olmak zorunda değildir; fakat hangilerinin ürün için kritik olduğu açıkça seçilmelidir. İşlevsel uygunluk, performans verimliliği, uyumluluk, etkileşim yeteneği, güvenilirlik, güvenlik, bakım yapılabilirlik, esneklik ve emniyet gibi özellikler farklı test kanıtları ister.

Bu ayrım bize ilk önemli prensibi verir:

**Test kapsamını kaynak kod yapısı değil, ürünün kalite riski belirlemelidir.**

Bir ödeme fonksiyonunda kuruş yuvarlama hatası kritik olabilir. Bir telemetri ekranında 100 ms gecikme kabul edilebilirken gerçek zamanlı bir kontrol döngüsünde aynı gecikme sistem davranışını değiştirebilir. Bir içerik sitesinde kısa süreli servis kesintisi tolere edilebilir; olay yönetim sisteminde aynı kesinti operasyonu durdurabilir.

Dolayısıyla “kaç testimiz var?” sorusu genellikle zayıf bir sorudur. Daha yararlı soru şudur:

> Hangi kalite risklerini hangi kanıtlarla kontrol altına aldık ve nerelerde hâlâ belirsizlik var?

### 1.1 Kalite güvencesi, kalite kontrol ve test

Bu üç kavramı ayırmak uygulamada önemlidir.

**Kalite güvencesi (Quality Assurance)** kusurun ürüne yerleşmeden önce azaltılabileceği süreçleri kurar. Kod inceleme ilkeleri, Definition of Done, geliştirme standartları, test stratejisi, otomasyon politikası ve değişiklik yönetimi bunun parçasıdır.

**Kalite kontrol (Quality Control)** ortaya çıkan ürünün belirlenmiş kalite ölçütlerine ne ölçüde uyduğunu değerlendirir.

**Test** bu değerlendirmede merkezi bir araçtır; fakat tek kanıt kaynağı değildir. Statik analiz, teknik inceleme, biçimsel doğrulama, telemetri ve üretim ölçümleri farklı hata sınıfları hakkında testin ulaşamadığı kanıtı sağlayabilir.

Kaliteyi yalnız test ekibine bırakan organizasyonlarda tipik sonuç şudur: geliştirici üretir, testçi hata bulur, geliştirici düzeltir, testçi yeniden dener. Bu döngü kaliteyi üretmek yerine hatayı ekipler arasında dolaştırır. Daha olgun yaklaşım, test edilebilirliği tasarıma taşıyarak hatayı mümkün olduğunca oluştuğu katmana yakın yakalamaktır.

---

## 2. Doğrulama ve geçerleme: doğru ürünü doğru biçimde mi geliştiriyoruz?

Yazılım mühendisliğinde klasik ayrım hâlâ kullanışlıdır:

- **Doğrulama (verification):** Ürünü tanımlanan gereksinim ve tasarıma uygun mu geliştiriyoruz?
- **Geçerleme (validation):** Geliştirdiğimiz ürün gerçek kullanıcı ihtiyacını karşılıyor mu?

Bir sistem teknik şartnamedeki bütün maddeleri karşılayabilir ve yine de kullanıcı açısından başarısız olabilir. Bunun tersi de mümkündür: kullanıcı arayüzünde beklenen iş akışı çalışırken veri bütünlüğü, güvenlik veya hata durumları gereksinimleri ihlal edilebilir.

Dolayısıyla test planı yalnız gereksinim maddelerinin uygulandığını kontrol etmemeli; gereksinimin kendisinin ölçülebilir, çelişkisiz ve gerçek ihtiyacı temsil eder durumda olup olmadığını da sınamalıdır.

Örneğin aşağıdaki ifade test açısından zayıftır:

```text
Sistem hızlı cevap vermelidir.
```

Bunu sınayabilmek için bağlam gerekir:

```text
P95 API yanıt süresi, 400 eşzamanlı oturum ve saniyede 1200 istek altında 250 ms'yi aşmamalıdır.
Hata oranı %0,1'in altında kalmalıdır.
Ölçüm 30 dakikalık kararlı yük bölümünde yapılmalıdır.
```

İkinci ifade yalnız daha ayrıntılı değildir; **yanlışlanabilir** olduğu için mühendislik açısından daha değerlidir.

Test edilebilir gereksinim yazmak, testçinin işinden önce analistin ve yazılım mühendisinin işidir.

---

## 3. Testin temel sınırı: tüm olasılıkları deneyemeyiz

Gerçek sistemlerin durum uzayı hızla patlar. Bir API'nin yalnızca on bağımsız boolean seçeneği varsa 1024 kombinasyon oluşur. Her biri 10 değer alabilen on alan teorik olarak `10^10` kombinasyona ulaşır. Buna kullanıcı rolleri, veri durumu, tarih, dil, ağ davranışı, işlem sırası ve eşzamanlılık eklendiğinde kapsamlı exhaustive testing pratik olarak imkânsızdır.

Profesyonel test mühendisliğinin asıl işi burada başlar: bütün olasılıkları tüketmeye çalışmak yerine, **risk hakkında en fazla bilgi üreten örnekleri seçmek**.

Buradan birkaç temel sonuç çıkar:

1. Test her zaman örnekleme yapar.
2. Örnekleme risk bilgisine dayanmalıdır.
3. Aynı davranışı tekrar tekrar sınayan yüz test, yeni bir sınır koşulunu sınayan tek testten daha az değerli olabilir.
4. Testlerin geçmesi hata olmadığını değil, test edilen örneklerde gözlenebilir hata bulunmadığını gösterir.
5. Yeni testler yalnız yeni kod için değil, yeni bilgi için yazılmalıdır.

Bu nedenle bir test paketini test sayısıyla değerlendirmem. Daha anlamlı ölçü, ürettiği bilginin yoğunluğudur: başarısız olduğunda bozulan davranışı daraltabiliyor mu, geçtiğinde hangi risk hakkında güven verdiği belli mi, aynı varsayımı daha ucuz sınayabilecek başka bir katman var mı?

---

## 4. Risk tabanlı test: önce nerede yanılmanın pahalı olduğunu bulun

Her testin maliyeti vardır. Yazılması, çalıştırılması, verisinin hazırlanması, başarısızlığının analiz edilmesi ve zaman içinde bakımı gerekir. Bu maliyet sınırsız olmadığı için test yatırımını riskle ilişkilendirmek gerekir.

Basit bir düşünme modeli:

```text
Risk ≈ gerçekleşme olasılığı × etki
```

Bu formül nicel kesinlik iddiası taşımaz; önceliklendirme aracıdır. Daha ayrıntılı modellerde tespit edilebilirlik, maruziyet süresi, geri dönüş maliyeti veya güvenlik etkisi de eklenebilir.

Bir değişikliğin test yatırımını belirlerken önce şu soruları netleştiririm:

- Hata oluşursa ne kaybederiz?
- Hatanın üretimde gerçekleşme olasılığı ne?
- Hata üretime çıkmadan hangi katmanda görülebilir?
- Otomatik test bunu güvenilir biçimde yakalayabilir mi?
- Hatanın geri dönüşü kolay mı, veri kaybına yol açıyor mu?
- Aynı kod yolu ne kadar sık çalışıyor?
- Değişiklik kaç başka bileşeni etkiliyor?
- Eşzamanlılık, zamanlama veya dış sistem bağımlılığı var mı?

Yüksek riskli alanlarda yalnız örnek tabanlı birkaç test yeterli olmayabilir; property-based testing, mutation testing, concurrency testing veya fault injection gibi daha güçlü teknikler gerekebilir. Düşük riskli bir yönetim ekranında ise geniş bir E2E paketi gereksiz bakım maliyeti üretebilir.

Test stratejisi bu ayrımı bilinçli yapmalıdır.

---

## 5. Test edilebilirlik: iyi tasarımın göz ardı edilen kalite özelliği

Bir sınıfı test etmek için uygulamanın yarısını ayağa kaldırmak zorundaysak yalnız test kodunda problem yoktur. Tasarım bize bir şey söylüyordur.

Test edilebilir bir bileşenin genellikle şu özellikleri vardır:

- girdileri açıktır,
- çıktıları ve yan etkileri gözlenebilir,
- bağımlılıkları dışarıdan verilebilir,
- global durum kullanımı sınırlıdır,
- zaman ve rastlantısallık kontrol edilebilir,
- dış sistemlerle kurduğu sınır nettir,
- hata davranışı belirgindir,
- aynı başlangıç koşulunda deterministik sonuç üretir.

Örneğin zaman bilgisini doğrudan sistem saatinden alan kod:

```java
public boolean isExpired(final Instant expiresAt) {
    return Instant.now().isAfter(expiresAt);
}
```

fonksiyonel olarak basittir fakat sınır testinde zamanı kontrol etmek zordur. Zamanı bağımlılık hâline getirmek testi ve tasarımı birlikte iyileştirir:

```java
public final class ExpirationService {
    private final Clock clock;

    public ExpirationService(final Clock clock) {
        this.clock = clock;
    }

    public boolean isExpired(final Instant expiresAt) {
        return clock.instant().isAfter(expiresAt);
    }
}
```

Artık 23:59:59, yaz saati geçişi veya belirli bir son kullanma anı deterministik biçimde sınanabilir.

Buradaki amaç “test uğruna abstraction üretmek” değildir. Ama kontrol edilemeyen zaman, ağ, dosya sistemi, rastgele sayı, process environment ve dış servisler zaten üretim davranışının belirsizlik kaynaklarıdır. Bunların sınırlarını görünür kılmak testten bağımsız olarak iyi mühendisliktir.

---

## 6. Gereksinimden test koşuluna: kabul kriteri nasıl türetilir?

Test tasarımının en pahalı hatalarından biri doğrudan test case yazmaya başlamaktır. Önce **test koşulu** belirlenmelidir: hangi davranış veya risk doğrulanacak?

Örnek gereksinim:

> Kullanıcı, hesabına arka arkaya beş başarısız parola denemesinden sonra 15 dakika boyunca giriş yapamamalıdır.

Bu cümleden tek test değil bir davranış modeli çıkar:

- İlk dört başarısız deneme hesabı kilitlememeli.
- Beşinci başarısız deneme kilitlemeli.
- Kilit 15 dakikadan önce kalkmamalı.
- 15 dakika sınırında davranış açık olmalı.
- Başarılı giriş sayacı sıfırlıyor mu?
- Farklı IP'den deneme davranışı değiştiriyor mu?
- Aynı kullanıcıya eşzamanlı denemeler atomic sayılıyor mu?
- Kilit durumu dağıtık düğümler arasında tutarlı mı?
- Yönetici kilidi açabiliyor mu?
- Kilit bilgisi saldırgana kullanıcı varlığını sızdırıyor mu?

Gereksinimden bu sorular çıkmıyorsa test case sayısını artırmak kaliteyi artırmayabilir.

### 6.1 Given–When–Then yararlıdır ama amaç değildir

Davranış odaklı bir test şu biçimde yazılabilir:

```text
Given: hesapta dört ardışık başarısız giriş var
When: beşinci yanlış parola gönderilir
Then: kimlik doğrulama reddedilir ve hesap 15 dakika kilitlenir
```

Bu format paydaşlar arasında ortak dil sağlar. Fakat her testi zorla `Given/When/Then` kalıbına sokmak da gereksizdir. Bir matematik fonksiyonunun parametrik testinde bu ritüel yalnız gürültü yaratabilir.

Test yazımında biçim, davranışın anlaşılmasını kolaylaştırdığı ölçüde değerlidir.

---

## 7. Test seviyeleri: sınırı isimle değil kapsadığı davranışla tanımlayın

“Birim testi nedir?” sorusuna iki ekip farklı cevap verebilir. Biri tek metodu kasteder, diğeri veritabanı dışındaki bütün servis katmanını. “Integration test” ifadesi de benzer biçimde belirsizdir.

Pratikte bu belirsizliği azaltmak için testleri iki ayrı eksende sınıflandırmak daha işe yarar:

### 7.1 Klasik test seviyeleri

- **Birim testi:** küçük bir davranış birimini izole veya kontrollü bağımlılıklarla sınar.
- **Tümleştirme testi:** bileşenler arasındaki sözleşmeyi ve birlikte çalışma davranışını sınar.
- **Sistem testi:** sistemin bütününü veya geniş bir dilimini dış davranış üzerinden sınar.
- **Kabul testi:** ürünün iş ve kullanıcı gereksinimlerini karşıladığını doğrular.

### 7.2 Test boyutu

Google'ın yıllardır kullandığı Small/Medium/Large yaklaşımı başka bir problemi çözer: ad yerine kaynak bağımlılığına bakar. Ağ, gerçek veritabanı, dosya sistemi, süreçler ve çalışma süresi arttıkça test büyür.

Bu sınıflandırma özellikle CI tasarımında faydalıdır:

```text
Small   -> her commit, saniyeler
Medium  -> her commit / PR, dakikalar
Large   -> seçilmiş PR / main / gece, daha uzun
```

Bir testin adı değil, **geri bildirim maliyeti** pipeline içindeki yerini belirlemelidir.

---

## 8. Test piramidi: dogma değil ekonomik model

Test piramidinin arkasındaki fikir oranlardan daha değerlidir: dar kapsamlı testler çoğunlukla hızlı ve teşhis gücü yüksekken, geniş UI/E2E testleri daha pahalıdır ve bir hata çıktığında nedenini daraltmakta zorlanır. Bu ekonomik fark, çok sayıda küçük test ile daha az sayıda uçtan uca test arasındaki klasik dağılımı açıklar.

Ancak piramidi sabit oranlara dönüştürmek doğru değildir. Bir compiler, bir veri boru hattı, bir mobil uygulama ve mikroservis ekosistemi aynı test dağılımını gerektirmez.

Ben piramidi şu ekonomik soruyla okurum:

> Bu davranışı güvenilir biçimde kanıtlayan en ucuz test seviyesi hangisidir?

Bir indirimin saf iş kuralını tarayıcı üzerinden test etmek pahalıdır. REST uç noktayın JSON serileştirmesini yalnız service unit testiyle sınamak ise eksiktir. Veritabanının transaction isolation davranışını mock repository ile kanıtlayamayız.

Doğru katman, riskin gerçekten ortaya çıktığı en dar sınırdır.

---

## 9. Bir testin anatomisi: Arrange, Act, Assert yeterli mi?

Birçok birim testinde yararlı olan temel yapı şöyledir:

```text
Arrange -> başlangıç durumu
Act     -> sınanan davranış
Assert  -> gözlenen sonuç
```

Fakat üretim seviyesinde bir test için dört ek soru gerekir:

1. **Oracle nedir?** Doğru sonucu nereden biliyoruz?
2. **İzolasyon nedir?** Test başka testlerden veya çevreden etkileniyor mu?
3. **Gözlenebilirlik nedir?** Hangi yan etkileri görebiliyoruz?
4. **Teşhis gücü nedir?** Test kırıldığında nedenini anlayabiliyor muyuz?

Örneğin yalnız HTTP 200 kontrol eden bir test çoğu zaman zayıftır:

```java
assertEquals(200, response.statusCode());
```

Yanıt şemasını, iş sonucunu, kalıcı durumu veya güvenlik etkisini doğrulamıyorsa endpoint yanlış davranıp yine 200 dönebilir.

İyi assertion yalnız “bir şey oldu” dememeli; **hangi sözleşmenin korunduğunu** göstermelidir.

---

## 10. Test oracle problemi: doğru cevabı kim söylüyor?

Test oracle, test sonucunun doğru mu yanlış mı olduğuna karar veren kaynaktır. Bu kaynak bir gereksinim, matematiksel özellik, referans uygulama, önceki sürüm, bağımsız hesaplama veya domain kuralı olabilir.

En tehlikeli durum, testin beklenen değeri üretim koduyla aynı algoritmadan hesaplamasıdır.

Üretim kodu:

```java
final BigDecimal result = price.multiply(rate).setScale(2, RoundingMode.HALF_UP);
```

Testte beklenen sonucu aynı formülle üretirsek algoritma yanlış seçilmiş olsa bile test geçebilir. Kritik finansal hesaplarda beklenen değer bağımsız bir oracle'dan gelmelidir: sabit örnekler, domain tablosu veya farklı bir doğrulanmış yöntem.

Bazı problemlerde kesin oracle yoktur. Görüntü işleme, optimizasyon, makine öğrenmesi veya büyük simülasyonlarda “tek doğru çıktı” tanımlamak zor olabilir. Bu durumda property-based veya metamorphic relations gibi teknikler devreye girer. Bunlara ileride döneceğiz.

---

## 11. Eşdeğer bölümlere ayırma: sonsuz girdi uzayını sınıflara küçültmek

Bir alan 0 ile 120 arasında yaş kabul ediyorsa her değeri test etmek gereksizdir. Davranış açısından eşdeğer sınıflar oluşturabiliriz:

```text
x < 0       -> geçersiz
0 <= x <=120 -> geçerli
x > 120     -> geçersiz
```

Her sınıftan temsilci seçmek test sayısını büyük ölçüde azaltır. Ancak en kritik hata çoğu zaman sınıfın ortasında değil sınırındadır.

Bu yüzden eşdeğer bölümleme, sınır değer analiziyle birlikte düşünülmelidir.

---

## 12. Sınır değer analizi: hataların sevdiği yer

`<` yerine `<=`, array üst sınırı, pagination offset'i, tarih aralığı ve numeric overflow gibi hatalar sınırda ortaya çıkar.

0–120 aralığı için tipik örnekler:

```text
-1, 0, 1, 119, 120, 121
```

Fakat gerçek sistemde sınır yalnız numeric değildir:

- boş string / tek karakter / maksimum uzunluk / maksimum + 1,
- 23:59:59 / 00:00:00,
- ay sonu / yıl sonu / artık gün,
- tam dolu kuyruk / bir boş slot / kapasite + 1,
- ilk sayfa / son sayfa / veri olmayan sayfa,
- tam timeout anı,
- `Long.MAX_VALUE`, `NaN`, sonsuzluk,
- Unicode birleşik karakterler,
- transaction commit ile başka işlemin okuması arasındaki sınır.

Sınır düşüncesini yalnız forma uygulamak test mühendisliğinin gücünü küçültür. Sistem davranışındaki her eşik potansiyel test sınırıdır.

---

## 13. Karar tablosu: iş kuralındaki kombinasyonları görünür kılmak

Bir davranış birden çok koşulun birleşimine bağlıysa test case listesi hızla okunamaz hâle gelir. Karar tablosu koşullar ile sonuçlar arasındaki ilişkiyi netleştirir.

Örneğin para transferi:

| Bakiye yeterli | Hesap aktif | Günlük limit uygun | Sonuç |
|---|---|---|---|
| Evet | Evet | Evet | Transfer |
| Hayır | Evet | Evet | Yetersiz bakiye |
| Evet | Hayır | Evet | Hesap pasif |
| Evet | Evet | Hayır | Limit aşıldı |
| Hayır | Hayır | Hayır | Öncelikli hata kuralına göre |

Son satır önemlidir. Birden fazla koşul başarısız olduğunda hangi hatanın döneceği tanımlı değilse test sırasında gereksinim boşluğu ortaya çıkar.

İyi test tasarımı yalnız hatayı bulmaz; **gereksinimdeki belirsizliği de bulur**.

---

## 14. Durum geçiş testi: yalnız değeri değil geçmişi test etmek

Bazı sistemlerde aynı girdi, önceki duruma göre farklı sonuç üretir. Oturum, sipariş, ödeme, çağrı, bağlantı, cihaz kontrolü veya iş akışı motoru gibi alanlarda state machine düşüncesi gerekir.

Örneğin:

```text
NEW -> APPROVED -> PROCESSING -> COMPLETED
              \-> CANCELLED
```

Yalnız geçerli geçişleri test etmek yetmez. Geçersiz geçişler de önemlidir:

```text
COMPLETED -> NEW       reddedilmeli
CANCELLED -> PROCESSING reddedilmeli
NEW -> COMPLETED       iş kuralına göre reddedilmeli
```

Daha zor hata, iki isteğin aynı anda aynı durumu değiştirmeye çalışmasıdır. Tek iş parçacığı altında bütün testler geçebilir; üretimdeki yarış durumu yine ortaya çıkabilir. Durum makineleri bu nedenle concurrency testleriyle birlikte ele alınmalıdır.

---

## 15. Pairwise ve kombinatoryal test: bütün kombinasyonlar yerine etkileşimleri hedeflemek

İşletim sistemi, tarayıcı, dil, veri tabanı, yetki rolü ve özellik bayrağı gibi çok sayıda parametrenin bütün kombinasyonlarını çalıştırmak pahalıdır.

Pairwise testing, her iki parametre değer çiftinin en az bir testte birlikte bulunmasını sağlayacak daha küçük bir kombinasyon kümesi üretir. Birçok entegrasyon hatası sınırlı sayıda parametrenin etkileşiminden doğduğu için pratikte güçlüdür.

Örneğin:

```text
OS:      Linux, Windows
DB:      PostgreSQL, Oracle, MySQL
Locale:  tr-TR, en-US, de-DE
Auth:    local, OIDC
```

Tam kombinasyon `2 × 3 × 3 × 2 = 36` testtir. Pairwise bir araç çok daha küçük bir küme oluşturabilir.

Fakat bunun garantisi “bütün hataları bulmak” değildir. Üçlü veya dörtlü etkileşim kritikse `t-wise` kapsam artırılmalıdır. Yine karar riskle verilir.


---

## 16. Keşif testi: test senaryosunun söylemediğini sistem söyler

Otomasyon tekrar edilebilir davranışlarda güçlüdür; fakat yalnız önceden düşündüğümüz soruları sorar. Keşif testi (exploratory testing), öğrenme, test tasarımı ve test yürütmenin eşzamanlı ilerlediği disiplindir.

Bu yaklaşım “rastgele tıklamak” değildir. İyi keşif testi bir charter ile başlar:

```text
Amaç: dosya yükleme iş akışının sınır ve hata davranışlarını incele
Odak: dosya boyutu, uzantı, MIME türü, ağ kesintisi, tekrar deneme, aynı isim
Süre: 45 dakika
Kanıt: ekran kaydı, request/response, log correlation id, bulunan anomaliler
```

Keşif testinde tecrübeli mühendisin domain bilgisi büyük fark yaratır. Uygulama “başarılı” mesajı verirken arka plandaki transaction rollback olmuş olabilir. UI doğru görünürken ikinci sekmede stale state bulunabilir. Browser geri tuşu, hızlı çift tıklama, ağ geçişi veya uygulamanın beklemediği işlem sırası otomatik senaryolarda hiç denenmemiş olabilir.

Bu tür bulgular sonradan otomatik regression testine dönüştürülmelidir. Keşif testi ile otomasyon rakip değildir; biri yeni bilgi keşfeder, diğeri öğrenilen davranışı kilitler.

---

## 17. Statik test: kodu çalıştırmadan hata bulmak

Her hata dinamik test gerektirmez. Gereksinim incelemesi, tasarım review, kod review, statik analiz, dependency scanning ve biçimsel denetimler yazılım çalıştırılmadan kusur bulabilir.

Statik testin en büyük ekonomik avantajı kusuru daha erken yakalamasıdır. Bir API sözleşmesindeki belirsizlik tasarım aşamasında fark edilirse birkaç satır doküman değişir. Aynı belirsizlik onlarca servis tarafından uygulandıktan sonra düzeltilirse geriye dönük uyumluluk ve veri migrasyonu problemi doğabilir.

Kod review sırasında özellikle şu sorular değerlidir:

- Davranış gereksinimle uyumlu mu?
- Hata yolu tasarlanmış mı?
- Race condition olasılığı var mı?
- Null, boş değer ve sınır davranışı açık mı?
- Yan etki idempotent mi?
- Kaynak yaşam döngüsü doğru mu?
- Güven sınırı nerede?
- Loglanan veri hassas mı?
- Bu kod nasıl test edilir?

Son soru çoğu zaman tasarım problemini ortaya çıkarır.

2026 itibarıyla ISO/IEC/IEEE 29119 serisine statik analiz için yeni bir bölüm hazırlanıyor olsa da bu çalışma henüz komite taslağı seviyesindedir. Statik gözden geçirmeler için ISO/IEC 20246 ve mevcut kalite süreçleri hâlen daha yerleşik başvuru noktalarıdır.

---

## 18. Birim testi: küçük olmak değil tek bir davranışı açıklamak

Birim testinin değeri, çok hızlı geri bildirim üretmesi ve hatayı dar bir bölgeye lokalize etmesidir.

Kötü birim testinde genellikle şunlar görülür:

- Spring context gereksiz yere açılır,
- veritabanı gerekir,
- test sınıfları arasında veri paylaşılır,
- private metoda erişmek için reflection kullanılır,
- bir test on farklı davranışı doğrular,
- assertion başarısız olduğunda neyin bozulduğu anlaşılmaz.

Temiz bir örnek:

```java
final class PriceCalculatorTest {
    private final PriceCalculator calculator = new PriceCalculator();

    @ParameterizedTest
    @CsvSource({
        "100.00,0.10,90.00",
        "100.00,0.00,100.00",
        "0.00,0.50,0.00"
    })
    void appliesDiscount(final BigDecimal price, final BigDecimal rate, final BigDecimal expected) {
        assertEquals(expected, calculator.applyDiscount(price, rate));
    }
}
```

Burada testin amacı sınıfın framework ile ayağa kalkabildiğini değil iş kuralını doğrulamaktır. Spring'e ihtiyaç yoksa Spring başlatılmamalıdır.

Spring Framework dokümantasyonunun da vurguladığı gibi IoC/DI'nin iyi uygulanması POJO'ların `new` ile oluşturulup JUnit altında test edilebilmesini kolaylaştırır. Bu yalnız hız kazancı değildir; mimari sınırların net olduğuna dair bir işarettir.

---

## 19. Assertion kalitesi: testin dili sonucu belirler

Test yalnız doğru çalışmalı değil, başarısız olduğunda iyi konuşmalıdır.

Aşağıdaki assertion teknik olarak çalışır:

```java
assertTrue(result.size() == 3);
```

Ancak hata mesajı bağlam açısından zayıftır. Koleksiyonun yalnız boyutunu değil içeriğini de doğrulamak gerekiyorsa daha ifade edici assertion tercih edilir.

Assertion seçiminde iki uçtan kaçınmak gerekir:

**Eksik assertion:** test yalnız exception atılmadığını doğrular.

**Aşırı assertion:** test davranışla ilgisi olmayan onlarca alanı sabitler ve en küçük refactor'da kırılır.

Testin görevi uygulamanın bütün iç durumunu dondurmak değil, **sözleşmeyi sabitlemektir**.

Örneğin REST yanıtında `createdAt` alanının tam milisaniyesini doğrulamak iş gereksinimi değilse test gürültüsü üretir. Buna karşılık `status`, `id` ve domain sonucunu doğrulamak sözleşme olabilir.

---

## 20. Test doubles: gerçek bağımlılığı ne zaman değiştirmeliyiz?

Test double genel bir üst kavramdır. Her double aynı amaçla kullanılmaz.

- **Dummy:** yalnız parametreyi doldurur, davranış beklenmez.
- **Stub:** belirli girdilere önceden hazırlanmış cevap verir.
- **Fake:** hafif fakat çalışan bir uygulamadır; bellek içi repository gibi.
- **Mock:** belirli etkileşimlerin gerçekleşip gerçekleşmediğini doğrular.
- **Spy:** gerçek veya kısmi davranış üzerinde çağrıları gözler.

Bu ayrım önemlidir çünkü yanlış double seçimi testi üretimden uzaklaştırabilir.

Örneğin bir repository mock'landığında JPA mapping, SQL üretimi, constraint, collation ve transaction davranışları test edilmez. Service'in repository ile konuşma mantığını sınamak için mock uygundur; repository'nin gerçekten çalıştığını kanıtlamak için değildir.

```java
final class UserServiceTest {
    private final UserRepository repository = mock(UserRepository.class);
    private final UserService service = new UserService(repository);

    @Test
    void returnsActiveUser() {
        final User user = new User(7L, true);
        when(repository.findById(7L)).thenReturn(Optional.of(user));

        final User result = service.getActive(7L);

        assertEquals(7L, result.id());
    }
}
```

Bu test `UserService` kararını sınar. Veritabanını sınamaz.

---

## 21. Mock kullanımı: izolasyon sağlarken gerçeği kaybetmemek

Mock'lar güçlüdür ama aşırı kullanıldığında test paketini uygulama ayrıntısına bağlar.

Şu tür testler kırılgandır:

```text
verify(repository).findById(7)
verify(mapper).toDto(user)
verify(logger).info(...)
verify(metrics).increment(...)
```

Eğer dışarıdan gözlenen davranış aynı kalırken iç algoritma değiştiğinde testlerin çoğu kırılıyorsa testler davranışı değil uygulama ayrıntısını kilitlemiştir.

Mock doğrulaması özellikle şu durumlarda değerlidir:

- mesajın gerçekten yayınlanması gerekiyorsa,
- ödeme sağlayıcısına ikinci kez çağrı yapılmaması gerekiyorsa,
- audit kaydı iş gereksinimiyse,
- yan etkinin hiç gerçekleşmemesi kritikse.

Diğer durumlarda sonuç üzerinden state verification daha dayanıklı olabilir.

Bir başka hata deep stubbing'dir:

```text
when(a.getB().getC().getD()).thenReturn(...)
```

Test böyle bir zincir gerektiriyorsa üretim kodundaki sıkı bağlılığı de sorgulamak gerekir.

---

## 22. TDD: test yazma tekniğinden çok tasarım geri besleme döngüsü

Test-Driven Development çoğu zaman “önce test yaz” cümlesine indirgenir. Oysa asıl mekanizma kısa geri besleme döngüsüdür:

```text
Red -> Green -> Refactor
```

**Red:** İstenen yeni davranışı tanımlayan ve doğru nedenle başarısız olan küçük test yazılır.

**Green:** Testi geçiren en küçük doğru değişiklik yapılır.

**Refactor:** Davranış korunurken tasarım iyileştirilir.

TDD'nin en değerli etkilerinden biri API tasarımında görülür. Kullanması zor sınıf genellikle test etmesi de zor sınıftır. Test ilk müşteri gibi davranır; gereksiz bağımlılık, karmaşık constructor, global state veya belirsiz sorumluluk erken görünür olur.

Ancak TDD her problemi çözmez. UI keşfi, performans karakteristiği, üretim konfigürasyonu veya dağıtık sistem hata modları yalnız unit TDD ile güvence altına alınamaz. TDD bir araçtır; test stratejisinin tamamı değildir.

### 22.1 Test önce yazıldı diye iyi test olmaz

Test kötü oracle kullanıyorsa, yanlış katmanı sınarsa veya uygulama ayrıntısına bağlanırsa önce yazılmış olması kalite sağlamaz. TDD disiplinini ritüele dönüştürmek yerine şu soruyu korumak gerekir:

> Bu test tasarıma hangi geri bildirimi veriyor?

---

## 23. BDD ve Specification by Example: gereksinimi yürütülebilir örneğe çevirmek

BDD, teknik test kodundan çok ortak dil problemini çözmeye çalışır. Ürün sahibi “müşteri indirim alır” derken geliştirici farklı, testçi farklı yorumlamamalıdır.

Örnekler gereksinimi keskinleştirir:

```text
Given müşteri GOLD seviyesinde
And sepet toplamı 1000 TL
When ödeme hesaplanır
Then %8 sadakat indirimi uygulanır
```

Fakat BDD dosyaları test otomasyonu uğruna yüzlerce mekanik senaryoya dönüşürse bakım maliyeti hızla artar. Her low-level teknik koşulu Gherkin ile ifade etmek gereksizdir.

Specification by Example yaklaşımında amaç, paydaşın anlayabildiği örnekleri aynı zamanda doğrulama varlığına dönüştürmektir. Domain açısından anlamlı sınırlar burada en değerlidir.

---

## 24. Property-based testing: örnek yerine değişmezi sınamak

Örnek tabanlı test belirli girdileri seçer. Property-based testing ise daha genel bir özelliği tanımlar ve çok sayıda üretilmiş girdi üzerinde sınar.

Bir sıralama algoritması için yalnız:

```text
[3, 1, 2] -> [1, 2, 3]
```

demek yerine şu özellikleri test edebiliriz:

```text
çıktı sıralıdır
çıktı girdinin aynı elemanlarını içerir
sort(sort(x)) == sort(x)
çıktı uzunluğu girdi uzunluğuna eşittir
```

Bu yaklaşım insanın düşünmediği kombinasyonları keşfetmekte güçlüdür. jqwik gibi JVM araçları başarısız girdiyi küçültüp en küçük karşı örneği üretebilir.

Örneğin kavramsal olarak:

```java
@Property
void reversingTwiceReturnsOriginal(@ForAll final List<Integer> values) {
    final List<Integer> reversed = new ArrayList<>(values);
    Collections.reverse(reversed);
    Collections.reverse(reversed);
    assertEquals(values, reversed);
}
```

Property seçimi zordur. Yanlış veya zayıf property yüz bin girdiyle çalışsa da az güven üretir. Asıl mühendislik, domain değişmezini doğru ifade etmektir.

---

## 25. Mutation testing: testlerin gerçekten hata yakalayıp yakalamadığını ölçmek

Kod kapsamı (code coverage) yalnız hangi kodun çalıştırıldığını söyler. Testin sonucu gerçekten kontrol edip etmediğini söylemez.

Mutation testing üretim koduna kontrollü küçük değişiklikler uygular:

```text
>  -> >=
+  -> -
true -> false
koşulu kaldır
return değerini değiştir
```

Sonra test paketi yeniden çalıştırılır. Testler mutantı öldürüyorsa davranış değişikliğini fark etmiştir. Testler yeşil kalıyorsa iki olasılık vardır:

1. test eksiktir,
2. mutant semantik olarak eşdeğerdir.

Bu teknik özellikle yüksek kod kapsamına sahip ama düşük assertion kalitesine sahip projelerde şaşırtıcı sonuç verir.

Örneğin:

```java
if (age >= 18) {
    return ADULT;
}
```

mutant:

```java
if (age > 18) {
    return ADULT;
}
```

18 yaş sınırını test etmiyorsak branch coverage %100 olsa bile mutant yaşayabilir.

JVM ekosisteminde PIT/PITest bu amaçla yaygın kullanılır. Mutation score bir hedef sayıya körü körüne çevrilmemelidir; fakat kritik iş kurallarında testlerin gerçek kusur yakalama gücünü değerlendirmek için çok değerlidir.

---

## 26. Fuzzing: girdiyi insan hayal gücünden kurtarmak

Fuzz testing, programa çok sayıda beklenmeyen, bozuk veya üretilmiş girdi vererek crash, hang, memory error, assertion failure veya güvenlik problemi arar.

Özellikle parser, protocol implementation, dosya formatı, binary decoder, ağ servisi ve native kodda etkilidir.

Basit random input fuzzing başlangıç olabilir fakat modern fuzzing coverage-guided veya structure-aware olabilir. Amaç yalnız rastgele veri üretmek değil, **daha önce keşfedilmemiş program durumlarına ulaşmaktır**.

Web güvenliğinde fuzzing input validation sınırlarını zorlar. Native kodda AddressSanitizer gibi runtime instrumentation ile birleştiğinde memory corruption bulabilir. API testing'de OpenAPI şemasından geçerli/geçersiz istek varyasyonları üretilebilir.

Fuzzer'ın bulduğu her çökme regresyon testine dönüştürülmelidir. Aksi hâlde aynı hata daha sonra yeniden ortaya çıkabilir.

---

## 27. Metamorphic testing: doğru cevabı bilmediğimizde ilişkiyi test etmek

Bazı sistemlerde tek bir girdi için doğru sonucu hesaplamak çok pahalı veya imkânsızdır. Arama motoru, görüntü işleme, bilimsel hesaplama, optimizasyon ve yapay zekâ modelleri buna örnektir.

Metamorphic testing, çıktıların kendisi yerine girdiler arasındaki beklenen ilişkiyi sınar.

Örneğin bir görüntü sınıflandırıcıda küçük parlaklık değişiminin tamamen ilgisiz sınıfa sıçramaması beklenebilir. Bir mesafe fonksiyonunda:

```text
d(a, b) == d(b, a)
d(a, a) == 0
```

özellikleri oracle görevi görür.

Vergi hesaplamasında bütün girdiler aynı kurallarla oransal artırıldığında belirli monotonluk ilişkileri beklenebilir. Route planner'da yol ağına kullanılmayan uzak bir düğüm eklemek mevcut rotayı değiştirmemelidir.

Metamorphic relation domain bilgisidir. Otomasyon yalnız onu çok sayıda örnek üzerinde yürütür.

---

## 28. Tümleştirme testi: mock'un bittiği yerde gerçek sözleşme başlar

Birim testlerinde bağımlılıkları kontrol ederiz. Tümleştirme testinde ise bileşenler arasındaki gerçek sınırı sınarız.

Riskler genellikle şuralardadır:

- serialization/deserialization,
- SQL dialect,
- transaction davranışı,
- HTTP header ve content type,
- timeout/retry,
- mesaj sıralaması,
- schema migration,
- TLS/sertifika,
- authentication token,
- encoding/collation,
- ağ hatası.

Mock servis bize `User` nesnesi döndürebilir; gerçek HTTP servisi `user_id` alanını `userId` yerine gönderebilir. Mock repository duplicate key davranışını taklit etmeyebilir. Fake broker mesajın gerçek acknowledgment semantiğini göstermeyebilir.

Test double ile kazandığımız hızın bedeli gerçek entegrasyondan uzaklaşmaktır. Bu açığı, az sayıda fakat doğrudan gerçek sözleşmeyi sınayan tümleştirme testleriyle kapatmak gerekir.

---

## 29. Veritabanı testi: in-memory başarı üretim doğruluğu değildir

Veritabanı testlerinde sık karşılaştığım yanlış güven kaynaklarından biri, üretimde kullanılan motordan farklı bir bellek içi veritabanında geçen repository testlerini gerçek veritabanı davranışının kanıtı saymaktır.

H2 veya başka gömülü motorlar hızlıdır ve bazı testlerde yararlıdır. Fakat SQL dialect, null sıralaması, collation, transaction isolation, locking, sequence davranışı, tarih tipleri, JSON fonksiyonları, constraint ve optimizer özellikleri üretim veritabanından farklı olabilir.

Özellikle şu tür kodlarda gerçek motor testi gerekir:

- vendor-specific SQL,
- karmaşık JPA mapping,
- pessimistic/optimistic locking,
- native query,
- migration script,
- büyük indeksli sorgu,
- concurrent update,
- transaction propagation.

Testcontainers burada güçlü bir araçtır. Test için geçici gerçek PostgreSQL, MySQL veya desteklenen başka servisleri container içinde başlatıp üretime daha yakın sözleşme testi yapılabilir.

Örnek yapı:

```java
@Testcontainers
@DataJpaTest
final class AccountRepositoryTest {
    @Container
    static final PostgreSQLContainer<?> POSTGRES = new PostgreSQLContainer<>("postgres:18");

    @DynamicPropertySource
    static void properties(final DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", POSTGRES::getJdbcUrl);
        registry.add("spring.datasource.username", POSTGRES::getUsername);
        registry.add("spring.datasource.password", POSTGRES::getPassword);
    }
}
```

Burada container tag'i projede sabit ve kontrollü tutulmalıdır. Testin tekrar üretilebilirliği “latest” etiketine bırakılmamalıdır.

### 29.1 Test veritabanı gerçekçi ama bağımsız olmalı

üretim veritabanı dökümünü doğrudan test ortamına kopyalamak KVKK/mahremiyet ve tekrar üretilebilirlik açısından ciddi risk taşır. Gerekli veri sentetik veya anonimleştirilmiş olmalı; test kendi başlangıç durumunu kurabilmelidir.

---

## 30. Transaction, kilit ve eşzamanlılık testleri

Tek iş parçacığı ile geçen CRUD testleri transaction sisteminin yalnız en kolay yolunu sınar.

Gerçek hata örnekleri şunlardır:

- lost update,
- dirty read,
- non-repeatable read,
- phantom read,
- duplicate insert race,
- deadlock,
- yanlış lock scope,
- transaction dışına taşan lazy load,
- retry sırasında aynı yan etkinin iki kez oluşması.

Örneğin iki thread aynı stok kaydını aynı anda azaltıyorsa şu invariant korunmalıdır:

```text
stock >= 0
```

Test iki işlemi kontrollü bir barrier ile aynı noktaya getirip sonucu tekrar tekrar sınayabilir. `Thread.sleep()` ile zamanlama ummak yerine `CountDownLatch`, `CyclicBarrier` veya uygun concurrency primitive kullanılmalıdır.

Eşzamanlılık testi deterministik hâle getirilemiyorsa en azından yarış penceresini büyüten kontrollü bir fixture kurulmalıdır. “1000 kez çalıştırınca belki hata verir” faydalı bir stress yaklaşımı olabilir ama temel regression testi için zayıftır.


---

## 31. API testi: uç noktayı değil sözleşmeyi sınamak

REST veya RPC servisinde yalnız HTTP status code kontrol etmek API testinin küçük bir bölümüdür. Gerçek sözleşme şunları kapsar:

- request schema,
- response schema,
- zorunlu/opsiyonel alanlar,
- null semantiği,
- hata modeli,
- idempotency,
- pagination,
- ordering,
- authentication/authorization,
- rate limit,
- timeout,
- versioning,
- geriye dönük uyumluluk.

Bir API'nin `200 OK` dönmesi, iş sonucu doğru olduğu anlamına gelmez. Örneğin transfer isteği 200 dönüp veritabanına hiç yazılmamış olabilir. Ya da response doğru görünürken aynı idempotency key ile ikinci istek ikinci transferi oluşturabilir.

API testinde en az üç seviye ayrımı yararlıdır:

```text
controller slice       -> HTTP mapping ve validation
application integration -> service + persistence + security
external contract       -> gerçek network sınırı ve wire format
```

Her şeyi full E2E yapmak teşhisi zorlaştırır. Her şeyi controller mock testine bırakmak ise gerçek sözleşmeyi kaçırır.

### 31.1 Negatif API testi

Başarılı yol testlerin yalnız başlangıcıdır. Şunlar ayrı değer taşır:

- eksik zorunlu alan,
- yanlış content type,
- bozuk JSON,
- aşırı büyük payload,
- bilinmeyen alan,
- geçersiz enum,
- yetkisiz nesne erişimi,
- expired token,
- duplicate request,
- concurrency,
- rate limit aşımı.

Hata yanıtının kararlı bir sözleşmesi olmalıdır. Stack trace veya iç exception mesajının doğrudan dışarı taşınması hem test edilebilirliği hem güvenliği bozar.

---

## 32. Contract testing: servisler birbirinden bağımsız değişebilir mi?

Mikroservis veya dağıtık sistemde bütün entegrasyonları tek E2E ortamında doğrulamak pahalı ve kırılgandır. Consumer-driven contract testing, tüketicinin gerçekten ihtiyaç duyduğu sözleşmeyi sağlayıcıya karşı otomatik doğrular.

Örneğin Consumer yalnız şu alanları kullanıyor olabilir:

```json
{
  "id": 42,
  "status": "ACTIVE"
}
```

Provider yeni alan eklediğinde sorun yoktur; `status` alanını kaldırdığında veya tipini değiştirdiğinde contract testi kırılır.

Contract testing E2E'nin tamamını ortadan kaldırmaz. Network routing, authentication infrastructure, deployment configuration ve gerçek veri akışı gibi riskler hâlâ daha geniş test ister. Fakat servisler arası schema uyumsuzluklarını çok daha erken yakalar.

---

## 33. Spring Boot test dilimleri: gerektiği kadar context açmak

Spring Boot test desteğinin güçlü taraflarından biri uygulamanın yalnız belirli bir dilimini ayağa kaldırabilmesidir.

`@WebMvcTest`, MVC/controller sınırını; `@DataJpaTest`, JPA veri erişim katmanını hedefler. `@SpringBootTest` ise tam `ApplicationContext` gerektiğinde kullanılır.

Bu ayrımı doğru kullanmak test paketi süresini dramatik biçimde etkileyebilir.

### 33.1 Controller testi

Kavramsal örnek:

```java
@WebMvcTest(UserController.class)
final class UserControllerTest {
    @Autowired
    private MockMvc mvc;

    @MockitoBean
    private UserService service;

    @Test
    void returnsUser() throws Exception {
        when(service.get(7L)).thenReturn(new UserDto(7L, "Ali"));

        mvc.perform(get("/api/users/7"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.id").value(7))
            .andExpect(jsonPath("$.name").value("Ali"));
    }
}
```

Burada hedef SQL değildir. Request mapping, JSON serileştirme, validation ve HTTP davranışı sınanır.

### 33.2 Full context ne zaman gerekir?

`@SpringBootTest` şu riskleri doğrulamak için değerlidir:

- bean wiring,
- configuration properties,
- security filter chain,
- application events,
- gerçek repository/service entegrasyonu,
- custom auto-configuration,
- startup failure.

Ancak her test sınıfını `@SpringBootTest` yapmak test paketini gereksiz yere yavaşlatır. Context cache bir miktar yardımcı olsa da farklı property/profile kombinasyonları cache'i böler.

Testin kapsamı riski geçmemelidir.

---

## 34. Web arayüzü testi: DOM ile kullanıcı deneyimi aynı şey değildir

UI otomasyonunda Selenium gibi browser-driven araçlar uzun süredir kullanılır. Modern test stratejisinde browser testi hâlâ değerlidir; özellikle gerçek JavaScript, tarayıcı davranışı, routing, form etkileşimi ve görsel olmayan kullanıcı akışını doğrular.

Fakat UI testleri doğal olarak geniş kapsamlıdır:

```text
browser
 -> frontend
 -> API
 -> authentication
 -> backend
 -> database
```

Bu zincirde herhangi bir bileşen başarısız olduğunda test kırılır. Dolayısıyla çok sayıda UI testi, test paketini yavaş ve kararsız hâle getirebilir.

### 34.1 Selector seçimi

CSS yapısına sıkı bağlı selector:

```text
body > div:nth-child(3) > div > table > tr:nth-child(2) > td:nth-child(4)
```

küçük bir UI yeniden düzenlemesinde kırılır.

Daha kararlı bir yaklaşım semantic rol veya açık test identifier kullanmaktır. Ancak üretim DOM'u yalnız test için anlamsız attribute'larla doldurmak da doğru değildir. Kullanıcı açısından anlamlı role/name erişilebiliyorsa önce onu tercih etmek gerekir.

### 34.2 UI testinin görevi

Browser testinde düşük seviye iş kuralını yüz farklı veriyle yeniden sınamak yerine kritik kullanıcı yolunu doğrulamak daha değerlidir:

```text
login
 -> arama
 -> kayıt açma
 -> değişiklik
 -> kaydetme
 -> yeniden yükleme
 -> sonucun korunması
```

Aynı iş kuralının sınır kombinasyonları daha hızlı katmanlarda test edilir.

---

## 35. Kullanılabilirlik testi: yazılım teknik olarak doğru ama insan için yanlış olabilir

Kullanılabilirlik testinde amaç yalnız butonun çalışması değildir. Kullanıcı görevi doğru, hızlı ve düşük hata oranıyla tamamlayabiliyor mu?

Ölçülebilecek göstergeler:

- görevi tamamlama oranı,
- görev süresi,
- hata sayısı,
- geri dönüş/iptal sayısı,
- yardım ihtiyacı,
- öğrenilebilirlik,
- kullanıcı memnuniyeti.

Think-aloud oturumları, ekran kaydı, gözlem ve kontrollü görevler nitel bilgi üretir. Telemetri, funnel ve A/B testleri nicel veri üretebilir.

Ancak A/B testi her zaman kalite testi değildir. Daha fazla tıklanan tasarım daha erişilebilir veya daha güvenli olmayabilir. Ölçülen metrik, gerçek kullanıcı hedefiyle ilişkilendirilmelidir.

Kritik sistemlerde kullanılabilirlik doğrudan güvenlik ve emniyet özelliğine dönüşebilir. Operatörün alarmı yanlış yorumlaması teknik olarak “çalışan” bir ekranın başarısızlığıdır.

---

## 36. Erişilebilirlik testi: kullanılabilirliğin ayrıcalıklı olmayan hâli

Web ve mobil uygulamada klavye erişimi, semantic HTML, screen reader davranışı, focus sırası, kontrast, zoom ve hareket kısıtları test edilmelidir.

Erişilebilirlik testinin bir bölümü otomatik araçlarla bulunabilir; fakat bütün sorunlar bulunamaz. Örneğin butonun accessible name'inin var olması, adın kullanıcı için anlamlı olduğu anlamına gelmez.

Pratik test akışı:

```text
mouse kullanmadan kritik akışı tamamla
focus sırasını izle
screen reader ile başlık/landmark yapısını dinle
200% ve 400% zoom altında akışı dene
renge bağımlı anlam olup olmadığını kontrol et
```

Erişilebilirlik sonradan “uyum” eklemek yerine UI component tasarımına yerleştirilirse test maliyeti düşer.

---

## 37. Mobil uygulama testi: cihaz farkı gerçek bir sistem parametresidir

Mobil uygulamada masaüstünden farklı riskler vardır:

- işletim sistemi sürümü,
- ekran boyutu ve yoğunluğu,
- düşük bellek,
- CPU sınırlaması,
- pil tüketimi,
- mobil ağ geçişleri,
- offline çalışma,
- izinler,
- uygulamanın arka plana alınması,
- çağrı/bildirim kesintileri,
- orientation,
- kurulum/güncelleme.

Bir mobil test stratejisi emulator/simulator ile gerçek cihazı birlikte kullanmalıdır. Emulator geniş kombinasyon taraması için ekonomiktir; kamera, bluetooth, sensör, termal davranış veya üreticiye özgü sorunlarda gerçek cihaz gerekir.

### 37.1 Kesme testi

Uygulama dosya yüklerken telefon çağrısı gelir. Ağ Wi-Fi'dan LTE'ye geçer. Uygulama arka plana alınır ve işletim sistemi süreci sonlandırır.

Bu durumlarda sistem:

- veri kaybetmemeli,
- yarım işlemi tutarlı bırakmalı,
- yeniden başlatmada durumu kurtarmalı,
- aynı işlemi iki kez göndermemeli.

Mobil testte “happy path” çok hızlı yetersiz kalır.

---

## 38. Test verisi mühendisliği: veri fixture değil sistem girdisidir

Test verisi çoğu projede test kodundan daha büyük sorun hâline gelir.

Kötü işaretler:

- bütün testler aynı devasa SQL dump'ına bağımlı,
- test sırası önem taşıyor,
- fixture değişince yüzlerce test kırılıyor,
- üretim ortamındaki kişisel veri test ortamında bulunuyor,
- test verisinin neden gerekli olduğu bilinmiyor.

İyi test verisi mümkün olduğunca **minimal ve amaca özgü** olmalıdır.

Örneğin yetkilendirme testi için yüz kullanıcıya gerek yoktur:

```text
owner
otherUser
admin
```

çoğu davranışı gösterebilir.

Builder yaklaşımı test kodunu okunabilir yapabilir:

```java
final User user = UserTestData.user()
    .id(7L)
    .role(USER)
    .active(true)
    .build();
```

Ancak builder bütün alanlara rastgele varsayılan değer verirse testin hangi veriye bağlı olduğu gizlenebilir. Kritik alanlar açık tutulmalıdır.

### 38.1 Sentetik veri ve anonimleştirme

Gerçekçi dağılım gerektiren performans testlerinde sentetik üretici, istatistiksel profile yakın veri üretebilir. Üretim verisi kullanılması zorunluysa geri döndürülemez anonimleştirme ve erişim kontrolü gerekir.

Test ortamı veri sızıntısının kolay olduğu bir yan kanal olmamalıdır.

---

## 39. Çevre yönetimi: “benim makinemde geçiyor” test sonucu değildir

Test sonucu yalnız kodun değil çevrenin de fonksiyonudur:

```text
result = f(code, config, data, OS, runtime, network, dependencies, time)
```

Test sonucunun hangi çevre koşullarında üretildiği görünür olmadıkça aynı sonucu yeniden üretmek de zorlaşır.

Container ve infrastructure-as-code yaklaşımları test ortamını tekrar üretilebilir yapar. Bununla birlikte container kullanmak üretim eşitliği sağlamaz. Kernel, storage, network policy, service mesh, TLS, secret provider ve dış bağımlılık farkları hâlâ bulunabilir.

Test çevreleri için şu politika yararlıdır:

- version pinning,
- deterministic configuration,
- otomatik provision/cleanup,
- environment drift kontrolü,
- secret ayrımı,
- üretime yakın kritik bağımlılıklar.

---

## 40. Flaky test: bazen geçen test test değildir

Aynı commit, aynı input ve aynı çevrede test bazen geçip bazen kalıyorsa test paketi güven üretmez. Kararsız test zamanla ekip tarafından görmezden gelinir; gerçek failure da “yine flaky” denerek yeniden çalıştırılır.

Google'ın yayımladığı verilerde daha büyük testlerin flaky olma eğiliminin belirgin biçimde yükseldiği görülmüştür. Bu şaşırtıcı değildir: ağ, thread, process, disk ve dış servis sayısı arttıkça deterministik olmayan davranış kaynakları artar.

Yaygın nedenler:

- gerçek saat,
- random seed,
- test sırasına bağımlılık,
- paylaşılan statik durum,
- dış network,
- timeout sınırında assertion,
- `sleep`,
- yarış durumu,
- cleanup eksikliği,
- paralel çalışmada port/file çakışması,
- eventually-consistent sistemi anında doğrulamak.

Flaky test için çözüm “retry 3” değildir. Retry geçici teşhis aracı olabilir; kök neden gizlenmemelidir.

---

## 41. Zamanı test etmek: `sleep` yerine kontrol

Zamana bağlı kod, test paketinin en sık kararsızlık üreten alanlarından biridir.

Kötü yaklaşım:

```java
Thread.sleep(1000);
assertTrue(cache.isExpired(key));
```

Bu test hem yavaş hem kırılgandır. CI sunucusu yavaşladığında farklı davranabilir.

Daha iyi yaklaşımda zaman injectable'dır:

```java
final Clock clock = Clock.fixed(Instant.parse("2026-08-31T08:00:00Z"), ZoneOffset.UTC);
final CachePolicy policy = new CachePolicy(clock);
```

Asenkron durumlarda gerçek zaman ilerlemesi gerekiyorsa polling tabanlı bounded wait kullanılabilir:

```text
10 saniyeye kadar bekle
koşulu belirli aralıklarla kontrol et
koşul oluşur oluşmaz devam et
```

Sabit 10 saniye uyumak hem hızlı durumda 9 saniye israf eder hem yavaş durumda yine başarısız olabilir.

---

## 42. Rastlantısallık testi: seed bir debug aracıdır

Randomized testing yararlıdır; fakat başarısız test yeniden üretilemiyorsa değerinin önemli kısmını kaybeder.

Test çalıştırmasında kullanılan seed loglanmalıdır:

```text
seed=784311257
```

Aynı seed ile aynı veri dizisi üretilebilmelidir. Property-based testing framework'leri bunu çoğunlukla otomatik sağlar.

Üretim kodunda güvenlik amaçlı CSPRNG ile deterministik test üretecini karıştırmamak gerekir. Test edilebilirlik için güvenlik algoritmasını zayıflatmak kabul edilemez; bunun yerine rastlantısallık kaynağının arayüzü kontrol edilir.

---

## 43. Asenkron ve event-driven sistem testi

Mesajlaşma sistemlerinde request/response zihniyeti yetersiz kalır. Mesaj yayınlanır, consumer daha sonra işler, başka bir event üretir ve sonuç eventual consistency ile görünür olabilir.

Testte şu özellikler önemlidir:

- mesaj en az bir kez mi, en fazla bir kez mi işleniyor?
- duplicate event idempotent mi?
- ordering garanti mi?
- consumer crash olursa mesaj ne olur?
- dead-letter queue çalışıyor mu?
- poison message sistemi kilitliyor mu?
- retry backoff doğru mu?
- transaction ile message publish arasında dual-write riski var mı?

`publish(); assertDatabaseImmediately();` çoğu zaman yanlış testtir. Sistem sözleşmesi eventual ise test de bounded eventual assertion kullanmalıdır.

Örneğin:

```text
publish order-created
within 5 s:
    invoice status == CREATED
```

Bu timeout “5 saniye bekle” değil, maksimum kabul edilen sistem davranışıdır.

---

## 44. Performans testi: hızlı mı sorusundan önce hangi yükte sorusunu cevaplayın

Performans tek sayı değildir. En az şu boyutlar ayrılmalıdır:

- latency,
- throughput,
- concurrency,
- resource utilization,
- queueing,
- hata oranı,
- saturation.

Bir servisin tek kullanıcıyla 10 ms cevap vermesi kapasitesi hakkında çok az bilgi verir. Yük arttığında connection pool, thread pool, GC, database lock veya dış servis darboğaz olabilir.

### 44.1 Performans test türleri

**Load test:** beklenen normal/yüksek iş yükünde davranışı ölçer.

**Stress test:** kapasite sınırının üzerine çıkarak sistemin nasıl bozulduğunu ölçer.

**Spike test:** ani yük artışını sınar.

**Soak/endurance test:** uzun süreli yükte memory leak, connection leak ve kaynak birikimini arar.

**Capacity test:** kabul edilebilir SLO altında maksimum kapasiteyi bulmaya çalışır.

**Scalability test:** kaynak artışının kapasiteye etkisini ölçer.

### 44.2 Test modeli üretim trafiği profilinden gelmeli

Gerçek sistem 90% read, 8% write, 2% ağır rapor çalıştırıyorsa bütün kullanıcıları aynı uç noktaya vuran test gerçekçi değildir.

İş yükü modeli şu bilgileri içermelidir:

```text
geliş hızı
istek dağılımı
yük dağılımı
oturum davranışı
think time
cache warm/cold state
veri kardinalitesi
```

JMeter, k6, Gatling veya başka araç seçimi ikincil konudur. Yanlış workload modeliyle en iyi araç da yanlış sonuç üretir.

---

## 45. Ortalama gecikme çoğu zaman gerçeği saklar

Latency dağılımı sağa çarpıksa ortalama kullanıcı deneyimini gizleyebilir.

Örnek:

```text
900 istek = 50 ms
90 istek  = 100 ms
9 istek   = 500 ms
1 istek   = 10 s
```

Ortalama kabul edilebilir görünebilir; fakat P99 ve max ciddi kuyruk veya timeout sorununu gösterir.

Bu dağılımı görebilmek için performans raporunda en az şu ölçüler birlikte okunmalıdır:

- median/P50,
- P90,
- P95,
- P99,
- maksimum,
- throughput,
- hata oranı

birlikte değerlendirilmelidir.

Percentile da tek başına yeterli değildir. Coordinated omission gibi ölçüm hataları yük üreticisinin gerçek gecikmeyi olduğundan iyi göstermesine yol açabilir. Sabit arrival-rate sistemiyle kapalı çevrim kullanıcı modelinin neyi ölçtüğü bilinmelidir.

Performans testi yalnız sonuç raporu değildir; **ölçüm sistemi de test edilmelidir**.


---

## 46. Performans sonucunu koddan önce sistem modeliyle açıklamak

Performans testinin en zayıf biçimi şu rapordur:

```text
Test başarılı: 10.000 kullanıcı desteklendi.
```

Bu cümle tek başına neredeyse anlamsızdır. 10.000 kullanıcı eşzamanlı mıydı? Her biri ne sıklıkla istek gönderdi? Veri kümesi ne büyüklükteydi? Cache sıcak mıydı? Hata oranı neydi? Test ne kadar sürdü? Sistem kaynakları hangi noktada doygunluğa ulaştı?

Daha yararlı rapor:

```text
1200 req/s sabit geliş hızı
400 aktif oturum
30 dakika kararlı durum
P95 = 182 ms
P99 = 410 ms
hata oranı = 0.04%
DB pool peak utilization = 86%
CPU = 64-72%
GC pause P99 = 9 ms
```

Bu veriler neden-sonuç kurmaya izin verir.

### 46.1 Little Yasası ile tutarlılık kontrolü

Kararlı bir sistemde kabaca:

```text
L = λW
```

- `L`: sistemde ortalama iş sayısı,
- `λ`: throughput/geliş hızı,
- `W`: ortalama sistemde kalma süresi.

Örneğin 1000 req/s ve ortalama 100 ms yanıt süresi varsa sistemde ortalama yaklaşık 100 aktif istek beklenir. Ölçüm bunun çok dışındaysa queueing, instrumentation veya workload modelini yeniden incelemek gerekir.

Performans mühendisliği yalnız benchmark çalıştırmak değil, ölçümün fiziksel/matematiksel olarak anlamlı olup olmadığını sınamaktır.

### 46.2 Tek değişken ilkesi

Aynı anda JVM heap, thread pool, Hikari pool, SQL indeksleri ve cache ayarları değiştirilip sonuç iyileşirse hangi değişikliğin etkili olduğunu bilemeyiz.

İyi deney:

```text
baseline ölç
tek değişkeni değiştir
aynı yükü uygula
karşılaştır
hipotezi değerlendir
```

Test mühendisliği bilimsel yönteme en çok burada yaklaşır.

---

## 47. Dayanıklılık testi: başarısızlığı engelleyemiyorsak davranışını sınayalım

Dağıtık sistemde ağ kesintisi, timeout, process restart, disk dolması veya bağımlı servis hatası istisna değildir. Bunlar sistem modelinin parçasıdır.

Dayanıklılık testinde şu soruları sormak gerekir:

- bağımlı servis 500 dönerse ne olur?
- connection timeout olursa thread birikir mi?
- retry aynı isteği çoğaltır mı?
- circuit breaker açılır mı?
- düğüm devre dışı kalırsa trafik başka node'a geçer mi?
- veritabanı failover sırasında transaction ne olur?
- önbellek kaybolursa sistem doğruluğunu korur mu?
- disk dolarsa log sistemi uygulamayı durdurur mu?

Fault injection kontrollü biçimde bu hataları üretir.

### 47.1 Retry testi

Retry mekanizması özellikle dikkat ister. Yanlış yapılandırılmış retry, kesinti anında yükü daha da artırabilir.

```text
normal trafik: 1000 req/s
her başarısız çağrı: 3 retry
bağımlılık çökerse teorik istek baskısı: ~4000 req/s
```

Backoff ve jitter bu nedenle yalnız “resilience pattern” değil, test edilmesi gereken sistem davranışıdır.

### 47.2 Idempotency

Retry'nin güvenli olabilmesi için işlem semantiği önemlidir. `GET` tekrar edilebilir; para transferi gibi yan etkili `POST` aynı kolaylıkla tekrar edilemez. Idempotency key veya başka deduplication mekanizması varsa test duplicate isteği bilinçli üretmelidir.

---

## 48. Chaos engineering: rastgele bozmak değil hipotez sınamak

Chaos engineering bazen “üretimde servis kapatmak” biçiminde anlaşılır. Olgun yaklaşımda önce steady-state davranışı ve hipotez tanımlanır:

```text
Hipotez:
Bir uygulama düğümü kaybedildiğinde başarılı istek oranı %99,9'un altına düşmeyecek ve P99 1 saniyeyi aşmayacak.
```

Sonra kontrollü fault uygulanır, ölçülür ve geri alınır.

Chaos testi için ön koşullar:

- güçlü gözlemlenebilirlik,
- blast radius kontrolü,
- otomatik rollback/durdurma,
- açık sahiplik,
- üretim öncesi daha dar ortamlarda doğrulama.

Kaos, gözlemleyemediğimiz sisteme uygulanmaz. Aksi hâlde deney değil arıza üretmiş oluruz.

---

## 49. Güvenlik testi: fonksiyonel testin düşmanca girdilerle genişletilmiş hâli değildir

Güvenlik testi farklı bir zihniyet ister. Fonksiyonel test “yetkili kullanıcı bu işlemi yapabiliyor mu?” diye sorar. Güvenlik testi “yetkisiz kullanıcı bunu başka bir yoldan yapabiliyor mu?” sorusunu ekler.

[Güvenli Yazılım Mühendisliği – Uygulamalı Siber Güvenlik](/guvenli-yazilim-muhendisligi-uygulamali-siber-guvenlik) notunda ele aldığım saldırı yüzeyi burada doğrudan test modeline dönüşür:

```text
kimlik
 -> yetkilendirme
 -> oturum
 -> girdi
 -> veri erişimi
 -> dosya sistemi
 -> dış servis
 -> yapılandırma
 -> log
```

OWASP Web Security Testing Guide bu alanı bilgi toplama, yapılandırma, kimlik, authentication, authorization, session management, input validation, hata yönetimi, kriptografi, iş mantığı ve client-side testler olarak sistematik biçimde ele alır.

### 49.1 Yetkilendirme testinde pozitif test yetmez

```text
A kullanıcısı A kaydını okuyabilir -> PASS
```

Bu tek başına güvenlik kanıtı değildir.

Gerekli negatif test:

```text
A kullanıcısı B kaydını okuyamaz -> PASS
```

Daha sonra:

```text
A admin uç noktayıne gidemez
expired token kullanamaz
silinmiş yetki cache'de yaşamaz
ID değiştirerek başka nesneye erişemez
```

Yetkilendirme test matrisi çoğu projede role × resource × action ekseninde düşünülmelidir.

### 49.2 Güvenlik otomasyonu neyi kaçırır?

SAST, DAST ve bağımlılık tarayıcıları yararlıdır; fakat iş mantığı açığını her zaman bulamaz. “Bir müşteri kendi kuponunu yüz kez kullanabiliyor” problemi çoğu tarayıcının anlayacağı bir pattern değildir.

Otomasyon bilinen sınıfları geniş ölçekte tarar; insan analizi sistemin anlamını sorgular.

---

## 50. Uyumluluk, kurulum, yükseltme ve geri dönüş testleri

Bir yazılım yalnız sıfırdan kurulumda çalışıyorsa yaşam döngüsünün küçük kısmı test edilmiştir.

Gerçek üretim akışında:

```text
v1 -> v2 -> v3
```

geçişi vardır. Database schema, config formatı, cache içeriği, persisted dosyalar ve dış istemciler eski sürümden gelir.

Yaşam döngüsünü gerçekten sınamak için en az şu geçişleri ayrı senaryolar olarak ele almak gerekir:

- temiz kurulum,
- önceki desteklenen sürümden yükseltme,
- migration'ın yarıda kesilmesi,
- rollback,
- eski client ile yeni server,
- yeni client ile eski server,
- config alanı kaldırılması/yeniden adlandırılması,
- veri formatı dönüşümü.

Migration testi yalnız script'in syntax olarak çalışması değildir. Veri kaybı, default değer, constraint ve büyük veri süresi de ölçülmelidir.

### 50.1 Geri dönüş gerçekten mümkün mü?

Deployment planında “sorun olursa rollback” yazmak kolaydır. Eğer migration geri alınamaz veri dönüşümü yaptıysa eski binary'nin yeniden deploy edilmesi rollback değildir.

Rollback test edilmemişse bir varsayımdır.

---

## 51. Regresyon testi: her şeyi tekrar çalıştırmak değil değişiklik riskini kontrol etmek

Regression suite zamanla büyür. Her bulunan hata için test eklenir fakat eskiler nadiren değerlendirilir. Sonuç saatler süren, büyük bölümü düşük bilgi üreten paket olabilir.

Regresyon stratejisini katmanlamak yararlıdır:

```text
smoke            -> sistem ayağa kalkıyor mu?
değişen alan     -> değişen davranışlar
kritik yol    -> iş açısından vazgeçilmez akışlar
tam regresyon  -> geniş kapsam
```

CI pipeline değişiklik hızına göre farklı seviyeleri çalıştırabilir.

### 51.1 Regression testinin ömrü

Her test sonsuza kadar yaşamamalıdır. Şunlar düzenli gözden geçirilmelidir:

- davranış artık var mı?
- aynı riski daha ucuz test eden başka test var mı?
- test yalnız eski uygulama ayrıntısını mı koruyor?
- başarısız olduğunda gerçek defect buluyor mu?
- bakım maliyeti ürettiği güvene değer mi?

Test kodu da üretim kodu gibi refactor ve silme hakkına sahiptir.

---

## 52. Kod kapsamı: harita, kalite sertifikası değil

Statement veya branch coverage, test sırasında hangi kodun çalıştırıldığını gösterir; fakat yüksek kod kapsamı güçlü assertion yazıldığını veya doğru oracle kullanıldığını garanti etmez.

Örnek:

```java
@Test
void callsMethod() {
    service.calculate(10);
}
```

Bu test satırları çalıştırabilir ve kod kapsamını artırabilir; hiçbir sonucu doğrulamaz.

Coverage şu amaçla faydalıdır:

- tamamen testsiz alanları bulmak,
- kritik branch'lerin hiç çalışmadığını görmek,
- değişiklikle test kapsamı arasındaki boşluğu incelemek.

Şu amaçla zayıftır:

- takım performansını ölçmek,
- “%90 ise kaliteli” demek,
- düşük değerli test üretimini teşvik etmek.

### 52.1 Branch coverage da yeterli değildir

Bir boolean ifadenin iki sonucu sınansa bile koşullar arasındaki bağımsız etki görünmeyebilir. Safety-critical alanlarda MC/DC gibi daha güçlü kapsam ölçütleri gerekebilir.

Coverage seviyesi sistem riskine göre seçilmelidir.

---

## 53. Test metrikleri: ölçmek davranışı değiştirir

Bir ölçü yönetim hedefine dönüştüğü anda ekip davranışı o sayıyı iyileştirecek yönde değişmeye başlar. Test metriklerini seçerken Goodhart etkisini göz ardı etmek, ölçümün kendisini kalite sorununa dönüştürebilir.

Yararlı olabilecek göstergeler:

- escaped defect rate,
- defect detection phase,
- test execution duration,
- flaky test rate,
- mean time to diagnose failed test,
- mutation score,
- kod kapsamı eğilimi,
- üretim olaylarının regresyon kapsamı,
- test failure cause distribution.

Tek başına “test case sayısı” veya “bulunan bug sayısı” çoğu durumda kötü performans göstergesidir. Testçinin çok bug bulması iyi testçilikten de kötü ürün kalitesinden de kaynaklanabilir.

### 53.1 Defect Removal Efficiency

Kavramsal olarak:

```text
DRE = geliştirme/test sırasında bulunan kusur /
      (geliştirme/test sırasında bulunan + üretime kaçan kusur)
```

Bu tür metrikler trend görmek için yararlıdır; kusur ağırlığı ve sınıflandırması hesaba katılmadan tek başına yönetim hedefi yapılmamalıdır.

---

## 54. Kusur raporu: tekrar üretilemeyen hata yarım bilgidir

İyi defect raporu geliştiricinin problemi yeniden üretmek için tahmin yapmasını azaltır.

Gerekli bağlam çoğu zaman şunlardır:

```text
sürüm / commit
çevre
ön koşul
test verisi
adımlar
beklenen davranış
gerçek davranış
zaman damgası
correlation/request id
log/trace/screenshot
tekrar oranı
```

“Hata var” ile “POST /api/orders çağrısı aynı idempotency-key ile 200 ms arayla gönderildiğinde iki ORDER_CREATED event'i üretiyor” arasında teşhis maliyeti açısından büyük fark vardır.

### 54.1 Severity ve priority aynı şey değildir

Severity teknik/iş etkisini, priority ne zaman ele alınacağını ifade eder. Düşük severity görsel hata lansman öncesinde yüksek priority olabilir. Kritik fakat yalnız kullanılmayan eski bir feature'daki hata geçici olarak daha düşük priority alabilir.

Bu ayrım kararın şeffaf olmasını sağlar.

---

## 55. Kök neden analizi: testi değil sistemi düzeltmek

Üretimde kusur bulunduğunda yalnız bug fix ve regression test eklemek yeterli olmayabilir.

Şu zinciri sorgulamak gerekir:

```text
Hata neden oluştu?
Neden review'da görülmedi?
Neden unit/integration test yakalamadı?
Neden staging'de görünmedi?
Neden monitoring erken alarm vermedi?
```

Bu sorular suçlu aramak için değil kontrol zincirindeki boşluğu bulmak içindir.

Örneğin BOLA açığı üretimde bulunduysa yalnız uç noktaya authorization kontrolü eklemek yerine:

- authorization policy mimarisi,
- ortak security interceptor,
- role/resource test matrisi,
- code review checklist,
- security integration testleri

gözden geçirilmelidir.

Tekil kusur, sistemik iyileştirme için veri üretmelidir.

---

## 56. CI/CD içinde test: geri bildirim süresini mimari karar olarak ele almak

Pipeline'ın görevi bütün testleri her adımda çalıştırmak değil, doğru kanıtı doğru zamanda üretmektir.

Örnek:

```text
commit
  -> compile
  -> static checks
  -> small tests
  -> changed-module integration
  -> package
  -> medium tests
  -> security/dependency checks
  -> deploy ephemeral env
  -> smoke / contract
  -> selected E2E
  -> performance/security scheduled suites
```

Bir geliştirici küçük değişiklik için 45 dakika geri bildirim bekliyorsa test paketi geliştirme akışından ayrılır. İnsanlar testleri lokal çalıştırmamaya, pipeline sonuçlarını beklememeye veya bypass etmeye başlar.

### 56.1 Quality gate

Kalite kapısı yalnız kod kapsamı yüzdesine indirgenmemelidir. Örneğin:

```text
unit/integration tests pass
critical mutation regression pass
no new critical vulnerability
migration validation pass
flaky rate below threshold
performance regression within budget
```

Her kapının neden var olduğu bilinmelidir. Kullanılmayan veya sürekli override edilen gate güvenlik tiyatrosuna dönüşür.

---

## 57. Test suite performansı da performans problemidir

Binlerce test çalıştıran sistemde test altyapısı kendi başına bir workload'dur.

Yavaşlığın tipik nedenleri:

- her sınıfta context yeniden başlatma,
- gereksiz container oluşturma,
- devasa fixture yükleme,
- serial çalışma,
- dış ağ,
- repeated schema migration,
- gereksiz UI testi,
- test başına process başlatma.

Optimizasyon sırası üretim performansına benzer olmalıdır:

```text
ölç
sıcak noktayı bul
tek değişiklik yap
tekrar ölç
```

### 57.1 Paralel test

JUnit 6 güncel sürümlerinde parallel execution desteği gelişmiştir. Ancak testler parallel çalıştırılınca gizli sıkı bağlılık görünür olur:

- aynı port,
- aynı dosya,
- aynı DB şeması,
- static singleton,
- global system property.

Paralel test başarısızlığı çoğu zaman “JUnit problemi” değil isolation problemidir.

Testleri bağımsız tasarlamak hem paralellik hem güvenilirlik kazandırır.

---

## 58. Legacy code testi: önce davranışı kilitle, sonra değiştir

Testsiz eski kodda doğrudan refactor risklidir çünkü “doğru davranış” dokümante edilmemiş olabilir.

Characterization test mevcut davranışı gözlemleyip kilitler. Amaç mevcut davranışın ideal olduğunu iddia etmek değildir; değişiklik sırasında neyin değiştiğini görünür kılmaktır.

Akış:

```text
mevcut davranışı çalıştır
çıktıyı gözle
gerekli davranışı test ile sabitle
küçük refactor yap
testleri çalıştır
tekrar et
```

### 58.1 Seam bulmak

Eski kod dış veritabanı, statik fonksiyon veya dosya sistemine sıkı bağlı olabilir. Test edilebilir bir seam oluşturmak için en küçük güvenli değişiklik yapılır.

Örneğin:

```text
System.currentTimeMillis()
```

doğrudan kullanımını bir zaman sağlayıcısı arkasına almak, büyük mimari dönüşümden daha güvenli ilk adımdır.

### 58.2 Golden master

Çok karmaşık, doğruluğu tam tanımlanmamış output üreten legacy sistemlerde mevcut çıktılar golden master olarak kaydedilebilir. Sonraki değişiklikte diff alınır.

Bu yaklaşım güçlü ama tehlikelidir: mevcut bug'ları da kilitler. Diff mutlaka insan/domain değerlendirmesinden geçmelidir.

---

## 59. Test smell'leri: test kodunun bize anlattığı tasarım sorunları

Sık görülen test smell'leri:

### Mystery Guest
Testin gerektirdiği veri dosya/database içinde gizlidir. Test okununca neden geçtiği anlaşılmaz.

### Eager Test
Tek test çok sayıda davranışı sınar. Kırılınca sebep belirsizdir.

### Fragile Test
Davranış değişmeden küçük refactor'da kırılır.

### Slow Test
Dar bir davranış için gereksiz altyapı çalıştırır.

### Test Code Duplication
Fixture kurulumu yüzlerce testte kopyalanmıştır.

### Conditional Test Logic
Test içinde karmaşık `if/loop` bulunur; testin kendisi hata kaynağı olur.

### Assertion Roulette
Çok sayıda benzer assertion vardır; hangisinin neden başarısız olduğu anlaşılmaz.

### Sleepy Test
Zamana `sleep` ile güvenir.

### Overspecified Interaction
Mock çağrı sırasını gereksiz ayrıntıyla kilitler.

Test smell çoğu zaman üretim kodundaki tasarım kokusu ile birlikte görülür. Test çok zor kuruluyorsa domain nesnesi fazla bağımlılık taşıyor olabilir. Private metotları ayrı test etmek zorunda hissediyorsak sınıfın sorumluluğu fazla olabilir.

---

## 60. Test otomasyonu: manuel testi silmek değil tekrarı makineye bırakmak

Her test otomasyona uygun değildir. Otomasyonun güçlü olduğu alanlar:

- sık tekrar,
- deterministik oracle,
- yüksek regresyon riski,
- çok veri kombinasyonu,
- performans ölçümü,
- API/contract doğrulaması.

İnsanın güçlü olduğu alanlar:

- keşif,
- kullanılabilirlik,
- yeni özelliğin anlaşılması,
- belirsiz gereksinimin sorgulanması,
- beklenmeyen davranışın yorumlanması.

“%100 otomasyon” hedefi genellikle anlamsızdır. Daha doğru hedef:

> Tekrarlanabilir ve makinece güvenilir biçimde değerlendirilebilen yüksek değerli kontrolleri otomatikleştir; insan dikkatini belirsizlik ve keşfe ayır.

### 60.1 Record/playback sınırı

UI record/playback araçları ilk senaryoyu hızlı üretir fakat test kodu tasarlanmadığında bakım maliyeti büyür. Tekrar kullanılan page/component abstraction'ları, semantic selector ve veri bağımsızlığı gerekir.

Otomasyon kodu da üretim kodudur. Version control, review, refactor ve performans disiplini ister.

---

## 61. Yapay zekâ ile test üretimi: hızlandırıcı, oracle değil

2025–2026 araştırmalarında LLM tabanlı test üretimi, REST API test amplification, multi-agent test generation ve test paketi değerlendirmesi belirgin biçimde öne çıkmıştır. Araçlar mevcut koddan unit test iskeleti, sınır değeri adayları veya API senaryoları üretebilir.

Bunun gerçek faydası özellikle geniş keşif alanında aday üretmektir. Fakat üç temel risk vardır:

1. Model üretim kodundaki yanlış davranışı “beklenen” kabul edebilir.
2. Çok sayıda yüzeysel test üretip kod kapsamını artırırken mutation score'u artırmayabilir.
3. Testler framework ve uygulama ayrıntılarına aşırı bağlanabilir.

Yapay zekâ tarafından üretilen bir testin doğrudan test paketine alınması yerine şu süzgeçten geçmesi gerekir:

```text
hangi risk?
hangi oracle?
hangi sınır?
hangi mutantı öldürüyor?
hangi üretim kusurunu yakalayabilir?
```

Bu sorulara cevap veremeyen test, otomatik üretilmiş gürültü olabilir.

### 61.1 yapay zekâ ile test review

LLM'ler mevcut test paketinde eksik sınır durumlarını veya yinelenen test adaylarını da bulabilir. Ancak karar domain bilgisine dayanmalıdır. Modelin önerisi hipotezdir; kanıt değildir.

---

## 62. Makine öğrenmesi sistemlerini test etmek: kod doğruyken model yanlış olabilir

ML sistemlerinde klasik yazılım testine yeni belirsizlikler eklenir:

```text
kod
model
veri
özellik çıkarımı
preprocessing
postprocessing
threshold
runtime
```

Unit test preprocessing fonksiyonunu doğrulayabilir; model kalitesini tek başına doğrulayamaz.

Test alanları:

- training/serving skew,
- veri şeması,
- label kalitesi,
- class imbalance,
- distribution shift,
- robustness,
- latency,
- determinism,
- fairness gereksinimleri,
- model version compatibility.

### 62.1 Sabit accuracy yeterli değildir

Bir sınıflandırıcıda global accuracy yüksekken kritik azınlık sınıfının recall değeri kötü olabilir. Confusion matrix, precision, recall, F1, ROC/PR ve domain-specific maliyet birlikte değerlendirilmelidir.

### 62.2 Metamorphic ilişkiler

Örneğin ses tanıma sisteminde küçük amplitude normalizasyonu metni tamamen ilgisiz sonuca çevirmemelidir. Görüntü sınıflandırmada anlamı bozmayan küçük transformasyonlarda tahmin tutarlılığı incelenebilir.

ML testinde oracle problemi klasik testten daha görünürdür.

---

## 63. Gerçek zamanlı ve siber-fiziksel sistem testi

Gerçek zamanlı sistemde yalnız doğru sonuç değil, **doğru zamanda sonuç** gereksinimdir.

```text
functional correctness + timing correctness
```

Bir kontrol komutu 500 ms sonra doğru üretiliyorsa fiziksel sistem açısından yanlış olabilir.

Test edilmesi gerekenler:

- worst-case response time,
- deadline miss,
- jitter,
- sensor noise,
- clock drift,
- packet loss,
- actuator failure,
- fail-safe state,
- degraded mode.

Güncel araştırmalarda autonomous driving testleri için probabilistic model checking, scenario generation ve simulation yaygın çalışma alanıdır. Bunun nedeni fiziksel dünyanın kombinasyon uzayının gerçek araçla exhaustive test edilemeyecek kadar büyük olmasıdır.

### 63.1 Hardware-in-the-loop

HIL testi gerçek kontrol donanımını simüle edilmiş fiziksel çevreyle birleştirir. Saf yazılım benzetiminin kaçırdığı timing, I/O ve driver davranışları görülebilir.

Safety-critical sistemde test sonuçlarının izlenebilirliği ve tekrar üretilebilirliği sıradan uygulamadan daha katı tutulmalıdır.

---

## 64. Üretimde test: test ortamı gerçeğin tamamı değildir

Staging ortamı üretime yaklaşabilir; yine de üretimin kendisi değildir. Gerçek trafik dağılımı, veri kardinalitesi, ağ topolojisi ve kaynak çekişmesi ancak canlı sistemde bütünüyle görünür.

Bu farkı kontrollü biçimde yönetebilmek için üretimde sınırlı doğrulama teknikleri kullanılır:

### Smoke after deploy
Kritik endpoint'ler deploy sonrasında otomatik doğrulanır.

### Canary
Yeni sürüm trafiğin küçük bölümüne açılır. Error rate ve latency karşılaştırılır.

### Blue/Green
Yeni çevre önceden hazırlanır; trafik kontrollü geçirilir.

### Shadow traffic
Üretim isteklerinin kopyası yeni sisteme gönderilir fakat sonuç kullanıcıya dönmez.

### Feature flag
Davranış sınırlı kullanıcı grubuna açılır.

Bunların hiçbiri pre-üretim testin alternatifi değildir. Son savunma katmanıdır.

---

## 65. Gözlemlenebilirlik testin uzantısıdır

Bir test başarısız olduğunda sistem nedenini açıklayamıyorsa teşhis maliyeti yükselir. Aynı durum üretim için daha kritiktir.

İyi gözlemlenebilirlik:

- structured log,
- metric,
- distributed trace,
- correlation id,
- domain event/audit

ile sistem davranışını görünür kılar.

Test sırasında şu da doğrulanabilir:

```text
hata oluştu
 -> doğru status döndü
 -> doğru metric arttı
 -> correlation id log'a taşındı
 -> hassas veri loglanmadı
```

Observability yalnız operations konusu değildir; test oracle'ın gözlem yeteneğini de güçlendirir.

---

## 66. Test mühendisinin üretim olayından öğrenmesi

En değerli test fikirlerinden bazıları üretim olayından gelir. Incident sonrası yalnız aynı girdiyi regression testine çevirmek yerine hata sınıfını genellemek gerekir.

Örnek:

```text
Bug: 29 Şubat tarihinde rapor üretilmedi.
```

Zayıf düzeltme:

```text
2024-02-29 için tek test ekle.
```

Daha güçlü çıkarım:

```text
tarih sınır sınıfı:
- ay sonu
- yıl sonu
- leap year
- DST geçişi
- timezone conversion
```

Tek kusur yeni bir test tasarım kuralı öğretmelidir.

---

## 67. Test dokümantasyonu: belge üretmek değil karar hafızası oluşturmak

ISO/IEC/IEEE 29119-3 test dokümantasyonunu sistematikleştirir. Uygulamada belge miktarı proje riskine göre ayarlanmalıdır.

Küçük çevik ekipte yüz sayfalık test planı değersiz olabilir; fakat şu kararlar kaybolmamalıdır:

- test kapsamı,
- dışarıda bırakılan alanlar,
- risk öncelikleri,
- çevre,
- giriş/çıkış kriterleri,
- kritik veri setleri,
- sorumluluk,
- bilinen sınırlamalar.

Kritik veya regüle sistemde izlenebilirlik daha ayrıntılı olmalıdır:

```text
requirement -> risk -> test condition -> test case -> result -> defect
```

Belgenin değeri denetimde klasör doldurmak değil, kararın nedenini daha sonra yeniden kurabilmektir.

---

## 68. Test stratejisi için uygulanabilir algoritma

Yeni bir sistem veya önemli değişiklik önüme geldiğinde test tasarımını şu sırayla kurmak en az gereksiz iş üreten yaklaşımlardan biridir.

### Adım 1 — Kalite hedefini belirle

```text
Bu sistemde başarısızlığın en pahalı biçimi nedir?
```

Fonksiyonel hata mı, veri kaybı mı, güvenlik ihlali mi, gecikme mi, kesinti mi?

### Adım 2 — Sistemin sınırlarını çiz

```text
UI -> API -> service -> DB -> broker -> dış servis
```

Her sınır farklı test riski taşır.

### Adım 3 — Test edilebilir gereksinimleri çıkar

Belirsiz sıfatları ölçülebilir koşullara çevir:

```text
hızlı -> P95 < 250 ms @ 1200 req/s
çok güvenilir -> aylık availability >= ...
```

### Adım 4 — Riskleri sırala

Etki, olasılık, değişiklik yoğunluğu ve tespit maliyetini birlikte düşün.

### Adım 5 — Her risk için en ucuz güvenilir katmanı seç

```text
saf iş kuralı -> unit
SQL semantiği -> real DB integration
wire format -> API/contract
kritik kullanıcı akışı -> E2E
kapasite -> performance
```

### Adım 6 — Test verisini ve oracle'ı tanımla

Beklenen sonucu nereden bildiğini açıkla.

### Adım 7 — Pozitif kadar negatif yolu tasarla

```text
ne olmalı?
ne olmamalı?
sınırda ne olur?
aynı anda olursa ne olur?
bağımlılık çökerse ne olur?
```

### Adım 8 — Otomasyonu ekonomik kur

Sık ve deterministik kontrolleri işlem hattına al. Pahalı geniş testleri uygun çalışma sıklığına yerleştir.

### Adım 9 — Test paketinin kendisini ölç

```text
süre
flaky rate
teşhis süresi
mutation effectiveness
```

### Adım 10 — Üretim geri bildirimini test tasarımına taşı

Incident, telemetry ve gerçek kullanım yeni test fikirlerinin kaynağıdır.

Bu algoritmanın özü test sayısını büyütmek değildir. **Belirsizliği sistematik olarak küçültmektir.**

---

## 69. Bir örnek üzerinden bütün zincir: sipariş servisini test edilebilir hâle getirmek

Basit görünen bir sipariş API'si düşünelim:

```text
POST /orders
```

İş kuralı:

- aktif kullanıcı sipariş oluşturabilir,
- stok yeterli olmalı,
- ödeme alınmalı,
- sipariş kaydedilmeli,
- event yayınlanmalı.

İlk bakışta bir E2E testi yeterli görünebilir. Fakat davranışı risklere bölelim.

### 69.1 Birim katmanı

Fiyat ve indirim hesapları saf fonksiyon olarak test edilir.

```text
sınır değerleri
yuvarlama
sıfır
geçersiz miktar
```

### 69.2 Service katmanı

Stok yetersizse payment çağrısının yapılmadığı doğrulanır. Burada mock interaction anlamlıdır çünkü “payment çağrısı olmaması” iş davranışıdır.

### 69.3 Repository katmanı

Gerçek database ile:

```text
constraint
transaction
concurrent stock decrement
locking
```

test edilir.

### 69.4 API katmanı

```text
JSON validation
status code
error contract
authentication
authorization
```

sınanır.

### 69.5 Event katmanı

Sipariş işlemi tamamlandığında olayın tam bir kez veya sistemin seçtiği teslim semantiğine uygun üretildiği kontrol edilir.

### 69.6 E2E

Bir veya birkaç kritik senaryo bütün zinciri gerçek servislerle yürütür.

### 69.7 Performans

Normal trafik dağılımı ve stok contention altında P95/P99 ölçülür.

### 69.8 Dayanıklılık

Payment provider timeout, broker unavailable ve veritabanı failover davranışları sınanır.

### 69.9 Güvenlik

Başka kullanıcının siparişini okuma/değiştirme, quantity tampering ve replay denenir.

Bu yapıdaki testlerin hiçbiri diğerinin aynısı değildir. Her biri farklı bir varsayımı sınar.

İyi test mimarisinin göstergesi budur.

---

## 70. Black-box, white-box ve gray-box: bakış açısı test seviyesi değildir

Black-box ve white-box ayrımı sık biçimde unit/integration ayrımıyla karıştırılır. Oysa bunlar test seviyesini değil **test tasarımında hangi bilginin kullanıldığını** anlatır.

**Black-box test**, iç uygulamayı bilmeden dış sözleşme üzerinden test tasarlar. Gereksinim, giriş, çıkış ve kullanıcı davranışı önemlidir.

**White-box test**, kod yapısı, control flow, branch, condition ve internal state bilgisini test tasarımında kullanır.

**Gray-box test**, iki bakışı birleştirir. Örneğin API'yi dışarıdan çağırırken veritabanındaki transaction modelini veya authorization mimarisini bildiğimiz için özel senaryolar seçebiliriz.

Bir API integration testi white-box olabilir; bir unit test black-box yazılabilir. Kavramları seviyeye bağlamak gereksiz terminoloji tartışması üretir.

### 70.1 Yapısal test ve control-flow kapsamı

White-box yaklaşımında yaygın ölçütler:

```text
statement coverage
branch/decision coverage
condition coverage
path coverage
MC/DC
```

Statement coverage bir satırın en az bir kez çalışmasını ister. Branch coverage kararın her sonucunu görür. Condition coverage bileşik ifadedeki alt koşulların etkisini inceler. MC/DC ise özellikle kritik sistemlerde her temel koşulun karar sonucunu bağımsız olarak etkileyebildiğini göstermeye çalışır.

Yol kapsamı teorik olarak güçlüdür; ancak döngü içeren gerçek programlarda olası yol sayısı kısa sürede yönetilemez hâle gelir. Sonlu test bütçesi bu noktada yeniden risk tabanlı örnekleme gerektirir.

### 70.2 Loop testleri

Döngüde klasik sınırlar hâlâ etkilidir:

```text
0 iterasyon
1 iterasyon
2 iterasyon
normal n
maksimum - 1
maksimum
maksimum + 1
```

Fakat modern kodda loop yalnız `for` ifadesi değildir. Stream pipeline, retry loop, pagination, queue consumer ve recursive traversal aynı sınır düşüncesini gerektirir.

---

## 71. Model-based testing: test senaryosunu modelden türetmek

Durum sayısı büyüdüğünde testleri elle listelemek yerine sistemin davranış modelinden üretmek daha güvenilir olabilir.

Bir protokol için model:

```text
DISCONNECTED
   -> CONNECTING
   -> AUTHENTICATED
   -> ACTIVE
   -> CLOSING
   -> DISCONNECTED
```

Model yalnız geçerli durumları değil, geçiş koşullarını ve yasak geçişleri de içerir. Test generator bu modelden yollar üretebilir.

Model-based testing özellikle:

- protokoller,
- workflow motorları,
- embedded sistemler,
- telekomünikasyon,
- GUI state machine,
- safety-critical controller

alanlarında değerlidir.

### 71.1 Model doğru değilse üretilen test de doğru değildir

Otomatik test üretimi modelin yanlışlığını telafi etmez; tersine, hatalı modeli büyük bir disiplinle çoğaltabilir. Bu yüzden davranış modelinin teknik incelemesi test üretiminden ayrı bir mühendislik adımıdır.

### 71.2 Zamanlı modeller

Gerçek zamanlı protokollerde state yalnız olayla değil zamanla değişebilir:

```text
ACK 200 ms içinde gelmezse TIMEOUT
```

Timed automata ve model checking bu tür sistemlerde güçlü araçlardır. Paylaşılan güncel araştırma notlarında quiescence ve timeout davranışlarının zamanlı otomatlarla modellenmesi gibi çalışmaların bulunması, klasik test tasarımının gerçek zamanlı sistemlerde neden genişletilmesi gerektiğini gösterir.

---

## 72. Biçimsel doğrulama ve test: rakip değil tamamlayıcı

Test sonlu örnekler üzerinde programı çalıştırır. Biçimsel yöntemler belirli bir model veya program özelliğini matematiksel olarak kanıtlamaya çalışabilir.

Araç ve teknik örnekleri:

- model checking,
- symbolic execution,
- bounded model checking,
- contract verification,
- theorem proving.

CBMC, Dafny, Isabelle/HOL ve benzeri araçlar belirli problem sınıflarında testin ulaşamadığı kapsam güvencesi sağlayabilir. Ancak model varsayımları, çalışma ortamının davranışı ve uygulama sınırı yine önemlidir.

Bir fonksiyonun overflow üretmediğini formel olarak kanıtlamak değerlidir; deployment sırasında yanlış config ile yanlış servise bağlandığını bu kanıt söylemez.

En güçlü yaklaşım katmanlıdır:

```text
formal property
 + statik analiz
 + unit/property tests
 + integration
 + system observation
```

Kritik yazılımda “test mi formal verification mı?” sorusu çoğu zaman yanlış ikiliktir.

---

## 73. Smoke, sanity, kabul, alpha ve beta: isimden çok amaç

Bu terimler organizasyonlar arasında farklı kullanılabildiği için ekip içinde açık tanım gerekir.

**Smoke test**, derleme/dağıtımın temel olarak kullanılabilir olup olmadığını hızlı kontrol eder. Sistem ayağa kalkıyor mu, kritik servis cevap veriyor mu, temel akış çalışıyor mu?

**Sanity test**, daha dar bir değişikliğin ana davranışının hâlâ makul olduğunu kontrol eden kısa pakettir. Her ekip bu terimi kullanmak zorunda değildir; önemli olan ortak anlamdır.

**Kabul testi**, ürünün tanımlanmış iş/operasyon kabul ölçütlerini karşıladığını doğrular. UAT bunun kullanıcı/paydaş odaklı biçimidir.

**Alpha test**, ürünün kontrollü ve çoğunlukla organizasyon içi ortamda gerçek kullanım benzeri değerlendirilmesidir.

**Beta test**, ürünün daha geniş gerçek kullanıcı kitlesinde sınırlı/ön sürüm olarak gözlenmesidir.

Alpha/beta yaklaşımı otomatik regresyonun alternatifi değildir. Gerçek kullanım çeşitliliğinden bilgi üretir.

---

## 74. Uyumluluk ve yerelleştirme testi: varsayılan ortam dışında ne oluyor?

Bir yazılım tek browser, tek locale ve tek timezone ile test edildiğinde önemli bir durum uzayı eksik kalabilir.

Uyumluluk matrisi örneği:

```text
OS × runtime × browser × database × locale × timezone
```

Tam Cartesian product çoğu zaman gereksizdir; risk, kullanım dağılımı ve pairwise teknikleriyle örnekleme yapılabilir.

### 74.1 Yerelleştirme

Yerelleştirme testi yalnız metin çevirisi değildir:

- tarih biçimi,
- ondalık ayırıcı,
- para birimi,
- büyük/küçük harf kuralları,
- Unicode normalization,
- metin uzaması,
- sağdan sola yazım,
- sorting/collation.

Türkçedeki `I`, `İ`, `ı`, `i` dönüşümleri klasik örnektir. İngilizce locale varsayımıyla yazılmış `toUpperCase()` mantığı kimlik veya arama davranışında gerçek kusur üretebilir.

Timezone testi de aynı derecede önemlidir. UTC'de saklanan zamanın kullanıcı yerel zamanına dönüşümü gün, ay veya raporlama periyodu sınırında değişebilir.

---

## 75. Backup, restore ve disaster recovery testi: alınan yedek geri dönmüyorsa yedek değildir

Yedekleme mekanizmasının logda “success” yazması kurtarma kabiliyeti kanıtlamaz.

Test edilmesi gereken gerçek akış:

```text
backup oluştur
izole ortamda restore et
uygulamayı bu veriyle ayağa kaldır
bütünlük kontrolü yap
RPO/RTO ölç
```

**RPO (Recovery Point Objective)** ne kadar veri kaybının kabul edilebilir olduğunu, **RTO (Recovery Time Objective)** hizmetin ne kadar sürede geri dönmesi gerektiğini ifade eder.

Restore testi şu riskleri açığa çıkarabilir:

- eksik encryption key,
- bozuk incremental chain,
- schema/application version uyumsuzluğu,
- backup dosyasında eksik obje,
- restore süresinin RTO'yu aşması,
- yanlış erişim izinleri.

Disaster recovery dokümanı test edilmediyse olay anında ilk kez çalıştırılacak bir hipotezdir.

---

## 76. Test otomasyon mimarisi: data-driven ve keyword-driven yaklaşımlar nerede işe yarar?

Paylaşılan notlarda test araçları ve otomasyon teknikleri geniş yer tutuyor. Araç seçiminden önce otomasyon mimarisini ayırmak gerekir.

**Data-driven testing**, aynı davranışı farklı veri setleriyle çalıştırır. Boundary ve karar tablosu senaryolarında etkilidir.

```java
@ParameterizedTest
@CsvSource({"0,FREE", "100,STANDARD", "1000,PREMIUM"})
void classifies(final int score, final String expected) {
    assertEquals(expected, classifier.classify(score));
}
```

**Keyword-driven testing**, test adımlarını `LOGIN`, `SEARCH`, `SAVE` gibi domain eylemlerine ayırıp veriden yürütür. ISO/IEC/IEEE 29119-5:2024 bu yaklaşımı standardize eden güncel bölümdür.

Keyword-driven yapı testçilerin kod yazmadan senaryo üretmesine yardımcı olabilir; fakat aşırı generic bir DSL yeni bir yazılım ürünü hâline gelebilir. Debug zorlaşır, gerçek API gizlenir ve testler okunmaz olur.

Prensip yine aynıdır: soyutlama tekrar eden anlamı azaltıyorsa değerlidir; yalnız yeni bir framework göstermek için ekleniyorsa maliyettir.

---

## 77. Sonuç: yeşil testler değil güvenilir değişiklik

Yazılım test mühendisliğini birkaç araç ve test türünün toplamı olarak gördüğümüzde doğal sonuç, giderek büyüyen bir test paketi olur. Bir süre sonra yüzlerce unit test, onlarca integration test ve uzun E2E senaryoları vardır; fakat ekip küçük bir üretim değişikliğini yine tedirginlikle yapar.

Burada testin gerçek amacı kaybolmuştur.

İyi bir test sistemi geliştiriciye şu kabiliyeti vermelidir:

> Bu değişikliğin hangi davranışları etkilediğini biliyorum; kritik varsayımlar otomatik olarak sınanacak; başarısızlık olursa sinyal yeterince dar olacak; geniş sistem riskleri de uygun katmanlarda ayrıca doğrulanacak.

Birim testi hızlı geri bildirim verir. Tümleştirme testi sınırların gerçeğini gösterir. Contract testi servislerin bağımsız değişimini korur. E2E testi kritik yolun birlikte çalıştığını kanıtlar. Property-based testing örneklerin dışına çıkar. Mutation testing testin gerçekten hata yakaladığını sorgular. Fuzzing beklenmeyen girdiyi üretir. Performans testi kapasiteyi, resilience testi bozulma biçimini, güvenlik testi saldırganın yolunu, üretim gözlemi ise laboratuvarın kaçırdığı gerçeği gösterir.

Bunların hiçbiri tek başına yeterli değildir. Hepsini her projede kullanmak da mühendislik değildir.

Doğru yaklaşım daha sadedir:

```text
riski anla
 -> davranışı tanımla
 -> sınırı seç
 -> oracle kur
 -> en ucuz güvenilir testi yaz
 -> sonucu ölç
 -> üretimden öğren
 -> gereksiz testi at
```

Test suite büyüklüğü değil, **değişiklik karşısında ürettiği güven / maliyet oranı** önemlidir.

Bir yazılımı test etmek sonuçta hatasız olduğunu kanıtlamaya çalışmak değildir. Hangi koşullarda yanlış olabileceğini sistematik biçimde aramak ve bulamadığımız her durumda güvenimizin nedenini açık hâle getirmektir.

İyi test mühendisini yalnız bulduğu kusur sayısıyla tanımlamak bu yüzden yetersizdir. Asıl değer; gereksinimdeki belirsizliği, tasarımdaki gereksiz sıkı bağlılığı, ölçüm yanlılığını, altyapıdaki deterministik olmayan davranışı ve üretime taşınabilecek gizli varsayımları kullanıcıdan önce görünür hâle getirebilmesidir.

---

## Kaynakça ve güncel başvuru kaynakları

Bu ders notu, paylaşılan yazılım test mühendisliği çalışma notları; yazılım kalite güvencesi, test tasarımı, TDD, Spring/JUnit testleri ve güncel test araştırmaları birlikte değerlendirilerek hazırlanmıştır. Eski araç ve sürüme bağımlı anlatımların öğretici kısmı korunmuş; güncel mühendislik yaklaşımıyla çelişen araç-merkezli reçeteler doğrudan aktarılmamıştır.

1. ISO/IEC/IEEE, **29119-1:2022 — Software and systems engineering — Software testing — Part 1: General concepts**. https://www.iso.org/standard/81291.html
2. ISO/IEC/IEEE, **29119-2:2021 — Software testing — Part 2: Test processes**.
3. ISO/IEC/IEEE, **29119-3:2021 — Software testing — Part 3: Test documentation**.
4. ISO/IEC/IEEE, **29119-4:2021 — Software testing — Part 4: Test techniques**.
5. ISO/IEC/IEEE, **29119-5:2024 — Software testing — Part 5: Keyword-driven testing**.
6. ISO/IEC TR, **29119-6:2021 — Guidelines for the use of ISO/IEC/IEEE 29119 in agile projects**.
7. ISO/IEC, **25010:2023 — Systems and software Quality Requirements and Evaluation (SQuaRE) — Product quality model**. https://www.iso.org/standard/78176.html
8. IEEE, **IEEE 730-2026 — IEEE Standard for Software Quality Assurance Processes**. https://standards.ieee.org/ieee/730/10854/
9. ISTQB, **Certified Tester Foundation Level Syllabus v4.0.1**, 2024. https://istqb.org/certifications/certified-tester-foundation-level-ctfl-v4-0/
10. JUnit Team, **JUnit 6.1.2 User Guide**, 2026. https://docs.junit.org/6.1.2/
11. Spring, **Spring Framework 7.0.9 Testing Reference**. https://docs.spring.io/spring-framework/reference/testing.html
12. Spring, **Spring Boot 4.1.1 Testing Reference**. https://docs.spring.io/spring-boot/reference/testing/
13. Testcontainers, **Testcontainers for Java Documentation**. https://java.testcontainers.org/ ve https://testcontainers.com/
14. OWASP Foundation, **Web Security Testing Guide — Stable**. https://owasp.org/www-project-web-security-testing-guide/stable/
15. Fowler, M., **Test Pyramid**, 2012. https://martinfowler.com/bliki/TestPyramid.html
16. Google Testing Blog, **Test Sizes**, 2010. https://testing.googleblog.com/2010/12/test-sizes.html
17. Google Testing Blog, **Where do our flaky tests come from?**, 2017. https://testing.googleblog.com/2017/04/where-do-our-flaky-tests-come-from.html
18. Beck, K., **Test Driven Development: By Example**, Addison-Wesley, 2002.
19. Meszaros, G., **xUnit Test Patterns: Refactoring Test Code**, Addison-Wesley, 2007.
20. Freeman, S.; Pryce, N., **Growing Object-Oriented Software, Guided by Tests**, Addison-Wesley, 2009.
21. Feathers, M., **Working Effectively with Legacy Code**, Prentice Hall, 2004.
22. Ammann, P.; Offutt, J., **Introduction to Software Testing**, 2nd ed., Cambridge University Press, 2016.
23. Copeland, L., **A Practitioner's Guide to Software Test Design**, Artech House, 2004.
24. Crispin, L.; Gregory, J., **Agile Testing: A Practical Guide for Testers and Agile Teams**, Addison-Wesley, 2009.
25. Adzic, G., **Bridging the Communication Gap: Specification by Example and Agile Acceptance Testing**, Neuri, 2009.
26. Meyer, B., **Seven Principles of Software Testing**, Computer, 41(8), 2008.
27. PITest, **Mutation Testing for Java**. https://pitest.org/
28. jqwik, **Property-Based Testing on the JVM**. https://jqwik.net/
29. Apache Software Foundation, **Apache JMeter Documentation**. https://jmeter.apache.org/
30. Selenium Project, **Selenium Documentation**. https://www.selenium.dev/documentation/
31. OWASP Foundation, **Application Security Verification Standard (ASVS)**. https://owasp.org/www-project-application-security-verification-standard/
32. IEEE/ACM International Conference on Automation of Software Test (AST) ve International Conference on Software Testing, Verification and Validation (ICST) güncel bildirileri.
33. **Testing Software and Systems / Advances in Intelligent and Automated Testing** kapsamında 2025–2026 dönemindeki model-based testing, test generation, LLM-assisted testing, metamorphic testing ve complex/critical systems çalışmalarından seçilmiş bildiriler.

### Standart ve araçları birlikte kullanmak için kısa not

Standart, test aracı değildir. ISO/IEC/IEEE 29119 ortak kavram, süreç, dokümantasyon ve tasarım teknikleri için; ISO/IEC 25010 kalite hedeflerini sınıflandırmak için; ISTQB ortak test terminolojisi ve temel pratikler için; IEEE 730 kalite güvence süreçleri için uygundur. JUnit, Spring Test, Testcontainers, Selenium, JMeter, PITest veya property-based testing araçları ise belirli mühendislik problemlerini uygular.

Araç seçimini standarttan, test stratejisini araçtan türetmek yerine önce risk ve kanıt ihtiyacını belirlemek daha doğru sonuç verir.

## Bu Çalışmaya Atıf

Köker, M. A. (2021). Yazılım Test Mühendisliği: Gereksinimden Üretime Uygulamalı Doğrulama ve Geçerleme. alikoker.com.tr. https://alikoker.com.tr/yazilim-test-muhendisligi-uygulamali-dogrulama-gecerleme

- BibTeX: https://alikoker.com.tr/yazilim-test-muhendisligi-uygulamali-dogrulama-gecerleme.bib
- RIS: https://alikoker.com.tr/yazilim-test-muhendisligi-uygulamali-dogrulama-gecerleme.ris
- CSL-JSON: https://alikoker.com.tr/yazilim-test-muhendisligi-uygulamali-dogrulama-gecerleme.csl.json
