İçeriğe geç
academia.sh

Ders 11 / 12

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.

İçindekiler

Ö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.

// 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.

// 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.

// 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.

// 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.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat