---
title: 'Sahte Bağımlılıklar'
source: 'https://academia.sh/tr/kurslar/birim-testi/sahte-bagimliliklar'
course: 'Birim Testi ve Test Odaklı Geliştirme'
language: tr
updated: '2026-08-17T18:10:54+00:00'
license: 'CC BY-SA 4.0'
---

# Sahte Bağımlılıklar

Boş nesne, güdük, taklit nesne, casus nesne ve sahte nesne türlerinin elle yazılması, her türün yanıtladığı sorunun ayrılması ve aynı değişiklikte hangi türün konuştuğunun ölçülmesi.

Önceki derste dışarıdan verilen bağımlılıklar sabit bir değer döndüren küçük işlevlerdi.
Bu, bir ailenin tek üyesidir. Sınanan koda gerçek bağımlılık yerine konan nesnelerin ortak
adı **sahte bağımlılıktır** (test double) ve aralarında yapı değil **amaç** farkı vardır:
bir tanesi yalnız parametre yerini doldurur, bir tanesi belirli bir yanıt verir, bir tanesi
gerçek davranışın çalışan bir kopyasıdır, bir tanesi çağrıları kaydeder, bir tanesi de
beklentiyi kendi içinde taşır.

Bu ayrımlar akademik değildir. Her tür farklı bir doğrulama sorusunu yanıtlar ve yanlış tür
seçildiğinde test ya hiçbir şey sınamaz ya da sınamak istemediği bir şeye bağlanır. Bu ders
beş türü de elle yazar ve sonunda hepsini aynı değişiklik karşısında sınar.

## Sınanan Servis

Ödünç servisi dört bağımlılık alır: kayıtları tutan katalog, üyeye haber veren bildirim
kanalı, işlem günlüğü ve gün numarasını veren saat.

```js
// servis.mjs — odunc servisi: katalog, bildirim, gunluk ve saat bagimliliklariyla
export const KURALLAR = {
  ogrenci: { oduncGun: 28, enCokOdunc: 10 },
  uye: { oduncGun: 14, enCokOdunc: 5 },
};

export function oduncServisiKur({ katalog, bildirim, gunluk, saat }) {
  return {
    oduncVer(uye, kitapNo) {
      const kural = KURALLAR[uye.tur];
      if (katalog.uyeOduncSayisi(uye.no) >= kural.enCokOdunc) {
        throw new Error(`odunc siniri asildi: ${kural.enCokOdunc}`);
      }
      const bugun = saat();
      const kayit = { uyeNo: uye.no, kitapNo, alisGunu: bugun, sonGun: bugun + kural.oduncGun };
      katalog.ekle(kayit);
      bildirim.gonder(uye.no, `son tarih: ${kayit.sonGun}`);
      return kayit;
    },

    iadeAl(kitapNo) {
      const kayit = katalog.bul(kitapNo);
      if (kayit === undefined) throw new Error(`odunc kaydi yok: ${kitapNo}`);
      katalog.sil(kitapNo);
      gunluk.yaz(`iade ${kitapNo}`);
      return kayit;
    },
  };
}
```

## Beş Türün Yazımı

Beşi de birkaç satırdır; hiçbiri bir kütüphane gerektirmez.

```js
// sahteler.mjs — dort sahte bagimlilik turu ve beklentili bir sahte nesne
export function bosNesne(ad) {
  return new Proxy({}, {
    get: (_hedef, alan) => () => {
      throw new Error(`${ad}.${String(alan)} cagrilmamaliydi`);
    },
  });
}

export function gudukKatalog(oduncSayisi) {
  return {
    uyeOduncSayisi: () => oduncSayisi,
    ekle() {},
    bul: () => undefined,
    sil() {},
  };
}

export function taklitKatalog() {
  const kayitlar = new Map();
  return {
    uyeOduncSayisi: (uyeNo) => [...kayitlar.values()].filter((k) => k.uyeNo === uyeNo).length,
    ekle: (kayit) => kayitlar.set(kayit.kitapNo, kayit),
    bul: (kitapNo) => kayitlar.get(kitapNo),
    sil: (kitapNo) => kayitlar.delete(kitapNo),
  };
}

export function casusNesne(...yontemler) {
  const cagrilar = [];
  const nesne = { cagrilar };
  for (const ad of yontemler) nesne[ad] = (...girdi) => cagrilar.push([ad, ...girdi]);
  return nesne;
}

export function sahteNesne(beklenen, ...yontemler) {
  const casus = casusNesne(...yontemler);
  return {
    ...casus,
    dogrula() {
      const gercek = JSON.stringify(casus.cagrilar);
      const bek = JSON.stringify(beklenen);
      if (gercek === bek) return;
      throw new Error(`beklenti karsilanmadi: beklenen ${bek}, gercek ${gercek}`);
    },
  };
}
```

**Kukla nesne** (dummy) yalnız parametre yerini doldurur; çağrılmaması beklenir. Buradaki
sürüm bu beklentiyi zorunlu kılar: herhangi bir yöntemi çağrıldığında hata fırlatır.

**Güdük** (stub) sınanan kodun okuduğu bir değeri sabitler. Sorusu şudur: kod bu girdiyle
hangi yolu izler? Güdüğün kendisi hiçbir şey doğrulamaz.

**Taklit nesne** (fake) gerçek bağımlılığın çalışan ama basitleştirilmiş bir kopyasıdır.
Buradaki katalog gerçek bir eşleme kullanır; ekleme, arama ve silme anlamlıdır. Çok adımlı
senaryolar ancak bu türle kurulabilir.

**Casus nesne** (spy) çağrıları kaydeder ve testin sonradan incelemesine bırakır. Sorusu
şudur: bu çağrı gerçekten yapıldı mı, hangi bağımsız değişkenlerle?

**Sahte nesne** (mock) beklentiyi kendi içinde taşır. Casus nesneden farkı, doğrulamanın
testin değil nesnenin sorumluluğunda olmasıdır.

## Her Türün Yanıtladığı Soru

Beş tür, beş ayrı testte kullanıldığında farkları görünür olur.

```js
// sahteler.test.mjs — her sahte bagimlilik turu ayri bir soruyu yanitlar
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { oduncServisiKur } from './servis.mjs';
import { bosNesne, gudukKatalog, taklitKatalog, casusNesne, sahteNesne } from './sahteler.mjs';

const OGRENCI = { no: 'U-17', tur: 'ogrenci' };
const UYE = { no: 'U-42', tur: 'uye' };

test('bos nesne: odunc verme gunluge hic dokunmaz', () => {
  const servis = oduncServisiKur({
    katalog: taklitKatalog(),
    bildirim: casusNesne('gonder'),
    gunluk: bosNesne('gunluk'),
    saat: () => 1000,
  });

  const kayit = servis.oduncVer(OGRENCI, 'K-903');

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

test('guduk: siniri dolu uyede odunc reddedilir', () => {
  const servis = oduncServisiKur({
    katalog: gudukKatalog(5),
    bildirim: bosNesne('bildirim'),
    gunluk: bosNesne('gunluk'),
    saat: () => 1000,
  });

  assert.throws(() => servis.oduncVer(UYE, 'K-903'), /odunc siniri asildi: 5/);
});

test('taklit nesne: iade edilen kitap yeniden odunc verilebilir', () => {
  const servis = oduncServisiKur({
    katalog: taklitKatalog(),
    bildirim: casusNesne('gonder'),
    gunluk: casusNesne('yaz'),
    saat: () => 1000,
  });

  servis.oduncVer(OGRENCI, 'K-903');
  servis.iadeAl('K-903');
  const ikinci = servis.oduncVer(UYE, 'K-903');

  assert.equal(ikinci.sonGun, 1014);
});

test('casus nesne: uyeye tek bildirim gider', () => {
  const casus = casusNesne('gonder');
  const servis = oduncServisiKur({
    katalog: taklitKatalog(),
    bildirim: casus,
    gunluk: bosNesne('gunluk'),
    saat: () => 1000,
  });

  servis.oduncVer(OGRENCI, 'K-903');

  assert.deepEqual(casus.cagrilar, [['gonder', 'U-17', 'son tarih: 1028']]);
});

test('sahte nesne: reddedilen oduncte bildirim gitmez', () => {
  const sahte = sahteNesne([], 'gonder');
  const servis = oduncServisiKur({
    katalog: gudukKatalog(5),
    bildirim: sahte,
    gunluk: bosNesne('gunluk'),
    saat: () => 1000,
  });

  assert.throws(() => servis.oduncVer(UYE, 'K-903'));

  sahte.dogrula();
});
```

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

```
ok 1 - bos nesne: odunc verme gunluge hic dokunmaz
ok 2 - guduk: siniri dolu uyede odunc reddedilir
ok 3 - taklit nesne: iade edilen kitap yeniden odunc verilebilir
ok 4 - casus nesne: uyeye tek bildirim gider
ok 5 - sahte nesne: reddedilen oduncte bildirim gitmez
# tests 5
# pass 5
# fail 0
```

Beş testin doğrula bölümleri birbirinden farklıdır ve fark tür seçiminden gelir. Birinci
testin iddiası dönen kayıtla ilgilidir; kukla nesne yalnız bir varsayımı korur. İkinci test
yine dönüş değerine bakar; güdük ise o dönüşü mümkün kılan girdiyi verir. Üçüncü test üç
adımlık bir senaryo kurar ve bu ancak durum tutan bir taklit nesneyle olur. Dördüncü ve
beşinci testler dönüş değerine değil, **yapılan çağrıya** bakar.

Son iki testin ayrımı incedir. Casus nesne kullanan test doğrulamayı kendi gövdesinde
yapar ve iddia açıkça okunur. Sahte nesne kullanan test beklentiyi düzenekte kurar ve
gövdesinde yalnız `dogrula()` çağırır. İkincisi, birden çok çağrı beklentisi olan
durumlarda gövdeyi kısaltır; karşılığında beklentinin nerede kurulduğu testin başına taşınır.

## Aynı Değişiklikte Kim Konuşur

Türlerin gerçek farkı, kod değiştiğinde hangisinin ses çıkardığıdır. Aşağıdaki betik
servise iki yeni yan etki ekler — her ödünç denemesi günlüğe yazılır ve reddedilen istekte
de üyeye bildirim gider — ve aynı üç türü bu yeni sürüme uygular.

```js
// sinama.mjs — servis yan etki kazandiginda hangi sahte bagimlilik konusur
import assert from 'node:assert/strict';
import { KURALLAR } from './servis.mjs';
import { bosNesne, gudukKatalog, sahteNesne } from './sahteler.mjs';

function degismisServisKur({ katalog, bildirim, gunluk, saat }) {
  return {
    oduncVer(uye, kitapNo) {
      const kural = KURALLAR[uye.tur];
      gunluk.yaz(`odunc denemesi ${kitapNo}`);
      if (katalog.uyeOduncSayisi(uye.no) >= kural.enCokOdunc) {
        bildirim.gonder(uye.no, 'odunc reddedildi');
        throw new Error(`odunc siniri asildi: ${kural.enCokOdunc}`);
      }
      const bugun = saat();
      const kayit = { uyeNo: uye.no, kitapNo, alisGunu: bugun, sonGun: bugun + kural.oduncGun };
      katalog.ekle(kayit);
      bildirim.gonder(uye.no, `son tarih: ${kayit.sonGun}`);
      return kayit;
    },
  };
}

const UYE = { no: 'U-42', tur: 'uye' };
const sessizGunluk = { yaz() {} };
const sessizBildirim = { gonder() {} };

function dene(ad, govde) {
  try {
    govde();
    console.log(`${ad.padEnd(12)} sessiz`);
  } catch (hata) {
    console.log(`${ad.padEnd(12)} ${hata.message.split('\n')[0]}`);
  }
}

dene('guduk', () => {
  const servis = degismisServisKur({
    katalog: gudukKatalog(5),
    bildirim: sessizBildirim,
    gunluk: sessizGunluk,
    saat: () => 1000,
  });

  assert.throws(() => servis.oduncVer(UYE, 'K-903'), /odunc siniri asildi: 5/);
});

dene('bos nesne', () => {
  const servis = degismisServisKur({
    katalog: gudukKatalog(0),
    bildirim: sessizBildirim,
    gunluk: bosNesne('gunluk'),
    saat: () => 1000,
  });

  servis.oduncVer(UYE, 'K-903');
});

dene('sahte nesne', () => {
  const sahte = sahteNesne([], 'gonder');
  const servis = degismisServisKur({
    katalog: gudukKatalog(5),
    bildirim: sahte,
    gunluk: sessizGunluk,
    saat: () => 1000,
  });

  assert.throws(() => servis.oduncVer(UYE, 'K-903'));
  sahte.dogrula();
});
```

```
guduk        sessiz
bos nesne    gunluk.yaz cagrilmamaliydi
sahte nesne  beklenti karsilanmadi: beklenen [], gercek [["gonder","U-42","odunc reddedildi"]]
```

Üç satır, tür seçiminin ne anlama geldiğini tek başına anlatır.

Güdük **sessiz** kaldı, çünkü dönüş değeri ve fırlatılan hata değişmemişti. Bu bir kusur
değildir: güdük yalnız girdi sağlar, doğrulama yapmaz. Dönüş değerine bakan bir test yeni
yan etkileri görmez ve görmemelidir.

Boş nesne **konuştu**, çünkü çağrılmaması gereken bir bağımlılık çağrıldı. Bu türün tek
işlevi budur ve karşılığında testin dayandığı varsayımı belge hâline getirir.

Sahte nesne de **konuştu**, çünkü beklentisi "hiç bildirim gitmesin" idi ve bir bildirim
gitti. Aynı sinyali casus nesne de verirdi; fark, doğrulamanın nerede durduğudur.

Buradan çıkan seçim kuralı üç maddedir. Doğrulanacak şey **dönüş değeri ya da fırlatılan
hata** ise güdük yeter. **Yan etkinin gerçekleşmesi** doğrulanacaksa casus ya da sahte
nesne gerekir. **Çok adımlı bir senaryo** kurulacaksa taklit nesne gerekir; güdükle
kurulan çok adımlı senaryolar hızla tutarsız hâle gelir.

Çağrı doğrulaması bir maliyet taşır: test, sınanan kodun **nasıl** çalıştığına bağlanır.
Bir yeniden düzenleme bildirim gönderimini başka bir katmana taşırsa casus ve sahte
nesneyle yazılmış testler düşer, oysa dışarıdan gözlenen davranış aynıdır. Sekizinci ders
bu bedeli ölçer.

## Koşucunun Sağladığı Yetenek

Yerleşik test koşucusu, çağrı kaydı tutan işlevleri elle yazmak zorunda kalmamak için bir
yetenek sağlar. Elle yazılan casus nesnenin yaptığı işi aynı yapar; kayıt biçimi hazır gelir.

```js
// yerlesik.test.mjs — kosucunun sagladigi sahte islev yetenegi
import { test, mock } from 'node:test';
import assert from 'node:assert/strict';
import { oduncServisiKur } from './servis.mjs';
import { bosNesne, taklitKatalog } from './sahteler.mjs';

test('yerlesik sahte islev cagri kaydini tutar', () => {
  const gonder = mock.fn();
  const servis = oduncServisiKur({
    katalog: taklitKatalog(),
    bildirim: { gonder },
    gunluk: bosNesne('gunluk'),
    saat: () => 1000,
  });

  servis.oduncVer({ no: 'U-17', tur: 'ogrenci' }, 'K-903');

  assert.equal(gonder.mock.callCount(), 1);
  assert.deepEqual(gonder.mock.calls[0].arguments, ['U-17', 'son tarih: 1028']);
});
```

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

```
ok 1 - yerlesik sahte islev cagri kaydini tutar
# tests 1
# pass 1
# fail 0
```

Hazır yetenek yazım yükünü azaltır, ama tür seçimini yapmaz. `mock.fn()` çağrı kaydı
tuttuğu için casus nesne olarak da sahte nesne olarak da kullanılabilir; hangisi olduğunu
belirleyen şey testin onu nasıl doğruladığıdır. Türü seçen karar hâlâ yazan kişinindir.

## Özet

- Sahte bağımlılıkların farkı yapıda değil amaçtadır; beş tür beş ayrı doğrulama sorusunu
  yanıtlar.
- Boş nesne yalnız yer doldurur, güdük girdi sabitler, taklit nesne çalışan bir kopyadır,
  casus nesne çağrıları kaydeder, sahte nesne beklentiyi kendi taşır.
- Servise iki yan etki eklendiğinde güdük sessiz kaldı, kukla nesne beklenmedik çağrıyı, sahte
  nesne karşılanmayan beklentiyi bildirdi.
- Dönüş değeri sınanacaksa güdük yeter; yan etki sınanacaksa casus ya da sahte nesne, çok
  adımlı senaryo kurulacaksa taklit nesne gerekir.
- Çağrı doğrulaması testi kodun nasıl çalıştığına bağlar; koşucunun sağladığı hazır yetenek
  yazım yükünü azaltır ama tür seçimini yapmaz.

## Sonraki Adım

Bu derste testlerin neyi sınadığı tür tür ayrıldı, ama takımın **ne kadarını** sınadığı
sorusu hiç sorulmadı. Bu soruya verilen yaygın yanıt bir yüzdedir: kodun kaçta kaçı test
sırasında çalıştı? Sonraki ders bu yüzdenin üç ayrı biçimde hesaplandığını gösterir ve
üçünü de elle sayar. Yüzde yüz satır kapsamına ulaşmış bir takımın kaçırdığı hata, aynı
derste koşturularak gösterilir.
