İçeriğe geç
academia.sh

Ders 25 / 27

Test Kararlılığı

Kırılgan testin tanımı ve maliyeti, zamanlama, sıraya bağımlılık ve paylaşılan durumun belirlenimci bir benzetimle sayılması, her nedenin çözümü ve yeniden deneme ile karantinanın yeri.

İçindekiler

Önceki dört dersin testleri bir varsayıma dayanıyordu: aynı kod aynı sonucu verir. Bu varsayım sık sık bozulur. Bir test yirmi koşumun on dokuzunda geçer, birinde düşer; kimse bir şey değiştirmemiştir. İkinci çalıştırmada yeşile döner ve iş devam eder.

Bu davranış zararsız görünür ama paketin verdiği işareti aşındırır. Kırmızı bir sonucun anlamı belirsizleştiğinde ekip önce yeniden çalıştırmayı dener, sonra bakmadan yeniden çalıştırır, en sonunda gerçek bir kusuru da kırılganlık sanıp geçer. Test paketinin bütün değeri, kırmızının bir şey ifade etmesindedir.

Kırılgan Test ve Ölçüsü

Kırılgan test (flaky test), sınadığı kod değişmediği hâlde koşumdan koşuma farklı sonuç veren testtir. Kusur testin kendisindedir: testin sonucu, sınadığı davranışın dışındaki bir şeye bağlıdır.

Bu bir izlenim olarak değil, sayı olarak ele alınmalıdır. Düşme oranı, bir testin belirli sayıda koşumda kaç kez düştüğüdür. Bir test bilinmeyen aralıklarla düşüyorsa, onu aynı koşullarda çok kez çalıştırıp oranı ölçmek hem sorunun varlığını hem de düzeltmenin işe yaradığını kanıtlar.

Üç Nedeni Saymak

Aşağıdaki benzetim üç kırılganlık nedenini ayrı ayrı modelliyor ve sözde rastgele ama belirlenimci bir üreteçle iki yüz koşum boyunca kaç kez düştüklerini sayıyor. Üreteç doğrusal eşlenik yöntemidir: aynı tohum her zaman aynı diziyi verir, dolayısıyla sonuçlar yeniden üretilebilir.

// kirilganlik.mjs — uc kirilganlik nedeninin belirlenimci benzetimi
export function uretec(tohum) {
  let s = tohum >>> 0;
  return () => {
    s = (Math.imul(s, 1664525) + 1013904223) >>> 0;
    return s / 4294967296;
  };
}

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

export function paketiKosur({ tohum = 7, kosum = 200, duzeltilmis = false, karistirma = true }) {
  const rastgele = uretec(tohum);
  const adlar = ['tabloYuklenir', 'suzgecDaraltir', 'formGonderilir'];
  const dusen = { tabloYuklenir: 0, suzgecDaraltir: 0, formGonderilir: 0 };
  let taslak = null; // modul duzeyinde yasayan paylasilan durum

  for (let n = 0; n < kosum; n += 1) {
    const sira = karistirma ? karistir(adlar, rastgele) : adlar;
    const kurulan = new Set();
    if (duzeltilmis) taslak = null; // her kosum kendi durumuyla baslar

    for (const ad of sira) {
      let gecti;
      if (ad === 'tabloYuklenir') {
        const yanitSuresi = 40 + 80 * rastgele();
        gecti = duzeltilmis ? yanitSuresi <= 500 : yanitSuresi <= 100;
        kurulan.add('tablo');
      } else if (ad === 'suzgecDaraltir') {
        if (duzeltilmis) kurulan.add('tablo'); // test kendi verisini kurar
        gecti = kurulan.has('tablo');
      } else {
        gecti = taslak === null; // form bos taslakla acilmali
        const gonderimBasarili = rastgele() < 0.5;
        taslak = gonderimBasarili ? null : { istasyon: 'kuzey-yamac-01' };
      }
      if (gecti === false) dusen[ad] += 1;
    }
  }
  return dusen;
}
// rapor.mjs — 200 kosumda hangi test kac kez dustu
import { paketiKosur } from './kirilganlik.mjs';

const senaryolar = [
  ['sabit sira, duzeltilmemis', { karistirma: false }],
  ['karisik sira, duzeltilmemis', { karistirma: true }],
  ['karisik sira, duzeltilmis', { karistirma: true, duzeltilmis: true }],
];

console.log('senaryo                       tabloYuklenir  suzgecDaraltir  formGonderilir');
for (const [ad, secenek] of senaryolar) {
  const d = paketiKosur({ tohum: 7, kosum: 200, ...secenek });
  console.log(
    ad.padEnd(30) +
      String(d.tabloYuklenir).padStart(9) +
      String(d.suzgecDaraltir).padStart(16) +
      String(d.formGonderilir).padStart(16),
  );
}
node rapor.mjs
senaryo                       tabloYuklenir  suzgecDaraltir  formGonderilir
sabit sira, duzeltilmemis            50               0              95
karisik sira, duzeltilmemis          48             102             104
karisik sira, duzeltilmis             0               0               0

Üç satır, üç ayrı ders veriyor.

Zamanlama

tabloYuklenir testi listenin gelmesi için sabit yüz milisaniye bekliyor. Benzetimde yanıt süresi kırk ile yüz yirmi milisaniye arasında değişiyor; süre beklemeyi aştığında test düşüyor. İki yüz koşumun ellisinde ve kırk sekizinde düştü — dörtte bire yakın bir oran, koşum sırasından bağımsız.

Sabit bekleme iki yönden yanlıştır. Yavaş bir koşumda yetmez; hızlı bir koşumda boşa harcanır. Yüz test için eklenen yüzer milisaniye, paketin süresine on saniye ekler ve hiçbir şeyi güvence altına almaz.

Doğru biçim koşula göre beklemedir: bir gözlemlenebilir sonuç oluşana kadar kısa aralıklarla denetleyip beklemek, cömert bir üst sınırla. Düzeltilmiş sürümde üst sınır beş yüz milisaniyedir ve iki yüz koşumun hiçbirinde aşılmadı. Üst sınırın cömert olması testi yavaşlatmaz — koşul erken sağlandığında bekleme erken biter. Üst sınırın büsbütün kaldırılması ise kabul edilmez: gerçekten donmuş bir uygulamada test sonsuza kadar bekler.

Eşzamansız JavaScript kursundaki mikro görev sırası burada da geçerlidir. Bir sözün sonuçlanması ile ekranın güncellenmesi aynı tur içinde olmayabilir; beklenecek şey sözün kendisi değil, kullanıcının göreceği sonuçtur.

Sıraya Bağımlılık

suzgecDaraltir testi, tablonun daha önce yüklenmiş olmasını bekliyor — veriyi kuran başka bir testtir. Birinci satırda bu test hiç düşmedi: sabit sırada tabloYuklenir her zaman önce çalışıyor. İkinci satırda sıra karıştırıldı ve iki yüz koşumun yüz ikisinde düştü.

Buradaki asıl bulgu şudur: sabit sıra kusuru gizler. Test paketi aylarca yeşil kalabilir, sonra bir dosya adı değiştiğinde ya da koşucu paralelleştiğinde birden kırmızıya döner. Sırayı bilerek karıştırmak bir tanı aracıdır; karıştırmada kullanılan tohum çıktıya yazıldığında, düşen sıralama birebir yeniden üretilebilir.

Çözüm sırayı düzeltmek değil, bağımlılığı kaldırmaktır. Her test ihtiyaç duyduğu veriyi kendisi kurar. Düzeltilmiş sürümde suzgecDaraltir kendi ölçümünü ekliyor ve sıradan bağımsız hâle geliyor.

Paylaşılan Durum

formGonderilir testi formun boş bir taslakla açılmasını bekliyor. Taslak modül düzeyinde yaşıyor ve yalnız gönderim başarılı olduğunda temizleniyor; başarısız gönderimde bir sonraki koşuma kalıyor. İki yüz koşumun doksan beşinde ve yüz dördünde düştü.

Bu neden diğer ikisinden bir yönüyle ayrılır: test kendi başına, temiz bir ortamda her zaman geçer. Düşmesi için önce bir koşumun ortamı kirletmiş olması gerekir. Geliştirici makinesinde tek başına çalıştırıldığında geçen, paket içinde düşen test tam olarak budur — ve tanıyı en çok zorlaştıran davranış budur, çünkü düşen testin kendisinde bir kusur yoktur.

Paylaşılan durumun kaynakları sayılabilir: modül düzeyinde tutulan değerler, yerel depolama ve oturum depolaması, çerezler, program denetimli önbellek, arka uçtaki veri ve dosya sistemi. Çözüm her testten önce kurulan ve sonra yıkılan bir düzenektir. Düzeltilmiş sürümde taslak her koşumun başında sıfırlanıyor ve düşme sıfıra iniyor.

Paralel koşum bu sorunu büyütür. Aynı arka uç kaydını ya da aynı bağlantı noktasını paylaşan iki koşucu birbirinin ortamını bozar; yalıtım, koşum başına ayrı veri kümesiyle sağlanır.

Kırılganlıkla Ne Yapılır

Yeniden deneme akla ilk gelen çözümdür ve tek başına yanlıştır. Düşen testi sessizce yeniden çalıştırmak kırılganlığı gizler; gizlenen kırılganlık zamanla artar ve arasına karışan gerçek kusurlar da gizlenir. Kabul edilebilir tek biçim, yeniden denemenin kaydedilmesi ve düşme oranının bir ölçü olarak izlenmesidir.

Bir test düzeltilemeyecek kadar karmaşıksa karantinaya alınır: paketten ayrılır, sonucu yayımı engellemez, ama listede bir sahibi ve bir son tarihi vardır. Sahipsiz karantina, testin sessizce silinmesinin uzun yoludur.

Belirlenimciliği artıran genel önlemler de vardır. Saat sabitlenir, yerel ayar ve saat dilimi açıkça verilir, rastgelelik bu dersteki gibi tohumlanır ve tohum çıktıya yazılır. Amaç aynıdır: testin sonucunu, sınadığı davranışın dışındaki hiçbir şeye bağlı bırakmamak.

Özet

  • Kırılgan test, sınadığı kod değişmediği hâlde koşumdan koşuma farklı sonuç veren testtir; maliyeti kırmızı sonucun anlamını yitirmesidir.
  • Sabit bekleme yanıt süresi dağılımının kuyruğunda düşer; koşula göre bekleme cömert bir üst sınırla bu nedeni sıfırladı.
  • Sabit koşum sırası sıraya bağımlılığı gizler: aynı test sabit sırada hiç düşmezken karışık sırada iki yüz koşumun yüz ikisinde düştü.
  • Paylaşılan durumun imzası, testin yalnız başına geçip paket içinde düşmesidir; her testin kendi düzeneğini kurup yıkması bu nedeni sıfırladı.
  • Yeniden deneme kırılganlığı gizler; kabul edilebilir biçimi kaydedilen ve düşme oranı olarak izlenen yeniden denemedir, karantina ise sahipli ve süreli olmalıdır.

Sonraki Adım

Kararlı bir test paketi, denetim altındaki bir ortamda beklenen davranışın korunduğunu söyler. Söylemediği şey, uygulamanın gerçek kullanıcıların elinde nasıl davrandığıdır. Test makinesi hızlıdır, ağı düzgündür, önbelleği bilinen bir durumdadır; alandaki bir gözlemci zayıf bir bağlantıda, yaşlı bir aygıtla ve dolu bir ekranla çalışır. Sonraki ders bu iki veri kaynağını — denetimli koşumdan gelen laboratuvar verisiyle gerçek oturumlardan gelen alan verisini — yan yana koyar ve aradaki farkı yüzdelik dilim hesabıyla ölçer.

İ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