İçeriğe geç
academia.sh

Ders 18 / 22

Her Aracın Döngüden Ayrı Sınanması

Beş araca konan on bir kurgu kusurun araç düzeyi sınamayla ne kadarının yakalandığının sayılması: on bir durumluk sınama kümesi değer kusurlarının dördünü de, sözleşme kusurlarının üçünü de düşürüyor, ama tanım kusurlarının dördünde de tertemiz geçiyor. Toplam yakalama 0,6364, sınıf bazında 1,0000 ve 1,0000 ile 0,0000. Görünmeyen sınıfın mekanizması yazılıdır: sınama kümesi aracın işlevini doğrudan çağırır, döngü ise aracın bildirdiği alan listesinden geçer ve tanım kusuru yalnız o listede durur. Katmanın karşılığı koşumdan bağımsızlıktır: aynı görev kümesinde döngünün düşen senaryosu tohum değişince G23'ten G15'e kayarken, sınama kümesinin düşen durum kümesi iki koşumda da boş ve aynıdır.

İçindekiler

Bir önceki ders bir ajan koşumunu iki sütuna ayırdı: dizinin doğruluğu ve yanıtın doğruluğu. İkisi de araçların doğru çalıştığını varsayar. Varsayım kaldırıldığı anda dört durum tablosu yetersiz kalır: bir görevin düştüğünü söyler, hangi aracın bozuk olduğunu söylemez.

Bu ders araçları döngüden söker ve tek tek sınar. Sınama seviyeleri M21 Yazılım Kalitesi ve Test müfredatının Kalite ve Test Temelleri kursunda sınıflandırıldı, birim sınamanın anatomisi Birim Testi ve Test Odaklı Geliştirme kursunda kuruldu; yordam burada tekrarlanmaz. Soru ölçüye aittir: beş araca konan kusurların kaçı bu katmanda görünüyor ve hangi sınıfı bu katman hiç göremiyor.

  • KL7. Kusurlar kurgudur ve araç tablosunun bir kopyasına konur; asıl tablo değişmez.
  • KL8. Üç kusur sınıfı vardır. Değer kusuru: işlev yanlış sonuç verir. Sözleşme kusuru: işlev, hata bildirmesi gereken yerde bildirmez. Tanım kusuru: işlev doğrudur, bildirilen alan listesi yanlıştır.
  • KL9. Araç düzeyi sınama aracı döngünün dışında, doğrudan işlevini çağırarak sınar; beklenen çıktı elle yazılmıştır.
  • KL10. Bir kusur, sınama kümesinden en az bir durum düşürüyorsa yakalanmış sayılır.
  • KL11. Sınama kümesi tohum kullanmaz; döngü ölçüsü sekiz adımlık bütçeyle ve iki tohumla koşulur.

Ortam, Araç Tanımları ve Ajan Döngüsü

Bir araç iki parçadan oluşur: işlev hesabı yapar, tanım o işlevin hangi alanları alacağını bildirir ve döngü çağrıyı yalnız bildirilen alanlarla kurar.

# 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("arac       bildirilen alanlar        yan etki")
for ad, v in ARAC.items():
    print(f"{ad:<10} {', '.join(v['alan']):<24}  {v['yan_etki']}")
arac       bildirilen alanlar        yan etki
sayac_oku  abone                     False
tuketim    onceki, simdiki           False
tarife     m3, tip                   False
belge_ara  anahtar                   False
kayit_yaz  abone, sutun, deger       True

Kusur Kümesi ve Sınama Kümesi

On bir kusur beş araca dağıtılır. Değer ve sözleşme kusurları işlevi değiştirir; tanım kusurları işleve dokunmaz, yalnız bildirilen alan listesini oynatır. Sınama kümesi her araç için bir olağan çağrı ve bir sınır durumu taşır.

KUSUR = {                       # arac , sinif , aciklama , kusurlu islev ya da alan listesi
    "D1": ("sayac_oku", "deger", "simdiki endeksi onceki ile ayni",
           lambda abone=None, **_: ({"hata": "abone yok"} if abone not in ABONE
                                    else dict(ABONE[abone], simdiki=ABONE[abone]["onceki"]))),
    "D2": ("tuketim", "deger", "sayac donusunde 10000 ekliyor",
           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", "ust dilim birimi 16 yerine 11",
           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", "en yuksek ortusme yerine ilki",
           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", "eksik alanda hata yerine sifir",
           lambda onceki=None, simdiki=None, **_: t_tuketim(onceki or 0, simdiki or 0)),
    "D6": ("tarife", "sozlesme", "eksik alanda hata yerine sifir",
           lambda m3=None, tip="mesken", **_: t_tarife(m3 or 0, tip)),
    "D7": ("kayit_yaz", "sozlesme", "deger yokken yazildi diyor",
           lambda abone=None, sutun=None, deger=None, **_: {
               "yazildi": True, "abone": abone, "sutun": sutun, "deger": deger}),
    "D8": ("tarife", "tanim", "tanimi tip alanini bildirmiyor", ("m3",)),
    "D9": ("kayit_yaz", "tanim", "tanimi deger alanini bildirmiyor", ("abone", "sutun")),
    "D10": ("belge_ara", "tanim", "tanimi anahtar yerine abone bildiriyor", ("abone",)),
    "D11": ("sayac_oku", "tanim", "tanimi kullanilmayan alan bildiriyor", ("abone", "sutun")),
}
SINIF = ("deger", "sozlesme", "tanim")


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, kusur = KUSUR[kod][0], KUSUR[kod][3]
    t[ad]["alan" if isinstance(kusur, tuple) else "islev"] = kusur
    return t


A1 = "A001"
SINAMA = [
    ("sayac_oku", {"abone": A1}, {"onceki": ABONE[A1]["onceki"],
                                  "simdiki": ABONE[A1]["simdiki"]}),
    ("sayac_oku", {"abone": "A999"}, {"hata": "abone yok"}),
    ("tuketim", {"onceki": 100, "simdiki": 150}, {"m3": 50}),
    ("tuketim", {"onceki": 99980, "simdiki": 30}, {"m3": 50}),
    ("tuketim", {"simdiki": 150}, {"hata": "eksik alan"}),
    ("tarife", {"m3": 50, "tip": "isyeri"}, {"tutar": 940}),
    ("tarife", {}, {"hata": "eksik alan"}),
    ("belge_ara", {"anahtar": "isyeri abonesinde birim ucret"}, {"kod": "TR02"}),
    ("belge_ara", {"anahtar": ""}, {"hata": "eksik alan"}),
    ("kayit_yaz", {"abone": A1, "sutun": "bedel", "deger": 500}, {"yazildi": True}),
    ("kayit_yaz", {"abone": A1, "sutun": "bedel"}, {"hata": "eksik alan"}),
]


def arac_sinamasi(tablo):
    """Her araci dongunun disinda, dogrudan islevini cagirarak sinar."""
    dusen = []
    for ad, girdi, beklenen in SINAMA:
        c = tablo[ad]["islev"](**girdi)
        if ("hata" in c) != ("hata" in beklenen) or any(
                c.get(k) != v for k, v in beklenen.items()):
            dusen.append(ad)
    return dusen


def dongu_basarim(tablo, tohum=TOHUM):
    global ARAC
    asil, ARAC = ARAC, tablo
    d = [g["ad"] for g in GOREV
         if ajan(g, en_cok_adim=8, tohum=tohum)["yanit"] != dogru_yanit(g)]
    ARAC = asil
    return d


print(f"sinama kumesi {len(SINAMA)} durum , kusursuz tabloda dusen "
      f"{len(arac_sinamasi(ARAC))}")
print(f"kusur kumesi {len(KUSUR)} kusur , {len(ARAC)} arac , {len(SINIF)} sinif\n")
print(f"{'kod':<5} {'arac':<11} {'sinif':<9} {'dusen':>5}  aciklama")
yak = {s: [0, 0] for s in SINIF}
for kod, (ad, sinif, acik, kusur) in KUSUR.items():
    d = arac_sinamasi(kusurlu(kod))
    yak[sinif][1] += 1
    yak[sinif][0] += 1 if d else 0
    print(f"{kod:<5} {ad:<11} {sinif:<9} {len(d):>5}  {acik}")

print("\nsinif      kusur  yakalanan     pay")
for s in SINIF:
    a, b = yak[s]
    print(f"{s:<10} {b:>5} {a:>10} {a / b:>7.4f}")
tp = sum(a for a, _ in yak.values())
print(f"{'toplam':<10} {len(KUSUR):>5} {tp:>10} {tp / len(KUSUR):>7.4f}")

print("\niki kosum , kusursuz tablo")
print(f"arac duzeyi sinama , dusen durum {arac_sinamasi(ARAC)} ve {arac_sinamasi(ARAC)}")
for t in (TOHUM, 20260219):
    d = dongu_basarim(ARAC, t)
    print(f"dongu tohum {t} , basarim {1 - len(d) / 24:.4f} , dusen gorev {d}")
sinama kumesi 11 durum , kusursuz tabloda dusen 0
kusur kumesi 11 kusur , 5 arac , 3 sinif

kod   arac        sinif     dusen  aciklama
D1    sayac_oku   deger         1  simdiki endeksi onceki ile ayni
D2    tuketim     deger         1  sayac donusunde 10000 ekliyor
D3    tarife      deger         1  ust dilim birimi 16 yerine 11
D4    belge_ara   deger         1  en yuksek ortusme yerine ilki
D5    tuketim     sozlesme      1  eksik alanda hata yerine sifir
D6    tarife      sozlesme      1  eksik alanda hata yerine sifir
D7    kayit_yaz   sozlesme      1  deger yokken yazildi diyor
D8    tarife      tanim         0  tanimi tip alanini bildirmiyor
D9    kayit_yaz   tanim         0  tanimi deger alanini bildirmiyor
D10   belge_ara   tanim         0  tanimi anahtar yerine abone bildiriyor
D11   sayac_oku   tanim         0  tanimi kullanilmayan alan bildiriyor

sinif      kusur  yakalanan     pay
deger          4          4  1.0000
sozlesme       3          3  1.0000
tanim          4          0  0.0000
toplam        11          7  0.6364

iki kosum , kusursuz tablo
arac duzeyi sinama , dusen durum [] ve []
dongu tohum 20260218 , basarim 0.9583 , dusen gorev ['G23']
dongu tohum 20260219 , basarim 0.9583 , dusen gorev ['G15']

Yakalanan Yedi, Görünmeyen Dört

Katmanın gücü ilk iki sınıfta tamdır: dört değer kusurunun dördü de, üç sözleşme kusurunun üçü de sınama kümesinden bir durum düşürüyor. Düşen durum sayısının birer olması da bir bilgidir; kusur kendi aracına yazılmış tek bir durumda görünür ve düşen durumun adı doğrudan bozuk aracı verir. Dört durum tablosunun söyleyemediği şey burada bir satırla söylenir.

Görünmeyen sınıf tesadüf değil, çağrı yolunun sonucudur. Sınama kümesi aracın işlevini doğrudan çağırır ve bildirilen alan listesine hiç bakmaz. Döngü ise çağrıyı cagir_arac üzerinden kurar ve durumdan yalnız bildirilen alanları süzer. Tanım kusuru tam bu süzgecin içindedir: tarife işlevi işyeri katsayısını doğru uygular, ama tanımı tip alanını bildirmediği için döngüde o alan hiç geçmez ve her işyeri abonesi mesken tarifesinden hesaplanır. İşlevi çağıran bir sınama bunu göremez, çünkü işlev doğrudur. Dört tanım kusuru için de sonuç 0,0000.

Toplam yakalama 0,6364. Bu sayı bu kusur kümesine özgüdür; taşınan şey oran değil, oranın nasıl bölündüğüdür. İşlevi sınayan bir katman, işlev ile tanım arasındaki uyumsuzluğa kördür ve bu körlük durum ekleyerek kapanmaz.

Koşumdan Bağımsız Katman

Katmanın karşılığı son bloktadır. Kusursuz araç tablosunda döngü iki tohumda da 0,9583 veriyor, ama düşen görev değişiyor: ilk koşumda G23, ikincide G15. Araç düzeyi sınama aynı tabloda iki kez koşuldu ve düşen durum kümesi iki kez de boş ve aynı çıktı, çünkü bu katman tohum kullanmaz.

Araç düzeyi sınamanın yargısı yinelenebilir: bir kusur ya her koşumda görünür ya da hiç görünmez. Döngü ölçüsünün yargısı bantlıdır: iki koşum farkı tek bir görevdir ve bandın altındaki hiçbir işaret bulgu sayılmaz.

Özet

  • Bir araç işlev ile tanımın birleşimidir; araç düzeyi sınama işlevi doğrudan çağırır, tanımı hiç kullanmaz.
  • On bir durumluk sınama kümesi dört değer kusurunun dördünü ve üç sözleşme kusurunun üçünü yakalar; her kusur tek bir durumda görünür ve düşen durum bozuk aracı adıyla verir.
  • Dört tanım kusurunun hiçbiri yakalanmaz: pay 0,0000, toplam yakalama 0,6364.
  • Körlüğün nedeni çağrı yoludur: döngü yalnız bildirilen alanları süzer, sınama kümesi işlevi doğrudan çağırıp o süzgeci atlar.
  • Katmanın karşılığı koşumdan bağımsızlıktır: döngünün düşen görevi tohum değişince G23’ten G15’e kayarken, sınama kümesinin düşen durum kümesi iki koşumda da boştur.

Sonraki Adım

Bu ders bir katmanın sınırını sayıyla çizdi: araç düzeyi sınama, aracın kendi sözünü tutup tutmadığını ucuza ve kesin söyler, ama aracın döngüye nasıl bağlandığını hiç görmez. Görülmeyen dört kusur bir yerde ortaya çıkmak zorundadır, çünkü ajan işyeri abonelerine yanlış bedel yazar. Bir sonraki ders senaryoları uçtan uca koşturur ve iki soruyu birlikte sorar: akış sınaması araç düzeyinin kaçırdığı kusurların kaçını yakalıyor, ve iki katmanın da göremediği kusur hangisidir.

İ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