---
title: 'Test Kâhini'
source: 'https://academia.sh/tr/kurslar/test-temelleri/test-kahini'
course: 'Kalite ve Test Temelleri'
language: tr
updated: '2026-08-17T18:10:53+00:00'
license: 'CC BY-SA 4.0'
---

# Test Kâhini

Beklenen sonucun kaynağı sorunu ve üç yanıtın karşılaştırılması — bağımsız ikinci hesap, metamorfik ilişki ve altın dosya.

Önceki dersin senaryolarında beklenen sonuç belirtimden okundu: kural yazılıydı, senaryo
onu tekrarladı. Bu, en kolay durumdur. Kural yazılı değilse, hesap elle doğrulanamayacak
kadar karmaşıksa ya da doğru sonucu kimse tam olarak bilmiyorsa beklenen sonuç nereden
gelecek?

Bu soruya **kâhin sorunu** (oracle problem) denir. Bir **test kâhini** (test oracle),
gözlenen sonucun doğru olup olmadığına karar veren kaynaktır. Test kâhini olmadan test
yoktur; yalnız program çalıştırılmış olur.

## Kâhin Sorunu

Kâhinin bir maliyeti vardır ve maliyeti çoğu zaman testin kendisinden yüksektir. Bir işlevi
çağırmak ucuzdur; dönen değerin doğru olduğunu **bağımsız olarak** bilmek pahalıdır.

En yaygın kâhin **belirtilmiş kâhindir** (specified oracle): beklenen değer belirtimden
okunur ve senaryoya yazılır. Sınırı ikinci derste görüldü — belirtim yanlışsa kâhin de
yanlıştır ve bütün testler yanlış bir doğruyu onaylar.

İkinci sınırı ölçektir. Yirmi girdi için beklenen değer elle yazılabilir; sekiz yüz girdi
için yazılamaz. Girdi uzayı büyüdüğünde beklenen değerin **üretilmesi** gerekir.

Karşılaştırma için ücret hesabına iki kural daha eklenir: şube dışı iade edilen kitap için
5 birim eklenir ve öğrenci üyelerden toplamın yarısı alınır, kuruş bulunmadığı için aşağı
yuvarlanarak.

```js
// ucret.mjs — uc kuralli toplam ucret hesabi
export function toplamUcret({ gecikmeGun, uyeTuru, subeDisi = false }) {
  const temel = Math.min(Math.max(gecikmeGun - 3, 0) * 2, 20);
  const toplam = temel + (subeDisi ? 5 : 0);
  return uyeTuru === 'ogrenci' ? Math.round(toplam / 2) : toplam;
}

export function girdiler() {
  const liste = [];
  for (const uyeTuru of ['uye', 'ogrenci']) {
    for (const subeDisi of [false, true]) {
      for (let gecikmeGun = 0; gecikmeGun <= 20; gecikmeGun += 1) {
        liste.push({ gecikmeGun, uyeTuru, subeDisi });
      }
    }
  }
  return liste;
}
```

Seksen dört girdi için beklenen değeri elle yazmak makul değildir. Üç ayrı kâhin kurulur.

## Bağımsız İkinci Hesap

Birinci yaklaşım aynı kuralı **başka bir yoldan** hesaplar. Asıl uygulama aritmetik bir
kapalı biçim kullanıyorsa, ikinci hesap günleri tek tek toplar. İki yol farklı olduğu için
aynı kusuru paylaşma olasılıkları düşüktür.

```js
// kahin-ikinci-hesap.mjs — kuraldan bagimsiz bicimde turetilen ikinci hesap
import { toplamUcret, girdiler } from './ucret.mjs';

function referans({ gecikmeGun, uyeTuru, subeDisi }) {
  let temel = 0;
  for (let gun = 1; gun <= gecikmeGun; gun += 1) {
    if (gun <= 3) continue;
    if (temel >= 20) break;
    temel += 2;
  }
  const toplam = temel + (subeDisi ? 5 : 0);
  return uyeTuru === 'ogrenci' ? Math.floor(toplam / 2) : toplam;
}

const sapmalar = girdiler().filter((g) => toplamUcret(g) !== referans(g));
console.log(`ikinci hesap: ${girdiler().length} girdi, ${sapmalar.length} sapma`);
for (const g of sapmalar.slice(0, 3)) {
  console.log(`  gun=${g.gecikmeGun} ${g.uyeTuru} subeDisi=${g.subeDisi}: hesap ${toplamUcret(g)}, referans ${referans(g)}`);
}
```

```
ikinci hesap: 84 girdi, 21 sapma
  gun=0 ogrenci subeDisi=true: hesap 3, referans 2
  gun=1 ogrenci subeDisi=true: hesap 3, referans 2
  gun=2 ogrenci subeDisi=true: hesap 3, referans 2
```

Yirmi bir sapma bulundu. Kusur yuvarlamadadır: kural aşağı yuvarlamayı söylüyor, uygulama
en yakına yuvarlıyor. Öğrenci ve şube dışı iade birleştiğinde toplam tek sayı oluyor ve iki
yuvarlama ayrışıyor.

Bu yaklaşıma **sözde kâhin** (pseudo-oracle) da denir. Bedeli açıktır: aynı işi iki kez
yapmak. Bu yüzden yalnız hesap ağırlıklı ve yüksek riskli bölgelerde uygulanır. Bir de
tuzağı vardır — ikinci hesap birinciye bakarak yazılırsa aynı yanlış anlamayı devralır ve
iki taraf birlikte susar.

## Metamorfik İlişki

İkinci yaklaşım beklenen değeri hiç bilmez. Bunun yerine, girdi belirli bir biçimde
değiştiğinde çıktının nasıl değişmesi gerektiğini söyleyen bir **metamorfik ilişki**
(metamorphic relation) kurar. İlişki sağlanmıyorsa bir kusur vardır; sağlanıyorsa yalnız o
ilişki hakkında bilgi edinilmiştir.

```js
// kahin-metamorfik.mjs — beklenen degeri bilmeden kurulan iliskiler
import { toplamUcret, girdiler } from './ucret.mjs';

const ILISKILER = {
  'MI1 gecikme arttikca ucret azalmaz': () => {
    for (const uyeTuru of ['uye', 'ogrenci']) {
      for (let gun = 1; gun <= 20; gun += 1) {
        const once = toplamUcret({ gecikmeGun: gun - 1, uyeTuru });
        const sonra = toplamUcret({ gecikmeGun: gun, uyeTuru });
        if (sonra < once) return `gun=${gun} ${uyeTuru}: ${once} -> ${sonra}`;
      }
    }
    return null;
  },
  'MI2 ucret 25 birimi asmaz': () => {
    for (const g of girdiler()) {
      if (toplamUcret(g) > 25) return `gun=${g.gecikmeGun} ${g.uyeTuru}: ${toplamUcret(g)}`;
    }
    return null;
  },
  'MI3 ogrenci ucretinin iki kati uye ucretini asmaz': () => {
    for (const g of girdiler().filter((x) => x.uyeTuru === 'uye')) {
      const uye = toplamUcret(g);
      const ogrenci = toplamUcret({ ...g, uyeTuru: 'ogrenci' });
      if (ogrenci * 2 > uye) return `gun=${g.gecikmeGun} subeDisi=${g.subeDisi}: ogrenci ${ogrenci}, uye ${uye}`;
    }
    return null;
  },
};

for (const [ad, iliski] of Object.entries(ILISKILER)) {
  const ihlal = iliski();
  console.log(`${ad}: ${ihlal === null ? 'saglandi' : `ihlal — ${ihlal}`}`);
}
```

```
MI1 gecikme arttikca ucret azalmaz: saglandi
MI2 ucret 25 birimi asmaz: saglandi
MI3 ogrenci ucretinin iki kati uye ucretini asmaz: ihlal — gun=0 subeDisi=true: ogrenci 3, uye 5
```

Üç ilişkiden ikisi sağlandı, biri ihlal edildi ve aynı kusuru gösterdi — hiçbir yerde
beklenen değer yazılmadığı hâlde. Metamorfik ilişkiler bu yüzden **kısmi kâhin** (partial
oracle) sayılır: kusurun varlığını gösterebilir, doğruluğu onaylayamaz.

İki ilişkinin sağlanması da bilgidir. Bir ilişkinin sağlanması, o ilişkinin tanımladığı
kusur sınıfının bulunmadığını söyler; hepsinin sağlanması programın doğru olduğunu söylemez.
İlişkiler girdi uzayının tamamında geçerli olduğu için üretilmiş girdilerle birlikte
kullanılmaya elverişlidir.

## Altın Dosya

Üçüncü yaklaşım beklenen değeri **kaydeder**. Programın bugünkü çıktısı bir dosyaya yazılır
ve sonraki koşularda o dosyayla karşılaştırılır. Bu dosyaya **altın dosya** (golden file)
denir.

```js
// kahin-altin.mjs — kaydedilmis ciktinin kahin olarak kullanilmasi
import { writeFileSync, readFileSync } from 'node:fs';
import { toplamUcret, girdiler } from './ucret.mjs';

const altin = girdiler().map((g) => ({ ...g, ucret: toplamUcret(g) }));
writeFileSync('altin.json', JSON.stringify(altin));
console.log(`altin dosya: ${altin.length} kayit yazildi`);

const kayitli = JSON.parse(readFileSync('altin.json', 'utf8'));
const aynisi = kayitli.filter((k) => toplamUcret(k) !== k.ucret);
console.log(`degismemis surum: ${aynisi.length} fark`);

function ustSiniri25Yapan({ gecikmeGun, uyeTuru, subeDisi }) {
  const temel = Math.min(Math.max(gecikmeGun - 3, 0) * 2, 25);
  const toplam = temel + (subeDisi ? 5 : 0);
  return uyeTuru === 'ogrenci' ? Math.round(toplam / 2) : toplam;
}
const degisen = kayitli.filter((k) => ustSiniri25Yapan(k) !== k.ucret);
console.log(`ust siniri degistiren surum: ${degisen.length} fark`);

const kusurluKayit = kayitli.find((k) => k.uyeTuru === 'ogrenci' && k.subeDisi && k.gecikmeGun === 0);
console.log(`altin dosyada gun=0 ogrenci subeDisi kaydi: ${kusurluKayit.ucret} (dogrusu 2)`);
```

```
altin dosya: 84 kayit yazildi
degismemis surum: 0 fark
ust siniri degistiren surum: 28 fark
altin dosyada gun=0 ogrenci subeDisi kaydi: 3 (dogrusu 2)
```

Altın dosya, üst sınırı sessizce değiştiren sürümü yirmi sekiz kayıtta yakaladı. Bu, güçlü
olduğu yerdir: **istenmeyen değişikliği** ucuza tespit eder ve beklenen değer yazma işini
tamamen ortadan kaldırır.

Son satır zayıflığını gösterir. Dosya, yuvarlama kusurunu taşıyan çıktıdan üretildiği için
kusuru da kaydetmiştir. Artık o kusur "beklenen davranış" sayılır ve düzeltilmesi altın
dosyayı düşürür — düzeltmeyi yapan kişi dosyayı yenileyerek testi susturur. Altın dosya
kusuru bulmaz; var olan davranışı dondurur.

Bu yüzden altın dosya kabul edilirken içeriği okunmalıdır. Okunamayacak kadar büyük bir
altın dosya, denetlenmemiş bir kâhindir.

## Kâhin Seçimi

Üç yaklaşım aynı işi yapmaz ve birlikte kullanılırlar.

Belirtilmiş kâhin az sayıda ve yüksek değerli senaryoda kullanılır: kuralın kendisini
tanımlayan örnekler. Bağımsız ikinci hesap, hesap ağırlıklı bölgelerde ve girdi uzayı
büyükken devreye girer. Metamorfik ilişkiler beklenen değerin bilinemediği yerde çalışır ve
üretilmiş girdilerle birlikte en verimli hâlini alır. Altın dosya, çıktısı geniş ve yapısal
olan yerlerde istenmeyen değişikliği yakalar.

Bir de **insan kâhini** vardır: doğru sonucu bilen kişi. En pahalı ve en yavaş kâhindir,
ama bazı sorularda tek kâhin odur — bir uyarı metninin anlaşılır olup olmadığına program
karar veremez.

Seçimde tek ölçüt vardır: kâhin, sınadığı programdan **bağımsız** olmalıdır. Programın
kendi çıktısından türetilen bir kâhin, programın kendi kendini onaylamasıdır.

## Özet

- Test kâhini, gözlenen sonucun doğru olup olmadığına karar veren kaynaktır; kâhin olmadan
  test değil yalnız çalıştırma vardır.
- Belirtilmiş kâhin belirtim yanlışsa yanlış doğruyu onaylar ve girdi uzayı büyüdüğünde elle
  yazılamaz.
- Bağımsız ikinci hesap aynı kuralı başka yoldan hesaplar; örnekte seksen dört girdide yirmi
  bir sapma buldu, ama birinciye bakılarak yazılırsa aynı yanlış anlamayı devralır.
- Metamorfik ilişki beklenen değeri hiç bilmeden kusur gösterir; kısmi kâhindir, sağlanması
  doğruluğu onaylamaz.
- Altın dosya istenmeyen değişikliği ucuza yakalar, ama üretildiği andaki kusuru beklenen
  davranış olarak dondurur.
- Kâhinin tek ölçütü sınadığı programdan bağımsız olmasıdır.

## Sonraki Adım

Bir kâhin sapma gösterdiğinde iş bitmez, başlar. Sapmanın kaydedilmesi, tekrarlanabilir
biçimde anlatılması, önceliklendirilmesi, giderilmesi ve giderildiğinin doğrulanması gerekir.
Bu adımların her biri bir durumdur ve aralarında yalnız belirli geçişler geçerlidir. Kursun
son dersi bu döngüyü kurar.
