---
title: 'Küçük Adımlarla İlerleme'
source: 'https://academia.sh/tr/kurslar/birim-testi/kucuk-adimlarla-ilerleme'
course: 'Birim Testi ve Test Odaklı Geliştirme'
language: tr
updated: '2026-08-17T18:10:54+00:00'
license: 'CC BY-SA 4.0'
---

# Küçük Adımlarla İlerleme

Aynı kural kümesine iki farklı adım boyuyla ulaşan yolların ölçülmesi, geri bildirim noktası sayısı ile hata arama alanının ilişkisi ve son yeşil noktaya dönüş maliyeti.

Önceki derste döngü iki kez döndü ve her turda tek bir kural ele alındı. Bu bir zorunluluk
değil, bir seçimdi: aynı iş, dört kuralı birden kapsayan tek bir turla da yürütülebilirdi.
Adım boyu test odaklı geliştirmenin en çok tartışılan ve en az ölçülen değişkenidir.

Bu ders adım boyunu ölçülebilir bir şeye bağlar. Aynı hedefe iki farklı yoldan gidilir ve
her durakta iki nicelik kaydedilir: kaç test düşüyor ve son duraktan bu yana kaç satır
değişti. İkinci nicelik önemlidir, çünkü bir test düştüğünde hatanın aranacağı alan tam
olarak odur.

## Hedef: Dört Kural

Yenileme kuralları dörde çıktı. Yenileme son tarihi bugünden itibaren üye türünün ödünç
süresi kadar ileri alır ve yenileme sayacını artırır. Rezerve edilmiş bir kitap yenilenemez.
Bir ödünç en çok iki kez yenilenebilir. Süresi geçmiş bir ödünç yenilenemez; son gününde
yapılan yenileme ise kabul edilir.

```js
// yenileme.mjs — dort kuralin tamami
export const KURALLAR = { ogrenci: { oduncGun: 28 }, uye: { oduncGun: 14 } };
export const EN_COK_YENILEME = 2;
export const SINIR_ILETISI = `en cok ${EN_COK_YENILEME} kez yenilenebilir`;

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

Dört kural yedi teste karşılık gelir. Sınır durumu — son gününde yapılan yenileme — ayrı
bir test olarak duruyor, çünkü karşılaştırma işlecinin yanlış seçilmesi tam orada
görünür.

```js
// yenileme.test.mjs — dort kuralin yedi testi
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { yenile } from './yenileme.mjs';

const kayit = (ek = {}) => ({
  uyeNo: 'U-17', uyeTuru: 'ogrenci', sonGun: 1028, yenilemeSayisi: 0, rezerveEdildi: false, ...ek,
});

test('ogrenci yenilemesi bugunden yirmi sekiz gun ileri alir', () => {
  assert.equal(yenile(kayit(), 1020).sonGun, 1048);
});

test('standart uye yenilemesi bugunden on dort gun ileri alir', () => {
  assert.equal(yenile(kayit({ uyeTuru: 'uye' }), 1020).sonGun, 1034);
});

test('yenileme sayaci bir artar', () => {
  assert.equal(yenile(kayit(), 1020).yenilemeSayisi, 1);
});

test('rezerve edilmis kitap yenilenemez', () => {
  assert.throws(() => yenile(kayit({ rezerveEdildi: true }), 1020), /rezerve edilmis/);
});

test('en cok iki kez yenilenebilir', () => {
  assert.throws(() => yenile(kayit({ yenilemeSayisi: 2 }), 1020), /en cok 2 kez/);
});

test('gecikmis odunc yenilenemez', () => {
  assert.throws(() => yenile(kayit(), 1029), /gecikmis odunc/);
});

test('son gunde yapilan yenileme kabul edilir', () => {
  assert.equal(yenile(kayit(), 1028).sonGun, 1056);
});
```

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

```
ok 1 - ogrenci yenilemesi bugunden yirmi sekiz gun ileri alir
ok 2 - standart uye yenilemesi bugunden on dort gun ileri alir
ok 3 - yenileme sayaci bir artar
ok 4 - rezerve edilmis kitap yenilenemez
ok 5 - en cok iki kez yenilenebilir
ok 6 - gecikmis odunc yenilenemez
ok 7 - son gunde yapilan yenileme kabul edilir
# tests 7
# pass 7
# fail 0
```

Bu, varış noktasıdır. Soru oraya nasıl gidildiğidir.

## İki Yolun Ölçümü

Aşağıdaki betik yolun aradaki duraklarını gerçekleştirim sürümleri olarak tutar. Büyük adım
yolunda iki durak vardır: ilk kuralın yazıldığı sürüm ve tamamlanmış sürüm. Küçük adım
yolunda beş durak vardır ve her durak bir kural ekler. Aynı yedi denetim her durakta
çalıştırılır; ayrıca ardışık iki durak arasında kaç gövde satırının değiştiği sayılır.

```js
// adim-boyu.mjs — ayni hedefe iki farkli adim boyuyla giden yolun olcumu
import assert from 'node:assert/strict';
import { yenile, KURALLAR, EN_COK_YENILEME, SINIR_ILETISI } from './yenileme.mjs';

function S1(kayit, bugun) {
  return {
    ...kayit,
    sonGun: bugun + KURALLAR[kayit.uyeTuru].oduncGun,
  };
}

function S2(kayit, bugun) {
  return {
    ...kayit,
    sonGun: bugun + KURALLAR[kayit.uyeTuru].oduncGun,
    yenilemeSayisi: kayit.yenilemeSayisi + 1,
  };
}

function S3(kayit, bugun) {
  if (kayit.rezerveEdildi) throw new Error('rezerve edilmis kitap yenilenemez');
  return {
    ...kayit,
    sonGun: bugun + KURALLAR[kayit.uyeTuru].oduncGun,
    yenilemeSayisi: kayit.yenilemeSayisi + 1,
  };
}

function S4(kayit, bugun) {
  if (kayit.rezerveEdildi) throw new Error('rezerve edilmis kitap yenilenemez');
  if (kayit.yenilemeSayisi >= EN_COK_YENILEME) throw new Error(SINIR_ILETISI);
  return {
    ...kayit,
    sonGun: bugun + KURALLAR[kayit.uyeTuru].oduncGun,
    yenilemeSayisi: kayit.yenilemeSayisi + 1,
  };
}

function S5Hatali(kayit, bugun) {
  if (kayit.rezerveEdildi) throw new Error('rezerve edilmis kitap yenilenemez');
  if (kayit.yenilemeSayisi >= EN_COK_YENILEME) throw new Error(SINIR_ILETISI);
  if (bugun >= kayit.sonGun) throw new Error('gecikmis odunc yenilenemez');
  return {
    ...kayit,
    sonGun: bugun + KURALLAR[kayit.uyeTuru].oduncGun,
    yenilemeSayisi: kayit.yenilemeSayisi + 1,
  };
}

const kayit = (ek = {}) => ({
  uyeNo: 'U-17', uyeTuru: 'ogrenci', sonGun: 1028, yenilemeSayisi: 0, rezerveEdildi: false, ...ek,
});

const TESTLER = {
  'ogrenci suresi': (y) => assert.equal(y(kayit(), 1020).sonGun, 1048),
  'uye suresi': (y) => assert.equal(y(kayit({ uyeTuru: 'uye' }), 1020).sonGun, 1034),
  'sayac artar': (y) => assert.equal(y(kayit(), 1020).yenilemeSayisi, 1),
  'rezerve reddi': (y) => assert.throws(() => y(kayit({ rezerveEdildi: true }), 1020), /rezerve edilmis/),
  'yenileme siniri': (y) => assert.throws(() => y(kayit({ yenilemeSayisi: 2 }), 1020), /en cok 2 kez/),
  'gecikme reddi': (y) => assert.throws(() => y(kayit(), 1029), /gecikmis odunc/),
  'son gun kabulu': (y) => assert.equal(y(kayit(), 1028).sonGun, 1056),
};

const TOPLAM = Object.keys(TESTLER).length;

function dusenler(surum) {
  return Object.entries(TESTLER).filter(([, t]) => {
    try {
      t(surum);
      return false;
    } catch {
      return true;
    }
  }).map(([ad]) => ad);
}

// Imza satiri sayilmaz; yalniz govde satirlari karsilastirilir.
function degisenSatir(eski, yeni) {
  const govde = (f) => f.toString().split('\n').slice(1).map((s) => s.trim()).filter(Boolean);
  const kalan = govde(eski);
  let eklenen = 0;
  for (const satir of govde(yeni)) {
    const i = kalan.indexOf(satir);
    if (i === -1) eklenen += 1;
    else kalan.splice(i, 1);
  }
  return eklenen + kalan.length;
}

const YOLLAR = { 'buyuk adim': [S1, yenile], 'kucuk adim': [S1, S2, S3, S4, yenile] };

for (const [ad, duraklar] of Object.entries(YOLLAR)) {
  console.log(ad);
  let enBuyuk = 0;
  duraklar.forEach((surum, i) => {
    const fark = i === 0 ? 0 : degisenSatir(duraklar[i - 1], surum);
    enBuyuk = Math.max(enBuyuk, fark);
    console.log(`  durak ${i + 1}  dusen ${dusenler(surum).length}/${TOPLAM}  degisen satir ${fark}`);
  });
  console.log(`  geri bildirim noktasi ${duraklar.length}, en buyuk arama alani ${enBuyuk} satir`);
}

const hataliFark = { 'buyuk adim': degisenSatir(S1, S5Hatali), 'kucuk adim': degisenSatir(S4, S5Hatali) };
console.log(`son adimda yanlis islec yazildiginda dusen test: ${dusenler(S5Hatali).join(', ')}`);
for (const [ad, fark] of Object.entries(hataliFark)) {
  console.log(`  ${ad}: hatanin aranacagi alan ${fark} satir`);
}
```

```
buyuk adim
  durak 1  dusen 4/7  degisen satir 0
  durak 2  dusen 0/7  degisen satir 4
  geri bildirim noktasi 2, en buyuk arama alani 4 satir
kucuk adim
  durak 1  dusen 4/7  degisen satir 0
  durak 2  dusen 3/7  degisen satir 1
  durak 3  dusen 2/7  degisen satir 1
  durak 4  dusen 1/7  degisen satir 1
  durak 5  dusen 0/7  degisen satir 1
  geri bildirim noktasi 5, en buyuk arama alani 1 satir
son adimda yanlis islec yazildiginda dusen test: son gun kabulu
  buyuk adim: hatanin aranacagi alan 4 satir
  kucuk adim: hatanin aranacagi alan 1 satir
```

## Ölçümün Okunması

İki yol da aynı yere varıyor ve varış noktasında iki tablo da yedi testi geçiyor. Fark
yolda.

Büyük adım yolunda düşen test sayısı dörtten sıfıra bir sıçramada iniyor. Bu sıçramanın
içinde dört satırlık bir yazım var ve dört satırın hiçbiri tek başına doğrulanmadı.
Yazılanların dördü de doğruysa sorun yoktur; biri yanlışsa hangi satırın hangi testi
düşürdüğü, ancak elle ayıklanarak bulunur.

Küçük adım yolunda düşen test sayısı birer birer iniyor: dört, üç, iki, bir, sıfır. Her
inişin karşılığı tek satırlık bir yazımdır. Bu, ölçümün asıl bulgusudur — **arama alanı**
dört satırdan bir satıra iner.

Son üç satır bunu doğrudan gösteriyor. Gecikme denetimindeki karşılaştırma işleci yanlış
seçildiğinde — büyüktür yerine büyük eşit — yalnız bir test düşüyor: son gününde yapılan
yenilemenin kabulü. Düşen test iki yolda da aynıdır. Farklı olan, o testin işaret ettiği
alandır: büyük adımda dört satır, küçük adımda bir satır.

Buradan geri bildirim süresi kavramı çıkar. Geri bildirim süresi, bir yazım hatasının
yapıldığı an ile fark edildiği an arasındaki mesafedir; birimi saniye değil, **o aralıkta
yazılmış satır sayısıdır**. Adım boyu küçüldükçe bu mesafe kısalır.

## Son Yeşil Noktaya Dönüş

Küçük adımın ikinci kazancı geri dönüş maliyetidir. Bir yazım beklenmedik biçimde
karmaşıklaşırsa en ucuz çözüm çoğu zaman onu atıp yeniden başlamaktır. Bu kararın maliyeti,
atılacak işin büyüklüğüdür.

Büyük adım yolunda son yeşil nokta birinci duraktır: geri dönüş, dört satırın tamamını
atmak demektir. Küçük adım yolunda son yeşil nokta her zaman bir satır geridedir. Aynı
şey, düşen testin yorumlanmasında da geçerlidir: küçük adımda "az önce yazdığım satır"
her zaman tek bir cümledir.

Bunun bir bedeli vardır ve tabloda görünüyor: küçük adım yolunda beş geri bildirim
noktası var, büyük adım yolunda iki. Her nokta bir koşum demektir. Koşum ucuzsa — dördüncü
derste ölçülen anlamda hızlı ve bağımsız testlerse — bu bedel önemsizdir. Koşum pahalıysa
adım boyu kendiliğinden büyür ve geri bildirim süresi uzar. İki dersin bağı budur: test
takımının hızı, geliştirme döngüsünün adım boyunu belirler.

## Adım Boyunun Seçimi

Adım boyu sabit bir kural değil, belirsizliğe göre ayarlanan bir değişkendir. Yazılacak şey
tanıdıksa büyük adım ucuzdur ve ara duraklar zaman kaybıdır. Yazılacak şey belirsizse —
yeni bir alan, tanınmayan bir arayüz, karmaşık bir sınır durumu — adım küçülür.

Pratikte ölçüt şudur: bir adımın sonunda düşen test sayısı beklenenden farklı çıkıyorsa
adım fazla büyüktür. Yukarıdaki küçük adım tablosunda her durak tam olarak bir testi
yeşile çeviriyor; bu, adım boyunun kuralın tanesine oturduğunu gösterir.

## Özet

- Adım boyu, iki geri bildirim noktası arasında yazılan satır sayısıyla ölçülür ve bir
  hatanın aranacağı alan tam olarak o sayıdır.
- Ölçümde büyük adım yolu düşen test sayısını dörtten sıfıra bir sıçramada indirdi; küçük
  adım yolu birer birer indirdi.
- Aynı yazım hatası iki yolda da tek bir testi düşürdü, ama arama alanı büyük adımda dört,
  küçük adımda bir satırdı.
- Küçük adımda son yeşil nokta her zaman bir satır geridedir; geri dönüş kararının maliyeti
  bu mesafedir.
- Küçük adımın bedeli daha çok koşumdur; test takımı yavaşsa adım boyu kendiliğinden büyür
  ve geri bildirim süresi uzar.

## Sonraki Adım

Bu dersteki yedi test, kuralları ayrı ayrı cümlelerle anlattı ve her cümle bir kod bloğu
oldu. Kurallar çoğaldığında bu yazım biçimi tekrar üretir: aynı düzenek, aynı çağrı, yalnız
girdi ve beklenen sonuç değişir. Kuralın kendisi ise koda gömülü kalır ve kütüphane
görevlisiyle konuşurken okunamaz. Sonraki ders kuralları örnek tablolarına taşır, o
tablolar için bir biçim tanımlar ve tabloyu koşulabilir teste çeviren bir çözümleyici yazar.
