Ders 04 / 12
Test Zihniyeti
Programın çalıştığını gösteren bakış ile kırıldığı yeri arayan bakışın aynı kod üzerinde farklı sonuç vermesi, doğrulama yanlılığı ve tekrarlanan soruların getirisinin azalması.
İçindekiler
Önceki iki derste kusur, birinin onu araması sayesinde ortaya çıktı: gişe kayıtları karşılaştırıldığı için, bütçe ölçüldüğü için. Kimse o karşılaştırmayı yapmasaydı iki kusur da görünmeyecekti. Testin teknikleri buradan sonra gelir; önce arayan kişinin ne aradığı sorusu gelir.
Bu ders iki bakışı aynı işlev üzerinde karşılaştırır. Birincisi programın çalıştığını göstermek ister, ikincisi kırıldığı yeri arar. İkisi de test yazar, ikisi de dürüsttür ve buldukları farklıdır.
İki Bakış Aynı Koda Bakar
Kitaplığa ödünç verme kararı eklenir. Yazılı kural üç maddedir: aktif ödünç sayısı üye türünün sınırına ulaşmışsa ödünç verilmez, borcu 20 birime ulaşmış üyeye ödünç verilmez, bilinmeyen üye türü reddedilir.
// odunc.mjs — odunc verme kararinin ilk uygulamasi const KURALLAR = { ogrenci: { enCokOdunc: 10 }, uye: { enCokOdunc: 5 } }; export const BORC_SINIRI = 20; export function oduncAlabilirMi(uyeTuru, aktifOdunc, borc) { const sinir = KURALLAR[uyeTuru]?.enCokOdunc ?? 5; if (aktifOdunc > sinir) return false; if (borc > BORC_SINIRI) return false; return true; }
Denetimleri koşturmak için önceki derslerde tekrarlanan döngü ayrı bir dosyaya alınır.
// kosucu.mjs — bir denetim listesini kosturan ortak surucu export function kostur(baslik, denetimler) { let gecen = 0; const dusen = []; for (const [ad, denetim] of Object.entries(denetimler)) { try { denetim(); gecen += 1; } catch { dusen.push(ad); } } console.log(`${baslik}: ${gecen}/${Object.keys(denetimler).length} gecti`); for (const ad of dusen) console.log(` dusen: ${ad}`); }
İki denetim kümesi yazılır. Birincisi kuralın anlatıldığı gibi çalıştığını gösterir: üç kitabı olan öğrenci alır, on kitabı olan üye alamaz. İkincisi aynı kuralın kenarlarını yoklar.
// iki-bakis.mjs — mutlu yol denetimleri ile sorgulayici denetimlerin karsilastirilmasi import assert from 'node:assert/strict'; import { oduncAlabilirMi } from './odunc.mjs'; import { kostur } from './kosucu.mjs'; const MUTLU_YOL = { 'ogrenci uc kitapla yeni kitap alir': () => assert.equal(oduncAlabilirMi('ogrenci', 3, 0), true), 'uye bir kitapla yeni kitap alir': () => assert.equal(oduncAlabilirMi('uye', 1, 0), true), 'ogrenci yirmi kitapla alamaz': () => assert.equal(oduncAlabilirMi('ogrenci', 20, 0), false), 'uye on kitapla alamaz': () => assert.equal(oduncAlabilirMi('uye', 10, 0), false), 'borcu elli olan alamaz': () => assert.equal(oduncAlabilirMi('uye', 0, 50), false), }; const SORGULAYICI = { 'uye tam sinirdayken alamaz': () => assert.equal(oduncAlabilirMi('uye', 5, 0), false), 'ogrenci tam sinirdayken alamaz': () => assert.equal(oduncAlabilirMi('ogrenci', 10, 0), false), 'borc tam sinirdayken alamaz': () => assert.equal(oduncAlabilirMi('uye', 0, 20), false), 'bilinmeyen uye turu reddedilir': () => assert.throws(() => oduncAlabilirMi('konuk', 0, 0)), 'negatif odunc sayisi reddedilir': () => assert.throws(() => oduncAlabilirMi('uye', -1, 0)), }; kostur('mutlu yol ', MUTLU_YOL); kostur('sorgulayici', SORGULAYICI);
mutlu yol : 5/5 gecti sorgulayici: 0/5 gecti dusen: uye tam sinirdayken alamaz dusen: ogrenci tam sinirdayken alamaz dusen: borc tam sinirdayken alamaz dusen: bilinmeyen uye turu reddedilir dusen: negatif odunc sayisi reddedilir
İlk küme kusursuz bir tablo çizer. İkinci küme aynı on satırlık işlevde beş ayrı kusur gösterir: iki karşılaştırma operatörü sınırı bir birim kaydırmış, borç sınırı da aynı kaymayı taşımış, bilinmeyen üye türü sessizce üye sayılmış, geçersiz sayı hiç denetlenmemiş.
Kusurların tamamı ilk kümenin kapsamı dışında kaldı. Sebep, ilk kümenin kötü yazılmış olması değildir; sorduğu soruların hepsi sınırdan uzak noktalardan seçilmiştir.
Doğrulama Yanlılığı
İlk kümeyi yazan kişi kuralı okudu ve kuralın çalıştığını gösteren örnekleri seçti. Bu, doğrulama yanlılığının (confirmation bias) test yazımındaki hâlidir: bir iddiayı sınamak yerine iddiayla uyumlu kanıt aramak.
Yanlılığın kaynağı, sorunun kurulma biçimidir. “Öğrenci kitap alabiliyor mu?” sorusu alabilen bir örnek arar. “Öğrenci hangi durumda alamaz ve o durumların sınırı nerededir?” sorusu ise sınırın iki yanını da yoklamayı gerektirir. İkinci soru daha çok iş çıkarır ve daha çok kusur bulur.
Zihniyetin tanımı buradadır: test yazan kişi, programın doğru olduğunu varsaymaz; doğru olduğu iddiasını sınayacak kanıtı arar. Bulamazsa iddia bir süreliğine ayakta kalır.
Sorgulayıcı Soruların Kaynağı
Sorgulayıcı denetimler rastgele üretilmedi. Her biri bir soru sınıfından geldi:
- Sınır: bir eşik geçiliyorsa eşiğin kendisi, bir altı ve bir üstü denenir. Beş kusurun üçü bu sınıftan çıktı.
- Boşluk: değer yoksa, bulunamadıysa, hesaplanamadıysa ne olur? Bilinmeyen üye türü bu sınıftandır.
- Tür ve alan: sayı beklenen yere negatif sayı, ondalıklı sayı ya da sayı olmayan bir değer gelirse ne olur?
- Sıra: işlemler beklenmedik sırada gelirse? İade edilmemiş kitabın yeniden ödünç verilmesi bu sınıftandır.
- Yineleme: aynı işlem iki kez gelirse toplam iki kat mı artar?
- Ölçek: girdi bin kat büyürse davranış aynı kalır mı?
- Yetki: işlemi yapan kişinin buna hakkı var mı?
Bu liste bir sağlama listesi (checklist) olarak kullanılır. Sağlama listesinin işlevi, kusuru bulmak değil, sormayı unutulan soruyu hatırlatmaktır.
Aynı Soruların Getirisi Azalır
Beş kusur giderilir. Karşılaştırmalar sınıra eşitliği de kapsayacak biçimde düzeltilir, bilinmeyen tür hata verir, geçersiz sayı reddedilir.
// odunc2.mjs — sorgulayici denetimlerin bulduklari giderildikten sonra const KURALLAR = { ogrenci: { enCokOdunc: 10 }, uye: { enCokOdunc: 5 } }; export const BORC_SINIRI = 20; export function oduncAlabilirMi(uyeTuru, aktifOdunc, borc) { const kural = KURALLAR[uyeTuru]; if (kural === undefined) throw new Error(`bilinmeyen uye turu: ${uyeTuru}`); if (!Number.isInteger(aktifOdunc) || aktifOdunc < 0) throw new Error('aktif odunc sayisi gecersiz'); if (aktifOdunc >= kural.enCokOdunc) return false; if (borc >= BORC_SINIRI) return false; return true; }
Aynı sorgulayıcı küme yeniden koşturulur ve yanına yeni bir soru sınıfı eklenir: girdi sayı olmadığında ne olur?
// yeni-sinif.mjs — ayni sorularin tekrari ile yeni bir soru sinifinin karsilastirilmasi import assert from 'node:assert/strict'; import { oduncAlabilirMi } from './odunc2.mjs'; import { kostur } from './kosucu.mjs'; const SORGULAYICI = { 'uye tam sinirdayken alamaz': () => assert.equal(oduncAlabilirMi('uye', 5, 0), false), 'ogrenci tam sinirdayken alamaz': () => assert.equal(oduncAlabilirMi('ogrenci', 10, 0), false), 'borc tam sinirdayken alamaz': () => assert.equal(oduncAlabilirMi('uye', 0, 20), false), 'bilinmeyen uye turu reddedilir': () => assert.throws(() => oduncAlabilirMi('konuk', 0, 0)), 'negatif odunc sayisi reddedilir': () => assert.throws(() => oduncAlabilirMi('uye', -1, 0)), }; const YENI_SORU_SINIFI = { 'ondalikli odunc sayisi reddedilir': () => assert.throws(() => oduncAlabilirMi('uye', 2.5, 0)), 'borc hesaplanamadiysa odunc verilmez': () => assert.throws(() => oduncAlabilirMi('uye', 0, Number.NaN)), }; kostur('eski sorular', SORGULAYICI); kostur('yeni sorular', YENI_SORU_SINIFI);
eski sorular: 5/5 gecti yeni sorular: 1/2 gecti dusen: borc hesaplanamadiysa odunc verilmez
Eski sorular artık hiçbir şey bulmuyor. Bu beklenen bir sonuçtur: bir kusur giderildikten sonra onu bulan test, o kusurun geri gelmesini bekleyen bir nöbetçiye dönüşür — değerlidir ama yeni bilgi üretmez. Bir test kümesinin kusur bulma getirisi tekrarlandıkça azalır; buna böcek ilacı yanılgısı (pesticide paradox) denir. Yeni kusurlar ancak yeni soru sınıflarıyla bulunur.
Bulunan altıncı kusur da öğreticidir: borç hesaplanamadığında değer sayı olmadığı için her karşılaştırma yanlış çıkar ve üye ödünç alır. Kusurların aynı bölgede kümelenmesi de sıradışı değildir; kusur bir yerde yoğunlaşıyorsa aramanın orada derinleştirilmesi verimlidir. Buna kusur kümelenmesi (defect clustering) denir.
Zihniyetin Sınırları
Sorgulayıcı tutum, her düşen testin bir kusur olduğu anlamına gelmez. Testin kendisi yanlış yazıldığında ya da beklenen değer eskidiğinde çıkan uyarı yanlış alarmdır (false positive) ve güveni aşındırır. Ters yönde, geçen bir test kusuru kaçırmış olabilir. İki hata türü de test kümesinin bilgi değerini düşürür.
Bütün testlerin geçmesi de tek başına bir teslim gerekçesi değildir. Ürün kusursuz çalışıp yanlış işi yapıyorsa test sonucu bir şey söylemez; ikinci derste ayrılan geçerleme sorusu buradadır. Bu duruma hatasızlık yanılgısı (absence-of-errors fallacy) denir.
Son sınır kişiseldir. Kendi yazdığı kodu sınayan kişi, kodu yazarken kurduğu varsayımların içinden bakar; aklına gelmeyen girdi, testte de aklına gelmez. Bunun çaresi tutumu terk etmek değil, bakışı çoğaltmaktır: gözden geçirme, ikinci bir kişinin senaryo yazması, tekniğin sistemli uygulanması. Sonraki konu bu tekniklere ayrılmıştır.
Özet
- Aynı işlev üzerinde mutlu yol denetimleri beşte beş geçerken sınır ve boşluk sorularını soran denetimler beş ayrı kusur gösterdi.
- Doğrulama yanlılığı, iddiayı sınamak yerine iddiayla uyumlu kanıt aramaktır; sorunun kurulma biçimi hangi kanıtın aranacağını belirler.
- Sorgulayıcı sorular sınır, boşluk, tür, sıra, yineleme, ölçek ve yetki sınıflarından sistemli olarak üretilir.
- Bir kusur giderildikten sonra onu bulan test nöbetçiye dönüşür; tekrarlanan kümenin kusur bulma getirisi azalır, yeni kusur yeni soru sınıfı ister.
- Yanlış alarm ve kaçırılan kusur test kümesinin bilgi değerini iki yönde düşürür; bütün testlerin geçmesi yanlış işin yapılmadığını göstermez.
Sonraki Adım
Buraya kadar test, tek bir kişinin bir işlevle baş başa kaldığı bir etkinlik gibi ele alındı. Gerçekte testin ne zaman yazıldığı, kimin yazdığı ve sonucunun kime ne kadar sürede ulaştığı çalışma biçimine bağlıdır. Aynı denetim kümesi, sürümün sonunda tek seferde koşturulduğunda ile her değişiklikte koşturulduğunda farklı değer taşır. Sonraki ders teslimat modelinin test pratiğini nasıl belirlediğini ve geri bildirim noktasının neyi değiştirdiğini ele alır.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.