Ders 01 / 13
Birim Testin Anatomisi
Bir hata raporunun yeniden üretim adımlarının koşulabilir teste çevrilmesi, test gövdesinin hazırla-uygula-doğrula bölümleri ve beklenen değerin gerçekleştirimden bağımsız kaynaktan gelmesi.
İçindekiler
Kalite ve Test Temelleri kursu, bir hatanın raporlanmasıyla, önceliklendirilmesiyle ve düzeltmenin doğrulanmasıyla kapandı. O kurs testin ne olduğunu ve hangi testin yazılacağını kurdu: denklik sınıfları, sınır değerler, test kâhini, hata kaydı. Elde kalan şey bir belgeydi — insanın okuyup elle uyguladığı adımlar.
Bu kurs aynı adımların nasıl yazıldığını ele alır. Bir hata kaydındaki “yeniden üretim adımları” alanı, aslında bir programın taslağıdır: belirli bir başlangıç durumu, tek bir işlem ve gözlenen sonuç. Bu üçlü koşulabilir hâle getirildiğinde, hatanın bir daha geri gelmediğini kimsenin elle denetlemesi gerekmez. Bu dersin sorusu şudur: o çevirme işlemi tam olarak neye benzer ve testin gövdesi hangi bölümlerden oluşur?
Yeniden Üretim Adımları
Kurs boyunca aynı alan sürer: kütüphane ödünç kurallarını uygulayan küçük bir kitaplık. Kurallar yazılıdır. Öğrenci üyeler bir kitabı yirmi sekiz gün, üyeler on dört gün ödünç alır. Geç iadede günlük ücret alınır, ilk birkaç gün ücretsizdir ve toplam ücretin bir üst sınırı vardır. Tarihler gün numarasıyla temsil edilir; takvim aritmetiği bu kursun konusu değildir ve sabit bir gün sayacı örnekleri belirlenimci tutar.
// kitaplik.mjs — surum 1 export const KURALLAR = { ogrenci: { oduncGun: 28, enCokOdunc: 10 }, uye: { oduncGun: 14, enCokOdunc: 5 }, }; export const GECIKME = { ucretsizGun: 3, gunlukUcret: 2, ustSinir: 40 }; export function sonTarih(alisGunu, uyeTuru) { const kural = KURALLAR[uyeTuru]; if (kural === undefined) throw new Error(`bilinmeyen uye turu: ${uyeTuru}`); return alisGunu + kural.oduncGun; } export function gecikmeUcreti(sonGun, iadeGunu) { const gecikme = iadeGunu - sonGun; if (gecikme <= GECIKME.ucretsizGun) return 0; return (gecikme - GECIKME.ucretsizGun) * GECIKME.gunlukUcret; }
Kütüphane görevlisinden gelen hata kaydı şunu söylüyor: bin numaralı günde kitap alan bir üye, son tarihten kırk gün sonra iade ettiğinde yetmiş dört birim ücret hesaplanıyor; oysa yazılı kural üst sınırı kırk olarak veriyor. Kaydın yeniden üretim adımları üç satırdır ve her satır bir program ifadesine karşılık gelir.
// yeniden-uretim.mjs — hata raporundaki adimlarin elle kosumu import { sonTarih, gecikmeUcreti } from './kitaplik.mjs'; const son = sonTarih(1000, 'uye'); console.log(`son tarih : ${son}`); console.log(`iade gunu : ${son + 40}`); console.log(`hesaplanan ucret: ${gecikmeUcreti(son, son + 40)}`); console.log(`beklenen ust sinir: 40`);
son tarih : 1014 iade gunu : 1054 hesaplanan ucret: 74 beklenen ust sinir: 40
Bu betik hatayı yeniden üretir ama bir test değildir. Sonuca insan bakar; kırk ile yetmiş dördü karşılaştıran bir göz gerekir. Betiği teste çeviren şey, karşılaştırmanın da programa girmesidir.
Hazırla, Uygula, Doğrula
Bir birim testinin gövdesi üç bölüme ayrılır ve bu bölümler her testte aynı sırayla gelir.
Hazırla (arrange) bölümü, işlemin çalışacağı başlangıç durumunu kurar: girdiler, nesneler, bağımlılıklar. Uygula (act) bölümü sınanan işlemi tek bir kez çağırır. Doğrula (assert) bölümü gözlenen sonucu beklenenle karşılaştırır ve uyuşmazlıkta testi düşürür.
// gecikme.test.mjs — hata raporunun yeniden uretim adimlari import { test } from 'node:test'; import assert from 'node:assert/strict'; import { sonTarih, gecikmeUcreti } from './kitaplik.mjs'; test('gecikme ucreti ust siniri asmaz', () => { const son = sonTarih(1000, 'uye'); const ucret = gecikmeUcreti(son, son + 40); assert.equal(ucret, 40); });
Üç bölüm boş satırlarla ayrılmıştır; ayrı bir yorum satırı gerekmez. Bu düzenin değeri estetik değildir. Uygula bölümünün tek satır olması, testin hangi çağrıyı sınadığını belirsizliğe yer bırakmadan söyler. İki çağrı varsa test düştüğünde hangisinin düşürdüğü bilinmez. Hazırla bölümünün uzaması ise ayrı bir sinyaldir: sınanan birimin çok fazla şeye bağlı olduğunu gösterir. Beşinci ders bu sinyali ölçer.
Testler Node çalışma zamanının yerleşik test koşucusuyla çalıştırılır. Koşucunun ayrıntılı çıktısı süre, dosya yolu ve süreç bilgisi de içerir; bunlar ortama göre değişir. Kurs boyunca yalnız sonuç satırları süzülür, çünkü öğretici olan bunlardır.
node --test --test-reporter=tap gecikme.test.mjs | grep -E '^(ok|not ok|# (tests|pass|fail))'
not ok 1 - gecikme ucreti ust siniri asmaz # tests 1 # pass 0 # fail 1
Test düştü ve bu beklenen sonuçtur: hata henüz düzeltilmedi. Düşen bir koşum, testin gerçekten bir şey sınadığının kanıtıdır. Hiç düşmemiş bir test, doğru olduğu için mi yoksa yanlış yazıldığı için mi yeşil olduğunu söyleyemez.
Beklenen Değer Nereden Gelir
Doğrula bölümündeki kırk sayısı yazılı kuraldan geldi. Bu ayrıntı, testin bütün değerini taşır. Beklenen değer gerçekleştirimin kendisinden türetildiğinde test bir totoloji hâline gelir: kodun yaptığı şeyin kodun yaptığı şey olduğunu doğrular.
// totoloji.test.mjs — beklenen degeri gerceklestirimle ayni yoldan hesaplayan test import { test } from 'node:test'; import assert from 'node:assert/strict'; import { GECIKME, gecikmeUcreti } from './kitaplik.mjs'; test('gecikme ucreti dogru hesaplanir', () => { const gecikme = 40; const beklenen = (gecikme - GECIKME.ucretsizGun) * GECIKME.gunlukUcret; assert.equal(gecikmeUcreti(1014, 1014 + gecikme), beklenen); });
node --test --test-reporter=tap totoloji.test.mjs | grep -E '^(ok|not ok|# (tests|pass|fail))'
ok 1 - gecikme ucreti dogru hesaplanir # tests 1 # pass 1 # fail 0
Bu test, hatanın durduğu kod üzerinde yeşil verdi. Beklenen değeri üreten formül, sınanan işlevin içindeki formülün aynısıdır; üst sınırı ikisi de unutur. Kalite ve Test Temelleri kursunda tanıtılan test kâhini kavramı tam olarak bu noktayı adlandırır: beklenen sonucun kaynağı sınanan koddan bağımsız olmalıdır. Kaynak yazılı kural, elle yapılmış bir hesap, bilinen bir referans değer ya da başka bir yoldan üretilmiş bir sonuç olabilir — ama sınanan işlevin kendi mantığı olamaz.
Düzeltme ve İki Testin Karşılaştırması
Düzeltme tek satırdır: ham ücret ile üst sınırın küçüğü alınır.
// kitaplik.mjs — surum 2: ust sinir uygulaniyor export const KURALLAR = { ogrenci: { oduncGun: 28, enCokOdunc: 10 }, uye: { oduncGun: 14, enCokOdunc: 5 }, }; export const GECIKME = { ucretsizGun: 3, gunlukUcret: 2, ustSinir: 40 }; export function sonTarih(alisGunu, uyeTuru) { const kural = KURALLAR[uyeTuru]; if (kural === undefined) throw new Error(`bilinmeyen uye turu: ${uyeTuru}`); return alisGunu + kural.oduncGun; } export function gecikmeUcreti(sonGun, iadeGunu) { const gecikme = iadeGunu - sonGun; if (gecikme <= GECIKME.ucretsizGun) return 0; const ham = (gecikme - GECIKME.ucretsizGun) * GECIKME.gunlukUcret; return Math.min(ham, GECIKME.ustSinir); }
İki test dosyası birlikte koşturulduğunda ikisinin de durumu tersine döner.
node --test --test-reporter=tap gecikme.test.mjs totoloji.test.mjs | grep -E '^(ok|not ok|# (tests|pass|fail))'
ok 1 - gecikme ucreti ust siniri asmaz not ok 2 - gecikme ucreti dogru hesaplanir # tests 2 # pass 1 # fail 1
Kurala bağlı test doğru davranışta yeşile döndü. Gerçekleştirime bağlı test ise doğru kod üzerinde düştü. Bir test, kod düzeldiğinde düşüyorsa sınadığı şey davranış değildir. Bu, sekizinci dersin konusu olan kırılganlığın en yalın biçimidir; burada yalnız adlandırılması yeterlidir.
Birimin Sınırı
“Birim” sözcüğü bir boyut değil, bir sınır belirtir. Bir birim testi, sınadığı kodun dışında hiçbir şeyin sonucu etkilemediği bir bağlam kurar. Uygulamada bu üç şeyi dışarıda bırakmak demektir: disk ve ağ gibi süreç dışı kaynaklar, sistem saati ve rastgelelik kaynakları, başka testlerin bıraktığı durum.
Bu kısıt keyfi değildir. Dışarıda bırakılan her kaynak testin süresine ve kararlılığına bir pay ekler. Yukarıdaki testin çalışması milisaniyenin altındadır ve sonucu makineye, saate ya da koşum sırasına bağlı değildir. Aynı testin bir veri tabanına yazması gerekseydi, düşen bir koşumun nedeni kod, yapılandırma, ağ ya da veri kalıntısı olabilirdi — dört olasılık arasından seçim yapmak, tek olasılığı okumaktan çok daha pahalıdır.
sonTarih çağrısının hazırla bölümünde durması bu sınıra bir örnektir. Sınanan birim
gecikmeUcreti işlevidir; sonTarih yalnız girdi üretir. İkisi aynı dosyada olsa bile
testin niyeti tektir ve düşen bir koşum tek bir işlevi işaret eder.
Özet
- Bir hata kaydının yeniden üretim adımları koşulabilir bir teste çevrilebilir; çeviriyi tamamlayan şey karşılaştırmanın da programa girmesidir.
- Test gövdesi hazırla, uygula ve doğrula bölümlerinden oluşur; uygula bölümünün tek çağrı olması düşen bir koşumun kaynağını belirsizlikten çıkarır.
- Düşen bir koşum, testin gerçekten bir şey sınadığının kanıtıdır; hiç düşmemiş bir test neden yeşil olduğunu söyleyemez.
- Beklenen değer sınanan koddan bağımsız bir kaynaktan gelmelidir; gerçekleştirimin formülünü tekrarlayan test, hatanın durduğu kodda da yeşil verir.
- Birim, bir boyut değil bir sınırdır: süreç dışı kaynaklar, saat, rastgelelik ve başka testlerin bıraktığı durum bu sınırın dışında kalır.
Sonraki Adım
Bu derste tek bir iddia kullanıldı: assert.equal. İki sayının eşitliği için yeterliydi,
çünkü sınanan işlev bir sayı döndürüyordu. Ödünç işlemi bir nesne, bir hata ya da bir liste
döndürdüğünde eşitlik denetimi ya çalışmaz ya da yanlış şeyi ölçer. Sonraki ders iddia
seçimini ele alır: hangi iddia hangi hatayı yakalar, aşırı geniş bir iddia ne kadar sinyal
kaybettirir ve bu kayıp nasıl ölçülür.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.