---
title: 'Hızlı ve Bağımsız Testler'
source: 'https://academia.sh/tr/kurslar/birim-testi/hizli-ve-bagimsiz-testler'
course: 'Birim Testi ve Test Odaklı Geliştirme'
language: tr
updated: '2026-08-17T18:10:54+00:00'
license: 'CC BY-SA 4.0'
---

# Hızlı ve Bağımsız Testler

Modül düzeyinde paylaşılan durumun ürettiği sıra bağımlılığının sabit tohumlu permütasyonla gösterilmesi, taze düzenekle kırılması ve süreç dışı düzeneğin koşum süresine eklediği payın ölçülmesi.

Önceki dersteki testlerin hepsi tek başına doğruydu ve hepsi tek başına kaldığı sürece
doğru kalır. Testler çoğaldıkça bu koşul kendiliğinden sürmez. İki test aynı nesneyi,
aynı modül değişkenini ya da aynı dosyayı kullanıyorsa, birincinin bıraktığı durum
ikincinin girdisine karışır. Sonuç, koşum sırasının bir girdiye dönüşmesidir.

Sıra bağımlılığı sinsi bir kusurdur, çünkü çoğu zaman görünmez: koşucu testleri hep aynı
sırada çalıştırdığı sürece takım yeşil kalır. Bağımlılık, dosyaya yeni bir test eklendiğinde
ya da testler paralel dağıtıldığında ortaya çıkar ve o an bozulanın kod mu test mi olduğu
belli değildir. Bu dersin ilk işi, o sırayı bir değişkene çevirip bağımlılığı görünür
kılmaktır.

## Sırayı Bir Değişkene Çevirmek

Sıra bağımlılığını aramanın yolu, koşum sırasını denetimli biçimde değiştirmektir. Bunun
için rastgeleliğe ihtiyaç yoktur; sabit **tohumdan** (seed) türeyen bir permütasyon yeterlidir
ve tekrarlanabilir olduğu için daha kullanışlıdır. Frontend Kalitesi kursundaki Test
Kararlılığı dersinde tanıtılan doğrusal eşlenik üreteç bu iş için yeterli bir kaynaktır.

```js
// kosucu.mjs — sabit tohumlu sira karistirmali kucuk kosucu
export function uretec(tohum) {
  let durum = (tohum * 2654435761) % 2147483647;
  return () => {
    durum = (durum * 48271) % 2147483647;
    return durum / 2147483647;
  };
}

export function karistir(dizi, tohum) {
  const rastgele = uretec(tohum);
  const kopya = [...dizi];
  for (let i = kopya.length - 1; i > 0; i -= 1) {
    const j = Math.floor(rastgele() * (i + 1));
    [kopya[i], kopya[j]] = [kopya[j], kopya[i]];
  }
  return kopya;
}

export function kos(testler, tohum) {
  const sira = karistir(Object.keys(testler), tohum);
  let gecen = 0;
  const satirlar = [];
  for (const ad of sira) {
    try {
      testler[ad]();
      gecen += 1;
      satirlar.push(`  ok      ${ad}`);
    } catch (hata) {
      satirlar.push(`  not ok  ${ad}  (${hata.message.split('\n')[0]})`);
    }
  }
  console.log(`tohum ${tohum}: ${gecen} gecti, ${sira.length - gecen} dustu`);
  console.log(satirlar.join('\n'));
}
```

Aynı tohum her zaman aynı sırayı verir; düşen bir koşum bildirildiğinde tohumu paylaşmak
sorunu yeniden üretmeye yeter.

## Paylaşılan Durumun Ürettiği Bağımlılık

Aşağıdaki katalog modülü, ödünç kayıtlarını modül düzeyinde bir eşlemede tutar. Modül bir
kez yüklenir ve süreç boyunca tek bir eşleme vardır.

```js
// katalog.mjs — modul duzeyinde paylasilan odunc kaydi
const kayitlar = new Map();

export function oduncEkle(uyeNo, kitapNo) {
  if (kayitlar.has(kitapNo)) throw new Error(`kitap zaten odunc verilmis: ${kitapNo}`);
  kayitlar.set(kitapNo, uyeNo);
}

export function iadeEt(kitapNo) {
  kayitlar.delete(kitapNo);
}

export function acikOduncSayisi() {
  return kayitlar.size;
}
```

Üç test bu modülün üç ayrı kuralını sınar. Her biri tek başına okunduğunda doğrudur.

```js
// paylasilan.mjs — paylasilan durum uzerinde tohumla belirlenen sirayla kosum
import assert from 'node:assert/strict';
import { kos } from './kosucu.mjs';
import { oduncEkle, iadeEt, acikOduncSayisi } from './katalog.mjs';

const TESTLER = {
  'yeni odunc katalogda gorunur': () => {
    oduncEkle('U-17', 'K-903');
    assert.equal(acikOduncSayisi(), 1);
  },
  'iade edilen kitap katalogdan duser': () => {
    oduncEkle('U-17', 'K-101');
    iadeEt('K-101');
    assert.equal(acikOduncSayisi(), 0);
  },
  'ayni kitap iki kez odunc verilemez': () => {
    oduncEkle('U-42', 'K-903');
    assert.throws(() => oduncEkle('U-58', 'K-903'), /zaten odunc verilmis/);
  },
};

kos(TESTLER, Number(process.argv[2] ?? 3));
```

İki farklı tohumla iki ayrı süreçte koşturmak yeter. Ayrı süreç gerekir, çünkü modül
düzeyindeki eşleme süreç ömrü boyunca yaşar.

```sh
node paylasilan.mjs 3
node paylasilan.mjs 4
```

```
tohum 3: 1 gecti, 2 dustu
  ok      yeni odunc katalogda gorunur
  not ok  iade edilen kitap katalogdan duser  (Expected values to be strictly equal:)
  not ok  ayni kitap iki kez odunc verilemez  (kitap zaten odunc verilmis: K-903)
tohum 4: 2 gecti, 1 dustu
  ok      iade edilen kitap katalogdan duser
  ok      yeni odunc katalogda gorunur
  not ok  ayni kitap iki kez odunc verilemez  (kitap zaten odunc verilmis: K-903)
```

Aynı kod, aynı üç test, iki farklı sonuç. Üçüncü tohumda geçen bir test dördüncüde düşer.
Bu tabloda hangi sonucun "doğru" olduğu sorusunun yanıtı yoktur, çünkü testler bir kuralı
değil, kendilerinden önce ne çalıştığını ölçüyor.

İki ayrı kirlenme biçimi görünüyor. `iade edilen kitap katalogdan duser` testi mutlak bir
sayı bekliyor: kataloğun boş olduğunu varsayıyor. `ayni kitap iki kez odunc verilemez`
testi ise tersine, kataloğun kendi eklediği kitabı içermediğini varsayıyor. İkisi de
başlangıç durumu hakkında yazılı olmayan bir varsayım taşıyor ve o varsayımı önceki testler
belirliyor.

## Taze Düzenek

Çözüm, testleri sıraya duyarsız kılmak değil, her testin kendi başlangıç durumunu
kurmasıdır. Bunun için paylaşılan tek eşleme yerine, her çağrıda taze bir katalog üreten
bir fabrika kullanılır.

```js
// katalog.mjs — surum 2: her cagrida taze bir katalog uretilir
export function katalogKur() {
  const kayitlar = new Map();
  return {
    oduncEkle(uyeNo, kitapNo) {
      if (kayitlar.has(kitapNo)) throw new Error(`kitap zaten odunc verilmis: ${kitapNo}`);
      kayitlar.set(kitapNo, uyeNo);
    },
    iadeEt(kitapNo) {
      kayitlar.delete(kitapNo);
    },
    acikOduncSayisi() {
      return kayitlar.size;
    },
  };
}
```

Bir testin çalışmaya başlamadan önce kurduğu bu başlangıç durumuna **düzenek** (fixture)
denir. Test koşucusu düzeneği kurmak ve gerekiyorsa sökmek için kancalar sağlar; her testten
önce çalışan kanca kuruluma, her testten sonra çalışan kanca yıkıma karşılık gelir.

```js
// bagimsiz.test.mjs — her teste taze duzenek, bildirim sirasi tohumdan gelir
import { test, beforeEach } from 'node:test';
import assert from 'node:assert/strict';
import { karistir } from './kosucu.mjs';
import { katalogKur } from './katalog.mjs';

let katalog;
beforeEach(() => {
  katalog = katalogKur();
});

const TESTLER = {
  'yeni odunc katalogda gorunur': () => {
    katalog.oduncEkle('U-17', 'K-903');
    assert.equal(katalog.acikOduncSayisi(), 1);
  },
  'iade edilen kitap katalogdan duser': () => {
    katalog.oduncEkle('U-17', 'K-101');
    katalog.iadeEt('K-101');
    assert.equal(katalog.acikOduncSayisi(), 0);
  },
  'ayni kitap iki kez odunc verilemez': () => {
    katalog.oduncEkle('U-42', 'K-903');
    assert.throws(() => katalog.oduncEkle('U-58', 'K-903'), /zaten odunc verilmis/);
  },
};

for (const ad of karistir(Object.keys(TESTLER), Number(process.env.TOHUM ?? 3))) {
  test(ad, TESTLER[ad]);
}
```

Testler artık yerleşik koşucuyla çalışır; bildirim sırası tohumdan gelir, düzenek her
testten önce yeniden kurulur.

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

```
ok 1 - yeni odunc katalogda gorunur
ok 2 - iade edilen kitap katalogdan duser
ok 3 - ayni kitap iki kez odunc verilemez
# tests 3
# pass 3
# fail 0
ok 1 - iade edilen kitap katalogdan duser
ok 2 - yeni odunc katalogda gorunur
ok 3 - ayni kitap iki kez odunc verilemez
# tests 3
# pass 3
# fail 0
```

Sıra değişti, sonuç değişmedi. Bağımsızlığın ölçütü budur: koşum sırası bir girdi olmaktan
çıktığında testin bildirdiği şey yalnızca sınadığı kuraldır.

Aynı ilkenin iki uygulama sonucu vardır. Testler arasında paylaşılan hiçbir yazılabilir
durum kalmadığı için testler **paralel** koşturulabilir. Düşen bir test tek başına
koşturulduğunda da düşer; yeniden üretmek için bütün takımı çalıştırmak gerekmez.

## Düzeneğin Süre Payı

Bağımsızlık ile hız ayrı erdemler gibi görünür, ama aynı kaynaktan beslenir. Her testin
kendi düzeneğini kurması, düzeneğin **ucuz** olmasını gerektirir. Bellekteki bir eşlemeyi
kurmak birkaç mikrosaniyedir; aynı düzeneği diskte kurmak süreç dışına çıkmayı gerektirir.

```js
// hiz.mjs — bellek ici duzenek ile disk destekli duzenegin karsilastirmasi
import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';

const TEST_SAYISI = 200;

function bellekIciDuzenek() {
  let dosyaIslemi = 0;
  for (let i = 0; i < TEST_SAYISI; i += 1) {
    const kayitlar = new Map([['K-903', 'U-17']]);
    if (kayitlar.size !== 1) throw new Error('duzenek bozuk');
  }
  return dosyaIslemi;
}

function diskDestekliDuzenek() {
  let dosyaIslemi = 0;
  const kok = mkdtempSync(join(tmpdir(), 'katalog-'));
  for (let i = 0; i < TEST_SAYISI; i += 1) {
    const yol = join(kok, `katalog-${i}.json`);
    writeFileSync(yol, JSON.stringify({ 'K-903': 'U-17' }));
    dosyaIslemi += 1;
    const kayitlar = new Map(Object.entries(JSON.parse(readFileSync(yol, 'utf8'))));
    dosyaIslemi += 1;
    if (kayitlar.size !== 1) throw new Error('duzenek bozuk');
  }
  rmSync(kok, { recursive: true, force: true });
  return dosyaIslemi;
}

function olc(islev) {
  const baslangic = performance.now();
  const dosyaIslemi = islev();
  return { sure: performance.now() - baslangic, dosyaIslemi };
}

const bellek = olc(bellekIciDuzenek);
const disk = olc(diskDestekliDuzenek);

console.log(`bellek ici duzenek   : ${TEST_SAYISI} test, ${bellek.dosyaIslemi} dosya islemi`);
console.log(`disk destekli duzenek: ${TEST_SAYISI} test, ${disk.dosyaIslemi} dosya islemi`);
console.log(`disk destekli surum daha uzun surdu : ${disk.sure > bellek.sure}`);
console.log(`sure farki en az yirmi kat           : ${disk.sure / bellek.sure >= 20}`);
```

```
bellek ici duzenek   : 200 test, 0 dosya islemi
disk destekli duzenek: 200 test, 400 dosya islemi
disk destekli surum daha uzun surdu : true
sure farki en az yirmi kat           : true
```

Mutlak süreler makineye ve dosya sistemine bağlıdır; bu yüzden çıktı ham milisaniye yerine
karşılaştırma sonucu bildiriyor. Değişmeyen şey ilişkinin yönüdür: iki yüz testlik bir
takım, test başına iki dosya işlemiyle dört yüz süreç dışı çağrı üretir ve bu çağrıların
her biri sabit bir taban maliyet taşır.

Buradan çıkan kural bir hız hedefi değil, bir sınır tanımıdır. Birinci derste "birim"
sınırının süreç dışı kaynakları dışarıda bıraktığı söylenmişti; bu ölçüm o kısıtın bedelini
gösteriyor. Diskle, ağla ya da veri tabanıyla çalışan testler yazılmayacak demek değildir —
o testlerin adı **tümleşim testidir** ve ayrı bir katmanda, daha seyrek koşturulur.

## Özet

- Paylaşılan yazılabilir durum, koşum sırasını testin girdilerinden birine dönüştürür.
- Sabit tohumdan türeyen bir permütasyon sıra bağımlılığını tekrarlanabilir biçimde ortaya
  çıkarır; aynı üç test iki tohumda iki farklı sonuç verdi.
- Kirlenme iki yönde işler: bir test kataloğun boş, başka bir test kataloğun dolu olmadığını
  varsaydı; ikisinin de varsayımını önceki testler belirledi.
- Her testin kendi düzeneğini kurması sırayı sonuçtan bağımsız kılar ve testleri paralel
  koşturulabilir yapar.
- Bağımsızlık ucuz düzenek gerektirir: iki yüz testlik bir takımda süreç dışı düzenek dört
  yüz dosya işlemi ekledi ve koşumu belirgin biçimde uzattı.

## Sonraki Adım

Bu derste düzenek testin elindeydi: katalog nesnesi bir fabrika çağrısıyla üretiliyordu.
Sınanan kod bağımlılığını kendi içinde yarattığında bu olanak kalmaz — testin
değiştiremeyeceği bir saat, bir rastgelelik kaynağı ya da bir depo, testin girdisi olmaktan
çıkar. Sonraki ders aynı iş kuralını iki biçimde yazar, bağımlılığın içeriden yaratıldığı
sürümle dışarıdan verildiği sürümü karşılaştırır ve testin kurduğu düzeneği satır sayısıyla
ölçer.
