---
title: 'Test Edilebilir Tasarım'
source: 'https://academia.sh/tr/kurslar/birim-testi/test-edilebilir-tasarim'
course: 'Birim Testi ve Test Odaklı Geliştirme'
language: tr
updated: '2026-08-17T18:10:54+00:00'
license: 'CC BY-SA 4.0'
---

# Test Edilebilir Tasarım

Bağımlılığını kendi içinde yaratan sürüm ile bağımlılığı dışarıdan alan sürümün karşılaştırılması, düzenek maliyetinin satırla ölçülmesi ve zaman ile rastgeleliğin dışarıdan verilmesi.

Önceki derste düzenek testin elindeydi: katalog nesnesi bir fabrika çağrısıyla üretiliyor,
her test kendi kopyasını kuruyordu. Bu olanak koddan geldi, testten değil. Sınanan kod
kataloğu kendi içinde yaratsaydı, testin elinde onu değiştirecek bir kol kalmazdı.

Test edilebilirlik testin bir niteliği değil, **tasarımın** bir niteliğidir. Aynı iş kuralı
iki farklı biçimde yazıldığında ikisi de doğru çalışır, ama biri sınanabilir bir kol
bırakır, diğeri bırakmaz. Bu dersin sorusu şudur: bu fark nerede ortaya çıkar ve ne kadar
maliyet üretir?

## Aynı Kural, İki Tasarım

Sınanacak iş şudur: ödünç verme işlemi bugünün gün numarasını okur, bir fiş numarası
üretir, kitabın halihazırda ödünçte olmadığını denetler ve ödünç kaydını döndürür. İşlemin
üç dış kaynağı vardır: saat, fiş numarası üreteci ve katalog.

Birinci sürüm üçünü de kendi içinde yaratır.

```js
// iceriden.mjs — bagimliliklarini kendi iceride yaratan surum
const katalog = new Map();

export function katalogTemizle() {
  katalog.clear();
}

export function oduncVer(uyeNo, kitapNo, uyeTuru) {
  const bugun = Math.floor(Date.now() / 86400000);
  const fisNo = `F-${String(Math.floor(Math.random() * 1000000)).padStart(6, '0')}`;
  if (katalog.has(kitapNo)) throw new Error(`kitap zaten odunc verilmis: ${kitapNo}`);
  const sure = uyeTuru === 'ogrenci' ? 28 : 14;
  katalog.set(kitapNo, uyeNo);
  return { fisNo, uyeNo, kitapNo, alisGunu: bugun, sonGun: bugun + sure };
}
```

İkinci sürüm üçünü de dışarıdan alır. İş kuralı — süre hesabı, çift ödünç denetimi, kayıt
biçimi — birebir aynıdır.

```js
// disaridan.mjs — bagimliliklari disaridan verilen surum
export function oduncServisiKur({ saat, fisUretici, katalog }) {
  return {
    oduncVer(uyeNo, kitapNo, uyeTuru) {
      const bugun = saat();
      const fisNo = fisUretici();
      if (katalog.has(kitapNo)) throw new Error(`kitap zaten odunc verilmis: ${kitapNo}`);
      const sure = uyeTuru === 'ogrenci' ? 28 : 14;
      katalog.set(kitapNo, uyeNo);
      return { fisNo, uyeNo, kitapNo, alisGunu: bugun, sonGun: bugun + sure };
    },
  };
}
```

Bu ikinci biçime **bağımlılık enjeksiyonu** (dependency injection) denir; bağımlılık
içeride yaratılmak yerine dışarıdan verilir. Adı
kulağa bir çatı özelliği gibi gelir, ama burada görüldüğü gibi yalnız bir imza kararıdır:
kod bağımlılığını aramak yerine kendisine verilmesini bekler.

## Düzeneğin Maliyeti

İki sürümün testleri aynı kuralı sınar; farkı, o kurala ulaşmak için kurulması gereken
düzenek yaratır.

```js
// duzenek.mjs — iki surumun testinin kurdugu duzenek
import { katalogTemizle, oduncVer } from './iceriden.mjs';
import { oduncServisiKur } from './disaridan.mjs';

export function iceridenDuzenek() {
  const gercekNow = Date.now;
  const gercekRandom = Math.random;
  Date.now = () => 1000 * 86400000;
  Math.random = () => 0.123456;
  katalogTemizle();
  const geriAl = () => {
    Date.now = gercekNow;
    Math.random = gercekRandom;
  };
  return { servis: { oduncVer }, geriAl };
}

export function disaridanDuzenek() {
  const servis = oduncServisiKur({
    saat: () => 1000,
    fisUretici: () => 'F-123456',
    katalog: new Map(),
  });
  return { servis, geriAl: () => {} };
}
```

Düzeneklerin boyutu ölçülebilir. İki düzenek de çalıştırılıp ürettikleri kayıt
karşılaştırıldığında, farkın davranışta değil yalnız hazırlıkta olduğu görülür.

```js
// olcum.mjs — iki duzenegin satir sayisi ve urettikleri kayit
import { iceridenDuzenek, disaridanDuzenek } from './duzenek.mjs';

const satir = (islev) => islev.toString().split('\n').length - 2;

for (const [ad, duzenek] of [['iceriden', iceridenDuzenek], ['disaridan', disaridanDuzenek]]) {
  const { servis, geriAl } = duzenek();
  const kayit = servis.oduncVer('U-17', 'K-903', 'ogrenci');
  geriAl();
  console.log(`${ad.padEnd(10)} duzenek ${String(satir(duzenek)).padStart(2)} satir  ${JSON.stringify(kayit)}`);
}
```

```
iceriden   duzenek 10 satir  {"fisNo":"F-123456","uyeNo":"U-17","kitapNo":"K-903","alisGunu":1000,"sonGun":1028}
disaridan  duzenek  6 satir  {"fisNo":"F-123456","uyeNo":"U-17","kitapNo":"K-903","alisGunu":1000,"sonGun":1028}
```

İki kayıt birebir aynı. Düzenek maliyeti on satıra karşı altı satır. Ama asıl fark satır
sayısı değil, o satırların **ne yaptığıdır**. Dışarıdan verilen sürümün düzeneği yalnız
nesne kurar. İçeriden yaratan sürümün düzeneği ise iki küresel işlevi değiştirir, sınanan
modülün iç durumunu temizler ve değiştirdiklerini geri almayı üstlenir.

Birinci derste "hazırla bölümünün uzaması ayrı bir sinyaldir" denmişti. Ölçülen sinyal
budur: düzenek büyüdükçe testin sınadığı kural azalmaz, ama teste giren risk artar.

## Geri Alınmayan Değişikliğin Bedeli

Küresel bir işlevi değiştiren düzeneğin taşıdığı risk somuttur. Aşağıdaki test dosyasında
birinci test saati dondurur ve geri almayı unutur.

```js
// sizinti.test.mjs — geri alinmayan kuresel degisiklik sonraki testi etkiler
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { katalogTemizle, oduncVer } from './iceriden.mjs';

test('ogrenci oduncu yirmi sekiz gun surer', () => {
  Date.now = () => 1000 * 86400000;
  katalogTemizle();

  const kayit = oduncVer('U-17', 'K-903', 'ogrenci');

  assert.equal(kayit.sonGun, 1028);
});

test('gun numarasi gercek saatten gelir', () => {
  const bugun = Math.floor(Date.now() / 86400000);

  assert.ok(bugun > 20000, `beklenmedik gun numarasi: ${bugun}`);
});
```

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

```
ok 1 - ogrenci oduncu yirmi sekiz gun surer
not ok 2 - gun numarasi gercek saatten gelir
# tests 2
# pass 1
# fail 1
```

İkinci test kendi başına doğrudur ve tek başına koşturulduğunda geçer. Düştüğü koşumda
gördüğü gün numarası bindir; çünkü saat hâlâ birinci testin bıraktığı gibi donmuştur.
Bu, önceki dersteki sıra bağımlılığının aynısıdır, ama kaynağı farklıdır: paylaşılan durum
kitaplığın kendisinde değil, **çalışma zamanının küresel nesnelerinde** birikiyor.

Küresel değişikliği geri almak bir çözümdür, ama unutulabilir bir çözümdür ve unutulduğunda
bozulan test, hatayı yapan test değildir. Dışarıdan verilen sürümde bu risk sınıfı hiç
oluşmaz.

```js
// temiz.test.mjs — disaridan verilen bagimlilik kuresel durumu bozmaz
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { oduncServisiKur } from './disaridan.mjs';

const kur = () => oduncServisiKur({
  saat: () => 1000,
  fisUretici: () => 'F-123456',
  katalog: new Map(),
});

test('ogrenci oduncu yirmi sekiz gun surer', () => {
  const servis = kur();

  const kayit = servis.oduncVer('U-17', 'K-903', 'ogrenci');

  assert.equal(kayit.sonGun, 1028);
});

test('gun numarasi gercek saatten gelir', () => {
  const bugun = Math.floor(Date.now() / 86400000);

  assert.ok(bugun > 20000, `beklenmedik gun numarasi: ${bugun}`);
});

test('fis numarasi dis kaynaktan gelir', () => {
  const servis = kur();

  const kayit = servis.oduncVer('U-17', 'K-903', 'uye');

  assert.equal(kayit.fisNo, 'F-123456');
});
```

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

```
ok 1 - ogrenci oduncu yirmi sekiz gun surer
ok 2 - gun numarasi gercek saatten gelir
ok 3 - fis numarasi dis kaynaktan gelir
# tests 3
# pass 3
# fail 0
```

Üçüncü test, ilk iki sürümde hiç yazılamayan bir şeyi sınıyor: fiş numarasının nereden
geldiği. İçeriden yaratan sürümde fiş numarası rastgelelik kaynağından gelir ve testin
onu doğrulaması için yine küresel bir değişiklik gerekir. Dışarıdan verilen sürümde
üreteç bir parametredir ve testin verdiği değer aynen kayda geçer.

## Belirlenimci Olmayan Kaynakların Yeri

Zaman ve rastgelelik, testin denetleyemediği kaynakların iki tipik örneğidir; aynı sınıfa
süreç kimliği, ortam değişkenleri, ağ adresleri ve dosya sistemi de girer. Ortak yanları,
**aynı girdiye aynı çıktıyı vermemeleridir**.

Bunları sınanabilir kılmanın yolu, kodun bu kaynaklara doğrudan erişmesini engellemek değil,
erişimi tek bir noktada toplayıp o noktayı dışarıdan verilebilir hâle getirmektir. Yukarıdaki
sürümde `saat` ve `fisUretici` bu noktalardır. Üretimde çalışan yapılandırma gerçek kaynakları
verir; testte çalışan yapılandırma sabit değerler verir.

Bu ayrımın bir sınırı vardır ve baştan söylenmelidir: bir kod parçası zamanı gerçekten
okumak zorundaysa, o okuma da bir yerde sınanmalıdır. Dışarıdan verme, o sınamayı ortadan
kaldırmaz — birim testinin dışına, tümleşim testine taşır. Kazanç, iş kuralının belirlenimci
bir bağlamda sınanabilmesidir.

## Özet

- Test edilebilirlik testin değil tasarımın niteliğidir; aynı iş kuralı iki biçimde de doğru
  çalışır, ama yalnız biri sınanabilir bir kol bırakır.
- Bağımlılığın dışarıdan verilmesi bir çatı özelliği değil bir imza kararıdır: kod
  bağımlılığını aramak yerine kendisine verilmesini bekler.
- Ölçümde iki düzenek aynı kaydı üretti; içeriden yaratan sürümün düzeneği on, dışarıdan
  alan sürümünki altı satırdı ve fark yalnız uzunlukta değil, küresel değişiklik içermesindeydi.
- Geri alınmayan küresel değişiklik sonraki testi düşürür; bozulan test, hatayı yapan test
  değildir.
- Zaman, rastgelelik ve benzeri belirlenimci olmayan kaynaklar tek bir noktada toplanıp
  dışarıdan verilebilir kılınır; bu, o kaynakların sınanmasını kaldırmaz, tümleşim testine
  taşır.

## Sonraki Adım

Bu derste dışarıdan verilen bağımlılıklar sabit değer döndüren küçük işlevlerdi: `saat`
her zaman bin, `fisUretici` her zaman aynı numarayı verdi. Bu, dört türü olan bir ailenin
yalnız bir üyesidir. Bir bağımlılık yalnız yer doldurmak için de verilebilir, çağrıldığında
kayıt tutmak için de, gerçek davranışın çalışan ama basitleştirilmiş bir kopyası olmak için
de. Sonraki ders bu türleri tek tek elle yazar ve her birinin hangi soruyu yanıtladığını
ayırır.
