Ders 19 / 22
Senaryo Temelli Uçtan Uca Doğrulama
İki sınama katmanının yakaladığı kusur kümelerinin yan yana sayılması: yirmi dört senaryoluk uçtan uca akış sınaması, araç düzeyi sınamanın hiç göremediği dört tanım kusurunun üçünü yakalıyor ve toplamda on bir kusurun beşini düşürüyor. Araç düzeyi katman yediyi yakalıyor, iki katmanın kesişimi iki, birleşimi on bir kusurda on yani 0,9091. Kaçırmanın iki ayrı nedeni ateşleme sayısıyla ayrılıyor: D2, D4 ve D11 döngü boyunca bir kez bile ateşlemiyor, D5, D6 ve D7 ateşliyor ama senaryo sonucu değişmiyor. Kalıntı D11, işlevi doğru olduğu hâlde tanımında kullanılmayan bir alan bildiren kusurdur ve hiçbir koşum onu göremez. Kusursuz tabanda düşen senaryo iki koşumda G23 ve G15.
İçindekiler
Bir önceki ders araç düzeyi sınamanın sınırını çizdi: on bir kusurun yedisi yakalandı, dört tanım kusurunun hiçbiri görünmedi. Görünmemeleri onları zararsız yapmaz; işyeri abonelerinin bedeli yanlış hesaplanıyor. Bu kusurlar ancak araçlar birbirine bağlıyken ortaya çıkar.
Bu ders ikinci katmanı kurar. Uçtan uca akış sınaması aracı değil senaryoyu koşturur ve sonunda hem diziyi hem yanıtı denetler. Ölçülen şey katman değil, iki katmanın kusur kümeleri arasındaki ilişkidir.
- KL12. Kusur kümesi bir önceki dersle aynıdır; kusurlar tablonun bir kopyasına konur. Araç düzeyi sütunu o dersin ölçümüdür, sınama kümesi burada tekrarlanmaz.
- KL13. Uçtan uca akış sınaması: yirmi dört senaryo sekiz adımlık bütçeyle koşulur. Senaryo ancak dizi de yanıt da doğruysa geçer.
- KL14. Kusursuz tabloda tabanı 24’te 23’tür. Bir kusur, düşen senaryo kümesi bu tabandan iki koşumda da farklıysa akış katmanınca yakalanmış sayılır.
- KL15. Ateşleme sayısı: her çağrıda aynı girdi kusursuz tabloya da verilir ve iki sonuç ayrıldığında sayaç artar. Sıfır ateşleme, kusurun döngüde bir kez bile görünmediğidir.
- KL16. Tohumlar 20260218 ve 20260219; iki koşum farkının altındaki fark ölçülmemiş sayılır.
Kurgu Ortam ve Ajan Döngüsü
Her senaryonun bilinen bir araç dizisi ve bilinen bir doğru yanıtı vardır; sınamanın kâhini bu ikilidir.
# KURGUDUR. Abone kayitlari, belge dagarcigi, arac kumesi, gorev kumesi ve ajan # dongusu ders icinde uretilir; gercek bir uc nokta ya da arac sunucusu cagrilmaz. TOHUM, M32 = 20260218, 0xFFFFFFFF def uretec(t): x = ((t ^ (t >> 16)) * 2246822507) & M32 x = ((x ^ (x >> 13)) * 3266489909) & M32 s = [(x ^ (x >> 16)) & M32] def sonraki(): s[0] = (s[0] * 1664525 + 1013904223) & M32 return s[0] / 4294967296 return sonraki def ayrik(u, w): t = 0.0 for i, x in enumerate(w): t += x if u < t: return i return len(w) - 1 def belirtecle(m): b = [] for s in str(m).lower().replace(",", " , ").split(): while len(s) > 5: b.append(s[:5]) s = s[5:] b.append(s) return b def izno(m): h = 2166136261 for c in str(m): h = ((h ^ ord(c)) * 16777619) & M32 return h BOLGE = ["dogu", "bati", "kuzey", "guney", "merkez"] TIP = ["mesken", "isyeri"] ABONE = {} for i in range(60): r = uretec(TOHUM + 31 * i) onceki = 10000 + int(r() * 80000) ABONE[f"A{i + 1:03d}"] = {"bolge": BOLGE[int(r() * 5)], "tip": TIP[int(r() * 2)], "onceki": onceki, "simdiki": onceki + int(r() * 70)} BASAMAK, USTU = ((10, 4), (25, 7), (40, 11)), 16 ISYERI_KAT = 2 BELGE = {"TR01": "tarife basamaklari 10 25 40 metrekup ve ustu", "TR02": "isyeri abonesinde birim ucret iki katidir", "SY01": "endeks farki tuketimi verir sayac donusu 100000 eklenir", "AR01": "ariza kaydi acildiginda saha ekibi iki gun icinde gider", "KS01": "planli kesinti duyurusu bir hafta once yayimlanir"} CAGRI = [0] # arac cagri sayaci def _bedel(m3, tip): kalan, alt, top = m3, 0, 0 for ust, birim in BASAMAK: dilim = min(kalan, ust - alt) if dilim <= 0: break top += dilim * birim kalan -= dilim alt = ust top += kalan * USTU return top * (ISYERI_KAT if tip == "isyeri" else 1) def t_sayac_oku(abone=None, **_): a = ABONE.get(abone) if a is None: return {"hata": "abone yok"} return {"onceki": a["onceki"], "simdiki": a["simdiki"], "tip": a["tip"], "bolge": a["bolge"]} def t_tuketim(onceki=None, simdiki=None, **_): if onceki is None or simdiki is None: return {"hata": "eksik alan"} f = simdiki - onceki return {"m3": f + 100000 if f < 0 else f} def t_tarife(m3=None, tip="mesken", **_): if m3 is None: return {"hata": "eksik alan"} return {"tutar": _bedel(m3, tip)} def t_belge(anahtar=None, **_): if not anahtar: return {"hata": "eksik alan"} ab = set(belirtecle(anahtar)) en, kod = -1, None for k, m in BELGE.items(): o = len(ab & set(belirtecle(m))) if o > en: en, kod = o, k return {"kod": kod, "metin": BELGE[kod]} if en > 0 else {"hata": "bulunamadi"} def t_kayit(abone=None, sutun=None, deger=None, **_): if not (abone and sutun and deger is not None): return {"hata": "eksik alan"} return {"yazildi": True, "abone": abone, "sutun": sutun, "deger": deger} ARAC = { "sayac_oku": {"islev": t_sayac_oku, "alan": ("abone",), "yan_etki": False}, "tuketim": {"islev": t_tuketim, "alan": ("onceki", "simdiki"), "yan_etki": False}, "tarife": {"islev": t_tarife, "alan": ("m3", "tip"), "yan_etki": False}, "belge_ara": {"islev": t_belge, "alan": ("anahtar",), "yan_etki": False}, "kayit_yaz": {"islev": t_kayit, "alan": ("abone", "sutun", "deger"), "yan_etki": True}, } ARAC_ADI = list(ARAC) def cagir_arac(ad, durum): """Sema disindaki alanlar suzulur; eksik alan aracin kendi hatasidir.""" CAGRI[0] += 1 if ad not in ARAC: return {"hata": "arac yok"} kw = {a: durum[a] for a in ARAC[ad]["alan"] if a in durum} return ARAC[ad]["islev"](**kw) GOREV = [] for i in range(24): r = uretec(TOHUM + 9973 * i) ab = f"A{1 + int(r() * 60):03d}" t = ayrik(r(), [0.30, 0.22, 0.20, 0.28]) if t == 0: GOREV.append({"ad": f"G{i + 1:02d}", "tur": "bedel", "abone": ab, "istek": f"{ab} abonesinin bu donem bedeli nedir", "dizi": ("sayac_oku", "tuketim", "tarife")}) elif t == 1: GOREV.append({"ad": f"G{i + 1:02d}", "tur": "tuketim", "abone": ab, "istek": f"{ab} abonesinin bu donem tuketimi kac metrekup", "dizi": ("sayac_oku", "tuketim")}) elif t == 2: GOREV.append({"ad": f"G{i + 1:02d}", "tur": "belge", "abone": ab, "istek": "sayac donusunde endeks farki nasil hesaplanir", "dizi": ("belge_ara",)}) else: GOREV.append({"ad": f"G{i + 1:02d}", "tur": "kayit", "abone": ab, "istek": f"{ab} abonesinin bedelini hesapla ve kayda yaz", "dizi": ("sayac_oku", "tuketim", "tarife", "kayit_yaz")}) def dogru_yanit(g): a = ABONE[g["abone"]] f = a["simdiki"] - a["onceki"] m3 = f + 100000 if f < 0 else f if g["tur"] == "tuketim": return m3 if g["tur"] == "belge": return "SY01" return _bedel(m3, a["tip"]) def gorme(d): return max(0.34, 0.80 - 0.09 * (d - 1)) # dogru araci secme payi, derinlikle duser def ajan(g, en_cok_adim=8, tohum=TOHUM, oz_elestiri=False, onay=False): """KURGU ajan dongusu. Gercek bir model cagrilmaz; arac secimi gorevin dizisine ve derinlige bagli tek bir cekilisten gelir.""" r = uretec(tohum ^ izno(g["ad"])) alan = {"abone": g["abone"], "anahtar": g["istek"], "sutun": "bedel"} iz, adim, bel, hata = [], 0, 0, 0 dizi, k = g["dizi"], 0 while adim < en_cok_adim: adim += 1 bel += 60 + 22 * len(iz) hedef = dizi[k] if k < len(dizi) else None if hedef is None: # is bitti , ajan durmali if r() < 0.88: return {"bitti": True, "adim": adim, "iz": iz, "belirtec": bel, "hata": hata, "yanit": alan.get("sonuc"), "onay": alan.get("onay", 0)} sec = ARAC_ADI[int(r() * len(ARAC_ADI))] else: sec = hedef if r() < gorme(k + 1) else ARAC_ADI[int(r() * len(ARAC_ADI))] if ARAC[sec]["yan_etki"] and onay: alan["onay"] = alan.get("onay", 0) + 1 if sec == "kayit_yaz": alan["deger"] = alan.get("sonuc") s = cagir_arac(sec, alan) iz.append((sec, "hata" in s)) bel += 30 if "hata" in s: hata += 1 if oz_elestiri: bel += 40 continue alan.update({kk: vv for kk, vv in s.items() if kk != "hata"}) if sec == "tuketim": alan["sonuc"] = s["m3"] if sec == "tarife": alan["sonuc"] = s["tutar"] if sec == "belge_ara": alan["sonuc"] = s["kod"] if sec == hedef: k += 1 return {"bitti": False, "adim": adim, "iz": iz, "belirtec": bel, "hata": hata, "yanit": alan.get("sonuc"), "onay": alan.get("onay", 0)} print(f"kurgu ortam {len(ABONE)} abone , {len(ARAC)} arac , {len(GOREV)} senaryo")
kurgu ortam 60 abone , 5 arac , 24 senaryo
İki Katman Yan Yana
Akış sınamasının gördüğü tek şey döngünün ürettiği dizi ile yanıttır. Ateşleme sayacı yanına bir sütun koyar: kusur o koşumda kaç çağrıda kusursuz tablodan ayrıldı. Araç düzeyi sütunu bir önceki dersten girdi olarak gelir.
KUSUR = { "D1": ("sayac_oku", "deger", lambda abone=None, **_: ({"hata": "abone yok"} if abone not in ABONE else dict(ABONE[abone], simdiki=ABONE[abone]["onceki"]))), "D2": ("tuketim", "deger", lambda onceki=None, simdiki=None, **_: ( {"hata": "eksik alan"} if onceki is None or simdiki is None else {"m3": simdiki - onceki + (10000 if simdiki < onceki else 0)})), "D3": ("tarife", "deger", lambda m3=None, tip="mesken", **_: ( {"hata": "eksik alan"} if m3 is None else {"tutar": _bedel(m3, tip) - max(m3 - 40, 0) * 5 * (ISYERI_KAT if tip == "isyeri" else 1)})), "D4": ("belge_ara", "deger", lambda anahtar=None, **_: next( ({"kod": k, "metin": m} for k, m in BELGE.items() if set(belirtecle(anahtar or "")) & set(belirtecle(m))), {"hata": "eksik alan" if not anahtar else "bulunamadi"})), "D5": ("tuketim", "sozlesme", lambda onceki=None, simdiki=None, **_: t_tuketim(onceki or 0, simdiki or 0)), "D6": ("tarife", "sozlesme", lambda m3=None, tip="mesken", **_: t_tarife(m3 or 0, tip)), "D7": ("kayit_yaz", "sozlesme", lambda abone=None, sutun=None, deger=None, **_: { "yazildi": True, "abone": abone, "sutun": sutun, "deger": deger}), "D8": ("tarife", "tanim", ("m3",)), # tip alanini bildirmiyor "D9": ("kayit_yaz", "tanim", ("abone", "sutun")), # deger alanini bildirmiyor "D10": ("belge_ara", "tanim", ("abone",)), # anahtar yerine abone "D11": ("sayac_oku", "tanim", ("abone", "sutun")), # islevin kullanmadigi alan } TEMIZ = {a: dict(v) for a, v in ARAC.items()} def kusurlu(kod): """Kusur konmus tablo kopyasi: dizi verilmisse bildirilen alan listesi, islev verilmisse aracin islevi degistirilir.""" t = {a: dict(v) for a, v in ARAC.items()} ad, p = KUSUR[kod][0], KUSUR[kod][2] t[ad]["alan" if isinstance(p, tuple) else "islev"] = p return t # Bir onceki dersin katmani girdidir: on bir durumluk sinama kumesi orada # kuruldu ve dusurdugu kusurlar sunlardi. Hesap burada tekrarlanmaz. ARAC_DUZEYI = {"D1", "D2", "D3", "D4", "D5", "D6", "D7"} ATES = [0] _cagir = cagir_arac def cagir_arac(ad, durum): """Cagriyi yururlukteki tabloyla kurar, ayni girdiyi kusursuz tabloya da verir ve iki sonuc ayrildiginda kusurun atesledigini sayar.""" b = _cagir(ad, durum) kw = {a: durum[a] for a in TEMIZ[ad]["alan"] if a in durum} ATES[0] += b != TEMIZ[ad]["islev"](**kw) return b def dizi_dogru(g, iz): k = 0 for ad, hatali in iz: if k < len(g["dizi"]) and ad == g["dizi"][k] and not hatali: k += 1 return k == len(g["dizi"]) def akis_sinamasi(tablo, tohum=TOHUM): """Uctan uca senaryo: dizi de yanit da dogruysa gecer.""" global ARAC asil, ARAC, ATES[0] = ARAC, tablo, 0 d = [] for g in GOREV: s = ajan(g, en_cok_adim=8, tohum=tohum) if not (dizi_dogru(g, s["iz"]) and s["yanit"] == dogru_yanit(g)): d.append(g["ad"]) ARAC = asil return d, ATES[0] taban = [akis_sinamasi(TEMIZ, t) for t in (TOHUM, 20260219)] print(f"akis sinamasi {len(GOREV)} senaryo , kusursuz tabloda dusen senaryo " f"{taban[0][0]} ve {taban[1][0]}") print("\nkod arac sinif arac-duzeyi akis-1 akis-2 ates-1 ates-2") AD, AK = set(), set() for kod, (ad, sinif, p) in KUSUR.items(): t, ar = kusurlu(kod), kod in ARAC_DUZEYI (d1, a1), (d2, a2) = akis_sinamasi(t, TOHUM), akis_sinamasi(t, 20260219) if ar: AD.add(kod) if d1 != taban[0][0] and d2 != taban[1][0]: AK.add(kod) print(f"{kod:<5} {ad:<11} {sinif:<9} {'+' if ar else '-':>11} {len(d1):>7} {len(d2):>7}" f" {a1:>7} {a2:>7}") n = len(KUSUR) print() for et, s in (("ikisi birden", AD & AK), ("yalniz akis", AK - AD), ("yalniz arac duzeyi", AD - AK), ("kalinti", set(KUSUR) - AD - AK)): print(f"{et:<19} {' '.join(k for k in KUSUR if k in s)}") print(f"arac duzeyi {len(AD)}/{n} = {len(AD) / n:.4f} , akis {len(AK)}/{n} = " f"{len(AK) / n:.4f} , birlesim {len(AD | AK)}/{n} = {len(AD | AK) / n:.4f}")
akis sinamasi 24 senaryo , kusursuz tabloda dusen senaryo ['G23'] ve ['G15'] kod arac sinif arac-duzeyi akis-1 akis-2 ates-1 ates-2 D1 sayac_oku deger + 17 17 18 20 D2 tuketim deger + 1 1 0 0 D3 tarife deger + 7 7 9 8 D4 belge_ara deger + 1 1 0 0 D5 tuketim sozlesme + 1 1 2 1 D6 tarife sozlesme + 1 1 4 5 D7 kayit_yaz sozlesme + 1 1 2 1 D8 tarife tanim - 5 6 6 5 D9 kayit_yaz tanim - 3 3 7 14 D10 belge_ara tanim - 7 8 44 49 D11 sayac_oku tanim - 1 1 0 0 ikisi birden D1 D3 yalniz akis D8 D9 D10 yalniz arac duzeyi D2 D4 D5 D6 D7 kalinti D11 arac duzeyi 7/11 = 0.6364 , akis 5/11 = 0.4545 , birlesim 10/11 = 0.9091
Kesişmeyen İki Küme
Araç düzeyi katman 7/11, akış katmanı 5/11 yakalıyor. Kesişim yalnız iki kusurdur:
D1 ve D3. D1 her sayaç okumasında ateşliyor ve yirmi dört senaryonun on yedisini
düşürüyor; D3 yalnız üst dilimi aşan tüketimlerde ateşliyor ve yedisini.
Asıl bilgi kesişimin dışındadır. Akış katmanı D8, D9, D10 kusurlarını, yani araç
düzeyinin hiç göremediği dört tanım kusurunun üçünü yakalıyor. Neden mekanizmadadır: akış sınaması
aracı işlevinden değil, döngünün kurduğu çağrıdan görür ve o çağrı bildirilen alan listesinden
geçer. D10 en yüksek ateşlemeyi veriyor: 44 ve 49. Ters yönde D2, D4, D5, D6,
D7 yalnız araç düzeyinde görünüyor. Birleşim 10/11 = 0,9091; hiçbir katman tek başına
buraya yaklaşmıyor.
Ateşlemeyen Kusur ve Kalıntı
Akışın kaçırdığı kusurların iki ayrı nedeni vardır ve ateşleme sütunu ikisini ayırır. D2 ve
D4 iki koşumda da sıfır ateşliyor: sayaç dönüşü dalına bu görev kümesinde hiç girilmiyor ve
belge sorgusunda ilk örtüşen belge zaten en yüksek örtüşen belge. Kusur bir kez bile ortaya
çıkmadı; akışın onu bulma şansı yoktu. D5, D6 ve D7 ise ateşliyor — sırasıyla 2, 4 ve 2
çağrıda — ama düşen senaryo kümesi değişmiyor: kusur koşuldu, sonucu bozdu ve senaryo yine de
geçti. İlki bir kapsam eksiği, ikincisi bir gözlem eksiğidir.
Ateşleme sayısı bir büyüklük olarak okunmaz. D9 ilk koşumda 7, ikincide 14 ateşliyor; D1 18 ve
20. Okunan şey sıfır olup olmadığıdır.
Kalıntı tek kusurdur ve adı D11. sayac_oku işlevi doğrudur, ama tanımı işlevin hiç kullanmadığı
bir alan bildirir. Araç düzeyi sınama göremez, çünkü işlev doğru çalışır. Akış sınaması göremez,
çünkü fazladan bildirilen alan çağrıya girer ve sessizce yutulur: ateşleme iki koşumda da
sıfır. Kusur davranışta değil sözleşmenin kendisindedir ve onu okuyacak olan, aracı
çağıracak bir başkasıdır.
Özet
- Akış sınaması senaryoyu koşturur ve yalnız dizi ile yanıtı görür; kusursuz tabanda düşen senaryo iki koşumda G23 ve G15’tir.
- Akış katmanı 11 kusurun 5’ini, araç düzeyi katman 7’sini yakalar; kesişim yalnız
D1veD3, birleşim 10/11 = 0,9091. - Akış katmanı, araç düzeyinin göremediği dört tanım kusurunun üçünü yakalar, çünkü çağrıyı bildirilen alan listesinden geçirir.
- Kaçırmanın nedeni ikiye ayrılır:
D2,D4veD11hiç ateşlemez;D5,D6veD7ateşler ama senaryo sonucunu değiştirmez. - Kalıntı
D11’dir: işlev doğru, tanım fazladan bir alan bildiriyor; ateşleme sıfır olduğu için hiçbir koşum bu kusuru gösteremez.
Sonraki Adım
İki katman birlikte on bir kusurun onunu buldu, ama ikisinin ortak bir yanı var: kusuru biz koyduk ve nerede olduğunu baştan biliyorduk. Sınama, ajanın sınandığı koşumları görür; üretimde koşan bir döngü hakkında hiçbir şey söylemez. Orada düşen bir senaryo yoktur, yalnız bir kullanıcı ve yanlış bir yanıt vardır. Bir sonraki ders koşumun geriye ne bıraktığını ölçer: üç iz düzeyinde başarısız bir görevin nedeninin bulunabildiği pay ve saklanan belirteç.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.