---
title: 'Test Altında Yeniden Düzenleme'
source: 'https://academia.sh/tr/kurslar/temiz-kod/test-altinda-yeniden-duzenleme'
course: 'Temiz Kod'
language: tr
updated: '2026-08-17T18:10:37+00:00'
license: 'CC BY-SA 4.0'
---

# Test Altında Yeniden Düzenleme

Testsiz yapılan bir ayıklama adımında sessizce değişen davranışın tarama ile bulunması, altı testlik kümenin yakalama gücünün bozma denemeleriyle ölçülmesi ve güçlendirilmiş kümenin aynı adımı kırmızıya çevirmesi.

Önceki derste her adımın ardından alınan yeşil koşum, davranışın korunduğunun kanıtı
sayıldı. Bu sayışın kendisi sınanmadı: altı test, ücret hesabının bütün davranışını
gerçekten kapsıyor mu? Bu ders önce testsiz yapılan bir ayıklama adımında sessizce değişen
bir davranışı gösterir, aynı adımın mevcut altı testle de yakalanmadığını ölçer, test
kümesinin yakalama gücünü sayıya döker ve güçlendirilmiş kümenin aynı adımı kırmızıya
çevirdiği anı gösterir.

Testlerin buradaki işi tektir: **davranış koruması**. Test yazımı, sahte bağımlılık kurma
ve testi tasarımın sürükleyicisi yapma, Yazılım Kalitesi ve Test müfredatının konularıdır.
Burada testlere yalnız tek bir soru sorulur — yeniden düzenlemeden önceki ve sonraki kod
aynı girdilere aynı yanıtı veriyor mu?

Başlangıç noktası önceki dersin bitiş durumudur.

```js
// tarife.mjs — onceki dersin bitis hali
export const KADEME = [
  { ustGram: 1000, kurus: 4990, ad: 'kucuk' },
  { ustGram: 5000, kurus: 7490, ad: 'orta' },
  { ustGram: 10000, kurus: 9900, ad: 'buyuk' },
  { ustGram: 20000, kurus: 12900, ad: 'cok-buyuk' },
  { ustGram: Infinity, kurus: 24900, ad: 'agir' },
];

const BOLGE_KATSAYI = { B1: 1.0, B2: 1.25, B3: 1.6 };

export function agirlikKademesi(gram) {
  return KADEME.find((k) => gram <= k.ustGram);
}

export function bolgeKatsayisi(bolge) {
  return BOLGE_KATSAYI[bolge];
}
```

```js
// ucret.mjs — onceki dersin bitis hali
import { agirlikKademesi, bolgeKatsayisi } from './tarife.mjs';

const ASGARI_KURUS = 6990;
const INDIRIM_ESIGI = 50;
const INDIRIM_ORANI = 0.88;

export function etkinAgirlik(gonderi) {
  const hacim = Math.ceil((gonderi.en * gonderi.boy * gonderi.yukseklik) / 3);
  return Math.max(gonderi.gram, hacim);
}

export function indirimOrani(adet) {
  return adet > INDIRIM_ESIGI ? INDIRIM_ORANI : 1;
}

export function gonderiUcreti(gonderi) {
  const taban = agirlikKademesi(etkinAgirlik(gonderi)).kurus;
  return Math.max(Math.round(taban * bolgeKatsayisi(gonderi.bolge)), ASGARI_KURUS);
}

export function donemUcreti(gonderiler) {
  const toplam = gonderiler.reduce((t, g) => t + gonderiUcreti(g), 0);
  return Math.round(toplam * indirimOrani(gonderiler.length));
}
```

```js
// ucret.test.mjs — onceki dersten devralinan alti test
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { gonderiUcreti, donemUcreti } from './ucret.mjs';

const kutu = { en: 20, boy: 15, yukseklik: 10 };
const donem = (adet) => Array.from({ length: adet }, () => ({ ...kutu, gram: 3000, bolge: 'B1' }));

test('kademe ucreti agirliktan okunur', () => {
  assert.equal(gonderiUcreti({ ...kutu, gram: 3000, bolge: 'B1' }), 7490);
});

test('bolge katsayisi taban ucreti carpar', () => {
  assert.equal(gonderiUcreti({ ...kutu, gram: 3000, bolge: 'B3' }), 11984);
});

test('asgari ucret bolge carpimindan sonra uygulanir', () => {
  assert.equal(gonderiUcreti({ ...kutu, gram: 500, bolge: 'B2' }), 6990);
});

test('hacim agirligi gercek agirliktan buyukse o kullanilir', () => {
  assert.equal(gonderiUcreti({ en: 40, boy: 30, yukseklik: 30, gram: 500, bolge: 'B2' }), 16125);
});

test('esigi asan donemde butun gonderiler indirimli toplanir', () => {
  assert.equal(donemUcreti(donem(60)), Math.round(60 * 7490 * 0.88));
});

test('esigin altindaki donemde indirim uygulanmaz', () => {
  assert.equal(donemUcreti(donem(10)), 10 * 7490);
});
```

Karşılaştırma yapılabilmesi için düzenlemeden önceki sürümün bir kopyası saklanıyor.

```sh
cp ucret.mjs ucret-onceki.mjs
node --test --test-reporter=tap ucret.test.mjs | grep -E '^# (tests|pass|fail)'
```

```
# tests 6
# pass 6
# fail 0
```

## Sessizce Değişen Davranış

Düzenleme, önceki dersteki ayıklamanın aynısıdır: dönem indiriminin eşik kararı kendi adını
alıyor. Adı verilirken karşılaştırmanın yönü kayıyor.

```js
// ucret.mjs — ayiklama: esik karari indirimliMi adiyla cikarildi
import { agirlikKademesi, bolgeKatsayisi } from './tarife.mjs';

const ASGARI_KURUS = 6990;
const INDIRIM_ESIGI = 50;
const INDIRIM_ORANI = 0.88;

export function etkinAgirlik(gonderi) {
  const hacim = Math.ceil((gonderi.en * gonderi.boy * gonderi.yukseklik) / 3);
  return Math.max(gonderi.gram, hacim);
}

export function indirimliMi(adet) {
  return adet >= INDIRIM_ESIGI;
}

export function indirimOrani(adet) {
  return indirimliMi(adet) ? INDIRIM_ORANI : 1;
}

export function gonderiUcreti(gonderi) {
  const taban = agirlikKademesi(etkinAgirlik(gonderi)).kurus;
  return Math.max(Math.round(taban * bolgeKatsayisi(gonderi.bolge)), ASGARI_KURUS);
}

export function donemUcreti(gonderiler) {
  const toplam = gonderiler.reduce((t, g) => t + gonderiUcreti(g), 0);
  return Math.round(toplam * indirimOrani(gonderiler.length));
}
```

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

```
ok 1 - kademe ucreti agirliktan okunur
ok 2 - bolge katsayisi taban ucreti carpar
ok 3 - asgari ucret bolge carpimindan sonra uygulanir
ok 4 - hacim agirligi gercek agirliktan buyukse o kullanilir
ok 5 - esigi asan donemde butun gonderiler indirimli toplanir
ok 6 - esigin altindaki donemde indirim uygulanmaz
# tests 6
# pass 6
# fail 0
```

Altı testin altısı yeşil. Testsiz bir kod tabanında bu adım fark edilmeden geçerdi; testli
bir kod tabanında da fark edilmeden geçti. Değişikliğin gerçekten davranışı koruyup
korumadığını görmenin yolu, iki sürümü aynı girdiler üzerinde karşılaştırmaktır.

```js
// fark.mjs — ayni girdilerde iki surumun sonucunu karsilastirir
import { donemUcreti as onceki } from './ucret-onceki.mjs';
import { donemUcreti as simdiki } from './ucret.mjs';

const kutu = { en: 20, boy: 15, yukseklik: 10 };
let denenen = 0;
const ayrisan = [];

for (const gram of [500, 3000, 12000]) {
  for (let adet = 1; adet <= 120; adet++) {
    const donem = Array.from({ length: adet }, () => ({ ...kutu, gram, bolge: 'B1' }));
    denenen += 1;
    const a = onceki(donem);
    const b = simdiki(donem);
    if (a !== b) ayrisan.push(`adet=${adet} gram=${gram} onceki=${a} simdiki=${b}`);
  }
}

console.log('denenen donem:', denenen);
console.log('sonucu degisen:', ayrisan.length);
console.log('ilk ornek:', ayrisan[0] ?? 'yok');
```

```
denenen donem: 360
sonucu degisen: 3
ilk ornek: adet=50 gram=500 onceki=349500 simdiki=307560
```

Üç yüz altmış dönemden üçünün sonucu değişti ve üçü de aynı noktada: tam elli gönderilik
dönemler. Alan karşılığı, tam eşikte duran bir sözleşmeli müşterinin faturasının 3.495,00
TL yerine 3.075,60 TL çıkmasıdır. Yeşil koşum yanlış bir şey söylemedi; **kendisine
sorulmayan** soruyu yanıtlamadı. Testler girdi uzayının tamamını değil, kendi girdilerini
sabitler.

## Korumanın Ölçülmesi

O hâlde ölçülmesi gereken büyüklük, test kümesinin **yakalama gücüdür**: kaç tür davranış
değişikliği koşumu kırmızıya çevirir? Bu, koda kasıtlı ve küçük bozmalar uygulayıp koşumun
tepkisini sayarak ölçülür. Her bozma tek bir karakter ya da tek bir sayı düzeyindedir ve
ölçümden sonra geri alınır; bunlara **bozma denemesi** (mutation) denir.

```sh
cp ucret.mjs ucret-ayiklanmis.mjs
cp ucret-onceki.mjs ucret.mjs
node --test --test-reporter=tap ucret.test.mjs | grep -E '^# (tests|pass|fail)'
```

```
# tests 6
# pass 6
# fail 0
```

```js
// koruma.mjs — test kumesinin kac davranis degisikligini yakaladigini olcer
import { readFileSync, writeFileSync } from 'node:fs';
import { execFileSync } from 'node:child_process';

const MUTASYON = [
  ['esik karsilastirmasi', 'ucret.mjs', 'adet > INDIRIM_ESIGI', 'adet >= INDIRIM_ESIGI'],
  ['asgari ucret', 'ucret.mjs', 'ASGARI_KURUS = 6990', 'ASGARI_KURUS = 6890'],
  ['indirim orani', 'ucret.mjs', 'INDIRIM_ORANI = 0.88', 'INDIRIM_ORANI = 0.89'],
  ['hacim yuvarlamasi', 'ucret.mjs', 'Math.ceil(', 'Math.floor('],
  ['bolge katsayisi', 'tarife.mjs', 'B2: 1.25', 'B2: 1.3'],
  ['kademe siniri', 'tarife.mjs', 'gram <= k.ustGram', 'gram < k.ustGram'],
];

function kirmiziMi() {
  try {
    execFileSync('node', ['--test', 'ucret.test.mjs'], { stdio: 'ignore' });
    return false;
  } catch {
    return true;
  }
}

let yakalanan = 0;
for (const [ad, dosya, eski, yeni] of MUTASYON) {
  const asil = readFileSync(dosya, 'utf8');
  writeFileSync(dosya, asil.replace(eski, yeni));
  const kirmizi = kirmiziMi();
  writeFileSync(dosya, asil);
  if (kirmizi) yakalanan += 1;
  console.log(`${ad}: ${kirmizi ? 'yakalandi' : 'kacti'}`);
}
console.log(`yakalanan: ${yakalanan}/${MUTASYON.length}`);
```

```
esik karsilastirmasi: kacti
asgari ucret: yakalandi
indirim orani: yakalandi
hacim yuvarlamasi: kacti
bolge katsayisi: yakalandi
kademe siniri: yakalandi
yakalanan: 4/6
```

Altı bozmadan dördü yakalandı. Kaçan ikisi tesadüfen kaçmıyor. Eşik karşılaştırması kaçtı,
çünkü dönem testleri 60 ve 10 gönderi deniyor; ikisi de eşiğin kendisi değil. Hacim
yuvarlaması kaçtı, çünkü testlerdeki iki kutunun hacim ağırlığı da tam sayı çıkıyor —
3.000 ve 12.000 gram — ve yukarı yuvarlama ile aşağı yuvarlama aynı sonucu veriyor. İki
kaçış da aynı desende: sınır denenmemiş.

Kapatma yolu, önceki dersin ayıklamalarının açtığı kapıdan geçer. Ayıklama sınanabilir
birim sayısını artırmıştı; sınır davranışı artık dış uçtan değil, o birimlerin kendisinden
sabitlenebilir.

```sh
cat >> ucret.test.mjs <<'SON'

import { etkinAgirlik, indirimOrani } from './ucret.mjs';

test('hacim agirligi yukari yuvarlanir', () => {
  assert.equal(etkinAgirlik({ en: 11, boy: 11, yukseklik: 11, gram: 100 }), 444);
});

test('esikteki gonderi sayisi indirim uretmez', () => {
  assert.equal(indirimOrani(50), 1);
});
SON
node --test --test-reporter=tap ucret.test.mjs | grep -E '^# (tests|pass|fail)'
node koruma.mjs
```

```
# tests 8
# pass 8
# fail 0
esik karsilastirmasi: yakalandi
asgari ucret: yakalandi
indirim orani: yakalandi
hacim yuvarlamasi: yakalandi
bolge katsayisi: yakalandi
kademe siniri: yakalandi
yakalanan: 6/6
```

İki test, kaçan iki bozmayı kapattı. Kazanılan büyüklük test sayısı değil, oran: yakalama
gücü altıda dörtten altıda altıya çıktı. Bu oran bir güvence belgesi değil, bir ölçüdür;
listede olmayan bir bozma türü hâlâ kaçabilir. Ölçünün değeri, "testlerim yeterli mi"
sorusunu sayılabilir bir soruya çevirmesindedir.

## Aynı Adım, Güçlendirilmiş Küme

Kaymış ayıklama şimdi geri getiriliyor.

```sh
cp ucret-ayiklanmis.mjs ucret.mjs
node --test --test-reporter=tap ucret.test.mjs \
  | grep -E '^ *(ok|not ok|expected:|actual:|# (tests|pass|fail))'
```

```
ok 1 - kademe ucreti agirliktan okunur
ok 2 - bolge katsayisi taban ucreti carpar
ok 3 - asgari ucret bolge carpimindan sonra uygulanir
ok 4 - hacim agirligi gercek agirliktan buyukse o kullanilir
ok 5 - esigi asan donemde butun gonderiler indirimli toplanir
ok 6 - esigin altindaki donemde indirim uygulanmaz
ok 7 - hacim agirligi yukari yuvarlanir
not ok 8 - esikteki gonderi sayisi indirim uretmez
  expected: 1
  actual: 0.88
# tests 8
# pass 7
# fail 1
```

Aynı düzenleme, aynı kod, farklı sonuç: sekizinci test kırmızı ve beklenen ile gerçekleşen
değerler koşumdan okunuyor. Eşik testi, üç yüz altmış dönemlik taramanın bulduğu sapmayı
tek bir çağrıyla gösteriyor. Düzeltme, kaymış karşılaştırmayı geri çevirmektir.

```js
// ucret.mjs — ayni ayiklama, karsilastirma yonu korunarak
import { agirlikKademesi, bolgeKatsayisi } from './tarife.mjs';

const ASGARI_KURUS = 6990;
const INDIRIM_ESIGI = 50;
const INDIRIM_ORANI = 0.88;

export function etkinAgirlik(gonderi) {
  const hacim = Math.ceil((gonderi.en * gonderi.boy * gonderi.yukseklik) / 3);
  return Math.max(gonderi.gram, hacim);
}

export function indirimliMi(adet) {
  return adet > INDIRIM_ESIGI;
}

export function indirimOrani(adet) {
  return indirimliMi(adet) ? INDIRIM_ORANI : 1;
}

export function gonderiUcreti(gonderi) {
  const taban = agirlikKademesi(etkinAgirlik(gonderi)).kurus;
  return Math.max(Math.round(taban * bolgeKatsayisi(gonderi.bolge)), ASGARI_KURUS);
}

export function donemUcreti(gonderiler) {
  const toplam = gonderiler.reduce((t, g) => t + gonderiUcreti(g), 0);
  return Math.round(toplam * indirimOrani(gonderiler.length));
}
```

```sh
node --test --test-reporter=tap ucret.test.mjs | grep -E '^# (tests|pass|fail)'
node fark.mjs
```

```
# tests 8
# pass 8
# fail 0
denenen donem: 360
sonucu degisen: 0
ilk ornek: yok
```

Koşum yeşil ve tarama sıfır sapma buluyor. İki ölçü birlikte okunduğunda anlam kazanır:
tarama bu düzenlemenin denenen girdilerde davranışı koruduğunu, bozma denemesi ölçüsü de
kümenin altı bozma türünün altısını yakaladığını söylüyor. Davranış koruması bir niyet
değil, iki sayıyla ifade edilen bir güvencedir.

## Özet

- Yeniden düzenleme adımının davranışı koruduğu iddiası, yeşil koşumdan değil, iki sürümün
  aynı girdilerde aynı sonucu vermesinden okunur.
- Kaymış tek bir karşılaştırma yönü altı testin altısını yeşil bıraktı; tarama 360 dönemin
  üçünde sapma buldu ve üçü de tam eşikte toplandı.
- Bir test kümesinin yakalama gücü ölçülebilir: koda uygulanan altı bozma denemesinin
  dördü yakalandı, ikisi kaçtı ve iki kaçış da sınanmamış sınırdan geldi.
- Sınır davranışı, ayıklama ile ortaya çıkan birimler üzerinden iki testle sabitlendi ve
  yakalama gücü altıda altıya çıktı.
- Güçlendirilmiş kümede aynı düzenleme sekizinci testi kırmızıya çevirdi; beklenen ile
  gerçekleşen değer koşumdan doğrudan okundu.
- Bozma denemesi ölçüsü yeterlilik belgesi değil, "testlerim yeterli mi" sorusunu sayıya
  çeviren bir araçtır.

## Kurs Kapanışı

Temiz Kod kursu on altı derste kodun iç yapısını üç açıdan ele aldı.

**Adlandırma ve Yapı**, okunabilirliği ölçülebilir bir büyüklüğe çevirdi: niyeti açıklayan
adlar, yorumun yerini alan adlandırma, tek soyutlama düzeyinde duran küçük fonksiyonlar,
girinti ve biçim tutarlılığının bilişsel maliyeti, çevrimsel karmaşıklığın dallanma
sayısıyla ölçülmesi ve koşul, arama tablosu ile çok biçimlilik arasındaki seçim.

**Arayüz ve Veri Kararları**, kararı imzaya taşıdı: boş değer geçirmemek, mantıksal bayrak
parametrelerini iki fonksiyona bölmek, en dar görünürlüğü seçmek, kodu değişim nedenine —
yani aktöre — göre gruplamak ve aynı işi projede tek biçimde yapmak.

**Sadelik İlkeleri**, kararların zaman içindeki maliyetini saydı: aynı bilginin tek yerde
durması ile tesadüfi benzerliğin yanlış soyutlamaya dönüşmesi, tasarımın küçük adımlarla
evrilmesi, gelmemiş gereksinim için yazılan genelleştirmenin bedeli, ayıklama–taşıma–yeniden
adlandırma teknikleri ve davranış korumasının ölçülmesi.

On altı dersin ortak yanı, her iddianın bir sayıya bağlanmasıydı: dokunulan dosya sayısı,
dallanma sayısı, kapsanmayan yol sayısı, ayrışan sınır sayısı, yakalanan bozma sayısı. Ortak
yanları olan başka bir şey daha var ve bu ders sonuna kadar adlandırılmadı: bütün kararlar
**tek bir programlama biçimi** içinde verildi. Fonksiyonlar, sabitler, modüller ve nesne
sözlükleri hiç tartışılmadan kullanıldı. Kodun nasıl yazıldığı sorgulandı; hangi modelle
düşünüldüğü sorgulanmadı.

Ücret kitaplığı bir dizi fonksiyon ve veri tablosu olarak modellendi. Aynı alan, davranışı
ve durumu bir arada tutan nesneler olarak da modellenebilirdi; ya da dönüşümlerin
zincirlendiği, durumun hiç değişmediği bir hesap olarak. Bu üç modelin hangisinin seçileceği
temiz kod sorusu değil, **paradigma** sorusudur. Sonraki kurs olan Programlama
Paradigmaları bunu ele alır: nesneye dayalı ve fonksiyonel yaklaşımların modelleme farkı,
kalıtım yerine bileşimin seçildiği durumlar ve aynı problemin birden çok paradigmayla
modellenip karşılaştırılması.
