---
title: 'Kabul Testi Odaklı Geliştirme'
source: 'https://academia.sh/tr/kurslar/birim-testi/kabul-testi-odakli-gelistirme'
course: 'Birim Testi ve Test Odaklı Geliştirme'
language: tr
updated: '2026-08-17T18:10:54+00:00'
license: 'CC BY-SA 4.0'
---

# Kabul Testi Odaklı Geliştirme

Bir kullanıcı isteğinin kabul ölçütlerinin adım biçiminde yazılması, adımları uygulamaya bağlayan bir sürücüyle otomatikleştirilmesi ve dış halkası kabul ölçütünde iç halkası birim testinde dönen iki katmanlı döngü.

Önceki derste örnekler tek bir işlevin sözleşmesini anlattı. Bir kullanıcı isteği ise
bundan geniştir. "Üye kitabının süresini kendi uzatabilsin" cümlesi bir işlevi değil bir
akışı tarif eder: kayıt bulunur, kural uygulanır, sonuç kalıcı hâle gelir ve üyeye haber
verilir. Bu akışın tamamlandığını söyleyen ölçütler de isteğin kendisiyle birlikte gelir.

**Kabul testi odaklı geliştirme** (acceptance test driven development) o ölçütleri
geliştirmenin başlangıç noktası yapar. Ölçüt önce yazılır, otomatik bir denetime çevrilir ve
kırmızı kalır; işin bittiğini söyleyen şey o denetimin yeşile dönmesidir. Birim testleri
ortadan kalkmaz — kabul ölçütünün gösterdiği yere yazılırlar.

## Kabul Ölçütünün Biçimi

Ölçüt üç tür adımdan oluşur. **Verilen** adımları başlangıç durumunu kurar, **yapıldığında**
adımı tek bir işlemi tetikler, **beklenen** adımları sonucu denetler. Bu ayrım birinci
dersteki hazırla, uygula, doğrula bölümlerinin akış düzeyindeki karşılığıdır.

```bash
cat > kabul.txt <<'KABUL'
# kabul olcutleri — uye kendi oduncunun suresini uzatabilsin
hikaye: Uye elindeki kitabin suresini kendi uzatabilsin

olcut: Uygun bir odunc yenilendiginde son tarih ileri alinir ve uyeye bildirim gider
  verilen: uye no=U-17 tur=ogrenci
  verilen: odunc kitapNo=K-903 uyeNo=U-17 sonGun=1028
  yapildiginda: yenileme uyeNo=U-17 kitapNo=K-903 bugun=1020
  beklenen: odunc kitapNo=K-903 sonGun=1048 yenilemeSayisi=1
  beklenen: bildirim uyeNo=U-17 ileti="yeni son tarih: 1048"

olcut: Rezerve edilmis kitabin yenilenmesi reddedilir ve son tarih degismez
  verilen: uye no=U-42 tur=uye
  verilen: odunc kitapNo=K-101 uyeNo=U-42 sonGun=1024 rezerveEdildi=evet
  yapildiginda: yenileme uyeNo=U-42 kitapNo=K-101 bugun=1020
  beklenen: red gerekce="rezerve edilmis kitap yenilenemez"
  beklenen: odunc kitapNo=K-101 sonGun=1024 yenilemeSayisi=0
KABUL
```

Önceki dersteki örnek satırlarından iki fark var. Adımlar **sıralıdır** ve durumu paylaşır:
ölçüt bir senaryo anlatır, tek bir çağrı değil. İkincisi, adımlarda geçen sözcükler
uygulamanın kavramlarıdır — üye, ödünç, bildirim, red — belirli bir işlevin imzası değil.

```js
// kabul-cozumleyici.mjs — kabul olcutlerini adim listesine cevirir
const ALAN = /(\w+)=("[^"]*"|\S+)/g;
const ADIM_ANAHTARLARI = ['verilen', 'yapildiginda', 'beklenen'];

const cevir = (ham) => {
  const deger = ham.startsWith('"') ? ham.slice(1, -1) : ham;
  if (deger === 'evet') return true;
  if (deger === 'hayir') return false;
  return /^-?\d+$/.test(deger) ? Number(deger) : deger;
};

const alanlar = (metin) => Object.fromEntries(
  [...metin.matchAll(ALAN)].map(([, ad, ham]) => [ad, cevir(ham)]),
);

export function cozumle(metin) {
  const olcutler = [];
  for (const ham of metin.split('\n')) {
    const satir = ham.trim();
    if (satir === '' || satir.startsWith('#') || satir.startsWith('hikaye:')) continue;
    if (satir.startsWith('olcut:')) {
      olcutler.push({ baslik: satir.slice(6).trim(), adimlar: [] });
      continue;
    }
    const [anahtar, ...kalan] = satir.split(':');
    if (ADIM_ANAHTARLARI.includes(anahtar) === false) throw new Error(`taninmayan adim: ${satir}`);
    if (olcutler.length === 0) throw new Error(`olcutsuz adim: ${satir}`);
    const govde = kalan.join(':').trim();
    olcutler.at(-1).adimlar.push({
      ad: `${anahtar} ${govde.split(/\s+/)[0]}`,
      alanlar: alanlar(govde),
      metin: satir,
    });
  }
  return olcutler;
}
```

## Uygulama ve Sürücü

Ölçütlerin dokunduğu birim tek bir kural işlevi değil, akışı yürüten servistir. Servis
kaydı yükler, kuralı uygular ve sonucu kaydeder. Bu ilk sürümde bildirim gönderimi
**yoktur**; ölçütün kırmızı kalacağı yer orasıdır.

```js
// yenileme.mjs — yenileme kurallari
export const KURALLAR = { ogrenci: { oduncGun: 28 }, uye: { oduncGun: 14 } };
export const EN_COK_YENILEME = 2;

export function yenile(kayit, bugun) {
  if (kayit.rezerveEdildi) throw new Error('rezerve edilmis kitap yenilenemez');
  if (kayit.yenilemeSayisi >= EN_COK_YENILEME) throw new Error('en cok 2 kez yenilenebilir');
  if (bugun > kayit.sonGun) throw new Error('gecikmis odunc yenilenemez');
  return {
    ...kayit,
    sonGun: bugun + KURALLAR[kayit.uyeTuru].oduncGun,
    yenilemeSayisi: kayit.yenilemeSayisi + 1,
  };
}
```

```js
// servis.mjs — surum 1: kaydi yukler, kurali uygular, sonucu kaydeder
import { yenile } from './yenileme.mjs';

export function servisKur({ depo, bildirim }) {
  return {
    yenile(uyeNo, kitapNo, bugun) {
      const uye = depo.uyeBul(uyeNo);
      const odunc = depo.oduncBul(kitapNo);
      if (uye === undefined || odunc === undefined || odunc.uyeNo !== uyeNo) {
        return { durum: 'red', gerekce: 'odunc bulunamadi' };
      }
      try {
        const yeni = yenile({ ...odunc, uyeTuru: uye.tur }, bugun);
        depo.oduncKaydet(yeni);
        return { durum: 'kabul', odunc: yeni };
      } catch (hata) {
        return { durum: 'red', gerekce: hata.message };
      }
    },
  };
}
```

Metin ile uygulama arasındaki bağ **sürücüdür** (driver): her adım türünü bir kod parçasına
eşleyen tablo. Sürücü, altıncı derste tanıtılan taklit nesnelerle bir "dünya" kurar ve
adımları o dünya üzerinde yürütür.

```js
// kabul.test.mjs — kabul olcutlerini surucuyle kosulabilir teste cevirir
import { readFileSync } from 'node:fs';
import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import { cozumle } from './kabul-cozumleyici.mjs';
import { servisKur } from './servis.mjs';

function dunyaKur() {
  const uyeler = new Map();
  const oduncler = new Map();
  const bildirimler = [];
  const depo = {
    uyeEkle: (uye) => uyeler.set(uye.no, uye),
    uyeBul: (no) => uyeler.get(no),
    oduncBul: (kitapNo) => oduncler.get(kitapNo),
    oduncKaydet: (odunc) => oduncler.set(odunc.kitapNo, odunc),
  };
  const bildirim = { gonder: (uyeNo, ileti) => bildirimler.push([uyeNo, ileti]) };
  return { depo, bildirimler, servis: servisKur({ depo, bildirim }), sonuc: undefined };
}

const VARSAYILAN_ODUNC = { yenilemeSayisi: 0, rezerveEdildi: false };

const SURUCU = {
  'verilen uye': (d, a) => d.depo.uyeEkle({ no: a.no, tur: a.tur }),
  'verilen odunc': (d, a) => d.depo.oduncKaydet({ ...VARSAYILAN_ODUNC, ...a }),
  'yapildiginda yenileme': (d, a) => {
    d.sonuc = d.servis.yenile(a.uyeNo, a.kitapNo, a.bugun);
  },
  'beklenen odunc': (d, a) => {
    const { kitapNo, ...beklenen } = a;
    const kayit = d.depo.oduncBul(kitapNo);
    for (const [alan, deger] of Object.entries(beklenen)) {
      assert.equal(kayit[alan], deger, `${kitapNo}.${alan} uyusmuyor`);
    }
  },
  'beklenen bildirim': (d, a) => assert.deepEqual(d.bildirimler, [[a.uyeNo, a.ileti]]),
  'beklenen red': (d, a) => {
    assert.equal(d.sonuc.durum, 'red');
    assert.equal(d.sonuc.gerekce, a.gerekce);
  },
};

const metin = readFileSync(new URL('./kabul.txt', import.meta.url), 'utf8');

for (const olcut of cozumle(metin)) {
  describe(olcut.baslik, () => {
    const dunya = dunyaKur();
    for (const adim of olcut.adimlar) {
      test(adim.metin, () => {
        const surucu = SURUCU[adim.ad];
        if (surucu === undefined) throw new Error(`surucusu olmayan adim: ${adim.ad}`);
        surucu(dunya, adim.alanlar);
      });
    }
  });
}
```

Her adım ayrı bir test olarak koşar ve aynı dünyayı paylaşır. Dördüncü derste kurulan
bağımsızlık kuralı burada ölçüt düzeyinde uygulanır: paylaşım **ölçüt içinde** kalır, her
ölçüt kendi dünyasını kurar.

## Dış Halka: Kırmızı Ölçüt

```sh
node --test --test-reporter=tap kabul.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))'
```

```
    ok 1 - verilen: uye no=U-17 tur=ogrenci
    ok 2 - verilen: odunc kitapNo=K-903 uyeNo=U-17 sonGun=1028
    ok 3 - yapildiginda: yenileme uyeNo=U-17 kitapNo=K-903 bugun=1020
    ok 4 - beklenen: odunc kitapNo=K-903 sonGun=1048 yenilemeSayisi=1
    not ok 5 - beklenen: bildirim uyeNo=U-17 ileti="yeni son tarih: 1048"
not ok 1 - Uygun bir odunc yenilendiginde son tarih ileri alinir ve uyeye bildirim gider
    ok 1 - verilen: uye no=U-42 tur=uye
    ok 2 - verilen: odunc kitapNo=K-101 uyeNo=U-42 sonGun=1024 rezerveEdildi=evet
    ok 3 - yapildiginda: yenileme uyeNo=U-42 kitapNo=K-101 bugun=1020
    ok 4 - beklenen: red gerekce="rezerve edilmis kitap yenilenemez"
    ok 5 - beklenen: odunc kitapNo=K-101 sonGun=1024 yenilemeSayisi=0
ok 2 - Rezerve edilmis kitabin yenilenmesi reddedilir ve son tarih degismez
# tests 10
# pass 9
# fail 1
```

İkinci ölçüt karşılanıyor, birincisi karşılanmıyor. Daha değerlisi, **hangi adımda**
karşılanmadığı görünüyor: son tarih doğru ilerledi, kayıt doğru güncellendi, eksik olan tek
şey bildirim. Kabul ölçütü böylece bir kırmızı adım değil, kırmızı adımın **adresi** oldu.

## İç Halka: Birim Testi

Kabul ölçütü doğrudan gerçekleştirime çevrilmez. İşaret ettiği yerde bir birim testi
yazılır ve döngü orada döner. Bu iki katmanlı yapıya **dış halka** ve **iç halka** denir:
dış halka bir kullanıcı isteği boyunca kırmızı kalır, iç halka o süre içinde birçok kez
kırmızıdan yeşile geçer.

```js
// servis.test.mjs — ic halka: kabul olcutunun isaret ettigi birim
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { servisKur } from './servis.mjs';

function kur() {
  const oduncler = new Map([['K-903', { kitapNo: 'K-903', uyeNo: 'U-17', sonGun: 1028, yenilemeSayisi: 0, rezerveEdildi: false }]]);
  const bildirimler = [];
  const depo = {
    uyeBul: () => ({ no: 'U-17', tur: 'ogrenci' }),
    oduncBul: (kitapNo) => oduncler.get(kitapNo),
    oduncKaydet: (odunc) => oduncler.set(odunc.kitapNo, odunc),
  };
  return { servis: servisKur({ depo, bildirim: { gonder: (u, i) => bildirimler.push([u, i]) } }), bildirimler };
}

test('kabul edilen yenileme uyeye yeni son tarihi bildirir', () => {
  const { servis, bildirimler } = kur();

  servis.yenile('U-17', 'K-903', 1020);

  assert.deepEqual(bildirimler, [['U-17', 'yeni son tarih: 1048']]);
});
```

```sh
node --test --test-reporter=tap servis.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))'
```

```
not ok 1 - kabul edilen yenileme uyeye yeni son tarihi bildirir
# tests 1
# pass 0
# fail 1
```

Yeşil adım tek satırdır.

```js
// servis.mjs — surum 2: kabul edilen yenilemede uyeye bildirim gonderilir
import { yenile } from './yenileme.mjs';

export function servisKur({ depo, bildirim }) {
  return {
    yenile(uyeNo, kitapNo, bugun) {
      const uye = depo.uyeBul(uyeNo);
      const odunc = depo.oduncBul(kitapNo);
      if (uye === undefined || odunc === undefined || odunc.uyeNo !== uyeNo) {
        return { durum: 'red', gerekce: 'odunc bulunamadi' };
      }
      try {
        const yeni = yenile({ ...odunc, uyeTuru: uye.tur }, bugun);
        depo.oduncKaydet(yeni);
        bildirim.gonder(uyeNo, `yeni son tarih: ${yeni.sonGun}`);
        return { durum: 'kabul', odunc: yeni };
      } catch (hata) {
        return { durum: 'red', gerekce: hata.message };
      }
    },
  };
}
```

```sh
node --test --test-reporter=tap servis.test.mjs | grep -E '^ *(ok|not ok|# (tests|pass|fail))'
node --test --test-reporter=tap kabul.test.mjs | grep -E '^(ok|not ok|# (tests|pass|fail))'
```

```
ok 1 - kabul edilen yenileme uyeye yeni son tarihi bildirir
# tests 1
# pass 1
# fail 0
ok 1 - Uygun bir odunc yenilendiginde son tarih ileri alinir ve uyeye bildirim gider
ok 2 - Rezerve edilmis kitabin yenilenmesi reddedilir ve son tarih degismez
# tests 10
# pass 10
# fail 0
```

İç halka yeşile döndü ve dış halka onunla birlikte kapandı. İkinci komutta yalnız girintisiz
satırlar süzülüyor; ölçüt düzeyinde bakıldığında iki satır, işin bittiğini söyleyen bütün
bilgidir.

## İki Halkanın İş Bölümü

Dış ve iç halkanın farkı hız ve kapsamdadır ve bu fark bilerek korunur.

Dış halka **yavaş ve geniştir**. Bir ölçüt akışın tamamına dokunur, bu yüzden düştüğünde
nerede bozulduğunu tek başına söylemez — yukarıdaki koşumda adım adım raporlama bu eksiği
kısmen kapatıyor. Sayısı azdır: bir kullanıcı isteği başına birkaç ölçüt.

İç halka **hızlı ve dardır**. Bir birim testi tek bir kuralı sınar ve düştüğünde adresi
verir. Sayısı çoktur ve ikinci derste ölçülen sinyal gücü orada aranır.

İki halkanın karıştırılması iki tipik hataya yol açar. Kabul ölçütleri birim testi gibi
çoğaltıldığında koşum yavaşlar ve ölçüt listesi okunamaz hâle gelir; kullanıcıyla
paylaşılabilir olma özelliği kaybolur. Ters yönde, birim testleri hiç yazılmayıp her şey
kabul ölçütüne bırakıldığında düşen bir ölçüt uzun bir arama başlatır — ikinci dersteki
arama alanı ölçümü burada da geçerlidir.

Kabul ölçütünün bittiğini söyleyen şey de bellidir: ölçüt yeşile döndüğünde iş **kabul
edilmiş** sayılır. Bu, "geliştirici bitti dedi" ölçütünden farklıdır, çünkü bitmişliğin
tanımı işi isteyen tarafla birlikte ve önceden yazılmıştır.

## Özet

- Kabul ölçütü, bir kullanıcı isteğinin karşılandığını söyleyen ve önceden yazılan denetimdir;
  verilen, yapıldığında ve beklenen adımlarından oluşur.
- Ölçüt adımları sıralıdır ve durumu paylaşır; paylaşım ölçüt içinde kalır, her ölçüt kendi
  dünyasını kurar.
- Sürücü, metindeki adım türlerini uygulama çağrılarına eşler; sürücüsü olmayan adım sessizce
  atlanmaz, hata verir.
- Kırmızı bir kabul ölçütü kırmızı adımın kendisi değil adresidir; iç halkada yazılan birim
  testi o adrese gider.
- Dış halka yavaş, geniş ve az sayıdadır; iç halka hızlı, dar ve çok sayıdadır. İkisinin
  karıştırılması ya okunamaz bir ölçüt listesi ya da uzun bir arama alanı üretir.

## Sonraki Adım

Bu konuda kurulan her şey boş bir sayfadan başladı: test önce yazıldı, kod sonra geldi.
Var olan bir kod tabanında bu sıra kurulamaz. Yıllardır çalışan, testi olmayan ve
bağımlılıklarını kendi içinde yaratan bir işlevin önce ne yaptığı kayda geçirilmeli, sonra
değiştirilebilir hâle getirilmelidir. Sonraki ders bu işi üç aşamada yapar ve kursu kapatır.
