---
title: 'Kalite ve Test Temelleri'
source: 'https://academia.sh/tr/kurslar/test-temelleri'
language: tr
updated: '2026-08-17T04:42:59+00:00'
license: 'CC BY-SA 4.0'
lessons: 12
outcomes:
    - 'Doğrulama ve geçerleme kavramlarını ayırır'
    - 'Test seviyelerini ve türlerini sınıflandırır'
    - 'İyi bir test senaryosu ve hata kaydı yazar'
---

# Kalite ve Test Temelleri

Süreç ve ürün kalitesinin ayrılması, doğrulama ile geçerlemenin farkı, işlevsel olmayan beklentilerin ölçülebilir ölçütlere çevrilmesi, test seviyeleri ve türleri, denklik sınıfı ile sınır değer teknikleri, test senaryosu yazımı, beklenen sonucun kaynağı ve hata yaşam döngüsü.

## Kalite Kavramı

Kalite güvencesinin süreç ve ürün yüzleri, doğrulama ile geçerlemenin ayrımı, işlevsel olmayan beklentilerin ölçülebilir ölçütlere çevrilmesi, kanıt arayan test zihniyeti ve teslimat modelinin geri bildirim süresine etkisi.

### Kalite Güvencesi Nedir

Source: https://academia.sh/tr/kurslar/test-temelleri/kalite-guvencesi-nedir

Kalitenin belirtilmiş ve örtük beklentilere uygunluk olarak tanımı, ürün kalitesi ile süreç kalitesinin ayrı ölçülmesi ve denetimsiz kalan kuralın gizlediği kusur.

Bir program çalışıyor olabilir ve yine de yanlış olabilir. Testleri geçiyor olabilir ve yine
de kimsenin istemediği bir şeyi yapıyor olabilir. Doğru çalışıyor olabilir ve on saniyede
yanıt verdiği için kullanılamaz olabilir. Bu üç cümlenin ortak yanı, "çalışıyor" sözcüğünün
tek başına bir yargı taşımamasıdır. Bu müfredat o yargıyı kurar: bir yazılımın istenen şey
olduğuna nasıl karar verilir ve bu karar hangi kanıtlara dayanır?

Frontend Kalitesi kursundaki Test ve İzleme konusu birim, bileşen, tümleşim ve uçtan uca
testleri tarayıcı bağlamında tanıttı. Orada test, belirli bir alanın — tarayıcıda çalışan
arayüzün — kalite aracıydı. Burada konu tersine döner: test disiplininin kendisi, alandan
bağımsız olarak ele alınır. Örnekler JavaScript ve Node.js Çalışma Zamanı kurslarında
kurulan araçlarla yazılır, ama sorular dile ve platforma bağlı değildir.

#### Kalite Bir Uygunluk Yargısıdır

Kalite, bir ürünün **belirtilmiş** (stated) ve **örtük** (implied) beklentileri karşılama
derecesidir. Bu tanımın iki yarısı da gereklidir.

Belirtilmiş beklenti yazılıdır: "öğrenci üyeler kitabı 28 gün ödünç alır". Örtük beklenti
yazılı değildir ama ihlal edildiğinde herkes fark eder: bir üyenin borcunun başka bir üyeye
görünmemesi, iade işleminin iki kez işlenmemesi, sayfanın açılmasının dakikalar sürmemesi.
Belirtim ne kadar dikkatli yazılırsa yazılsın, örtük beklentilerin tamamını içermez.

Bu yüzden kalite mutlak bir nitelik değil, bir **uygunluk yargısıdır**: neye uygunluk
sorusunun yanıtı verilmeden ölçülemez. Kurs boyunca kullanılacak alan küçük bir kütüphane
ödünç kitaplığıdır. Bu alan Backend müfredatındaki kütüphane sistemiyle aynıdır; burada o
sistemin kodu varsayılmaz, kurallar sıfırdan kurulur ve dersler boyunca aynı kitaplık
geliştirilir.

```js
// kitaplik.mjs — odunc kurallari
export const KURALLAR = {
  ogrenci: { oduncGun: 28, enCokOdunc: 10 },
  uye: { oduncGun: 14, enCokOdunc: 5 },
};

export function oduncSuresi(uyeTuru) {
  const kural = KURALLAR[uyeTuru];
  if (kural === undefined) throw new Error(`bilinmeyen uye turu: ${uyeTuru}`);
  return kural.oduncGun;
}

export function enCokOdunc(uyeTuru) {
  return KURALLAR[uyeTuru]?.enCokOdunc ?? 5;
}

export function sonTarih(alisGunu, uyeTuru) {
  return alisGunu + oduncSuresi(uyeTuru);
}
```

Tarihler gün numarasıyla temsil edilir: `sonTarih(1000, 'uye')` bin numaralı günde alınan
kitabın bin on dördüncü günde iade edileceğini söyler. Takvim aritmetiği ve saat dilimi
sorunları kalite tartışmasının konusu değildir; sabit bir gün sayacı örnekleri
belirlenimci (deterministic) tutar.

#### Ürün Kalitesi ve Süreç Kalitesi

**Ürün kalitesi** (product quality) elde duran şeyin niteliğidir: kitaplık kuralları doğru
uyguluyor mu, hızlı mı, güvenli mi? **Süreç kalitesi** (process quality) o şeyi üreten
çalışma biçiminin niteliğidir: her kural bir denetime bağlanmış mı, değişiklikler gözden
geçiriliyor mu, kusurlar nereden geldiğiyle birlikte kaydediliyor mu?

Aradaki bağ tek yönlü ve olasılıksaldır. İyi bir süreç iyi bir ürünü garanti etmez, ama
kötü bir süreç iyi ürünü **tekrarlanamaz** kılar: bir sürüm iyi çıkar, sonraki çıkmaz ve
farkın nereden geldiği bilinemez. Kalite güvencesinin ilgi alanı asıl budur.

İki niteliği ayrı ayrı ölçmek için önce kuralların makine okunur bir listesi gerekir.

```js
// belirtim.mjs — yazili kurallarin makine okunur listesi
export const BELIRTIM = [
  { no: 'K1', metin: 'Ogrenci odunc suresi 28 gundur' },
  { no: 'K2', metin: 'Uye odunc suresi 14 gundur' },
  { no: 'K3', metin: 'Ogrenci en cok 10 kitap odunc alabilir' },
  { no: 'K4', metin: 'Uye en cok 5 kitap odunc alabilir' },
  { no: 'K5', metin: 'Bilinmeyen uye turu reddedilir' },
  { no: 'K6', metin: 'Son tarih, alis gunu ile odunc suresinin toplamidir' },
];
```

#### İki Ölçümü Ayırmak

Ürün kalitesi ölçümü "yazılmış denetimlerin kaçı geçiyor" sorusunu yanıtlar. Süreç kalitesi
ölçümü bambaşka bir soru sorar: "belirtimdeki kuralların kaçının bir denetimi var?" İkinci
soruya **izlenebilirlik** (traceability) denir ve yanıtı ürünün kendisine değil, ürünle
belirtim arasındaki bağa bakar.

```js
// denetim.mjs — iki ayri olcum: urun kalitesi ve surec kalitesi
import assert from 'node:assert/strict';
import { BELIRTIM } from './belirtim.mjs';
import { oduncSuresi, enCokOdunc, sonTarih } from './kitaplik.mjs';

const DENETIMLER = {
  K1: () => assert.equal(oduncSuresi('ogrenci'), 28),
  K2: () => assert.equal(oduncSuresi('uye'), 14),
  K3: () => assert.equal(enCokOdunc('ogrenci'), 10),
  K6: () => assert.equal(sonTarih(1000, 'uye'), 1014),
};

let gecen = 0;
const dusen = [];
for (const [no, denetim] of Object.entries(DENETIMLER)) {
  try {
    denetim();
    gecen += 1;
  } catch {
    dusen.push(no);
  }
}

const denetimsiz = BELIRTIM.filter((k) => DENETIMLER[k.no] === undefined).map((k) => k.no);

console.log(`urun kalitesi : ${gecen}/${gecen + dusen.length} denetim gecti`);
console.log(`surec kalitesi: ${BELIRTIM.length} kuraldan ${BELIRTIM.length - denetimsiz.length} tanesi denetleniyor`);
console.log(`denetimsiz kural: ${denetimsiz.join(', ')}`);
```

```
urun kalitesi : 4/4 denetim gecti
surec kalitesi: 6 kuraldan 4 tanesi denetleniyor
denetimsiz kural: K4, K5
```

Birinci satır kusursuz görünür. İkinci satır, birinci satırın neyi kapsamadığını söyler.
Ürün kalitesi ölçümü yalnız **sorulmuş sorular** hakkında bilgi verir; sorulmamış sorular
o ölçümün içinde görünmez. Süreç kalitesi ölçümü tam olarak bu boşluğu adlandırır.

#### Denetimsiz Kalan Kuralın Bedeli

İki eksik denetim yazıldığında ne olduğu görülür.

```js
// denetim-tam.mjs — denetimsiz kalan iki kural da yazildi
import assert from 'node:assert/strict';
import { oduncSuresi, enCokOdunc, sonTarih } from './kitaplik.mjs';

const DENETIMLER = {
  K1: () => assert.equal(oduncSuresi('ogrenci'), 28),
  K2: () => assert.equal(oduncSuresi('uye'), 14),
  K3: () => assert.equal(enCokOdunc('ogrenci'), 10),
  K4: () => assert.equal(enCokOdunc('uye'), 5),
  K5: () => {
    assert.throws(() => oduncSuresi('konuk'));
    assert.throws(() => enCokOdunc('konuk'));
  },
  K6: () => assert.equal(sonTarih(1000, 'uye'), 1014),
};

for (const [no, denetim] of Object.entries(DENETIMLER)) {
  try {
    denetim();
    console.log(`${no} gecti`);
  } catch (hata) {
    console.log(`${no} dustu: ${hata.message.split('\n')[0]}`);
  }
}
```

```
K1 gecti
K2 gecti
K3 gecti
K4 gecti
K5 dustu: Missing expected exception.
K6 gecti
```

Kusur `enCokOdunc` işlevindedir: bilinmeyen bir üye türü geldiğinde hata vermek yerine
sessizce beş döndürür. Kayıtlı olmayan bir kişi kitap alabilir ve sistem bunu bir üye
davranışı sanır. Kusur her zaman oradaydı; ürün kalitesi ölçümü onu göremiyordu, çünkü
kimse o soruyu sormamıştı.

Buradaki ders, dördüncü satırın çıkmasından daha geneldir. Ürün ölçümü yalnız kendi
kapsamı içinde konuşur; kapsamın nerede bittiğini söyleyen şey süreç ölçümüdür. "Bütün
testler geçiyor" cümlesi, hangi soruların hiç sorulmadığı bilinmeden bir kalite iddiası
değildir.

#### Güvence, Kontrol ve Test

Üç terim sık sık birbirinin yerine kullanılır; sınırları farklıdır.

**Kalite güvencesi** (quality assurance) sürece yöneliktir ve önleyicidir. Kuralların
yazılı olması, her kuralın bir denetime bağlanması, değişikliklerin gözden geçirilmesi,
kusurların kaydedilmesi bu başlığa girer. Sorusu şudur: kusurun ortaya çıkmasını ne
engelliyor?

**Kalite kontrolü** (quality control) ürüne yöneliktir ve tespit edicidir. Elde duran
sürümün beklentileri karşılayıp karşılamadığına bakar. Sorusu şudur: bu sürümde kusur var
mı?

**Test** kalite kontrolünün başlıca yöntemidir, ama tek yöntemi değildir. Gözden geçirme,
biçimsel çözümleme ve statik denetim de kusur bulur. Test, programı **çalıştırarak** kanıt
üretmesiyle ayrılır: gözlenen davranışı beklenenle karşılaştırır.

Üçünün ilişkisi hiyerarşiktir: test, kalite kontrolünün bir aracıdır; kalite kontrolü,
kalite güvencesinin bir parçasıdır. Bu kurs ağırlıklı olarak test hakkındadır, ama testin
neyi kanıtladığı ve neyi kanıtlayamadığı sorusu güvence düzeyinde yanıtlanır.

Testin kanıtlayamadığı şeyin adı da bellidir: bir test yalnız kusurun **varlığını**
gösterebilir, yokluğunu gösteremez. Sonlu sayıda girdiyle sonsuz girdi kümesi denenemez.
Bu yüzden testin işi "kusursuzluğu kanıtlamak" değil, "kusur bulma olasılığı en yüksek
girdileri seçmektir". Test tasarımının bütün teknikleri bu seçim probleminin yanıtıdır.

#### Özet

- Kalite, belirtilmiş ve örtük beklentilere uygunluk derecesidir; neye uygunluk sorusu
  yanıtlanmadan ölçülemez.
- Ürün kalitesi elde duran şeyin, süreç kalitesi onu üreten çalışma biçiminin niteliğidir;
  iyi süreç iyi ürünü garanti etmez ama kötü süreç iyi ürünü tekrarlanamaz kılar.
- İzlenebilirlik ölçümü "kuralların kaçının denetimi var" sorusunu sorar; ürün ölçümü
  yalnız sorulmuş sorular hakkında bilgi verir.
- Örnekte dört denetimin dördü geçiyordu; denetimsiz iki kuraldan biri yazıldığında
  bilinmeyen üye türünün sessizce kabul edildiği kusur ortaya çıktı.
- Kalite güvencesi önleyici ve sürece, kalite kontrolü tespit edici ve ürüne yöneliktir;
  test kalite kontrolünün programı çalıştırarak kanıt üreten yöntemidir.
- Test kusurun varlığını gösterebilir, yokluğunu gösteremez.

#### Sonraki Adım

Bu derste denetimler belirtimden türetildi: yazılı kural neyse, denetim onu sordu. Bu,
"kitaplık belirtime uyuyor mu" sorusudur. Ama belirtimin kendisi yanlışsa ne olur? Kurallar
eksiksiz uygulanmış, her denetim yeşil ve sonuç yine de hatalı olabilir — çünkü yazılı
kural, kütüphanenin gerçekten uyguladığı kural değildir. Sonraki ders bu iki soruyu ayırır:
doğru inşa etmek ile doğru şeyi inşa etmek.

### Doğrulama ve Geçerleme

Source: https://academia.sh/tr/kurslar/test-temelleri/dogrulama-ve-gecerleme

Ürünün belirtime uygunluğunu sınayan doğrulama ile belirtimin gerçek ihtiyaca uygunluğunu sınayan geçerlemenin ayrılması ve her denetimi geçtiği hâlde yanlış ücret hesaplayan bir örnek.

Önceki ders denetimleri belirtimden türetti: yazılı kural neyse, denetim onu sordu.
Denetimlerin tamamı geçtiğinde varılan sonuç şudur — kitaplık, yazılı kurala uyuyor. Bu
sonuç sanıldığı kadar geniş değildir. Yazılı kuralın kendisi kütüphanenin gerçekten
uyguladığı kural olmayabilir. O durumda program kusursuz biçimde yanlış bir şey yapar.

Bu ders o iki soruyu ayırır. Birincisi ürünün belirtime uygunluğunu, ikincisi belirtimin
ihtiyaca uygunluğunu sorar. Aynı kitaplığa yeni bir kural ekleyerek ikisinin bağımsız
olduğu gösterilecek: gecikme ücreti hesabı.

#### İki Ayrı Soru

**Doğrulama** (verification) şunu sorar: ürünü doğru mu inşa ediyoruz? Ölçüt üründen bir
adım geridedir — belirtim, tasarım kararı, kabul ölçütü. Yanıtı üretmek için ürünle o
yazılı ölçüt karşılaştırılır.

**Geçerleme** (validation) şunu sorar: doğru şeyi mi inşa ediyoruz? Ölçüt belirtimin
kendisinden bir adım geridedir — kullanıcının işi, kurumun kuralı, hizmetin amacı. Yanıtı
üretmek için belirtim gerçek kullanımla karşılaştırılır.

İki soru arasındaki fark ölçütün nereden geldiğidir. Doğrulama ölçütü belgedir; geçerleme
ölçütü belgenin dışındaki dünyadır. Bu yüzden yalnız doğrulama yapan bir çalışma kendi
içine kapalıdır: belge ne diyorsa onu onaylar, belgenin yanlış olma ihtimalini hiç
gündeme getirmez.

#### Belirtime Uyan Yanlış Program

Kütüphaneye gecikme ücreti kuralı eklenir. Çözümleme sırasında yazılan madde şudur:

- **K7** — Gecikme ücreti gün başına 2 birimdir.
- **K8** — Negatif gecikme reddedilir.

Uygulaması maddeyi birebir izler.

```js
// ucret.mjs — belirtimdeki K7 kuralinin birebir uygulanmasi
export const GUNLUK_UCRET = 2;

export function gecikmeUcreti(gecikmeGun) {
  if (gecikmeGun < 0) throw new Error('gecikme gun sayisi negatif olamaz');
  return gecikmeGun * GUNLUK_UCRET;
}
```

Denetimler de aynı maddeden türetilir. Önceki dersteki düzenin aynısı kullanılır: her
denetim bir kural numarasına bağlıdır.

```js
// dogrulama.mjs — yazili belirtimden turetilmis denetimler
import assert from 'node:assert/strict';
import { gecikmeUcreti } from './ucret.mjs';

const DENETIMLER = {
  'K7 gun basina 2 birim': () => assert.equal(gecikmeUcreti(5), 10),
  'K7 gecikme yoksa ucret yok': () => assert.equal(gecikmeUcreti(0), 0),
  'K7 ucret gecikmeyle dogru orantili': () => {
    assert.equal(gecikmeUcreti(10), 2 * gecikmeUcreti(5));
  },
  'K8 negatif gecikme reddedilir': () => assert.throws(() => gecikmeUcreti(-1)),
};

let gecen = 0;
for (const [ad, denetim] of Object.entries(DENETIMLER)) {
  try {
    denetim();
    gecen += 1;
  } catch (hata) {
    console.log(`dustu: ${ad} — ${hata.message.split('\n')[0]}`);
  }
}
console.log(`dogrulama: ${gecen}/${Object.keys(DENETIMLER).length} denetim gecti`);
```

```
dogrulama: 4/4 denetim gecti
```

Doğrulama tamdır. Kural iki maddeden ibaretti, iki madde de denetlendi, ikisi de geçti.
İzlenebilirlik ölçümü de kusursuz çıkardı: yazılı her kuralın bir denetimi var. Bu
noktada elde, belirtimine tam uyduğu kanıtlanmış bir program vardır.

#### Gerçeğe Karşı Sınamak

Kütüphanenin gişesinde yıllardır ücret tahsil ediliyordu. Geçerlemenin ölçütü budur:
belge değil, gerçekten yapılmış işlemler. Program o işlemleri yeniden hesaplar ve tahsil
edilmiş tutarla karşılaştırır.

```js
// gecerleme.mjs — gisede gercekten tahsil edilmis ucretlerle karsilastirma
import { gecikmeUcreti } from './ucret.mjs';

const GISE_KAYITLARI = [
  { islem: 'A-101', gecikmeGun: 0, tahsil: 0 },
  { islem: 'A-102', gecikmeGun: 2, tahsil: 0 },
  { islem: 'A-103', gecikmeGun: 3, tahsil: 0 },
  { islem: 'A-104', gecikmeGun: 4, tahsil: 2 },
  { islem: 'A-105', gecikmeGun: 10, tahsil: 14 },
  { islem: 'A-106', gecikmeGun: 40, tahsil: 20 },
];

let uyusan = 0;
for (const k of GISE_KAYITLARI) {
  const hesap = gecikmeUcreti(k.gecikmeGun);
  if (hesap === k.tahsil) uyusan += 1;
  else console.log(`${k.islem}: ${k.gecikmeGun} gun — program ${hesap}, gise ${k.tahsil}`);
}
console.log(`gecerleme: ${uyusan}/${GISE_KAYITLARI.length} kayit uyustu`);
```

```
A-102: 2 gun — program 4, gise 0
A-103: 3 gun — program 6, gise 0
A-104: 4 gun — program 8, gise 2
A-105: 10 gun — program 20, gise 14
A-106: 40 gun — program 80, gise 20
gecerleme: 1/6 kayit uyustu
```

Altı kayıttan biri uyuşuyor. Uyuşan kayıt gecikmesi olmayan işlemdir; ücret hesabının hiç
çalışmadığı durumdur. Uyuşmayan beş kaydın örüntüsü iki eksik kuralı ele verir: ilk üç gün
ücretsizdir ve ücret bir üst sınırda durur. Bu iki kural gişede uygulanıyordu ama belirtime
girmemişti.

Kusur kodda değildir. Kod, kendisinden istenen şeyi doğru yapmıştır. Kusur çözümleme
aşamasındadır ve doğrulamanın hiçbir miktarı onu bulamaz — çünkü doğrulama, ölçütü olarak
tam da kusurlu belgeyi kullanır.

#### Düzeltilen Belirtim

Geçerlemenin çıktısı bir kod düzeltmesi değil, bir belirtim düzeltmesidir. K7 üç maddeye
ayrılır: **ücretsiz gün payı** (grace period) üç gündür, sonraki her gün 2 birimdir, ücret
20 birimi aşmaz.

```js
// ucret2.mjs — gecerlemeden sonra duzeltilen belirtimin uygulanmasi
export const UCRETSIZ_GUN = 3;
export const GUNLUK_UCRET = 2;
export const UST_SINIR = 20;

export function gecikmeUcreti(gecikmeGun) {
  if (gecikmeGun < 0) throw new Error('gecikme gun sayisi negatif olamaz');
  const ucretli = Math.max(gecikmeGun - UCRETSIZ_GUN, 0);
  return Math.min(ucretli * GUNLUK_UCRET, UST_SINIR);
}
```

Artık iki sınav da aynı anda uygulanabilir. Doğrulama yeni maddelerden türetilir,
geçerleme aynı gişe kayıtlarını kullanır.

```js
// iki-sinav.mjs — duzeltilmis belirtim hem dogrulanir hem gecerlenir
import assert from 'node:assert/strict';
import { gecikmeUcreti } from './ucret2.mjs';

const DENETIMLER = {
  'K7a ilk 3 gun ucretsiz': () => assert.equal(gecikmeUcreti(3), 0),
  'K7b sonraki her gun 2 birim': () => assert.equal(gecikmeUcreti(6), 6),
  'K7c ucret 20 birimi asmaz': () => assert.equal(gecikmeUcreti(100), 20),
  'K8 negatif gecikme reddedilir': () => assert.throws(() => gecikmeUcreti(-1)),
};

let gecen = 0;
for (const [ad, denetim] of Object.entries(DENETIMLER)) {
  try { denetim(); gecen += 1; } catch { console.log(`dustu: ${ad}`); }
}

const GISE_KAYITLARI = [
  { gecikmeGun: 0, tahsil: 0 }, { gecikmeGun: 2, tahsil: 0 },
  { gecikmeGun: 3, tahsil: 0 }, { gecikmeGun: 4, tahsil: 2 },
  { gecikmeGun: 10, tahsil: 14 }, { gecikmeGun: 40, tahsil: 20 },
];
const uyusan = GISE_KAYITLARI.filter((k) => gecikmeUcreti(k.gecikmeGun) === k.tahsil).length;

console.log(`dogrulama : ${gecen}/${Object.keys(DENETIMLER).length} denetim gecti`);
console.log(`gecerleme : ${uyusan}/${GISE_KAYITLARI.length} kayit uyustu`);
```

```
dogrulama : 4/4 denetim gecti
gecerleme : 6/6 kayit uyustu
```

İlk sürümde birinci satır da doluydu. Ayıran şey ikinci satırdır. Bir kalite iddiası
kurulurken hangi satırın gösterildiği sorulmalıdır.

#### Her Aşamanın Kendi Sınavı

Doğrulama tek bir etkinlik değildir; her ara ürünün kendi doğrulaması vardır. Tasarım
belirtime karşı, kod tasarıma karşı, yapılandırma koda karşı doğrulanır. Bu eşleşme
geliştirme aşamalarıyla sınama aşamalarını karşılıklı dizen **V modeli** (V-model) olarak
bilinir: soldaki her ayrıştırma adımının sağda bir birleştirme ve sınama adımı vardır.
Model bir süreç önerisi olarak tartışmalıdır, ama kurduğu eşleştirme kalıcıdır — her karar
düzeyinin kendi ölçütü ve kendi sınavı olur.

Doğrulamanın yöntemleri programı çalıştırmayı gerektirmez. **Gözden geçirme** (review),
**statik çözümleme** (static analysis) ve tür denetimi de üründe belirtimden sapma arar.
Test bunların programı çalıştıran türüdür.

Geçerlemenin yöntemleri ise ürünü kullanacak kişiyi ya da onun işini gerektirir: kabul
testi, gerçek işlem kayıtlarıyla karşılaştırma, kullanıcı denemesi, **örnekle belirtim**
(specification by example) — kuralın soyut cümlesi yerine üzerinde anlaşılmış somut
örneklerin yazılması. Yukarıdaki gişe kayıtları bu son yöntemin ham hâlidir.

Geçerlemenin geç kalmasının bedeli doğrulamanınkinden büyüktür. Yanlış kod düzeltilir;
yanlış belirtim üzerine kurulmuş tasarım, veri şeması, arayüz ve eğitim malzemesinin
tamamı yeniden yapılır. Ücret hesabı örneğinde tek bir işlev değişti, ama tahsil edilmiş
tutarlar yanlış olsaydı geriye dönük düzeltme de gerekirdi. Bu yüzden geçerleme sorusu
belirtim yazılırken sorulur — birkaç somut örnek üzerinde anlaşmak, sonradan yapılacak
düzeltmenin küçük bir kesridir.

#### Özet

- Doğrulama ürünün belirtime, geçerleme belirtimin gerçek ihtiyaca uygunluğunu sorar;
  ikisinin ölçütü ayrı yerlerden gelir.
- Yalnız doğrulama yapan bir çalışma kendi içine kapalıdır: belgeyi ölçüt aldığı için
  belgenin yanlış olma ihtimalini gündeme getirmez.
- Örnekte dört denetimin dördü geçti, ama altı gişe kaydından yalnız biri uyuştu; kusur
  kodda değil çözümleme aşamasındaydı.
- Geçerlemenin çıktısı çoğu zaman bir kod düzeltmesi değil, bir belirtim düzeltmesidir.
- Doğrulama gözden geçirme ve statik çözümlemeyle de yapılabilir; geçerleme ürünü
  kullanacak kişinin işini gerektirir.
- Yanlış belirtimin bedeli yanlış kodunkinden büyüktür, çünkü üzerine kurulan her şeyi
  birlikte götürür.

#### Sonraki Adım

Bu derste ücret hesabının doğru sonucu vermesi tartışıldı. Doğru sonuç, beklentilerin
yalnız bir bölümüdür. Kitaplık doğru ücreti hesaplasa bile yanıt vermesi bir dakika
sürüyorsa, on bin kayıtta belleğe sığmıyorsa ya da başka bir üyenin borcunu gösteriyorsa
kullanılamaz. Bu beklentiler belirtimde çoğu zaman yazılı değildir ve yazılı olduklarında
da "hızlı olsun" gibi ölçülemez cümlelerle geçer. Sonraki ders bu beklentileri adlandırır
ve ölçülebilir ölçütlere çevirir.

### Kalite Nitelikleri

Source: https://academia.sh/tr/kurslar/test-temelleri/kalite-nitelikleri

İşlevsel beklentiler ile kalite niteliklerinin ayrılması, nitelik ailelerinin sınıflandırılması ve ölçülemez bir beklentinin uyaran-ortam-yanıt-ölçüt biçiminde ölçülebilir bir ölçüte çevrilmesi.

Önceki ders ücret hesabının doğru sonucu vermesini konu aldı. Doğru sonuç, beklentilerin
yalnız bir bölümüdür. Kitaplık doğru ücreti hesaplayıp da raporu dakikalarca ürettiğinde,
bir üyenin borcunu başka bir üyeye gösterdiğinde ya da bozuk tek bir kayıt yüzünden hiç
çalışmadığında kullanılamaz. Bu beklentiler belirtimde çoğu zaman yazılı değildir; yazılı
olduklarında da "hızlı olsun", "sağlam olsun" gibi sınanamaz cümlelerle geçer.

Bu ders o cümleleri adlandırır ve sınanabilir hâle getirir. Sınanabilir olmanın ölçütü
tektir: bir beklenti, geçip geçmediğine bakılabilen bir ölçüte çevrilebiliyorsa
sınanabilir.

#### İşlevsel Beklenti ve Kalite Niteliği

**İşlevsel beklenti** (functional requirement) sistemin **ne yaptığını** söyler: dört gün
geciken kitaptan 2 birim ücret alınır, bilinmeyen üye türü reddedilir. Doğru ya da yanlış
biçiminde yanıtlanır; ara değeri yoktur.

**Kalite niteliği** (quality attribute) sistemin o işi **nasıl yaptığını** söyler: ne kadar
hızlı, ne kadar dayanıklı, ne kadar güvenli, ne kadar değiştirilebilir. Yanıtı derecelidir
ve bir eşikle birlikte anlam kazanır. Bu yüzden nitelikler için doğruluk değil, **bütçe**
konuşulur.

Ayrım tek başına bir sınıflandırma alıştırması değildir; iki beklenti türü farklı biçimde
sınanır. İşlevsel beklenti tek bir girdi–çıktı çiftiyle sınanabilir. Nitelik ise ancak bir
ölçüm düzeneği, bir yük ve bir eşikle sınanır.

#### Nitelik Aileleri

Nitelikler adlandırılmadığında konuşulamaz. Sık kullanılan aileler ve kitaplık bağlamındaki
karşılıkları şunlardır:

| Nitelik ailesi | İngilizce | Kitaplıktaki sorusu |
|---|---|---|
| işlevsel uygunluk | functional suitability | Ücret hesabı kurala uyuyor mu? |
| başarım verimliliği | performance efficiency | Rapor ne kadar sürede çıkıyor? |
| güvenilirlik | reliability | Bozuk kayıt geldiğinde ne oluyor? |
| kullanılabilirlik | usability | Görevli borcu kaç adımda görüyor? |
| güvenlik | security | Bir üyenin borcu başkasına görünüyor mu? |
| sürdürülebilirlik | maintainability | Ücret kuralı kaç yerden değişiyor? |
| taşınabilirlik | portability | Başka bir çalışma zamanında kuruluyor mu? |
| uyumluluk | compatibility | Eski kayıt biçimi okunabiliyor mu? |

Liste kapalı değildir; her alanın kendi ekleri olur. Önemli olan, bir beklentinin hangi
aileye girdiğinin sorulmasıdır — çünkü aile, ölçünün nasıl kurulacağını belirler.

#### Ölçülemez Beklentiyi Ölçüte Çevirmek

"Rapor hızlı olsun" cümlesi sınanamaz, çünkü ne kadar hızlı, hangi yükte, hangi ortamda
sorularının yanıtı yoktur. Bu boşluğu dolduran biçim dört parçalıdır ve **nitelik senaryosu**
(quality attribute scenario) olarak bilinir:

- **Uyaran** (stimulus): görevli borçlu üye raporunu ister.
- **Ortam** (environment): kitaplıkta 8.000 üye ve 80.000 ödünç kaydı vardır, sistem
  olağan yükte çalışmaktadır.
- **Yanıt** (response): rapor her üyenin toplam gecikme borcunu üretir.
- **Yanıt ölçütü** (response measure): beş koşunun ortancası 100 milisaniyeyi geçmez.

Dördüncü parça beklentiyi sınanabilir kılan parçadır. Frontend Kalitesi kursundaki Başarım
Bütçesi dersinde kurulan bütçe düşüncesi burada alan bağımsız hâliyle kullanılır: ölçüt bir
sayı, bir yük ve bir ölçüm yöntemiyle birlikte yazılır.

#### Ölçütü Ölçmek

Ölçüt yazıldıysa ölçülebilir. Rapor iki biçimde yazılır: her üye için bütün ödünç kayıtlarını
tarayan sürüm ve kayıtları bir kez gezip üye numarasına göre toplayan sürüm.

```js
// rapor.mjs — borclu uye raporunun iki uygulamasi
export function veriUret(uyeSayisi, oduncSayisi) {
  const uyeler = Array.from({ length: uyeSayisi }, (_, i) => ({ no: i, tur: i % 3 === 0 ? 'ogrenci' : 'uye' }));
  const oduncler = Array.from({ length: oduncSayisi }, (_, i) => ({
    uyeNo: (i * 7919) % uyeSayisi,
    gecikmeGun: i % 47,
  }));
  return { uyeler, oduncler };
}

function ucret(gecikmeGun) {
  return Math.min(Math.max(gecikmeGun - 3, 0) * 2, 20);
}

export function raporTaramali(uyeler, oduncler) {
  return uyeler.map((u) => {
    let borc = 0;
    for (const o of oduncler) if (o.uyeNo === u.no) borc += ucret(o.gecikmeGun);
    return { no: u.no, borc };
  });
}

export function raporDizinli(uyeler, oduncler) {
  const borclar = new Map();
  for (const o of oduncler) borclar.set(o.uyeNo, (borclar.get(o.uyeNo) ?? 0) + ucret(o.gecikmeGun));
  return uyeler.map((u) => ({ no: u.no, borc: borclar.get(u.no) ?? 0 }));
}
```

İki sürüm de aynı işlevsel gereksinimi karşılar; ürettikleri rapor birebir aynıdır. Ayrıldıkları
yer nitelik ölçütüdür.

```js
// olcum.mjs — yanit suresi olcutunun olculmesi
import { veriUret, raporTaramali, raporDizinli } from './rapor.mjs';

const BUTCE_MS = 100;
const { uyeler, oduncler } = veriUret(8000, 80000);

function ortancaSure(islev, kosu = 5) {
  const sureler = [];
  for (let i = 0; i < kosu; i += 1) {
    const bas = performance.now();
    islev(uyeler, oduncler);
    sureler.push(performance.now() - bas);
  }
  sureler.sort((a, b) => a - b);
  return sureler[Math.floor(kosu / 2)];
}

for (const [ad, islev] of [['taramali', raporTaramali], ['dizinli', raporDizinli]]) {
  const ortanca = ortancaSure(islev);
  const yargi = ortanca < BUTCE_MS ? 'gecti' : 'kaldi';
  console.log(`${ad.padEnd(9)}: ${BUTCE_MS} ms butcesi — ${yargi}`);
}
```

```
taramali : 100 ms butcesi — kaldi
dizinli  : 100 ms butcesi — gecti
```

Çıktı ham süreyi değil yargıyı yazar; ham süre makineye bağlıdır ve bir dersten diğerine
taşınamaz. Bu metni hazırlarken kullanılan makinede tarama sürümünün ortancası 548,6 ms,
dizinli sürümünkü 2,7 ms ölçüldü — iki sayı da çalıştırıldığı donanıma ve çalışma zamanı
sürümüne göre değişir. Değişmeyen şey, ölçütün iki sürümü ayırmasıdır: bütçe belirlendikten
sonra "hızlı mı" sorusunun yanıtı tartışma konusu olmaktan çıkar.

Ölçümün tek koşuya değil beş koşunun ortancasına dayanması bilinçlidir. İlk koşu çalışma
zamanının ısınma etkisini taşır; ortanca bu sıçramayı dışarıda bırakır.

#### İkinci Bir Nitelik: Güvenilirlik

Başarım tek nitelik değildir ve her nitelik kendi ölçüsünü ister. Güvenilirlik senaryosu
şöyle yazılır: kayıtların bir bölümü bozuk geldiğinde rapor durmaz, bozuk kaydı atlar ve
atlanan oranı bildirir; oran yüzde beşi aşarsa rapor geçersiz sayılır.

```js
// dayaniklilik.mjs — guvenilirlik olcutunun olculmesi
import { veriUret } from './rapor.mjs';

const ESIK = 0.05;
const { oduncler } = veriUret(8000, 80000);
for (let i = 0; i < oduncler.length; i += 33) oduncler[i].gecikmeGun = -1;

let islenen = 0;
let atlanan = 0;
for (const o of oduncler) {
  if (!Number.isInteger(o.gecikmeGun) || o.gecikmeGun < 0) atlanan += 1;
  else islenen += 1;
}

const oran = atlanan / oduncler.length;
console.log(`islenen kayit: ${islenen}`);
console.log(`atlanan kayit: ${atlanan}`);
console.log(`atlanan orani: ${(oran * 100).toFixed(2)} (esik ${ESIK * 100})`);
console.log(`rapor durumu : ${oran <= ESIK ? 'gecerli' : 'gecersiz'}`);
```

```
islenen kayit: 77575
atlanan kayit: 2425
atlanan orani: 3.03 (esik 5)
rapor durumu : gecerli
```

Bu ölçüm süre içermediği için her makinede aynı sonucu verir. Nitelik ölçütlerinin hepsi
zamana bağlı değildir: oran, sayı, boyut ve adım sayısı da ölçüttür ve belirlenimci
oldukları için sınanmaları daha ucuzdur.

#### Nitelikler Birbiriyle Yarışır

Dizinli sürüm süreyi kısalttı, ama bunu üye sayısı kadar girdi tutan bir eşleme kurarak
yaptı. Bellek tüketimi arttı. Bozuk kayıtları atlayan sürüm raporu ayakta tuttu, ama sessizce
eksik bir toplam üretti; doğruluk pahasına süreklilik satın alındı.

Bu genel bir durumdur: nitelikler birbirinden bağımsız artırılamaz. Başarım ile bellek,
güvenlik ile kullanılabilirlik, esneklik ile yalınlık çoğu zaman ters yönde çeker. Bu yüzden
nitelik kararları **ödünleşimdir** (trade-off) ve gerekçesiyle birlikte yazılmalıdır: hangi
nitelik hangi nitelik uğruna ne kadar bırakıldı? Bütçe yazmanın asıl işlevi burada görünür.
Bütçe, ödünleşimi bir kez tartışıp karara bağlar; sonraki her ölçüm o karara karşı yapılır.

#### Özet

- İşlevsel beklenti sistemin ne yaptığını, kalite niteliği bunu nasıl yaptığını söyler;
  birincisi doğru-yanlış, ikincisi dereceli yanıtlanır.
- Bir nitelik beklentisi uyaran, ortam, yanıt ve yanıt ölçütü olarak yazıldığında sınanabilir
  hâle gelir; ölçütü olmayan beklenti sınanamaz.
- Aynı işlevi karşılayan iki uygulama aynı bütçeye karşı farklı sonuç verebilir; örnekte
  tarama sürümü 100 ms bütçesinde kaldı, dizinli sürüm geçti.
- Ölçüm süre içeriyorsa çıktıya ham süre değil yargı yazılır; ham süre makineye bağlıdır.
- Oran, sayı ve boyut ölçütleri belirlenimcidir ve süre ölçütlerinden ucuza sınanır.
- Nitelikler birbiriyle yarışır; bütçe, ödünleşimi bir kez karara bağlayıp sonraki ölçümlere
  ortak ölçüt verir.

#### Sonraki Adım

İki derste de kusur, birinin onu araması sayesinde ortaya çıktı: gişe kayıtları
karşılaştırıldığı için, bütçe ölçüldüğü için. Kimse o karşılaştırmayı yapmasaydı iki kusur da
görünmeyecekti. Buradan bir soru doğar — kusuru arayan kişi neyi farklı yapar? Sonraki ders
testin bir teknikler kümesi olmadan önce bir tutum olduğunu ele alır: programın çalıştığını
göstermeye çalışan bakış ile kırıldığı yeri arayan bakış aynı koda bakıp farklı şeyler bulur.

### Test Zihniyeti

Source: https://academia.sh/tr/kurslar/test-temelleri/test-zihniyeti

Programın çalıştığını gösteren bakış ile kırıldığı yeri arayan bakışın aynı kod üzerinde farklı sonuç vermesi, doğrulama yanlılığı ve tekrarlanan soruların getirisinin azalması.

Önceki iki derste kusur, birinin onu araması sayesinde ortaya çıktı: gişe kayıtları
karşılaştırıldığı için, bütçe ölçüldüğü için. Kimse o karşılaştırmayı yapmasaydı iki kusur
da görünmeyecekti. Testin teknikleri buradan sonra gelir; önce arayan kişinin ne aradığı
sorusu gelir.

Bu ders iki bakışı aynı işlev üzerinde karşılaştırır. Birincisi programın çalıştığını
göstermek ister, ikincisi kırıldığı yeri arar. İkisi de test yazar, ikisi de dürüsttür ve
buldukları farklıdır.

#### İki Bakış Aynı Koda Bakar

Kitaplığa ödünç verme kararı eklenir. Yazılı kural üç maddedir: aktif ödünç sayısı üye
türünün sınırına ulaşmışsa ödünç verilmez, borcu 20 birime ulaşmış üyeye ödünç verilmez,
bilinmeyen üye türü reddedilir.

```js
// odunc.mjs — odunc verme kararinin ilk uygulamasi
const KURALLAR = { ogrenci: { enCokOdunc: 10 }, uye: { enCokOdunc: 5 } };
export const BORC_SINIRI = 20;

export function oduncAlabilirMi(uyeTuru, aktifOdunc, borc) {
  const sinir = KURALLAR[uyeTuru]?.enCokOdunc ?? 5;
  if (aktifOdunc > sinir) return false;
  if (borc > BORC_SINIRI) return false;
  return true;
}
```

Denetimleri koşturmak için önceki derslerde tekrarlanan döngü ayrı bir dosyaya alınır.

```js
// kosucu.mjs — bir denetim listesini kosturan ortak surucu
export function kostur(baslik, denetimler) {
  let gecen = 0;
  const dusen = [];
  for (const [ad, denetim] of Object.entries(denetimler)) {
    try { denetim(); gecen += 1; } catch { dusen.push(ad); }
  }
  console.log(`${baslik}: ${gecen}/${Object.keys(denetimler).length} gecti`);
  for (const ad of dusen) console.log(`  dusen: ${ad}`);
}
```

İki denetim kümesi yazılır. Birincisi kuralın anlatıldığı gibi çalıştığını gösterir:
üç kitabı olan öğrenci alır, on kitabı olan üye alamaz. İkincisi aynı kuralın kenarlarını
yoklar.

```js
// iki-bakis.mjs — mutlu yol denetimleri ile sorgulayici denetimlerin karsilastirilmasi
import assert from 'node:assert/strict';
import { oduncAlabilirMi } from './odunc.mjs';
import { kostur } from './kosucu.mjs';

const MUTLU_YOL = {
  'ogrenci uc kitapla yeni kitap alir': () => assert.equal(oduncAlabilirMi('ogrenci', 3, 0), true),
  'uye bir kitapla yeni kitap alir': () => assert.equal(oduncAlabilirMi('uye', 1, 0), true),
  'ogrenci yirmi kitapla alamaz': () => assert.equal(oduncAlabilirMi('ogrenci', 20, 0), false),
  'uye on kitapla alamaz': () => assert.equal(oduncAlabilirMi('uye', 10, 0), false),
  'borcu elli olan alamaz': () => assert.equal(oduncAlabilirMi('uye', 0, 50), false),
};

const SORGULAYICI = {
  'uye tam sinirdayken alamaz': () => assert.equal(oduncAlabilirMi('uye', 5, 0), false),
  'ogrenci tam sinirdayken alamaz': () => assert.equal(oduncAlabilirMi('ogrenci', 10, 0), false),
  'borc tam sinirdayken alamaz': () => assert.equal(oduncAlabilirMi('uye', 0, 20), false),
  'bilinmeyen uye turu reddedilir': () => assert.throws(() => oduncAlabilirMi('konuk', 0, 0)),
  'negatif odunc sayisi reddedilir': () => assert.throws(() => oduncAlabilirMi('uye', -1, 0)),
};

kostur('mutlu yol  ', MUTLU_YOL);
kostur('sorgulayici', SORGULAYICI);
```

```
mutlu yol  : 5/5 gecti
sorgulayici: 0/5 gecti
  dusen: uye tam sinirdayken alamaz
  dusen: ogrenci tam sinirdayken alamaz
  dusen: borc tam sinirdayken alamaz
  dusen: bilinmeyen uye turu reddedilir
  dusen: negatif odunc sayisi reddedilir
```

İlk küme kusursuz bir tablo çizer. İkinci küme aynı on satırlık işlevde beş ayrı kusur
gösterir: iki karşılaştırma operatörü sınırı bir birim kaydırmış, borç sınırı da aynı
kaymayı taşımış, bilinmeyen üye türü sessizce üye sayılmış, geçersiz sayı hiç
denetlenmemiş.

Kusurların tamamı ilk kümenin kapsamı dışında kaldı. Sebep, ilk kümenin kötü yazılmış
olması değildir; sorduğu soruların hepsi sınırdan uzak noktalardan seçilmiştir.

#### Doğrulama Yanlılığı

İlk kümeyi yazan kişi kuralı okudu ve kuralın çalıştığını gösteren örnekleri seçti. Bu,
**doğrulama yanlılığının** (confirmation bias) test yazımındaki hâlidir: bir iddiayı
sınamak yerine iddiayla uyumlu kanıt aramak.

Yanlılığın kaynağı, sorunun kurulma biçimidir. "Öğrenci kitap alabiliyor mu?" sorusu
alabilen bir örnek arar. "Öğrenci hangi durumda alamaz ve o durumların sınırı nerededir?"
sorusu ise sınırın iki yanını da yoklamayı gerektirir. İkinci soru daha çok iş çıkarır ve
daha çok kusur bulur.

Zihniyetin tanımı buradadır: test yazan kişi, programın doğru olduğunu varsaymaz; doğru
olduğu iddiasını sınayacak kanıtı arar. Bulamazsa iddia bir süreliğine ayakta kalır.

#### Sorgulayıcı Soruların Kaynağı

Sorgulayıcı denetimler rastgele üretilmedi. Her biri bir soru sınıfından geldi:

- **Sınır**: bir eşik geçiliyorsa eşiğin kendisi, bir altı ve bir üstü denenir. Beş kusurun
  üçü bu sınıftan çıktı.
- **Boşluk**: değer yoksa, bulunamadıysa, hesaplanamadıysa ne olur? Bilinmeyen üye türü bu
  sınıftandır.
- **Tür ve alan**: sayı beklenen yere negatif sayı, ondalıklı sayı ya da sayı olmayan bir
  değer gelirse ne olur?
- **Sıra**: işlemler beklenmedik sırada gelirse? İade edilmemiş kitabın yeniden ödünç
  verilmesi bu sınıftandır.
- **Yineleme**: aynı işlem iki kez gelirse toplam iki kat mı artar?
- **Ölçek**: girdi bin kat büyürse davranış aynı kalır mı?
- **Yetki**: işlemi yapan kişinin buna hakkı var mı?

Bu liste bir sağlama listesi (checklist) olarak kullanılır. Sağlama listesinin işlevi,
kusuru bulmak değil, sormayı unutulan soruyu hatırlatmaktır.

#### Aynı Soruların Getirisi Azalır

Beş kusur giderilir. Karşılaştırmalar sınıra eşitliği de kapsayacak biçimde düzeltilir,
bilinmeyen tür hata verir, geçersiz sayı reddedilir.

```js
// odunc2.mjs — sorgulayici denetimlerin bulduklari giderildikten sonra
const KURALLAR = { ogrenci: { enCokOdunc: 10 }, uye: { enCokOdunc: 5 } };
export const BORC_SINIRI = 20;

export function oduncAlabilirMi(uyeTuru, aktifOdunc, borc) {
  const kural = KURALLAR[uyeTuru];
  if (kural === undefined) throw new Error(`bilinmeyen uye turu: ${uyeTuru}`);
  if (!Number.isInteger(aktifOdunc) || aktifOdunc < 0) throw new Error('aktif odunc sayisi gecersiz');
  if (aktifOdunc >= kural.enCokOdunc) return false;
  if (borc >= BORC_SINIRI) return false;
  return true;
}
```

Aynı sorgulayıcı küme yeniden koşturulur ve yanına yeni bir soru sınıfı eklenir: girdi
sayı olmadığında ne olur?

```js
// yeni-sinif.mjs — ayni sorularin tekrari ile yeni bir soru sinifinin karsilastirilmasi
import assert from 'node:assert/strict';
import { oduncAlabilirMi } from './odunc2.mjs';
import { kostur } from './kosucu.mjs';

const SORGULAYICI = {
  'uye tam sinirdayken alamaz': () => assert.equal(oduncAlabilirMi('uye', 5, 0), false),
  'ogrenci tam sinirdayken alamaz': () => assert.equal(oduncAlabilirMi('ogrenci', 10, 0), false),
  'borc tam sinirdayken alamaz': () => assert.equal(oduncAlabilirMi('uye', 0, 20), false),
  'bilinmeyen uye turu reddedilir': () => assert.throws(() => oduncAlabilirMi('konuk', 0, 0)),
  'negatif odunc sayisi reddedilir': () => assert.throws(() => oduncAlabilirMi('uye', -1, 0)),
};

const YENI_SORU_SINIFI = {
  'ondalikli odunc sayisi reddedilir': () => assert.throws(() => oduncAlabilirMi('uye', 2.5, 0)),
  'borc hesaplanamadiysa odunc verilmez': () => assert.throws(() => oduncAlabilirMi('uye', 0, Number.NaN)),
};

kostur('eski sorular', SORGULAYICI);
kostur('yeni sorular', YENI_SORU_SINIFI);
```

```
eski sorular: 5/5 gecti
yeni sorular: 1/2 gecti
  dusen: borc hesaplanamadiysa odunc verilmez
```

Eski sorular artık hiçbir şey bulmuyor. Bu beklenen bir sonuçtur: bir kusur giderildikten
sonra onu bulan test, o kusurun geri gelmesini bekleyen bir nöbetçiye dönüşür — değerlidir
ama yeni bilgi üretmez. Bir test kümesinin kusur bulma getirisi tekrarlandıkça azalır; buna
**böcek ilacı yanılgısı** (pesticide paradox) denir. Yeni kusurlar ancak yeni soru
sınıflarıyla bulunur.

Bulunan altıncı kusur da öğreticidir: borç hesaplanamadığında değer sayı olmadığı için her
karşılaştırma yanlış çıkar ve üye ödünç alır. Kusurların aynı bölgede kümelenmesi de
sıradışı değildir; kusur bir yerde yoğunlaşıyorsa aramanın orada derinleştirilmesi
verimlidir. Buna **kusur kümelenmesi** (defect clustering) denir.

#### Zihniyetin Sınırları

Sorgulayıcı tutum, her düşen testin bir kusur olduğu anlamına gelmez. Testin kendisi
yanlış yazıldığında ya da beklenen değer eskidiğinde çıkan uyarı **yanlış alarmdır** (false
positive) ve güveni aşındırır. Ters yönde, geçen bir test kusuru **kaçırmış** olabilir.
İki hata türü de test kümesinin bilgi değerini düşürür.

Bütün testlerin geçmesi de tek başına bir teslim gerekçesi değildir. Ürün kusursuz çalışıp
yanlış işi yapıyorsa test sonucu bir şey söylemez; ikinci derste ayrılan geçerleme sorusu
buradadır. Bu duruma **hatasızlık yanılgısı** (absence-of-errors fallacy) denir.

Son sınır kişiseldir. Kendi yazdığı kodu sınayan kişi, kodu yazarken kurduğu varsayımların
içinden bakar; aklına gelmeyen girdi, testte de aklına gelmez. Bunun çaresi tutumu terk
etmek değil, bakışı çoğaltmaktır: gözden geçirme, ikinci bir kişinin senaryo yazması,
tekniğin sistemli uygulanması. Sonraki konu bu tekniklere ayrılmıştır.

#### Özet

- Aynı işlev üzerinde mutlu yol denetimleri beşte beş geçerken sınır ve boşluk sorularını
  soran denetimler beş ayrı kusur gösterdi.
- Doğrulama yanlılığı, iddiayı sınamak yerine iddiayla uyumlu kanıt aramaktır; sorunun
  kurulma biçimi hangi kanıtın aranacağını belirler.
- Sorgulayıcı sorular sınır, boşluk, tür, sıra, yineleme, ölçek ve yetki sınıflarından
  sistemli olarak üretilir.
- Bir kusur giderildikten sonra onu bulan test nöbetçiye dönüşür; tekrarlanan kümenin kusur
  bulma getirisi azalır, yeni kusur yeni soru sınıfı ister.
- Yanlış alarm ve kaçırılan kusur test kümesinin bilgi değerini iki yönde düşürür; bütün
  testlerin geçmesi yanlış işin yapılmadığını göstermez.

#### Sonraki Adım

Buraya kadar test, tek bir kişinin bir işlevle baş başa kaldığı bir etkinlik gibi ele
alındı. Gerçekte testin ne zaman yazıldığı, kimin yazdığı ve sonucunun kime ne kadar sürede
ulaştığı çalışma biçimine bağlıdır. Aynı denetim kümesi, sürümün sonunda tek seferde
koşturulduğunda ile her değişiklikte koşturulduğunda farklı değer taşır. Sonraki ders
teslimat modelinin test pratiğini nasıl belirlediğini ve geri bildirim noktasının neyi
değiştirdiğini ele alır.

### Teslimat Modelleri ve Test

Source: https://academia.sh/tr/kurslar/test-temelleri/teslimat-modelleri-ve-test

Testin ayrı bir aşama mı yoksa süreç boyunca süren bir etkinlik mi olduğu sorusu, geri bildirim noktasının gecikme ve aday değişiklik sayısına etkisi ve kümenin süresinin yerleşimini belirlemesi.

Buraya kadar test, tek bir kişinin bir işlevle baş başa kaldığı bir etkinlik gibi ele
alındı. Gerçekte testin ne zaman yazıldığı, kimin yazdığı ve sonucunun kime ne kadar sürede
ulaştığı çalışma biçimine bağlıdır. Aynı denetim kümesi sürümün sonunda tek seferde
koşturulduğunda ile her değişiklikte koşturulduğunda farklı değer taşır.

Bu ders o farkı ölçülebilir kılar. Ölçülen şey testin kendisi değil, bir kusurun ortaya
çıkmasıyla fark edilmesi arasındaki mesafedir.

#### Teslimat Modeli Testin Yerini Belirler

**Şelale modelinde** (waterfall) çalışma aşamalara bölünür ve her aşama bir sonrakine
belge devreder: çözümleme, tasarım, geliştirme, test, teslim. Test burada bir **aşamadır**
— önceden bitmiş bir ürünün üzerinde, sonda yapılır. Modelin gücü, her aşamanın kendi
çıktısını yazılı bırakmasıdır; zayıflığı, geçerleme sorusunun en sona ertelenmesidir.

**Artımlı ve yinelemeli modelde** (incremental and iterative) ürün çalışan parçalar hâlinde
büyür. Her yineleme kendi içinde çözümleme, geliştirme ve testi barındırır. Test bir
aşamadan çok, her yinelemenin içine dağılmış bir etkinliğe dönüşür.

**Sürekli teslimde** (continuous delivery) her değişiklik teslim edilebilir bir aday
sayılır; teslimin önündeki tek engel, otomatik denetim kümesinin sonucudur. Test burada
etkinlik olmaktan da çıkıp bir **kapıya** dönüşür: kümesi geçmeyen değişiklik ilerlemez.

İkinci derste anılan V modelinin eşleştirmesi üç modelde de geçerlidir — her karar
düzeyinin karşılık gelen bir sınama düzeyi vardır. Değişen şey eşleştirme değil, iki ucun
arasındaki süredir.

#### Geri Bildirim Noktası

Bir kusur, kodun yazıldığı anda doğar. Fark edilmesi ise kümesinin koşturulduğu ilk noktada
olur. Aradaki mesafenin iki ölçüsü vardır: **gecikme**, kusurun doğmasıyla haberin gelmesi
arasındaki süre; **aday değişiklik sayısı**, o sürede biriken ve kusurun kaynağı olabilecek
değişikliklerin sayısı.

```js
// geri-bildirim.mjs — ayni denetim kumesinin farkli noktalarda verdigi geri bildirim
const NOKTALAR = [
  { ad: 'kaydetmede', gecikmeDk: 0.5, biriken: 1 },
  { ad: 'gonderim oncesi', gecikmeDk: 3, biriken: 4 },
  { ad: 'tumlestirme', gecikmeDk: 20, biriken: 12 },
  { ad: 'gece kosusu', gecikmeDk: 720, biriken: 40 },
  { ad: 'surum oncesi', gecikmeDk: 20160, biriken: 380 },
];

for (const n of NOKTALAR) {
  console.log(`${n.ad.padEnd(16)}: ${String(n.gecikmeDk).padStart(7)} dk gecikme, ${String(n.biriken).padStart(3)} degisiklik aday`);
}

const ilk = NOKTALAR[0];
const son = NOKTALAR[NOKTALAR.length - 1];
console.log(`oran: gecikme ${son.gecikmeDk / ilk.gecikmeDk} kat, aday ${son.biriken / ilk.biriken} kat`);
```

```
kaydetmede      :     0.5 dk gecikme,   1 degisiklik aday
gonderim oncesi :       3 dk gecikme,   4 degisiklik aday
tumlestirme     :      20 dk gecikme,  12 degisiklik aday
gece kosusu     :     720 dk gecikme,  40 degisiklik aday
surum oncesi    :   20160 dk gecikme, 380 degisiklik aday
oran: gecikme 40320 kat, aday 380 kat
```

Tablodaki sayılar bir kitaplık ekibinin çalışma temposunu temsil eden varsayımlardır;
ölçülmüş değerler değil, model girdileridir. Modelden çıkan sonuç ise sayılara pek bağlı
değildir: geri bildirim noktası geriye kaydıkça gecikme ve aday sayısı birlikte büyür.

İki büyümenin etkisi ayrıdır. Gecikme, kusuru yazan kişinin bağlamını kaybetmesine yol
açar; iki hafta önceki kararın gerekçesi hatırlanmaz. Aday sayısı ise ayıklama işini
büyütür: tek bir değişiklik varken sorun onda, üç yüz seksen değişiklik varken sorun
aralarında bir yerdedir ve bulunması ayrı bir arama problemidir.

Şelale modelinin pratikteki maliyeti buradadır. Test aşaması sürüm sonunda tek noktada
toplandığında, bütün kusurlar en pahalı geri bildirim noktasında ortaya çıkar.

#### Kümenin Süresi Yerleşimini Belirler

Geri bildirim noktası özgürce seçilemez. Bir denetim kümesi her kaydetmede
koşturulabiliyorsa süresi saniyenin altında olmalıdır; kümenin süresi arttıkça noktası
geriye kayar. Süre burada bir yerleşim ölçütüdür.

```js
// bekleme.mjs — dis kaynaga erisimi taklit eden engelleyen bekleme
export function bekle(ms) {
  Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
}
```

```js
// kume-suresi.mjs — iki denetim kumesinin suresi ve ait olduklari geri bildirim noktasi
import assert from 'node:assert/strict';
import { bekle } from './bekleme.mjs';

const ucret = (gun) => Math.min(Math.max(gun - 3, 0) * 2, 20);

const HIZLI = [
  () => assert.equal(ucret(0), 0),
  () => assert.equal(ucret(3), 0),
  () => assert.equal(ucret(4), 2),
  () => assert.equal(ucret(13), 20),
  () => assert.equal(ucret(99), 20),
];

const YAVAS = [
  () => { bekle(400); assert.equal(ucret(10), 14); },
  () => { bekle(400); assert.equal(ucret(20), 20); },
  () => { bekle(400); assert.equal(ucret(1), 0); },
];

const ESIK_MS = 1000;
for (const [ad, kume] of [['hizli', HIZLI], ['yavas', YAVAS]]) {
  const bas = performance.now();
  for (const denetim of kume) denetim();
  const sure = performance.now() - bas;
  const yer = sure < ESIK_MS ? 'her kaydetmede kosturulabilir' : 'gonderim oncesine tasinir';
  console.log(`${ad} kume (${kume.length} denetim): ${ESIK_MS} ms esigi — ${yer}`);
}
```

```
hizli kume (5 denetim): 1000 ms esigi — her kaydetmede kosturulabilir
yavas kume (3 denetim): 1000 ms esigi — gonderim oncesine tasinir
```

İki kümenin sınadığı kural aynıdır; ayrıldıkları yer, yavaş kümedeki her denetimin dış bir
kaynağa erişimi taklit eden bir bekleme içermesidir. Beş denetimli küme üç denetimli
kümeden hızlıdır, çünkü denetim sayısı değil dış bağımlılık belirleyicidir. Bu ölçüm engelli
bekleme kullandığı için her makinede aynı yargıyı verir; yalnız ham süre ortama bağlıdır.

Çıkan kural pratiktir: bir kümenin geri bildirim noktası, denetim sayısına değil, en yavaş
bağımlılığına göre belirlenir. Kümenin bir bölümünü hızlı tutmak istiyorsa, dış
bağımlılığın oradan çıkarılması gerekir. Sonraki kursun konusu tam olarak budur.

#### Modelin Getirdiği Pratikler

Teslimat modeli yalnız zamanlamayı değil, hangi test pratiklerinin gerekli olduğunu da
belirler.

Sürüm sonunda tek noktada test yapılan bir modelde **sürüm dondurma** (code freeze) dönemi
kaçınılmazdır: kusurları bulmak ve gidermek için değişikliklerin durdurulduğu bir aralık
gerekir. Değişiklik her an teslim edilebilir olduğunda böyle bir aralık yoktur; onun yerine
her değişikliğin geçmesi gereken bir **kabul kapısı** (quality gate) tanımlanır.

Geri bildirim noktası geride olduğunda kusur toplu hâlde bulunur ve önceliklendirme ayrı
bir iş kalemi olur; ileride olduğunda kusur tek tek bulunur ve çoğu anında giderilir. Bu
yüzden hata kaydı tutma disiplini de modele göre ağırlık değiştirir.

Ortak nokta şudur: hiçbir model testi ucuzlatmaz. Modelin belirlediği şey, aynı sınamanın
ne zaman ve hangi maliyetle yapılacağıdır.

#### Özet

- Şelalede test bir aşama, artımlı modelde her yinelemenin içine dağılmış bir etkinlik,
  sürekli teslimde ilerlemenin önündeki bir kapıdır.
- Geri bildirim noktası iki büyüklükle ölçülür: gecikme ve aday değişiklik sayısı; nokta
  geriye kaydıkça ikisi birlikte büyür.
- Gecikme bağlam kaybına, aday sayısının artması ayıklamanın ayrı bir arama problemine
  dönüşmesine yol açar.
- Bir kümenin geri bildirim noktası denetim sayısına değil en yavaş bağımlılığına göre
  belirlenir; örnekte beş denetimli küme üç denetimli kümeden hızlı çıktı.
- Model, testi ucuzlatmaz; aynı sınamanın ne zaman ve hangi maliyetle yapılacağını belirler.

#### Sonraki Adım

Bu konu kalitenin ne olduğunu, hangi sorularla sınandığını ve sürecin bu sınamayı nasıl
konumlandırdığını kurdu. Eksik olan, sınamanın kendi iç düzenidir: bir denetim neyi
kapsıyor, nereye kadar uzanıyor, hangi bilgiyle yazılıyor? Sonraki konu testleri
sınıflandırır. İlk soru kapsamla ilgilidir — aynı kusur tek bir işlevi sınayan bir testle
de, bütün sistemi ayağa kaldıran bir testle de yakalanabilir; ikisinin maliyeti ve
söylediği şey aynı değildir.

## Test Sınıflandırması

Test seviyeleri ve maliyetleri, kara–beyaz–gri kutu ayrımı, işlevsel test türleri, denklik sınıfı ile sınır değer ve karar tablosu teknikleri, senaryo yazımı, beklenen sonucun kaynağı olarak test kâhini ve hata yaşam döngüsü.

### Test Seviyeleri

Source: https://academia.sh/tr/kurslar/test-temelleri/test-seviyeleri

Birim, tümleşim, sistem ve kabul seviyelerinin kapsadıkları sınıra göre ayrılması, aynı kusurun üç seviyede yakalanması ve teşhis mesafesinin seviyeyle birlikte büyümesi.

Önceki konu kalitenin ne olduğunu, hangi sorularla sınandığını ve sürecin bu sınamayı nasıl
konumlandırdığını kurdu. Eksik olan, sınamanın kendi iç düzenidir. Aynı kusur, tek bir
işlevi sınayan bir testle de bütün sistemi ayağa kaldıran bir testle de yakalanabilir;
ikisinin maliyeti ve söylediği şey aynı değildir.

Bu ders testleri **kapsadıkları sınıra** göre ayırır. Sınıf adları — birim, tümleşim,
sistem, kabul — bu tek ölçütün adlarıdır.

#### Seviyeyi Belirleyen Şey Kapsamdır

**Birim testi** (unit test) tek bir işlevi ya da modülü, başka hiçbir parçaya dokunmadan
sınar. Girdisi doğrudan işlevin girdisidir, çıktısı doğrudan dönüş değeridir.

**Tümleşim testi** (integration test) iki ya da daha çok parçanın birlikte çalışmasını
sınar. Kusur artık parçaların içinde değil, aralarındaki sözleşmede olabilir: yanlış
sıralanan bağımsız değişken, uyuşmayan birim, beklenmedik boş değer.

**Sistem testi** (system test) bütün parçaları bir arada, gerçek giriş noktasından sınar.
Sorusu, kullanıcının gördüğü sonucun doğru olup olmadığıdır.

**Kabul testi** (acceptance test) kapsam olarak sistem testiyle örtüşür; ayrıldığı yer
ölçütün kaynağıdır. Kabul testinin beklenen sonucu ürünü kullanacak tarafın diliyle
yazılır ve teslimin koşulunu oluşturur. İkinci derste ayrılan geçerleme sorusu bu seviyede
sorulur.

Frontend Kalitesi kursundaki Test ve İzleme konusu aynı ayrımı tarayıcı bağlamında kurmuştu;
burada seviyeler alandan bağımsız olarak ele alınır.

#### Aynı Kusur Üç Seviyede

Kitaplık üç dosyaya ayrılır. Kural katmanı tek bir gecikmenin ücretini hesaplar, toplama
katmanı bir üyenin borcunu çıkarır, işlem katmanı iade fişini üretir.

```js
// ucret.mjs — kural katmani: tek bir gecikmenin ucreti
export const UCRETSIZ_GUN = 3;
export const GUNLUK_UCRET = 2;
export const UST_SINIR = 20;

export function gecikmeUcreti(gecikmeGun) {
  return Math.max(gecikmeGun - UCRETSIZ_GUN, 0) * GUNLUK_UCRET;
}
```

```js
// borc.mjs — toplama katmani: bir uyenin butun gecikmelerinin toplami
import { gecikmeUcreti } from './ucret.mjs';

export function uyeBorcu(oduncler) {
  return oduncler.reduce((toplam, o) => toplam + gecikmeUcreti(o.gecikmeGun), 0);
}
```

```js
// iade.mjs — islem katmani: iade fisinin uretilmesi
import { gecikmeUcreti } from './ucret.mjs';
import { uyeBorcu } from './borc.mjs';

export function iadeFisi(uye, oduncler) {
  return {
    uyeNo: uye.no,
    kalemler: oduncler.map((o) => ({ kitap: o.kitap, ucret: gecikmeUcreti(o.gecikmeGun) })),
    toplam: uyeBorcu(oduncler),
  };
}
```

Kural katmanında bir kusur vardır: `UST_SINIR` tanımlı olduğu hâlde hesapta kullanılmaz.
Üç seviyede de aynı kusur aranır. Aşağıdaki blok üç test dosyasını üretir ve her birini
Node'un yerleşik test koşucusuyla ayrı ayrı çalıştırır. Çıktıdaki süre alanları ortama
bağlı olduğu için yalnız sonuç sayısı ve iddia iletisi süzülür.

```bash
cat > birim.test.mjs <<'EOF'
import test from 'node:test';
import assert from 'node:assert/strict';
import { gecikmeUcreti } from './ucret.mjs';

test('tek gecikmenin ucreti ust siniri asmaz', () => {
  const ucret = gecikmeUcreti(100);
  assert.ok(ucret <= 20, `ucret ${ucret} birim`);
});
EOF

cat > tumlesim.test.mjs <<'EOF'
import test from 'node:test';
import assert from 'node:assert/strict';
import { uyeBorcu } from './borc.mjs';

test('uyenin toplam borcu ust siniri asmaz', () => {
  const borc = uyeBorcu([{ gecikmeGun: 100 }, { gecikmeGun: 30 }]);
  assert.ok(borc <= 20, `borc ${borc} birim`);
});
EOF

cat > sistem.test.mjs <<'EOF'
import test from 'node:test';
import assert from 'node:assert/strict';
import { iadeFisi } from './iade.mjs';

test('iade fisinin toplami ust siniri asmaz', () => {
  const fis = iadeFisi({ no: 7 }, [{ kitap: 'K-1', gecikmeGun: 100 }]);
  assert.ok(fis.toplam <= 20, `fis toplami ${fis.toplam} birim`);
});
EOF

for kume in birim tumlesim sistem; do
  node --test "$kume.test.mjs" > "$kume.log" 2>&1
  printf '%-9s %s | %s\n' "$kume" "$(grep -E '^ℹ fail ' "$kume.log")" \
    "$(grep -m1 AssertionError "$kume.log" | sed 's/.*: //')"
done
```

```
birim     ℹ fail 1 | ucret 194 birim
tumlesim  ℹ fail 1 | borc 248 birim
sistem    ℹ fail 1 | fis toplami 194 birim
```

Üç seviye de kusuru yakaladı. Bu, seviyelerin gereksiz olduğu anlamına gelmez; yakalamanın
maliyeti farklıdır.

#### Teşhis Mesafesi

Bir testin düşmesi, kusurun nerede olduğunu doğrudan söylemez; yalnız kapsamı içinde bir
yerde olduğunu söyler. Kapsam ne kadar genişse, kusurun aranacağı alan o kadar büyüktür.

```js
// seviyeler.mjs — ayni kusurun uc seviyede yakalanmasi ve kapsadigi dosya sayisi
import assert from 'node:assert/strict';
import { gecikmeUcreti } from './ucret.mjs';
import { uyeBorcu } from './borc.mjs';
import { iadeFisi } from './iade.mjs';

const SEVIYELER = [
  {
    ad: 'birim', dosya: ['ucret.mjs'],
    denetim: () => assert.equal(gecikmeUcreti(100), 20),
  },
  {
    ad: 'tumlesim', dosya: ['ucret.mjs', 'borc.mjs'],
    denetim: () => assert.equal(uyeBorcu([{ gecikmeGun: 100 }, { gecikmeGun: 2 }]), 20),
  },
  {
    ad: 'sistem', dosya: ['ucret.mjs', 'borc.mjs', 'iade.mjs'],
    denetim: () => {
      const fis = iadeFisi({ no: 7 }, [{ kitap: 'K-1', gecikmeGun: 100 }]);
      assert.equal(fis.toplam, 20);
    },
  },
];

for (const s of SEVIYELER) {
  let sonuc = 'gecti';
  try { s.denetim(); } catch { sonuc = 'dustu'; }
  console.log(`${s.ad.padEnd(9)}: ${sonuc} — ${s.dosya.length} dosya kapsaminda`);
}
```

```
birim    : dustu — 1 dosya kapsaminda
tumlesim : dustu — 2 dosya kapsaminda
sistem   : dustu — 3 dosya kapsaminda
```

Birim testi düştüğünde bakılacak tek bir dosya vardır. Sistem testi düştüğünde kusur üç
dosyadan herhangi birinde olabilir ve hangisi olduğunu bulmak ayrı bir çalışmadır. Buna
**teşhis mesafesi** denebilir: testin düşmesiyle kusurun bulunması arasındaki iş.

Kapsamla birlikte büyüyen üç maliyet daha vardır. **Kurulum maliyeti**: sistem testi veri,
yapılandırma ve giriş noktası ister. **Süre**: kapsam genişledikçe koşu süresi artar ve
önceki dersteki geri bildirim noktası geriye kayar. **Kırılganlık**: kapsam içindeki her
parça testi düşürebilir, bu yüzden geniş kapsamlı testler ilgisiz nedenlerle daha sık
düşer — dördüncü derste anılan yanlış alarmın başlıca kaynağı budur.

Buna karşılık dar kapsamın da bir bedeli vardır. Birim testi parçaların **arasındaki**
kusuru göremez: her parça kendi başına doğru çalışıp bir araya geldiklerinde yanlış sonuç
üretebilirler. Yukarıdaki kusur her seviyede görünürdü, çünkü tek bir katmandaydı;
sözleşme uyuşmazlıkları böyle davranmaz.

#### Seviyelerin Dağılımı

Seviyelerin göreli sayısı bir tercihtir ve iki uçtan biriyle anılır. **Test piramidinde**
(test pyramid) taban geniştir: çok sayıda hızlı birim testi, daha az tümleşim testi, en
üstte az sayıda sistem testi. Gerekçesi maliyet dağılımıdır — kusurların çoğu ucuz
seviyede yakalanır, pahalı seviye yalnız parçaların birleşimini sınar.

Ters dizilime **dondurma külahı** (ice-cream cone) denir: az sayıda birim testi, çok
sayıda sistem testi ve tepede el testi. Bu dizilimde her geri bildirim yavaş ve her düşen
test pahalıdır.

Piramit bir kural değil, bir maliyet gözlemidir. Alan değiştikçe oranlar değişir: kuralı
ağır bir hesap katmanı tabanı büyütür, ince bir arayüz katmanı üst seviyeye ağırlık verir.
Karar verilirken sorulacak soru sabittir — bu kusur, en ucuz hangi seviyede yakalanabilir?

#### Özet

- Test seviyeleri ne sınadıklarıyla değil, kapsadıkları sınırla ayrılır: birim tek parçayı,
  tümleşim parçaların birleşimini, sistem bütünü sınar.
- Kabul testi kapsam olarak sistem testiyle örtüşür; ölçütünün ürünü kullanacak tarafın
  dilinden gelmesiyle ayrılır.
- Örnekte tek bir katmandaki kusur üç seviyede de yakalandı; ayrım yakalamada değil,
  yakalamanın maliyetindeydi.
- Kapsam genişledikçe teşhis mesafesi, kurulum maliyeti, süre ve kırılganlık birlikte
  büyür; dar kapsam ise parçalar arasındaki kusuru göremez.
- Test piramidi bir kural değil maliyet gözlemidir; karar ölçütü, kusurun en ucuz hangi
  seviyede yakalanacağıdır.

#### Sonraki Adım

Seviye, testin nereye kadar uzandığını belirler. Bağımsız bir soru daha vardır: testi yazan
kişi neye bakarak yazdı? Yukarıdaki üç test de belirtimden türetildi — üst sınır kuralını
okuyup onu sordular. Kodun içine bakılarak yazılsalardı başka sorular çıkardı, çünkü kodda
belirtimde hiç bulunmayan yollar olabilir. Sonraki ders bu bilgi düzeyi ayrımını ele alır
ve iki yaklaşımın farklı kusur türleri bulduğunu gösterir.

### Kara, Beyaz ve Gri Kutu

Source: https://academia.sh/tr/kurslar/test-temelleri/kara-beyaz-ve-gri-kutu

Testin hangi bilgiye dayanarak yazıldığı ayrımı; belirtim tabanlı testin eksik işlevi, yapı tabanlı testin belgelenmemiş dalı bulması ve iki yaklaşımın dal kapsamıyla karşılaştırılması.

Önceki ders testin nereye kadar uzandığını sordu. Bağımsız ikinci bir soru daha vardır:
testi yazan kişi neye bakarak yazdı? Oradaki üç test de belirtimden türetilmişti — üst
sınır kuralı okunup sorulmuştu. Kodun içine bakılarak yazılsalardı başka sorular çıkardı,
çünkü kodda belirtimde hiç bulunmayan yollar olabilir.

Bu ders o bilgi düzeyi ayrımını kurar ve iki yaklaşımın **farklı türde** kusur bulduğunu
aynı işlev üzerinde gösterir.

#### Bilgi Düzeyi Ayrımı

**Kara kutu testi** (black-box testing) yalnız dışarıdan görüneni kullanır: belirtim, arayüz,
girdi ve çıktı. Kodun nasıl yazıldığı bilinmez. Bu yüzden **belirtim tabanlı test**
(specification-based testing) olarak da anılır.

**Beyaz kutu testi** (white-box testing) kaynak koda bakarak yazılır: hangi koşullar var,
hangi dallar var, hangi döngü sınırları var? **Yapı tabanlı test** (structure-based testing)
adı bu bakışı daha iyi anlatır — ölçütü programın yapısıdır.

**Gri kutu testi** (grey-box testing) ikisinin arasındadır. Kaynak kod okunmaz, ama iç
yapının bir bölümü bilinir: veri şeması, kayıt biçimi, günlük kayıtları, hata kodları. Bu
kısmi bilgi girdi seçimini yönlendirir.

Ayrım seviyeden bağımsızdır. Bir birim testi kara kutu da yazılabilir beyaz kutu da; bir
sistem testi için de aynısı geçerlidir.

#### İzli Ücret Hesabı

Karşılaştırmanın ölçülebilir olması için ücret hesabı, hangi dalların çalıştığını kaydeden
bir biçimde yazılır. `IZ` kümesi çalışan dalların numarasını tutar.

```js
// ucret-izli.mjs — hangi dallarin calistigini kaydeden ucret hesabi
export const DALLAR = ['D1', 'D2', 'D3', 'D4'];
export const IZ = new Set();

export function gecikmeUcreti(gecikmeGun, uyeNo, subeDisi = false) {
  if (uyeNo === 0) { IZ.add('D1'); return 0; }
  IZ.add('D2');
  let ucret = Math.max(gecikmeGun - 3, 0) * 2;
  if (ucret > 20) { IZ.add('D3'); ucret = 20; } else { IZ.add('D4'); }
  return ucret;
}
```

Belirtim dört madde içerir: ilk üç gün ücretsizdir, sonraki her gün 2 birimdir, ücret 20
birimi aşmaz ve şube dışı iadede 5 birim ek ücret alınır. Son madde kütüphanenin yeni
kararıdır ve K12 numarasını taşır.

#### Kara Kutu Ne Bulur

Kara kutu denetimleri yalnız bu dört maddeden türetilir. Kodun içindeki `uyeNo` koşulu
bilinmediği için hiçbir denetim onu hedeflemez.

```js
// kara-kutu.mjs — yalnizca belirtimden turetilen denetimler
import assert from 'node:assert/strict';
import { gecikmeUcreti, DALLAR, IZ } from './ucret-izli.mjs';

const DENETIMLER = {
  'K7a ilk 3 gun ucretsiz': () => assert.equal(gecikmeUcreti(3, 41), 0),
  'K7b gun basina 2 birim': () => assert.equal(gecikmeUcreti(6, 41), 6),
  'K7c ucret 20 birimi asmaz': () => assert.equal(gecikmeUcreti(100, 41), 20),
  'K12 sube disi iadede 5 birim ek ucret': () => assert.equal(gecikmeUcreti(4, 41, true), 7),
};

let gecen = 0;
const dusen = [];
for (const [ad, denetim] of Object.entries(DENETIMLER)) {
  try { denetim(); gecen += 1; } catch { dusen.push(ad); }
}

console.log(`kara kutu  : ${gecen}/${Object.keys(DENETIMLER).length} denetim gecti`);
for (const ad of dusen) console.log(`  dusen: ${ad}`);
console.log(`dal kapsami: ${IZ.size}/${DALLAR.length} dal calisti`);
console.log(`  ulasilmayan: ${DALLAR.filter((d) => !IZ.has(d)).join(', ')}`);
```

```
kara kutu  : 3/4 denetim gecti
  dusen: K12 sube disi iadede 5 birim ek ucret
dal kapsami: 3/4 dal calisti
  ulasilmayan: D1
```

Kara kutu, kodda **hiç bulunmayan** bir işlevi buldu: şube dışı iade kuralı belirtimde
vardır, uygulamada yoktur. Beyaz kutu bu kusuru yapısı gereği bulamaz — var olmayan bir
dala test yazılamaz. Eksik işlev yalnız belirtimle karşılaştırarak görülür.

Çıktının son satırı ise kara kutunun körlüğünü gösterir: dört daldan biri hiç çalışmadı.
Belirtimde karşılığı olmayan bir dal, belirtimden türetilen hiçbir denetimin hedefi
değildir. **Dal kapsamı** (branch coverage) ölçümünün buradaki işlevi, kusuru bulmak değil,
kimsenin bakmadığı bölgeyi adlandırmaktır.

#### Beyaz Kutu Ne Bulur

Kaynak koda bakan kişi `D1` dalını görür ve doğrudan onu hedefleyen bir denetim yazar.

```js
// beyaz-kutu.mjs — kaynak koddan gorulen ulasilmayan dali hedefleyen denetim
import assert from 'node:assert/strict';
import { gecikmeUcreti, DALLAR, IZ } from './ucret-izli.mjs';

const ucret = gecikmeUcreti(100, 0);
try {
  assert.ok(ucret === 20, `uye numarasi 0 icin ucret ${ucret}, beklenen 20`);
  console.log('D1 denetimi: gecti');
} catch (hata) {
  console.log(`D1 denetimi: dustu — ${hata.message}`);
}
console.log(`dal kapsami: ${IZ.size}/${DALLAR.length} dal calisti`);
```

```
D1 denetimi: dustu — uye numarasi 0 icin ucret 0, beklenen 20
dal kapsami: 1/4 dal calisti
```

Bulunan şey bir işlem kısayoludur: sıfır numaralı üyeye hiç ücret yansıtılmaz. Böyle bir
dal genellikle bir deneme sırasında eklenip kaldırılmayı unutan koddur; belirtimde
bulunmadığı için hiçbir kara kutu denetimi onu sormaz ve ürüne sessizce yerleşir.

Aynı çıktının ikinci satırı beyaz kutunun kendi körlüğünü gösterir. Tek bir dalı hedefleyen
küme yalnız o dalı çalıştırır; yapıya bakarak yazılan testler kodun **var olan** yollarını
kapsar, kodun **eksik** yollarını değil. Yapıyı ölçüt alan bir çalışma, hiç yazılmamış bir
kuralı hiçbir zaman aramaz.

#### Gri Kutu Aradaki Bilgiyi Kullanır

Gri kutu yaklaşımında kaynak kod okunmaz, ama üye kayıtlarının şeması bilinir: üye numarası
sıfırdan başlayan bir tam sayıdır. Bu bilgi, girdi seçimini yönlendirmeye yeter.

```js
// gri-kutu.mjs — veri semasi bilgisinden turetilen yoklamalar
import { gecikmeUcreti, DALLAR, IZ } from './ucret-izli.mjs';

const UYE_NO_ARALIGI = { enKucuk: 0, enBuyuk: 9999 };
const GECIKME_ORNEKLERI = [0, 4, 100];

const kusurlu = [];
for (const uyeNo of [UYE_NO_ARALIGI.enKucuk, UYE_NO_ARALIGI.enBuyuk]) {
  for (const gun of GECIKME_ORNEKLERI) {
    const beklenen = Math.min(Math.max(gun - 3, 0) * 2, 20);
    const gozlenen = gecikmeUcreti(gun, uyeNo);
    if (gozlenen !== beklenen) kusurlu.push(`uyeNo=${uyeNo} gun=${gun}: ${gozlenen} (beklenen ${beklenen})`);
  }
}

console.log(`gri kutu   : ${kusurlu.length} sapma bulundu`);
for (const s of kusurlu) console.log(`  ${s}`);
console.log(`dal kapsami: ${IZ.size}/${DALLAR.length} dal calisti`);
```

```
gri kutu   : 2 sapma bulundu
  uyeNo=0 gun=4: 0 (beklenen 2)
  uyeNo=0 gun=100: 0 (beklenen 20)
dal kapsami: 4/4 dal calisti
```

Şema bilgisi kaynak koda erişim vermez; yalnız hangi girdilerin sınır olduğunu söyler.
Sıfırın bir üye numarası aralığının alt ucu olduğunu bilmek, o değeri denemeye yeter ve
belgelenmemiş dal kaynak koda hiç bakılmadan ortaya çıkar. Dört dalın dördü de çalıştı.

#### Hangi Yaklaşım Ne Zaman

Üç yaklaşım birbirinin yerine geçmez, farklı boşlukları kapatır.

Kara kutu, belirtimle ürün arasındaki farkı bulur ve kodun değişmesinden etkilenmez; kod
yeniden düzenlendiğinde bu testler ayakta kalır. Kabul seviyesinde ve dış arayüzlerde tek
geçerli yaklaşım budur, çünkü ölçüt kullanıcının gördüğüdür.

Beyaz kutu, belirtimde karşılığı olmayan yolları bulur ve kapsam boşluklarını adlandırır.
Bedeli, kodun yapısına bağlanmasıdır: yapı değiştiğinde testin kendisi de değişir.

Gri kutu, dış arayüzden çalışırken iç bilgiyi girdi seçiminde kullanır. Uygulamada en sık
karşılaşılan durum budur; testi yazan kişi çoğu zaman şemayı, hata kodlarını ve günlük
kayıtlarını bilir.

Pratik ölçüt şudur: denetimlerin gövdesi belirtimden yazılır, kapsam ölçümü yapıdan okunur.
Kapsam boşluğu görüldüğünde sorulacak soru "buraya test yazayım mı" değil, "bu dalın
belirtimde karşılığı var mı" olmalıdır. Karşılığı yoksa yazılacak şey test değil, o dalın
kaldırılmasıdır.

#### Özet

- Kara kutu belirtime, beyaz kutu kaynak kodun yapısına, gri kutu şema ve kayıt biçimi gibi
  kısmi iç bilgiye dayanır; ayrım test seviyesinden bağımsızdır.
- Kara kutu kodda hiç bulunmayan işlevi bulur; beyaz kutu var olmayan dala test yazamadığı
  için bu kusuru göremez.
- Beyaz kutu belirtimde karşılığı olmayan dalları bulur; örnekte sıfır numaralı üyeye ücret
  yansıtmayan belgelenmemiş kısayol böyle ortaya çıktı.
- Dal kapsamı ölçümü kusur bulmaz; kimsenin bakmadığı bölgeyi adlandırır.
- Kapsam boşluğunun doğru sorusu "test yazayım mı" değil, "bu dalın belirtimde karşılığı var
  mı" sorusudur.

#### Sonraki Adım

Seviye ve bilgi düzeyi bir testin yerini belirler, amacını belirlemez. Aynı kara kutu birim
testi, sürümün ayakta olup olmadığını anlamak için de koşturulabilir, giderilmiş bir kusurun
geri gelip gelmediğini denetlemek için de. İkisinde de test aynıdır; değişen şey neden
koşturulduğu ve sonucundan ne yapılacağıdır. Sonraki ders testleri bu amaca göre adlandırır:
duman, sağlık, gerileme ve kabul.

### İşlevsel Test Türleri

Source: https://academia.sh/tr/kurslar/test-temelleri/islevsel-test-turleri

Duman, sağlık, gerileme, yeniden test ve kabul testinin amaçlarına göre ayrılması, aynı sürümde farklı kusurları yakalamaları ve gerileme kümesinin bakım maliyeti.

Seviye ve bilgi düzeyi bir testin yerini belirler, amacını belirlemez. Aynı kara kutu birim
testi, sürümün ayakta olup olmadığını anlamak için de koşturulabilir, giderilmiş bir kusurun
geri gelip gelmediğini denetlemek için de. İkisinde de test aynıdır; değişen şey neden
koşturulduğu ve sonucundan ne yapılacağıdır.

Bu ders testleri amaçlarına göre adlandırır. Adların işlevi, bir kümenin ne zaman
koşturulacağı ve düştüğünde ne yapılacağı konusunda ortak bir anlayış kurmaktır.

#### Amaç Testi Adlandırır

**Duman testi** (smoke test) geniş ve sığdır: her ana yolu bir kez dener, hiçbirini
derinlemesine sınamaz. Sorusu tek cümledir — bu sürüm üzerinde çalışmaya değer mi? Düştüğünde
verilecek karar da tektir: sürüm geri çevrilir, ayrıntılı sınamaya girilmez.

**Sağlık testi** (sanity test) dar ve derindir: belirli bir değişikliğin dokunduğu bölgeyi
birkaç dakikada yoklar. Sorusu şudur — bu düzeltme makul görünüyor mu? Tam kümeyi
koşturmadan önce yapılan bir ön elemedir.

**Gerileme testi** (regression test) daha önce çalışan bir davranışın bozulup bozulmadığını
sınar. Kümesi zamanla birikir: her giderilen kusur, geri geldiğinde uyaracak bir nöbetçi
bırakır.

**Yeniden test** (confirmation test) giderildiği bildirilen tek bir kusurun gerçekten
giderildiğini sınar. Hata kaydındaki adımların birebir tekrarıdır ve kapsamı o kayıttan
ibarettir.

**Kabul testi** teslimin koşuludur ve ölçütü ürünü kullanacak taraftan gelir; önceki iki
derste tanımlanan yeri budur.

#### Duman ve Gerileme Aynı Sürümde

Kitaplığın üç sürümü kurulur: sağlam sürüm, çekirdek bir işlemi bozulmuş sürüm ve daha önce
giderilmiş bir kusuru geri gelmiş sürüm.

```js
// surum.mjs — kitapligin saglam surumu ve iki bozuk surumu
export function temelKitaplik() {
  return {
    uyeBul: (no) => (no >= 0 ? { no, tur: no % 3 === 0 ? 'ogrenci' : 'uye' } : null),
    gecikmeUcreti: (gun) => Math.min(Math.max(gun - 3, 0) * 2, 20),
    oduncVer: (uye, kitap) => ({ uyeNo: uye.no, kitap, durum: 'verildi' }),
  };
}

export const SURUMLER = {
  'S0 saglam': temelKitaplik(),
  'S1 cekirdek bozuk': { ...temelKitaplik(), oduncVer: () => { throw new Error('kayit acilamadi'); } },
  'S2 eski kusur geri geldi': { ...temelKitaplik(), gecikmeUcreti: (gun) => Math.max(gun - 3, 0) * 2 },
};
```

Duman kümesi üç ana yolu birer kez dener. Gerileme kümesi ise üç hata kaydının nöbetçisidir;
her denetimin adı geldiği kaydın numarasını taşır.

```js
// turler.mjs — duman ve gerileme kumelerinin uc surumde karsilastirilmasi
import assert from 'node:assert/strict';
import { SURUMLER } from './surum.mjs';

const DUMAN = {
  'uye kaydi acilir': (k) => assert.ok(k.uyeBul(41)),
  'odunc verilir': (k) => assert.equal(k.oduncVer(k.uyeBul(41), 'K-1').durum, 'verildi'),
  'ucret hesaplanir': (k) => assert.equal(typeof k.gecikmeUcreti(5), 'number'),
};

const GERILEME = {
  'H-14 ucret ust siniri asmaz': (k) => assert.equal(k.gecikmeUcreti(100), 20),
  'H-22 ucretsiz gun payi uc gundur': (k) => assert.equal(k.gecikmeUcreti(3), 0),
  'H-31 gecersiz uye numarasi bos doner': (k) => assert.equal(k.uyeBul(-1), null),
};

function kostur(kume, kitaplik) {
  const dusen = [];
  for (const [ad, denetim] of Object.entries(kume)) {
    try { denetim(kitaplik); } catch { dusen.push(ad); }
  }
  return dusen;
}

for (const [surumAdi, kitaplik] of Object.entries(SURUMLER)) {
  const satir = [];
  for (const [kumeAdi, kume] of [['duman', DUMAN], ['gerileme', GERILEME]]) {
    const dusen = kostur(kume, kitaplik);
    satir.push(`${kumeAdi} ${Object.keys(kume).length - dusen.length}/${Object.keys(kume).length}${dusen.length ? ` (${dusen[0]})` : ''}`);
  }
  console.log(`${surumAdi.padEnd(24)}: ${satir.join(' | ')}`);
}
```

```
S0 saglam               : duman 3/3 | gerileme 3/3
S1 cekirdek bozuk       : duman 2/3 (odunc verilir) | gerileme 3/3
S2 eski kusur geri geldi: duman 3/3 | gerileme 2/3 (H-14 ucret ust siniri asmaz)
```

İki kusur, iki ayrı küme tarafından yakalandı. Çekirdek işlem bozulduğunda gerileme kümesi
susuyor, çünkü nöbetçileri geçmiş kusurların bölgesine yerleşmiştir; ödünç verme hiç kusur
çıkarmadığı için orada nöbetçi yoktur. Eski kusur geri geldiğinde duman kümesi susuyor,
çünkü ücret hesabına yalnız bir sayı dönüp dönmediğini soruyordu.

İki kümenin de amacı doğrudur ve ikisi de eksiktir. Bu eksiklik bir kusur değil, tanımın
sonucudur: sığ küme derinliği, dar küme genişliği bilmez.

#### Yeniden Test Tek Başına Yetmez

Ücret kusuru için düzeltme yapılır. Düzeltmeyi yazan kişi üst sınırı geri koyar, ama
ücretsiz gün payını düşürür.

```js
// saglik.mjs — yeniden test, saglik testi ve gerileme kumesinin ayni duzeltmede verdigi sonuc
import assert from 'node:assert/strict';
import { temelKitaplik } from './surum.mjs';

const duzeltilmis = { ...temelKitaplik(), gecikmeUcreti: (gun) => Math.min(gun * 2, 20) };

const YENIDEN_TEST = {
  'H-14 kayittaki durum': () => assert.equal(duzeltilmis.gecikmeUcreti(100), 20),
};

const SAGLIK = {
  'ucret 0 gunde 0': () => assert.equal(duzeltilmis.gecikmeUcreti(0), 0),
  'ucret 3 gunde 0': () => assert.equal(duzeltilmis.gecikmeUcreti(3), 0),
  'ucret 4 gunde 2': () => assert.equal(duzeltilmis.gecikmeUcreti(4), 2),
  'ucret 13 gunde 20': () => assert.equal(duzeltilmis.gecikmeUcreti(13), 20),
  'ucret 14 gunde 20': () => assert.equal(duzeltilmis.gecikmeUcreti(14), 20),
};

const GERILEME = {
  'H-14 ucret ust siniri asmaz': () => assert.equal(duzeltilmis.gecikmeUcreti(100), 20),
  'H-22 ucretsiz gun payi uc gundur': () => assert.equal(duzeltilmis.gecikmeUcreti(3), 0),
  'H-31 gecersiz uye numarasi bos doner': () => assert.equal(duzeltilmis.uyeBul(-1), null),
};

for (const [ad, kume] of [['yeniden test', YENIDEN_TEST], ['saglik testi', SAGLIK], ['gerileme   ', GERILEME]]) {
  const dusen = Object.entries(kume).filter(([, d]) => { try { d(); return false; } catch { return true; } });
  console.log(`${ad}: ${Object.keys(kume).length - dusen.length}/${Object.keys(kume).length} gecti${dusen.length ? ` — ilk dusen: ${dusen[0][0]}` : ''}`);
}
```

```
yeniden test: 1/1 gecti
saglik testi: 3/5 gecti — ilk dusen: ucret 3 gunde 0
gerileme   : 2/3 gecti — ilk dusen: H-22 ucretsiz gun payi uc gundur
```

Yeniden test geçti: hata kaydındaki durum artık doğru sonucu veriyor. Kayıt kapatılabilirdi.
Sağlık testi ise aynı bölgede birkaç saniye içinde ikinci bir bozulma gösterdi — düzeltme
kendi çevresine zarar vermiş.

Buradan iki kural çıkar. Bir kusur giderildiğinde yalnız kayıttaki durum değil, o kuralın
sınır değerleri de sınanır. Ve yeniden testin geçmesi kaydı kapatmaya yetmez; giderilen
kusurun bölgesi ayrıca yoklanır.

#### Kümelerin Bakım Maliyeti

Gerileme kümesi tek yönlü büyür: her yeni kusur bir nöbetçi ekler, hiçbiri kendiliğinden
çıkmaz. Zamanla küme, geri bildirim noktasını geriye itecek kadar yavaşlar.

Üç tutamak vardır. **Seçmeli gerileme** (regression test selection) yalnız değişen bölgeye
dokunan denetimleri koşturur; tam küme daha geride bir noktaya bırakılır. **Ayıklama**,
karşılığı kalmamış kuralın nöbetçisini kaldırır — kural yoksa nöbetçi de yoktur.
**Kırılgan testlerin** ayıklanması ise güveni korur: ilgisiz nedenlerle düşen bir denetim,
düştüğünde kimsenin bakmadığı bir gürültüye dönüşür ve kümenin tamamının değerini düşürür.

Duman kümesinin bakımı ters yöndedir: küçük kalmalıdır. Bir duman kümesi büyüdükçe kendi
işini yapamaz hâle gelir, çünkü hızlı yanıt vermek onun tek işidir.

#### Özet

- Test türleri amaçlarına göre ayrılır: duman geniş ve sığ, sağlık dar ve derin, gerileme
  geçmiş kusurların nöbetçisi, yeniden test tek bir kaydın tekrarıdır.
- Örnekte çekirdek bozulmayı yalnız duman kümesi, geri gelen kusuru yalnız gerileme kümesi
  yakaladı; iki kümenin de eksikliği tanımlarının sonucudur.
- Yeniden testin geçmesi kusurun giderildiğini gösterir, düzeltmenin çevresine zarar
  vermediğini göstermez; örnekte sağlık testi ikinci bozulmayı ortaya çıkardı.
- Gerileme kümesi tek yönlü büyür; seçmeli koşturma, karşılığı kalmamış denetimlerin
  ayıklanması ve kırılgan testlerin giderilmesi bakımının parçalarıdır.
- Duman kümesinin bakımı küçük kalmasıdır; büyüdüğünde hızlı yanıt verme işlevini yitirir.

#### Sonraki Adım

Buraya kadar denetimlerin girdileri örnek olarak seçildi: 3 gün, 4 gün, 100 gün. Seçimin
gerekçesi sezgiydi. Sonsuz girdi kümesinden sonlu bir alt küme seçmenin sistemli yolları
vardır ve bu yollar hangi girdilerin denendiğini gerekçelendirir. Sonraki ders bu teknikleri
kurar: aynı davranan girdileri sınıflara ayırmak, sınıfların kenarlarını yoklamak ve koşul
birleşimlerini eksiksiz saymak.

### Test Tasarım Teknikleri

Source: https://academia.sh/tr/kurslar/test-temelleri/test-tasarim-teknikleri

Sonsuz girdi uzayından sonlu denetim kümesi seçmenin sistemli yolları: denklik sınıfları, sınır değer analizi ve koşul birleşimlerini eksiksiz sayan karar tablosu.

Buraya kadar denetimlerin girdileri örnek olarak seçildi: 3 gün, 4 gün, 100 gün. Seçimin
gerekçesi sezgiydi ve sezgi tekrarlanabilir değildir — aynı işlevi iki kişi sınadığında iki
farklı küme çıkar, ikisinin de neyi kapsadığı belirsiz kalır.

Bu ders girdi seçimini gerekçelendirilebilir bir işleme dönüştürür. Üç teknik ele alınır:
aynı davranan girdileri sınıflara ayırmak, sınıfların kenarlarını yoklamak ve koşul
birleşimlerini eksiksiz saymak.

#### Girdi Uzayı Sonsuz, Denetim Sayısı Sonlu

Birinci derste kurulan sınır burada işlerlik kazanır: test kusurun varlığını gösterebilir,
yokluğunu gösteremez, çünkü sonlu sayıda girdiyle sonsuz girdi kümesi denenemez. O hâlde
sorulacak soru "hepsi denendi mi" değil, "denenenler neyi temsil ediyor" olmalıdır.

Test tasarım teknikleri bu temsil ilişkisini kurar. Her teknik, girdi uzayını bir ölçüte
göre böler ve bölümlerden seçim yapmayı gerekçelendirir. Ölçüt yazılı olduğu için hangi
girdilerin **neden** seçildiği ve neyin dışarıda kaldığı da yazılı olur.

Örnek olarak ödünç verme kararı kullanılır. Karar üç kurala bağlıdır: kitap başkası adına
rezerve edilmişse verilmez, üyenin borcu sınıra ulaşmışsa verilmez, üyenin aktif ödünç
sayısı türünün sınırına ulaşmışsa verilmez.

```js
// karar.mjs — odunc kararinin uc kuralli uygulamasi
export const ODUNC_SINIRI = { ogrenci: 10, uye: 5 };
export const BORC_SINIRI = 20;

export function oduncKarari({ uyeTuru, aktifOdunc, borc, baskasinaRezerve }) {
  if (!(uyeTuru in ODUNC_SINIRI)) throw new Error(`bilinmeyen uye turu: ${uyeTuru}`);
  if (!Number.isInteger(aktifOdunc) || aktifOdunc < 0) throw new Error('aktif odunc gecersiz');
  if (baskasinaRezerve) return 'rezerve';
  if (borc > BORC_SINIRI) return 'borc';
  if (aktifOdunc >= ODUNC_SINIRI[uyeTuru]) return 'sinir';
  return 'verildi';
}
```

#### Denklik Sınıfları

**Denklik sınıfı** (equivalence class), programın aynı biçimde işlediği girdilerden oluşan
kümedir. Varsayım şudur: bir sınıfın bir üyesi kusuru ortaya çıkarıyorsa, aynı sınıfın
diğer üyeleri de çıkarır. Bu varsayım doğruysa sınıf başına bir temsilci yeterlidir.

Borç değeri iki sınıfa ayrılır: sınırın altındakiler ve sınıra ulaşanlar. Aktif ödünç sayısı
da aynı biçimde ikiye ayrılır. Rezerve durumu zaten iki değerlidir.

```js
// siniflar.mjs — her denklik sinifindan bir temsilci
import { oduncKarari } from './karar.mjs';

const temel = { uyeTuru: 'uye', aktifOdunc: 2, borc: 8, baskasinaRezerve: false };
const TEMSILCILER = [
  ['borc sinifi: sinir alti', { borc: 8 }, 'verildi'],
  ['borc sinifi: sinir ustu', { borc: 35 }, 'borc'],
  ['odunc sinifi: sinir alti', { aktifOdunc: 2 }, 'verildi'],
  ['odunc sinifi: sinir ustu', { aktifOdunc: 7 }, 'sinir'],
  ['rezerve: yok', { baskasinaRezerve: false }, 'verildi'],
  ['rezerve: var', { baskasinaRezerve: true }, 'rezerve'],
];

let gecen = 0;
for (const [ad, degisiklik, beklenen] of TEMSILCILER) {
  const gozlenen = oduncKarari({ ...temel, ...degisiklik });
  if (gozlenen === beklenen) gecen += 1;
  else console.log(`  sapma: ${ad} — beklenen ${beklenen}, gozlenen ${gozlenen}`);
}
console.log(`denklik siniflari: ${gecen}/${TEMSILCILER.length} temsilci uyustu`);
```

```
denklik siniflari: 6/6 temsilci uyustu
```

Altı temsilcinin altısı da uyuştu. Tekniğin getirisi burada görülür: on binlerce olası borç
değeri yerine iki değer denendi ve her sınıfın kapsandığı yazılı hâle geldi.

Sınıflar ayrılırken iki tür ayrım yapılır. **Geçerli sınıf** (valid partition) programın
kabul etmesi gereken girdileri, **geçersiz sınıf** (invalid partition) reddetmesi gerekenleri
toplar — negatif ödünç sayısı ve bilinmeyen üye türü geçersiz sınıflardır. Geçersiz sınıflar
tek tek denenir; iki geçersiz değer aynı denetimde birleştirilirse ilk reddin ikincisini
gölgelemesi ve kusurun görünmez kalması olağandır.

#### Sınır Değer Analizi

Denklik sınıfının varsayımı bir yerde zayıflar: sınıfların **kenarında**. Kusurların büyük
bölümü karşılaştırma operatörlerinde toplanır ve bir operatör hatası yalnız kenarda görünür
— sınıfın ortasından seçilen temsilci onu göremez.

**Sınır değer analizi** (boundary value analysis) her sınıf kenarı için üç değer üretir:
eşiğin bir altı, eşiğin kendisi ve bir üstü. Değerler elle değil, bildirilen eşiklerden
türetilir; eşik değiştiğinde denetimler de kendiliğinden değişir.

```js
// sinir.mjs — siniflarin kenarlarindan turetilen degerler
import { oduncKarari, ODUNC_SINIRI, BORC_SINIRI } from './karar.mjs';

const komsular = (esik) => [esik - 1, esik, esik + 1];
const temel = { uyeTuru: 'uye', aktifOdunc: 2, borc: 8, baskasinaRezerve: false };

const DENETIMLER = [
  ...komsular(BORC_SINIRI).map((borc) => ({
    ad: `borc=${borc}`, girdi: { borc }, beklenen: borc >= BORC_SINIRI ? 'borc' : 'verildi',
  })),
  ...komsular(ODUNC_SINIRI.uye).map((aktifOdunc) => ({
    ad: `aktifOdunc=${aktifOdunc}`, girdi: { aktifOdunc },
    beklenen: aktifOdunc >= ODUNC_SINIRI.uye ? 'sinir' : 'verildi',
  })),
];

let sapma = 0;
for (const d of DENETIMLER) {
  const gozlenen = oduncKarari({ ...temel, ...d.girdi });
  if (gozlenen !== d.beklenen) {
    sapma += 1;
    console.log(`  sapma: ${d.ad} — beklenen ${d.beklenen}, gozlenen ${gozlenen}`);
  }
}
console.log(`sinir degerleri: ${DENETIMLER.length} denetim, ${sapma} sapma`);
```

```
  sapma: borc=20 — beklenen borc, gozlenen verildi
sinir degerleri: 6 denetim, 1 sapma
```

Altı denetim, tek sapma. Borcu tam 20 birim olan üyeye ödünç veriliyor; kural "sınıra
ulaşan" derken uygulama "sınırı aşan" diyor. Bu kusur, sınıfların ortasından seçilen hiçbir
temsilciyle görünmezdi ve bir önceki kümede gerçekten de görünmedi.

Kaç değer denenmelidir? Üç değerli sınır (bir altı, eşik, bir üstü) yukarıdaki gibi bütün
operatör hatalarını yakalar. İki değerli sınır (eşik ve bir üstü) daha ucuzdur ve aynı
kusurların çoğunu bulur. Seçim, eşiğin taşıdığı riske göre yapılır — para, yetki ve süre
eşiklerinde üç değer tercih edilir.

#### Karar Tablosu

Denklik sınıfları ve sınır değerleri girdileri **tek tek** ele alır. Kurallar birbirini
etkilediğinde bu yetmez: hangi koşul birleşimlerinin denendiği ve hangilerinin atlandığı
görünmez kalır.

**Karar tablosu** (decision table) koşulları satırlara, birleşimleri sütunlara yazar ve her
birleşimin hangi eylemi ürettiğini belirtir. Üç koşul sekiz birleşim demektir; sayım
programla yapıldığında hiçbiri atlanmaz.

```js
// karar-tablosu.mjs — uc kosulun butun birlesimlerinin sayilmasi ve kural birlestirme
const KOSULLAR = ['rezerve', 'borc', 'odunc'];

function eylem([rezerve, borc, sinir]) {
  if (rezerve) return 'rezerve';
  if (borc) return 'borc';
  if (sinir) return 'sinir';
  return 'verildi';
}

const birlesimler = [];
for (let i = 0; i < 2 ** KOSULLAR.length; i += 1) {
  const satir = KOSULLAR.map((_, j) => Boolean((i >> (KOSULLAR.length - 1 - j)) & 1));
  birlesimler.push({ satir, eylem: eylem(satir) });
}

console.log(`tam sayim: ${birlesimler.length} birlesim (${KOSULLAR.join(' / ')})`);
for (const b of birlesimler) {
  console.log(`  ${b.satir.map((v) => (v ? 'E' : 'H')).join(' ')} -> ${b.eylem}`);
}

const gruplar = new Map();
for (const b of birlesimler) {
  if (!gruplar.has(b.eylem)) gruplar.set(b.eylem, []);
  gruplar.get(b.eylem).push(b.satir);
}

console.log(`birlestirilmis kural sayisi: ${gruplar.size}`);
for (const [ey, satirlar] of gruplar) {
  const kural = KOSULLAR.map((_, j) => {
    const degerler = new Set(satirlar.map((s) => s[j]));
    return degerler.size === 1 ? ([...degerler][0] ? 'E' : 'H') : '-';
  });
  const kapsam = kural.reduce((n, k) => n * (k === '-' ? 2 : 1), 1);
  console.log(`  ${kural.join(' ')} -> ${ey.padEnd(7)} ${satirlar.length} birlesim, kural ${kapsam} birlesim kapsiyor`);
}
```

```
tam sayim: 8 birlesim (rezerve / borc / odunc)
  H H H -> verildi
  H H E -> sinir
  H E H -> borc
  H E E -> borc
  E H H -> rezerve
  E H E -> rezerve
  E E H -> rezerve
  E E E -> rezerve
birlestirilmis kural sayisi: 4
  H H H -> verildi 1 birlesim, kural 1 birlesim kapsiyor
  H H E -> sinir   1 birlesim, kural 1 birlesim kapsiyor
  H E - -> borc    2 birlesim, kural 2 birlesim kapsiyor
  E - - -> rezerve 4 birlesim, kural 4 birlesim kapsiyor
```

Sekiz birleşim dört kurala indi. `-` işareti **ilgisiz koşulu** (don't care) gösterir: kitap
başkası adına rezerve edilmişse borç ve ödünç sayısı sonucu değiştirmez, çünkü rezerve
denetimi diğerlerinden önce gelir. Birleştirmenin doğruluğu son sütunda sağlanır — kuralın
kapsadığı birleşim sayısı, gruptaki birleşim sayısına eşit olmalıdır.

Karar tablosunun asıl getirisi, kuralların **önceliğini** görünür kılmasıdır. Borcu sınırda
ve kitabı rezerve olan bir üyeye hangi yanıt verilecek? Tablo bu soruyu sormaya zorlar;
belirtim bunu yazmamışsa eksiklik tablo çizilirken ortaya çıkar. Denetim sayısı da kararlı
biçimde belirlenir: dört kural için dört denetim yeterlidir, sekiz denetim gereksizdir.

Koşul sayısı arttıkça birleşim sayısı iki katına çıkar; on koşul bin yirmi dört birleşim
demektir. Bu noktada tam sayım bırakılır ve her koşulun sonucu tek başına belirlediği
birleşimler seçilir. Seçim ölçütü değişir, tablonun kendisi değişmez.

#### Tekniklerin İş Bölümü

Üç teknik aynı soruyu farklı yerlerden yanıtlar ve birbirinin yerine geçmez.

Denklik sınıfları girdi uzayını küçültür ve neyin temsil edildiğini yazılı kılar. Sınır
değerleri sınıfların kenarındaki kusurları yakalar — örnekte tek kusuru yalnız bu teknik
buldu. Karar tablosu koşul birleşimlerinin eksiksiz sayılmasını sağlar ve belirtimdeki
öncelik boşluklarını görünür yapar.

Sıralı davranışların kendi tekniği vardır: bir kaydın durumdan duruma geçişi söz konusuysa
denenmesi gereken şey değerler değil geçişlerdir. Bu teknik son derste ele alınır.

#### Özet

- Test tasarım teknikleri girdi seçimini sezgiden çıkarıp gerekçelendirilebilir bir işleme
  çevirir; ölçüt yazılı olduğu için neyin dışarıda kaldığı da yazılı olur.
- Denklik sınıfı, programın aynı biçimde işlediği girdilerin kümesidir; sınıf başına bir
  temsilci varsayımı sınıfın ortasında geçerli, kenarında zayıftır.
- Geçersiz sınıflar tek tek denenir; birleştirildiklerinde ilk ret ikincisini gölgeler.
- Sınır değer analizi eşiğin bir altını, eşiği ve bir üstünü dener; örnekte borcu tam sınırda
  olan üyeye ödünç verildiği kusuru yalnız bu teknik buldu.
- Karar tablosu koşul birleşimlerini eksiksiz sayar, ilgisiz koşulları birleştirerek denetim
  sayısını düşürür ve belirtimdeki öncelik boşluklarını görünür kılar.

#### Sonraki Adım

Teknikler hangi girdilerin deneneceğini belirledi. Denenen şeyin bir başkasına
anlatılabilmesi ayrı bir iştir: hangi durumda başlanacak, hangi adımlar izlenecek, ne
gözlenecek? Otomatik denetim bu bilgiyi kodun içinde taşır ve yalnız kodu okuyan kişiye
anlatır. Sonraki ders bu bilgiyi yazılı bir biçime kavuşturur ve o biçimden çalıştırılabilir
teste geçişi gösterir.

### Test Senaryosu Yazımı

Source: https://academia.sh/tr/kurslar/test-temelleri/test-senaryosu-yazimi

Bir test senaryosunun kimlik, ön koşul, adım ve beklenen sonuç parçaları, iyi senaryonun ölçütleri ve yazılı senaryodan çalıştırılabilir teste geçiş.

Teknikler hangi girdilerin deneneceğini belirledi. Denenen şeyin bir başkasına
anlatılabilmesi ayrı bir iştir: hangi durumda başlanacak, hangi adımlar izlenecek, ne
gözlenecek? Otomatik denetim bu bilgiyi kodun içinde taşır ve yalnız kodu okuyan kişiye
anlatır. Kütüphane görevlisinin, gözden geçiren kişinin ya da kuralı yazan çözümleyicinin
okuyabileceği bir biçim gerekir.

Bu ders o biçimi kurar ve biçimden çalıştırılabilir teste geçişi gösterir.

#### Senaryonun Parçaları

Bir **test senaryosu** (test case) altı parçadan oluşur.

**Kimlik** senaryoya sabit bir numara verir; hata kaydı ve gerileme kümesi bu numaraya
atıf yapar. **Başlık** senaryonun ne sınadığını tek cümlede söyler ve beklenen sonucu içerir
— başlığı okuyan kişi testin niyetini anlar.

**İzlenebilirlik bağı** senaryonun hangi kuraldan geldiğini gösterir. Birinci derste kurulan
süreç kalitesi ölçümü tam olarak bu bağa dayanır: hangi kuralın senaryosu var, hangisinin
yok?

**Ön koşul** (precondition) senaryonun başlayabilmesi için sistemin hangi durumda olması
gerektiğini yazar. Ön koşul eksikse senaryo tekrarlanabilir olmaz; iki koşuda iki farklı
sonuç verir.

**Adımlar** yapılacak işi sırayla verir. Bir senaryonun tek bir amacı olduğu için adım
sayısı azdır; uzun adım listesi çoğu zaman birden çok senaryonun birbirine yapışmış hâlidir.

**Beklenen sonuç** (expected result) gözlenecek şeyi yazar. Gözlenebilir olmalıdır: ekranda
görünen, dönen değer, kaydedilen satır. "Sistem doğru davranır" gözlenebilir bir sonuç
değildir.

Bunlara **son koşul** eklenebilir: senaryo bittiğinde sistemin hangi durumda kalacağı. Son
koşul yazılıysa senaryolar birbirinin kurduğu duruma bağımlı kalmaz.

#### İyi Senaryonun Ölçütleri

Aynı kuralı sınayan iki senaryo yazımı karşılaştırılabilir. Zayıf yazım şöyledir: "Borçlu
üye ödünç almaya çalışır, sistem uygun tepkiyi verir." Bu cümlenin üç kusuru vardır. Borcun
ne kadar olduğu yazılı değil, bu yüzden ön koşul belirsiz. Sınırın altında mı üstünde mi
olduğu bilinmiyor, bu yüzden hangi denklik sınıfını temsil ettiği belirsiz. "Uygun tepki"
gözlenebilir bir sonuç değil, bu yüzden senaryo geçti mi kaldı mı kararı okuyan kişiye
kalıyor.

Güçlü yazım aynı kuralı şöyle sorar: ön koşul "borcu tam 20 birim olan bir üye, bir aktif
ödüncü var", adım "üye rafta duran bir kitabı ödünç almak ister", beklenen sonuç "istek
reddedilir ve gerekçe olarak borç sınırı gösterilir".

Ölçütler dört başlıkta toplanır. **Tek amaç**: bir senaryo bir kuralı sınar; iki kural iki
senaryodur, çünkü düştüğünde hangisinin bozulduğu belli olmalıdır. **Belirlenimcilik**: aynı
ön koşulla aynı sonucu verir; bugünün tarihine, rastgele değere ya da sıraya bağlı değildir.
**Bağımsızlık**: başka bir senaryonun bıraktığı duruma dayanmaz. **Gözlenebilirlik**: sonuç
bakılarak karar verilebilecek bir şeydir.

#### Senaryolar Veri Olarak

Senaryo yapısı düzenliyse veri olarak temsil edilebilir. Bu temsil iki kazanç sağlar:
senaryolar okunabilir kalır ve aynı listeden hem rapor hem çalıştırılabilir test üretilir.

```js
// karar.mjs — sinir kusuru giderilmis odunc karari
export const ODUNC_SINIRI = { ogrenci: 10, uye: 5 };
export const BORC_SINIRI = 20;

export function oduncKarari({ uyeTuru, aktifOdunc, borc, baskasinaRezerve }) {
  if (!(uyeTuru in ODUNC_SINIRI)) throw new Error(`bilinmeyen uye turu: ${uyeTuru}`);
  if (baskasinaRezerve) return 'rezerve';
  if (borc >= BORC_SINIRI) return 'borc';
  if (aktifOdunc >= ODUNC_SINIRI[uyeTuru]) return 'sinir';
  return 'verildi';
}
```

```js
// senaryolar.mjs — yazili senaryolarin veri olarak temsili
export const KURALLAR = ['K3', 'K4', 'K13', 'K14', 'K15'];

export const SENARYOLAR = [
  {
    no: 'TS-01', kural: 'K4',
    baslik: 'Odunc siniri dolan uyeye yeni kitap verilmez',
    onKosul: { uyeTuru: 'uye', aktifOdunc: 5, borc: 0, baskasinaRezerve: false },
    adim: 'Uye rafta duran bir kitabi odunc almak ister',
    beklenen: 'sinir',
  },
  {
    no: 'TS-02', kural: 'K13',
    baslik: 'Borcu sinira ulasan uyeye kitap verilmez',
    onKosul: { uyeTuru: 'uye', aktifOdunc: 1, borc: 20, baskasinaRezerve: false },
    adim: 'Uye rafta duran bir kitabi odunc almak ister',
    beklenen: 'borc',
  },
  {
    no: 'TS-03', kural: 'K14',
    baslik: 'Baskasi adina rezerve kitap verilmez',
    onKosul: { uyeTuru: 'ogrenci', aktifOdunc: 0, borc: 0, baskasinaRezerve: true },
    adim: 'Ogrenci rezerve listesindeki kitabi odunc almak ister',
    beklenen: 'rezerve',
  },
  {
    no: 'TS-04', kural: 'K3',
    baslik: 'Siniri dolmamis ogrenciye kitap verilir',
    onKosul: { uyeTuru: 'ogrenci', aktifOdunc: 9, borc: 0, baskasinaRezerve: false },
    adim: 'Ogrenci rafta duran bir kitabi odunc almak ister',
    beklenen: 'verildi',
  },
  {
    no: 'TS-05', kural: 'K3',
    baslik: 'Odunc siniri dolan ogrenciye yeni kitap verilmez',
    onKosul: { uyeTuru: 'ogrenci', aktifOdunc: 10, borc: 0, baskasinaRezerve: false },
    adim: 'Ogrenci rafta duran bir kitabi odunc almak ister',
    beklenen: 'sinir',
  },
];
```

Listeyi koşturan sürücü aynı zamanda izlenebilirlik raporunu üretir.

```js
// senaryo-kosucu.mjs — senaryolarin kosturulmasi ve izlenebilirlik raporu
import { oduncKarari } from './karar.mjs';
import { SENARYOLAR, KURALLAR } from './senaryolar.mjs';

let uyusan = 0;
for (const s of SENARYOLAR) {
  const gozlenen = oduncKarari(s.onKosul);
  const sonuc = gozlenen === s.beklenen ? 'uyustu' : `SAPMA (gozlenen ${gozlenen})`;
  console.log(`${s.no} [${s.kural}] ${sonuc}: ${s.baslik}`);
  if (gozlenen === s.beklenen) uyusan += 1;
}

const kapsanan = new Set(SENARYOLAR.map((s) => s.kural));
const senaryosuz = KURALLAR.filter((k) => !kapsanan.has(k));
console.log(`sonuc: ${uyusan}/${SENARYOLAR.length} senaryo uyustu`);
console.log(`izlenebilirlik: ${KURALLAR.length} kuraldan ${kapsanan.size} tanesinin senaryosu var`);
console.log(`senaryosuz kural: ${senaryosuz.join(', ')}`);
```

```
TS-01 [K4] uyustu: Odunc siniri dolan uyeye yeni kitap verilmez
TS-02 [K13] uyustu: Borcu sinira ulasan uyeye kitap verilmez
TS-03 [K14] uyustu: Baskasi adina rezerve kitap verilmez
TS-04 [K3] uyustu: Siniri dolmamis ogrenciye kitap verilir
TS-05 [K3] uyustu: Odunc siniri dolan ogrenciye yeni kitap verilmez
sonuc: 5/5 senaryo uyustu
izlenebilirlik: 5 kuraldan 4 tanesinin senaryosu var
senaryosuz kural: K15
```

Beş senaryonun beşi de uyuştu, ama son satır kara kutu dersindeki uyarıyı tekrarlar: şube dışı
iade kuralının hiçbir senaryosu yok. Senaryo listesi yazılı olduğu için bu boşluk sayılabilir
bir büyüklüğe dönüşmüştür.

#### Senaryodan Çalıştırılabilir Teste

Aynı liste, koşucuya verilecek testleri de üretebilir. Aşağıdaki blok senaryo listesini
gezerek her senaryo için bir test tanımlar ve Node'un yerleşik koşucusuyla çalıştırır.
Çıktıdaki süre alanları ortama bağlı olduğu için yalnız sayım satırları süzülür.

```bash
cat > senaryo.test.mjs <<'EOF'
import test from 'node:test';
import assert from 'node:assert/strict';
import { oduncKarari } from './karar.mjs';
import { SENARYOLAR } from './senaryolar.mjs';

for (const s of SENARYOLAR) {
  test(`${s.no} ${s.baslik}`, () => {
    assert.equal(oduncKarari(s.onKosul), s.beklenen);
  });
}
EOF
node --test senaryo.test.mjs 2>&1 | grep -E '^ℹ (tests|pass|fail) '
```

```
ℹ tests 5
ℹ pass 5
ℹ fail 0
```

Geçiş üç eşleştirmeden ibarettir: ön koşul kurulum verisine, adım çağrıya, beklenen sonuç
iddiaya karşılık gelir. Senaryo bu üç parçayı taşımıyorsa çalıştırılabilir teste
çevrilemiyor demektir — ve çevrilemiyorsa elle koşturulduğunda da aynı belirsizliği taşır.

Test adının senaryo numarasıyla başlaması bilinçlidir. Bir test düştüğünde okunacak ilk şey
hangi senaryonun bozulduğudur; numara, hata kaydından senaryoya ve senaryodan kurala giden
yolu açık tutar.

#### Elle Koşturulan Senaryonun Yeri

Her senaryo otomatik teste çevrilmez. Kütüphane görevlisinin ekrandaki yerleşimi anlaşılır
bulup bulmadığı, bir uyarı metninin yeterince açık olup olmadığı yazılı senaryoyla sınanır
ama insan yargısı gerektirir.

Ayrı bir tür daha vardır: **keşifsel test** (exploratory testing). Burada senaryo önceden
yazılmaz; sınayan kişi ürünü kullanırken bir sonraki denemesini bir öncekinin sonucuna göre
seçer. Keşif testi yazılı senaryonun yerine geçmez, onun boşluğunu doldurur — dördüncü
derste anılan sormayı unutulan soru çoğu zaman böyle bulunur. Bulunduğunda ise yazılı bir
senaryoya çevrilir ve gerileme kümesine katılır.

#### Özet

- Test senaryosu kimlik, başlık, izlenebilirlik bağı, ön koşul, adımlar ve beklenen
  sonuçtan oluşur; son koşul senaryoları birbirinden bağımsız kılar.
- İyi senaryonun ölçütleri tek amaç, belirlenimcilik, bağımsızlık ve gözlenebilir sonuçtur;
  "sistem uygun tepkiyi verir" gözlenebilir bir sonuç değildir.
- Senaryolar veri olarak temsil edildiğinde aynı listeden hem izlenebilirlik raporu hem
  çalıştırılabilir test üretilir.
- Senaryodan teste geçiş üç eşleştirmedir: ön koşul kuruluma, adım çağrıya, beklenen sonuç
  iddiaya karşılık gelir.
- Örnekte beş senaryonun beşi uyuştu, ama beş kuraldan birinin hiç senaryosu olmadığı
  sayılabilir biçimde ortaya çıktı.
- Keşif testi yazılı senaryonun yerine geçmez; bulduğu şey yazılı senaryoya çevrilerek
  gerileme kümesine katılır.

#### Sonraki Adım

Senaryonun en kritik alanı sessizce geçildi: beklenen sonuç nereden geliyor? Yukarıdaki beş
senaryoda değerler belirtimden okundu, çünkü kural yazılıydı. Kural yazılı değilse, hesap
elle doğrulanamayacak kadar karmaşıksa ya da doğru sonucu kimse bilmiyorsa beklenen sonuç
nasıl belirlenir? Sonraki ders bu soruyu ele alır ve beklenen sonucun kaynağını üç ayrı
yöntemle kurup karşılaştırır.

### Test Kâhini

Source: https://academia.sh/tr/kurslar/test-temelleri/test-kahini

Beklenen sonucun kaynağı sorunu ve üç yanıtın karşılaştırılması — bağımsız ikinci hesap, metamorfik ilişki ve altın dosya.

Önceki dersin senaryolarında beklenen sonuç belirtimden okundu: kural yazılıydı, senaryo
onu tekrarladı. Bu, en kolay durumdur. Kural yazılı değilse, hesap elle doğrulanamayacak
kadar karmaşıksa ya da doğru sonucu kimse tam olarak bilmiyorsa beklenen sonuç nereden
gelecek?

Bu soruya **kâhin sorunu** (oracle problem) denir. Bir **test kâhini** (test oracle),
gözlenen sonucun doğru olup olmadığına karar veren kaynaktır. Test kâhini olmadan test
yoktur; yalnız program çalıştırılmış olur.

#### Kâhin Sorunu

Kâhinin bir maliyeti vardır ve maliyeti çoğu zaman testin kendisinden yüksektir. Bir işlevi
çağırmak ucuzdur; dönen değerin doğru olduğunu **bağımsız olarak** bilmek pahalıdır.

En yaygın kâhin **belirtilmiş kâhindir** (specified oracle): beklenen değer belirtimden
okunur ve senaryoya yazılır. Sınırı ikinci derste görüldü — belirtim yanlışsa kâhin de
yanlıştır ve bütün testler yanlış bir doğruyu onaylar.

İkinci sınırı ölçektir. Yirmi girdi için beklenen değer elle yazılabilir; sekiz yüz girdi
için yazılamaz. Girdi uzayı büyüdüğünde beklenen değerin **üretilmesi** gerekir.

Karşılaştırma için ücret hesabına iki kural daha eklenir: şube dışı iade edilen kitap için
5 birim eklenir ve öğrenci üyelerden toplamın yarısı alınır, kuruş bulunmadığı için aşağı
yuvarlanarak.

```js
// ucret.mjs — uc kuralli toplam ucret hesabi
export function toplamUcret({ gecikmeGun, uyeTuru, subeDisi = false }) {
  const temel = Math.min(Math.max(gecikmeGun - 3, 0) * 2, 20);
  const toplam = temel + (subeDisi ? 5 : 0);
  return uyeTuru === 'ogrenci' ? Math.round(toplam / 2) : toplam;
}

export function girdiler() {
  const liste = [];
  for (const uyeTuru of ['uye', 'ogrenci']) {
    for (const subeDisi of [false, true]) {
      for (let gecikmeGun = 0; gecikmeGun <= 20; gecikmeGun += 1) {
        liste.push({ gecikmeGun, uyeTuru, subeDisi });
      }
    }
  }
  return liste;
}
```

Seksen dört girdi için beklenen değeri elle yazmak makul değildir. Üç ayrı kâhin kurulur.

#### Bağımsız İkinci Hesap

Birinci yaklaşım aynı kuralı **başka bir yoldan** hesaplar. Asıl uygulama aritmetik bir
kapalı biçim kullanıyorsa, ikinci hesap günleri tek tek toplar. İki yol farklı olduğu için
aynı kusuru paylaşma olasılıkları düşüktür.

```js
// kahin-ikinci-hesap.mjs — kuraldan bagimsiz bicimde turetilen ikinci hesap
import { toplamUcret, girdiler } from './ucret.mjs';

function referans({ gecikmeGun, uyeTuru, subeDisi }) {
  let temel = 0;
  for (let gun = 1; gun <= gecikmeGun; gun += 1) {
    if (gun <= 3) continue;
    if (temel >= 20) break;
    temel += 2;
  }
  const toplam = temel + (subeDisi ? 5 : 0);
  return uyeTuru === 'ogrenci' ? Math.floor(toplam / 2) : toplam;
}

const sapmalar = girdiler().filter((g) => toplamUcret(g) !== referans(g));
console.log(`ikinci hesap: ${girdiler().length} girdi, ${sapmalar.length} sapma`);
for (const g of sapmalar.slice(0, 3)) {
  console.log(`  gun=${g.gecikmeGun} ${g.uyeTuru} subeDisi=${g.subeDisi}: hesap ${toplamUcret(g)}, referans ${referans(g)}`);
}
```

```
ikinci hesap: 84 girdi, 21 sapma
  gun=0 ogrenci subeDisi=true: hesap 3, referans 2
  gun=1 ogrenci subeDisi=true: hesap 3, referans 2
  gun=2 ogrenci subeDisi=true: hesap 3, referans 2
```

Yirmi bir sapma bulundu. Kusur yuvarlamadadır: kural aşağı yuvarlamayı söylüyor, uygulama
en yakına yuvarlıyor. Öğrenci ve şube dışı iade birleştiğinde toplam tek sayı oluyor ve iki
yuvarlama ayrışıyor.

Bu yaklaşıma **sözde kâhin** (pseudo-oracle) da denir. Bedeli açıktır: aynı işi iki kez
yapmak. Bu yüzden yalnız hesap ağırlıklı ve yüksek riskli bölgelerde uygulanır. Bir de
tuzağı vardır — ikinci hesap birinciye bakarak yazılırsa aynı yanlış anlamayı devralır ve
iki taraf birlikte susar.

#### Metamorfik İlişki

İkinci yaklaşım beklenen değeri hiç bilmez. Bunun yerine, girdi belirli bir biçimde
değiştiğinde çıktının nasıl değişmesi gerektiğini söyleyen bir **metamorfik ilişki**
(metamorphic relation) kurar. İlişki sağlanmıyorsa bir kusur vardır; sağlanıyorsa yalnız o
ilişki hakkında bilgi edinilmiştir.

```js
// kahin-metamorfik.mjs — beklenen degeri bilmeden kurulan iliskiler
import { toplamUcret, girdiler } from './ucret.mjs';

const ILISKILER = {
  'MI1 gecikme arttikca ucret azalmaz': () => {
    for (const uyeTuru of ['uye', 'ogrenci']) {
      for (let gun = 1; gun <= 20; gun += 1) {
        const once = toplamUcret({ gecikmeGun: gun - 1, uyeTuru });
        const sonra = toplamUcret({ gecikmeGun: gun, uyeTuru });
        if (sonra < once) return `gun=${gun} ${uyeTuru}: ${once} -> ${sonra}`;
      }
    }
    return null;
  },
  'MI2 ucret 25 birimi asmaz': () => {
    for (const g of girdiler()) {
      if (toplamUcret(g) > 25) return `gun=${g.gecikmeGun} ${g.uyeTuru}: ${toplamUcret(g)}`;
    }
    return null;
  },
  'MI3 ogrenci ucretinin iki kati uye ucretini asmaz': () => {
    for (const g of girdiler().filter((x) => x.uyeTuru === 'uye')) {
      const uye = toplamUcret(g);
      const ogrenci = toplamUcret({ ...g, uyeTuru: 'ogrenci' });
      if (ogrenci * 2 > uye) return `gun=${g.gecikmeGun} subeDisi=${g.subeDisi}: ogrenci ${ogrenci}, uye ${uye}`;
    }
    return null;
  },
};

for (const [ad, iliski] of Object.entries(ILISKILER)) {
  const ihlal = iliski();
  console.log(`${ad}: ${ihlal === null ? 'saglandi' : `ihlal — ${ihlal}`}`);
}
```

```
MI1 gecikme arttikca ucret azalmaz: saglandi
MI2 ucret 25 birimi asmaz: saglandi
MI3 ogrenci ucretinin iki kati uye ucretini asmaz: ihlal — gun=0 subeDisi=true: ogrenci 3, uye 5
```

Üç ilişkiden ikisi sağlandı, biri ihlal edildi ve aynı kusuru gösterdi — hiçbir yerde
beklenen değer yazılmadığı hâlde. Metamorfik ilişkiler bu yüzden **kısmi kâhin** (partial
oracle) sayılır: kusurun varlığını gösterebilir, doğruluğu onaylayamaz.

İki ilişkinin sağlanması da bilgidir. Bir ilişkinin sağlanması, o ilişkinin tanımladığı
kusur sınıfının bulunmadığını söyler; hepsinin sağlanması programın doğru olduğunu söylemez.
İlişkiler girdi uzayının tamamında geçerli olduğu için üretilmiş girdilerle birlikte
kullanılmaya elverişlidir.

#### Altın Dosya

Üçüncü yaklaşım beklenen değeri **kaydeder**. Programın bugünkü çıktısı bir dosyaya yazılır
ve sonraki koşularda o dosyayla karşılaştırılır. Bu dosyaya **altın dosya** (golden file)
denir.

```js
// kahin-altin.mjs — kaydedilmis ciktinin kahin olarak kullanilmasi
import { writeFileSync, readFileSync } from 'node:fs';
import { toplamUcret, girdiler } from './ucret.mjs';

const altin = girdiler().map((g) => ({ ...g, ucret: toplamUcret(g) }));
writeFileSync('altin.json', JSON.stringify(altin));
console.log(`altin dosya: ${altin.length} kayit yazildi`);

const kayitli = JSON.parse(readFileSync('altin.json', 'utf8'));
const aynisi = kayitli.filter((k) => toplamUcret(k) !== k.ucret);
console.log(`degismemis surum: ${aynisi.length} fark`);

function ustSiniri25Yapan({ gecikmeGun, uyeTuru, subeDisi }) {
  const temel = Math.min(Math.max(gecikmeGun - 3, 0) * 2, 25);
  const toplam = temel + (subeDisi ? 5 : 0);
  return uyeTuru === 'ogrenci' ? Math.round(toplam / 2) : toplam;
}
const degisen = kayitli.filter((k) => ustSiniri25Yapan(k) !== k.ucret);
console.log(`ust siniri degistiren surum: ${degisen.length} fark`);

const kusurluKayit = kayitli.find((k) => k.uyeTuru === 'ogrenci' && k.subeDisi && k.gecikmeGun === 0);
console.log(`altin dosyada gun=0 ogrenci subeDisi kaydi: ${kusurluKayit.ucret} (dogrusu 2)`);
```

```
altin dosya: 84 kayit yazildi
degismemis surum: 0 fark
ust siniri degistiren surum: 28 fark
altin dosyada gun=0 ogrenci subeDisi kaydi: 3 (dogrusu 2)
```

Altın dosya, üst sınırı sessizce değiştiren sürümü yirmi sekiz kayıtta yakaladı. Bu, güçlü
olduğu yerdir: **istenmeyen değişikliği** ucuza tespit eder ve beklenen değer yazma işini
tamamen ortadan kaldırır.

Son satır zayıflığını gösterir. Dosya, yuvarlama kusurunu taşıyan çıktıdan üretildiği için
kusuru da kaydetmiştir. Artık o kusur "beklenen davranış" sayılır ve düzeltilmesi altın
dosyayı düşürür — düzeltmeyi yapan kişi dosyayı yenileyerek testi susturur. Altın dosya
kusuru bulmaz; var olan davranışı dondurur.

Bu yüzden altın dosya kabul edilirken içeriği okunmalıdır. Okunamayacak kadar büyük bir
altın dosya, denetlenmemiş bir kâhindir.

#### Kâhin Seçimi

Üç yaklaşım aynı işi yapmaz ve birlikte kullanılırlar.

Belirtilmiş kâhin az sayıda ve yüksek değerli senaryoda kullanılır: kuralın kendisini
tanımlayan örnekler. Bağımsız ikinci hesap, hesap ağırlıklı bölgelerde ve girdi uzayı
büyükken devreye girer. Metamorfik ilişkiler beklenen değerin bilinemediği yerde çalışır ve
üretilmiş girdilerle birlikte en verimli hâlini alır. Altın dosya, çıktısı geniş ve yapısal
olan yerlerde istenmeyen değişikliği yakalar.

Bir de **insan kâhini** vardır: doğru sonucu bilen kişi. En pahalı ve en yavaş kâhindir,
ama bazı sorularda tek kâhin odur — bir uyarı metninin anlaşılır olup olmadığına program
karar veremez.

Seçimde tek ölçüt vardır: kâhin, sınadığı programdan **bağımsız** olmalıdır. Programın
kendi çıktısından türetilen bir kâhin, programın kendi kendini onaylamasıdır.

#### Özet

- Test kâhini, gözlenen sonucun doğru olup olmadığına karar veren kaynaktır; kâhin olmadan
  test değil yalnız çalıştırma vardır.
- Belirtilmiş kâhin belirtim yanlışsa yanlış doğruyu onaylar ve girdi uzayı büyüdüğünde elle
  yazılamaz.
- Bağımsız ikinci hesap aynı kuralı başka yoldan hesaplar; örnekte seksen dört girdide yirmi
  bir sapma buldu, ama birinciye bakılarak yazılırsa aynı yanlış anlamayı devralır.
- Metamorfik ilişki beklenen değeri hiç bilmeden kusur gösterir; kısmi kâhindir, sağlanması
  doğruluğu onaylamaz.
- Altın dosya istenmeyen değişikliği ucuza yakalar, ama üretildiği andaki kusuru beklenen
  davranış olarak dondurur.
- Kâhinin tek ölçütü sınadığı programdan bağımsız olmasıdır.

#### Sonraki Adım

Bir kâhin sapma gösterdiğinde iş bitmez, başlar. Sapmanın kaydedilmesi, tekrarlanabilir
biçimde anlatılması, önceliklendirilmesi, giderilmesi ve giderildiğinin doğrulanması gerekir.
Bu adımların her biri bir durumdur ve aralarında yalnız belirli geçişler geçerlidir. Kursun
son dersi bu döngüyü kurar.

### Hata Yaşam Döngüsü

Source: https://academia.sh/tr/kurslar/test-temelleri/hata-yasam-dongusu

Hata, kusur ve arıza ayrımı, hata kaydının alanları, önem ile önceliğin ayrılması ve kaydın yaşam döngüsünün durum makinesi olarak modellenip geçiş kapsamının ölçülmesi.

Bir kâhin sapma gösterdiğinde iş bitmez, başlar. Sapmanın kaydedilmesi, tekrarlanabilir
biçimde anlatılması, önceliklendirilmesi, giderilmesi ve giderildiğinin doğrulanması
gerekir. Bu adımların her biri bir durumdur ve aralarında yalnız belirli geçişler
geçerlidir.

Bu ders o döngüyü kurar. Döngünün kendisi de bir sistemdir ve önceki derslerdeki
tekniklerle sınanabilir; son bölümde bu yapılır.

#### Hata, Kusur ve Arıza

Türkçede de İngilizcede de birbirinin yerine kullanılan üç terim ayrı şeyleri adlandırır.

**Hata** (error) insanın yaptığı yanlıştır: kuralın yanlış anlaşılması, bir sınırın yanlış
okunması, bir operatörün yanlış yazılması. Hata bir üründe değil, bir kişide olur.

**Kusur** (defect) hatanın üründe bıraktığı izdir: yanlış yazılmış koşul, eksik denetim,
yanlış belirtim maddesi. Kusur üründe durur ve kimse çalıştırmasa da oradadır.

**Arıza** (failure) kusurun dışa vurmasıdır: programın gözlenen davranışının beklenenden
sapması. Her kusur arızaya dönüşmez — kusurun bulunduğu dal hiç çalışmıyorsa arıza görünmez.

Ayrım pratikte işe yarar. Bir hata kaydı arızayı anlatır, çünkü gözlenen budur. Düzeltme
kusuru hedefler. Kusurun tekrarlanmaması için yapılan iş ise hataya bakar: aynı yanlış
anlama başka yerlerde de var mı?

#### Hata Kaydının Alanları

İyi bir hata kaydının ölçütü tektir: kaydı okuyan başka bir kişi, yazarına hiç soru sormadan
arızayı yeniden üretebilmelidir.

Kayıt şu alanları taşır. **Kimlik** ve **başlık**: başlık arızayı tek cümlede söyler, gerekçe
değil gözlem içerir. **Ortam**: sürüm, yapılandırma, veri kümesi — arızanın yeniden
üretilebilmesi için gereken bağlam. **Ön koşul ve adımlar**: senaryo yazımı dersindeki
biçimin aynısı. **Gözlenen sonuç** ve **beklenen sonuç**: ikisi ayrı ayrı yazılır, çünkü
farkı okuyan kişi görmelidir. **Kanıt**: günlük kaydı, çıktı, ekran görüntüsü.
**İzlenebilirlik bağı**: hangi kural, hangi senaryo, hangi sürüm.

En sık atlanan alan beklenen sonuçtur. "Ücret yanlış hesaplanıyor" cümlesi bir kayıt değil,
bir şikâyettir; kayıt "gün=0, öğrenci, şube dışı iade için 3 birim hesaplandı, kural gereği 2
birim olmalıydı" der.

#### Önem ve Öncelik

İki alan sık sık karıştırılır ve karıştırıldığında yanlış iş sırası doğar.

**Önem** (severity) arızanın etkisinin büyüklüğüdür: veri kaybı mı, yanlış tutar mı, yerleşim
bozukluğu mu? Ürünün kendisine bakılarak belirlenir ve büyük ölçüde nesneldir.

**Öncelik** (priority) düzeltmenin ne zaman yapılacağıdır. Etkiye ek olarak kaç kullanıcıyı
etkilediğine, kaçınma yolu olup olmadığına, sürüm takvimine ve düzeltmenin riskine bakar.

İkisi bağımsızdır. Yılda bir çalışan bir raporda veri kaybı yüksek önemli, düşük öncelikli
olabilir. Açılış ekranındaki bir yazım yanlışı düşük önemli, yüksek öncelikli olabilir.
İki alanı tek alana indiren bir kayıt biçimi, bu iki kararı birbirine karıştırmaya zorlar.

#### Yaşam Döngüsü Bir Durum Makinesidir

Kaydın izlediği yol serbest değildir. Doğrulanmamış bir kayıt kapatılamaz, kapatılmış bir
kayda düzeltme iliştirilemez. Bu kısıtlar bir durum makinesidir.

```js
// dongu.mjs — hata kaydinin durum makinesi
export const GECISLER = {
  yeni: { ata: 'atandi', reddet: 'reddedildi' },
  atandi: { duzelt: 'duzeltildi', reddet: 'reddedildi' },
  duzeltildi: { dogrula: 'dogrulandi', yenidenAc: 'yenidenAcildi' },
  dogrulandi: { kapat: 'kapandi' },
  kapandi: { yenidenAc: 'yenidenAcildi' },
  reddedildi: { yenidenAc: 'yenidenAcildi' },
  yenidenAcildi: { ata: 'atandi' },
};

export const DURUMLAR = Object.keys(GECISLER);
export const OLAYLAR = ['ata', 'duzelt', 'dogrula', 'kapat', 'reddet', 'yenidenAc'];

export function uygula(durum, olay) {
  const sonraki = GECISLER[durum]?.[olay];
  if (sonraki === undefined) throw new Error(`gecersiz gecis: ${durum} + ${olay}`);
  return sonraki;
}
```

Makine yazılı olduğunda geçerli ve geçersiz geçişler sayılabilir.

```js
// sayim.mjs — butun durum-olay ciftlerinin sayilmasi
import { DURUMLAR, OLAYLAR, GECISLER, uygula } from './dongu.mjs';

let gecerli = 0;
let reddedilen = 0;
for (const durum of DURUMLAR) {
  for (const olay of OLAYLAR) {
    if (GECISLER[durum][olay] !== undefined) { gecerli += 1; continue; }
    try { uygula(durum, olay); } catch { reddedilen += 1; }
  }
}

const toplam = DURUMLAR.length * OLAYLAR.length;
console.log(`durum sayisi  : ${DURUMLAR.length}`);
console.log(`olay sayisi   : ${OLAYLAR.length}`);
console.log(`tum ciftler   : ${toplam}`);
console.log(`gecerli gecis : ${gecerli}`);
console.log(`gecersiz cift : ${toplam - gecerli}, reddedilen: ${reddedilen}`);
```

```
durum sayisi  : 7
olay sayisi   : 6
tum ciftler   : 42
gecerli gecis : 10
gecersiz cift : 32, reddedilen: 32
```

Kırk iki çiftin yalnız onu geçerlidir; geri kalan otuz ikisi reddedilmelidir ve reddedilmiştir.
Bu ikinci sayı çoğu zaman gözden kaçar. Kaydın "doğrulandı" durumuna gelmeden kapatılabilmesi
bir kusurdur, ama hiçbir mutlu yol senaryosu bunu göstermez.

**Doğrulandı** durumunun ayrı bir adım olması da bilinçlidir. Düzeltmeyi yapan kişi kaydı
kapatamaz; kapatma yetkisi arızayı bildiren ya da doğrulayan taraftadır. Yeniden test bu
adımda koşturulur.

#### Durum Geçiş Testi

Dördüncü derste ertelenen teknik burada uygulanır. **Durum geçiş testi** (state transition
testing) denenmesi gereken şeyin değerler değil geçişler olduğu durumlarda kullanılır ve
ölçüsü **geçiş kapsamıdır**: geçerli geçişlerin kaçı en az bir kez denendi?

```js
// gecis-kapsami.mjs — iki senaryo kumesinin gecis kapsami
import { GECISLER, uygula } from './dongu.mjs';

const TUM_GECISLER = Object.entries(GECISLER)
  .flatMap(([durum, olaylar]) => Object.keys(olaylar).map((olay) => `${durum}+${olay}`));

function kapsam(yollar) {
  const gorulen = new Set();
  for (const yol of yollar) {
    let durum = 'yeni';
    for (const olay of yol) {
      gorulen.add(`${durum}+${olay}`);
      durum = uygula(durum, olay);
    }
  }
  return gorulen;
}

const MUTLU_YOL = [['ata', 'duzelt', 'dogrula', 'kapat']];
const GENIS_KUME = [
  ['ata', 'duzelt', 'dogrula', 'kapat', 'yenidenAc', 'ata'],
  ['ata', 'duzelt', 'yenidenAc', 'ata'],
  ['reddet', 'yenidenAc', 'ata', 'reddet'],
];

for (const [ad, yollar] of [['mutlu yol', MUTLU_YOL], ['genis kume', GENIS_KUME]]) {
  const gorulen = kapsam(yollar);
  const eksik = TUM_GECISLER.filter((g) => !gorulen.has(g));
  console.log(`${ad.padEnd(10)}: ${gorulen.size}/${TUM_GECISLER.length} gecis denendi`);
  if (eksik.length) console.log(`  denenmeyen: ${eksik.join(', ')}`);
}
```

```
mutlu yol : 4/10 gecis denendi
  denenmeyen: yeni+reddet, atandi+reddet, duzeltildi+yenidenAc, kapandi+yenidenAc, reddedildi+yenidenAc, yenidenAcildi+ata
genis kume: 10/10 gecis denendi
```

Mutlu yol geçişlerin yüzde kırkını dener. Denenmeyen altı geçişin hepsi olağandışı akışlardır:
reddetme, yeniden açma, doğrulamanın başarısız olması. Bunlar gerçek kullanımda en çok tartışma
çıkaran akışlardır ve tam da bu yüzden en az sınananlardır.

Üç yol ekleyerek kapsam tamamlandı. Ölçüm, hangi yolların eklenmesi gerektiğini de söylüyordu —
eksik geçişlerin listesi doğrudan yazılacak senaryoların listesidir.

#### Özet

- Hata insanın yaptığı yanlış, kusur onun üründe bıraktığı iz, arıza kusurun gözlenen dışa
  vurumudur; her kusur arızaya dönüşmez.
- Hata kaydının ölçütü, okuyan başka bir kişinin arızayı soru sormadan yeniden
  üretebilmesidir; gözlenen ve beklenen sonuç ayrı ayrı yazılır.
- Önem arızanın etkisini, öncelik düzeltmenin ne zaman yapılacağını söyler; ikisi bağımsızdır
  ve tek alana indirildiğinde kararlar karışır.
- Yaşam döngüsü bir durum makinesidir; örnekte kırk iki durum-olay çiftinden onu geçerli,
  otuz ikisi reddedilmesi gerekendir.
- Durum geçiş testinin ölçüsü geçiş kapsamıdır; mutlu yol on geçişten dördünü denedi,
  denenmeyenlerin listesi yazılacak senaryoların listesini verdi.

#### Kurs Kapanışı

Kurs iki soruyu yanıtladı. Birincisi testin **ne** olduğuydu. Kalite bir uygunluk yargısı
olarak tanımlandı; ürün kalitesiyle süreç kalitesi ayrıldı; doğrulama ile geçerleme
birbirinden koparıldı ve belirtimine tam uyan bir programın yine de yanlış olabileceği
gösterildi. İşlevsel olmayan beklentiler adlandırılıp ölçülebilir ölçütlere çevrildi. Testin
bir teknikler kümesi olmadan önce bir tutum olduğu, aynı koda bakan iki bakışın farklı şeyler
bulduğu ölçüldü. Teslimat modelinin testi ucuzlatmadığı, yalnız aynı sınamanın ne zaman
yapılacağını belirlediği gösterildi.

İkincisi **hangi** testin yazılacağıydı. Testler kapsadıkları sınıra, dayandıkları bilgiye ve
amaçlarına göre sınıflandırıldı. Girdi seçimi sezgiden çıkarılıp denklik sınıfları, sınır
değerleri ve karar tablosuyla gerekçelendirildi. Sınanacak şey bir senaryo biçimine
kavuşturuldu ve o biçimden çalıştırılabilir teste geçildi. Beklenen sonucun nereden geldiği
sorusu ayrı bir problem olarak ele alındı ve üç kâhin karşılaştırıldı. Son olarak bulunan
sapmanın kayıttan kapanışa giden yolu bir durum makinesi olarak kuruldu.

Yanıtlanmayan soru testin **nasıl** yazılacağıdır. Bir denetimin iç düzeni nedir; hazırlık,
uygulama ve doğrulama nasıl ayrılır? İddia ne söylemeli, ne söylememeli? Bir birim, dış
dünyaya dokunmadan nasıl sınanır — bağımlılıklar sahte bağımlılıklarla nasıl değiştirilir ve
hangi türü nerede kullanılır? Kapsam ölçümü ne söyler, hangi soruya yanıt vermez? Testi koddan
önce yazmak tasarımı nasıl değiştirir?

Sonraki kurs, **Birim Testi ve Test Odaklı Geliştirme**, bu soruları ele alır. Hızlı ve
bağımsız testlerin yazımı, iddiaların anlamlı kurulması, bağımlılıkların dışarıdan verilmesi,
sahte bağımlılıkların doğru türde kullanılması, satır ve dal kapsamının okunması ve
kırmızı–yeşil–yeniden düzenle döngüsü orada kurulur. Bu kursta seçilen testler, orada
yazılacak testlerdir.

