Ders 02 / 13
İddialar
Eşitlik ile derin eşitliğin ayrımı, bir iddianın yakaladığı bozulma sayısıyla ölçülen sinyal gücü, kısmi eşleştirmenin gereksiz bağı kesmesi ve hata beklentisinin eşleştirmeyle daraltılması.
İçindekiler
Önceki ders tek bir iddia kullandı: iki sayının eşitliği. Sınanan işlev bir sayı döndürdüğü için bu yeterliydi. Ödünç verme işlemi ise bir sayı değil, bir kayıt döndürür: üye numarası, kitap numarası, alış günü, son gün ve durum. Aynı iddia bu kayıtta bambaşka davranır ve testin ne kadarını sınadığı, seçilen iddianın biçimine göre değişir.
Bu dersin sorusu iki katmanlıdır. Birincisi biçimseldir: hangi iddia hangi değer türü için doğru araçtır? İkincisi ölçülebilirdir: bir iddia, sınadığı kodun kaç farklı bozulmasını yakalar? İkinci soru birincisini kesin hâle getirir, çünkü “iyi iddia” sezgisel bir yargı olmaktan çıkıp sayılabilir bir niceliğe dönüşür.
Eşitlik ve Derin Eşitlik
Sınanacak işlev, ödünç kaydını üreten oduncVer işlevidir. Üye türü ödünç süresini ve
açıktaki en çok ödünç sayısını belirler; sınır aşıldığında işlem reddedilir.
// odunc.mjs — odunc kaydi ureten islev export const KURALLAR = { ogrenci: { oduncGun: 28, enCokOdunc: 10 }, uye: { oduncGun: 14, enCokOdunc: 5 }, }; export function oduncVer(uye, kitapNo, gun) { const kural = KURALLAR[uye.tur]; if (kural === undefined) throw new Error(`bilinmeyen uye turu: ${uye.tur}`); if (uye.aciktakiOdunc >= kural.enCokOdunc) { throw new Error(`odunc siniri asildi: ${kural.enCokOdunc}`); } return { uyeNo: uye.no, kitapNo, alisGunu: gun, sonGun: gun + kural.oduncGun, durum: 'odunc', }; }
Aynı beklentiyi iki iddia biçimiyle yazmak farkı görünür kılar.
// esitlik.test.mjs — nesne donduren islevde esitlik ve derin esitlik import { test } from 'node:test'; import assert from 'node:assert/strict'; import { oduncVer } from './odunc.mjs'; const UYE = { no: 'U-17', tur: 'ogrenci', aciktakiOdunc: 2 }; test('esitlik nesneyi kimlikle karsilastirir', () => { const kayit = oduncVer(UYE, 'K-903', 1000); assert.equal(kayit, { uyeNo: 'U-17', kitapNo: 'K-903', alisGunu: 1000, sonGun: 1028, durum: 'odunc', }); }); test('derin esitlik yapiyi karsilastirir', () => { const kayit = oduncVer(UYE, 'K-903', 1000); assert.deepEqual(kayit, { uyeNo: 'U-17', kitapNo: 'K-903', alisGunu: 1000, sonGun: 1028, durum: 'odunc', }); });
node --test --test-reporter=tap esitlik.test.mjs | grep -E '^(ok|not ok|# (tests|pass|fail))'
not ok 1 - esitlik nesneyi kimlikle karsilastirir ok 2 - derin esitlik yapiyi karsilastirir # tests 2 # pass 1 # fail 1
Katı eşitlik iddiası nesneleri kimlik üzerinden karşılaştırır: iki ayrı nesne, alanları birebir aynı olsa da eşit sayılmaz. Derin eşitlik ise yapıyı dolaşır ve karşılık gelen alanları karşılaştırır. Kural yalındır: sayı, dizgi ve mantıksal değer için eşitlik; nesne, dizi ve iç içe yapı için derin eşitlik.
İddianın Sinyal Gücü
Bir iddianın değeri, geçtiğinde değil düştüğünde ortaya çıkar. Ölçüsü şudur: sınanan kodun kaç farklı bozulmasını yakalar? Bu soru sayısal olarak yanıtlanabilir. Aşağıdaki betik doğru kaydın beş ayrı bozulmuş sürümünü üretir ve üç iddia biçimini her birine uygular.
// iddia-gucu.mjs — uc iddia bicimi bes bozulmus gerceklestirimde denenir import assert from 'node:assert/strict'; import { oduncVer } from './odunc.mjs'; const UYE = { no: 'U-17', tur: 'ogrenci', aciktakiOdunc: 2 }; const dogru = () => oduncVer(UYE, 'K-903', 1000); const BOZUKLAR = { 'sure-sabitlendi': () => ({ ...dogru(), sonGun: 1014 }), 'durum-yanlis': () => ({ ...dogru(), durum: 'rezerve' }), 'gun-kaydi': () => ({ ...dogru(), alisGunu: 1001 }), 'kitap-dustu': () => { const k = dogru(); delete k.kitapNo; return k; }, 'uye-dustu': () => ({ ...dogru(), uyeNo: null }), }; const BEKLENEN = { uyeNo: 'U-17', kitapNo: 'K-903', alisGunu: 1000, sonGun: 1028, durum: 'odunc', }; const IDDIALAR = { 'ok(kayit)': (k) => assert.ok(k), 'equal(kayit.sonGun)': (k) => assert.equal(k.sonGun, 1028), 'deepEqual(kayit)': (k) => assert.deepEqual(k, BEKLENEN), }; for (const [iddiaAdi, iddia] of Object.entries(IDDIALAR)) { const yakalanan = []; for (const [bozukAd, bozuk] of Object.entries(BOZUKLAR)) { try { iddia(bozuk()); } catch { yakalanan.push(bozukAd); } } const sayi = `${yakalanan.length}/${Object.keys(BOZUKLAR).length}`; console.log(`${iddiaAdi.padEnd(21)} ${sayi} ${yakalanan.join(', ') || 'yok'}`); }
ok(kayit) 0/5 yok equal(kayit.sonGun) 1/5 sure-sabitlendi deepEqual(kayit) 5/5 sure-sabitlendi, durum-yanlis, gun-kaydi, kitap-dustu, uye-dustu
Birinci satır aşırı geniş iddianın maliyetini gösterir. assert.ok(kayit) yalnız “bir
nesne döndü” der; kaydın içeriği ne olursa olsun yeşil kalır. Böyle bir test koşum
listesinde yer kaplar, süre harcar ve hiçbir bozulmayı bildirmez. Kalite ve Test Temelleri
kursunda tanıtılan yanlış alarm kavramının aynası budur: burada sinyal yanlış değil,
yok.
İkinci satır dar bir iddianın dürüst sınırını gösterir: tek alan sınandığında yalnız o alanın bozulması yakalanır. Bu bir kusur değildir; testin adı da tek alanı söylüyorsa beklenen davranış budur.
Üçüncü satır tam kapsamayı verir. Ama bu ölçüm tek yönlüdür: yakalanan bozulma sayısı arttıkça testin kırılganlık riski de artar. Sonraki bölüm bu bedeli gösterir.
Kısmi Eşleştirme
Kayda, işlemi izlemeye yarayan bir işlem numarası eklendiği varsayılsın. Davranış kurallarının hiçbiri değişmez; yalnız kayıtta yeni bir alan vardır.
// odunc.mjs — surum 2: kayda islem numarasi eklendi export const KURALLAR = { ogrenci: { oduncGun: 28, enCokOdunc: 10 }, uye: { oduncGun: 14, enCokOdunc: 5 }, }; let sayac = 0; export function oduncVer(uye, kitapNo, gun) { const kural = KURALLAR[uye.tur]; if (kural === undefined) throw new Error(`bilinmeyen uye turu: ${uye.tur}`); if (uye.aciktakiOdunc >= kural.enCokOdunc) { throw new Error(`odunc siniri asildi: ${kural.enCokOdunc}`); } sayac += 1; return { islemNo: `I-${String(sayac).padStart(4, '0')}`, uyeNo: uye.no, kitapNo, alisGunu: gun, sonGun: gun + kural.oduncGun, durum: 'odunc', }; }
node --test --test-reporter=tap esitlik.test.mjs | grep -E '^(ok|not ok|# (tests|pass|fail))'
not ok 1 - esitlik nesneyi kimlikle karsilastirir not ok 2 - derin esitlik yapiyi karsilastirir # tests 2 # pass 0 # fail 2
Derin eşitlik iddiası düştü, çünkü tam eşleşme tam demektir: kayıtta beklenmedik bir alan bulunması da uyuşmazlıktır. Oysa sınanan kural — öğrenci ödüncünün yirmi sekiz gün sürmesi — değişmedi. Test, ilgilenmediği bir alana bağlandığı için düştü.
Çözüm iddiayı zayıflatmak değil, daraltmaktır: beklenen alanların varlığı ve değeri sınanır, geri kalanı serbest bırakılır.
// kismi.test.mjs — beklenen alanlarin varligini sinayan kismi eslestirme import { test } from 'node:test'; import assert from 'node:assert/strict'; import { oduncVer } from './odunc.mjs'; function icerir(gercek, beklenen, yol = 'kayit') { for (const [alan, deger] of Object.entries(beklenen)) { const altYol = `${yol}.${alan}`; assert.ok(alan in gercek, `${altYol} alani yok`); if (deger !== null && typeof deger === 'object') icerir(gercek[alan], deger, altYol); else assert.equal(gercek[alan], deger, `${altYol} uyusmuyor`); } } const UYE = { no: 'U-17', tur: 'ogrenci', aciktakiOdunc: 2 }; test('ogrenci oduncunun son gunu yirmi sekiz gun sonradir', () => { const kayit = oduncVer(UYE, 'K-903', 1000); icerir(kayit, { uyeNo: 'U-17', kitapNo: 'K-903', sonGun: 1028, durum: 'odunc' }); }); test('kismi eslestirme yanlis alani gosterir', () => { const kayit = oduncVer(UYE, 'K-903', 1000); assert.throws(() => icerir(kayit, { sonGun: 1014 }), /kayit.sonGun uyusmuyor/); });
node --test --test-reporter=tap kismi.test.mjs | grep -E '^(ok|not ok|# (tests|pass|fail))'
ok 1 - ogrenci oduncunun son gunu yirmi sekiz gun sonradir ok 2 - kismi eslestirme yanlis alani gosterir # tests 2 # pass 2 # fail 0
icerir yardımcısı beklenen nesnenin alanlarını dolaşır, eksik alanı ve uyuşmayan değeri
ayrı iletilerle bildirir. İkinci test bu yardımcının kendisini sınar: yanlış bir beklenti
verildiğinde hangi alanın uyuşmadığını ileti içinde söylemelidir. Yardımcıyı sınamak
gereksiz görünebilir, ama bir iddia yardımcısı sessizce geçerse ona bağlı bütün testler
sinyalini yitirir.
Kısmi eşleştirmenin dozu bir seçimdir: fazla gevşek bırakılırsa assert.ok kadar zayıf,
fazla sıkı tutulursa tam derin eşitlik kadar bağlı olur. Ölçüt şudur: iddiada geçen her
alan, testin adında geçen kuralın parçası olmalıdır.
Hata Bekleme
Kural ihlali durumunda işlemin reddedilmesi de bir davranıştır ve sınanması gerekir. Hata bekleyen iddianın eşleştirmesiz biçimi ise sık rastlanan bir yanlış alarm kaynağıdır.
// hata.test.mjs — esletirmesiz ve esletirmeli hata beklentisi import { test } from 'node:test'; import assert from 'node:assert/strict'; import { oduncVer } from './odunc.mjs'; const SINIRDAKI = { no: 'U-17', aciktakiOdunc: 10 }; test('esletirmesiz beklenti: sinir asiminda hata verir', () => { assert.throws(() => oduncVer(SINIRDAKI, 'K-903', 1000)); }); test('esletirmeli beklenti: sinir asiminda hata verir', () => { assert.throws(() => oduncVer(SINIRDAKI, 'K-903', 1000), /odunc siniri asildi: 10/); });
node --test --test-reporter=tap hata.test.mjs | grep -E '^(ok|not ok|# (tests|pass|fail))'
ok 1 - esletirmesiz beklenti: sinir asiminda hata verir not ok 2 - esletirmeli beklenti: sinir asiminda hata verir # tests 2 # pass 1 # fail 1
Aynı çağrı, aynı düzenek, iki farklı sonuç. Gerçekte fırlatılan hataya bakmak ayrımı açıklar.
// firlatilan.mjs — testin gercekte yakaladigi hata import { oduncVer } from './odunc.mjs'; const SINIRDAKI = { no: 'U-17', aciktakiOdunc: 10 }; try { oduncVer(SINIRDAKI, 'K-903', 1000); } catch (hata) { console.log(`${hata.constructor.name}: ${hata.message}`); }
Error: bilinmeyen uye turu: undefined
Düzenekteki üye nesnesinde tur alanı unutulmuş. İşlev ödünç sınırına hiç bakmadan, daha
önceki denetimde başka bir hata fırlatıyor. Eşleştirmesiz beklenti “herhangi bir hata”
gördüğü için yeşil kaldı ve sınamak istediği kural hiç çalışmadı. Eşleştirmeli beklenti
ise düşerek düzeneğin bozuk olduğunu bildirdi.
Bu, iddia seçiminin en pahalı hatasıdır: yeşil bir test, sınadığını sandığı kuralı hiç çalıştırmamış olabilir. Hata beklentisi her zaman bir ölçüte bağlanmalıdır — hata sınıfı, ileti örüntüsü ya da hata nesnesinin bir alanı.
Özet
- Katı eşitlik nesneleri kimlikle karşılaştırır; nesne ve dizi döndüren işlevlerde derin eşitlik ya da kısmi eşleştirme gerekir.
- Bir iddianın sinyal gücü, sınanan kodun kaç bozulmasını yakaladığıyla ölçülür; ölçümde
assert.ok(kayit)beş bozulmanın hiçbirini, derin eşitlik beşini de yakaladı. - Tam derin eşitlik davranışla ilgisiz alanlara da bağlanır; kayda yeni bir alan eklendiğinde kural değişmediği hâlde test düştü.
- Kısmi eşleştirme beklenen alanları sınar, geri kalanını serbest bırakır; iddiada geçen her alan testin adında geçen kuralın parçası olmalıdır.
- Eşleştirmesiz hata beklentisi yanlış hatayı da kabul eder ve sınamak istediği kural hiç çalışmadan yeşil kalabilir.
Sonraki Adım
Bu derste iddiaların ne söylediği ölçüldü, ama testlerin adları sessizce arka planda kaldı.
Oysa düşen bir koşumun ilk verdiği bilgi iddia değil, addır: not ok 2 satırının
yanındaki cümle. O cümle “kayit dogru” gibi bir şey söylüyorsa okuyanın kaynağa gitmesi
gerekir; kuralı söylüyorsa çoğu zaman gerekmez. Sonraki ders test adlandırmasını ele alır
ve bir test kümesinin adlarını okunabilir bir kural listesine dönüştürür.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.