İçeriğe geç
academia.sh

Ders 19 / 26

Belge ve Test Üretimi

Üretilen sınama kümesinin ayırt etme gücünün ölçülmesi: kurgu kod tabanının altı bozuk sürümü üzerinde koşturulan on dört üretilmiş sınamanın ikisi temiz kodda düşüyor, kalan on ikinin dokuzu yedi sürümün hepsinde aynı sonucu veriyor ve yalnız üçü bir kusuru ayırt ediyor. Yakalama 3/6, havuzun tamamıyla ulaşılabilecek tavan 6/6, elle eklenen tek bir hiza maddesi 4/6 yapıyor. Üretilen belgede on iddia makinece denetlenebiliyor, altısı doğru, biri olmayan bir işlevi anlatıyor.

İçindekiler

Önceki derste kabul sınaması elle yazılmıştı: on madde, bilinen bir kapsam, bilinen bir kör nokta. Sınamanın kendisi de bir yazma işidir ve bir kod yardımcısına en sık devredilen görev budur. Belge yazımı da öyledir. İkisinin ortak yanı, çıktının doğrudan çalıştırılmaması: üretilen sınama koşulmadıkça, üretilen belge okunmadıkça yanlışlığı görünmez.

Bu ders o iki çıktıyı ölçülebilir kılar. Sınama için ölçüt ayırt etme gücüdür: kod tabanının bozuk sürümlerini temiz sürümden ayırabiliyor mu. Belge için ölçüt denetlenebilirliktir: iddianın doğruluğu koda bakılarak makinece sınanabiliyor mu. İki ölçüt de üretilen metnin kendisine değil, onun verdiği bilgiye bakar.

  • YG39. Kod tabanı kurgudur ve beş modül, 53 satırdır. Hiçbir gerçek depo, paket ya da ürün değildir. Tohum 20260218.
  • YG40. Altı kusur kaynak metinde tek dizge değişimidir; her biri ayrı bir sürüm üretir. Temizle birlikte yedi sürüm vardır.
  • YG41. Bir sınama (test) bir gözlem noktası ile bir beklenen değerden oluşur. Kümedeki gözlem havuzu yirmi bir noktadır.
  • YG42. Üretilen küme havuzdan on dört nokta seçer ve beklenen değeri koddan okur. Okuma her zaman doğru değildir: bir pay yanlış beklenti taşır.
  • YG43. Yanlış beklentili sınama temiz sürümde düşen sınamadır. Geçerlilik denetimi budur ve ayırt etme ölçümünden önce yapılır.
  • YG44. Hep geçen sınama, yedi sürümün hepsinde aynı sonucu veren sınamadır. Bu, sınamanın değersiz olduğu anlamına gelmez; bu altı kusura karşı bilgi taşımadığı anlamına gelir.
  • YG45. Üretilen küme çıktı biçimini içerik ve satır sayısıyla ölçer, sütun genişliğiyle ölçmez. Bu, önceki derste elle yazılan sınamanın da kör noktasıydı.
  • YG46. Üretilen belge iki parçadır: işlev adı ve parametre sayısı taşıyan denetlenebilir iddialar, ve modül başına bir serbest cümle. Serbest cümle makinece sınanamaz.
  • YG47. Bu ders bir saldırı dersi değildir; koşan kod sınama, sayım ve doğrulamadır.

Yedi Sürüm ve Kusurun Göründüğü Yer

Ayırt etme gücünü ölçmek için ayırt edilecek bir şey gerekir. Kod tabanına konan altı kusur bunu sağlar: her biri kaynak metinde tek bir dizgeyi değiştirir ve gözlemlenebilir bir sonucu oynatır. Aşağıdaki blok altı sürümü kurar ve her kusurun hangi gözlemde göründüğünü basar.

# KURGU kod tabani: olcum aginin kendi yardimci betikleri. Gercek bir depo,
# paket ya da urun degildir. Bes modul, 53 satir. Tohum 20260218.
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 izno(m):
    h = 2166136261
    for c in m:
        h = ((h ^ ord(c)) * 16777619) & M32
    return h


KOD = {
    "okuma.py": """
def ayristir(satir):
    p = satir.split(";")
    if len(p) != 4:
        return None
    if not p[3].lstrip("-").isdigit():
        return None
    return {"abone": p[0], "bolge": p[1], "donem": p[2], "endeks": int(p[3])}

def tuketim(onceki, simdiki):
    fark = simdiki - onceki
    if fark < 0:
        fark = fark + 100000
    return fark
""",
    "tarife.py": """
BASAMAK = ((10, 4), (25, 7), (40, 11))
USTU = 16

def bedel(m3):
    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
    return top + kalan * USTU
""",
    "denetim.py": """
GECERLI_BOLGE = ("dogu", "bati", "kuzey", "guney", "merkez")

def gecerli(kayit):
    if kayit is None:
        return False
    if kayit["endeks"] < 0:
        return False
    return kayit["bolge"] in GECERLI_BOLGE

def ayikla(kayitlar):
    return [k for k in kayitlar if gecerli(k)]
""",
    "ozet.py": """
def bolge_toplam(kayitlar, tuketimler):
    top = {}
    for k, t in zip(kayitlar, tuketimler):
        top[k["bolge"]] = top.get(k["bolge"], 0) + t
    return top

def bolge_sayi(kayitlar):
    say = {}
    for k in kayitlar:
        say[k["bolge"]] = say.get(k["bolge"], 0) + 1
    return say
""",
    "bicim.py": """
def satir(bolge, m3, tutar):
    return bolge.ljust(8) + str(m3).rjust(7) + str(tutar).rjust(9)

def rapor(baslik, satirlar):
    return "\\n".join([baslik] + satirlar)
""",
}
HATA = [("H1", "tarife.py", "USTU = 16", "USTU = 11"),
        ("H2", "okuma.py", "if fark < 0:", "if fark < -100000:"),
        ("H3", "denetim.py", 'kayit["endeks"] < 0', 'kayit["endeks"] < -1'),
        ("H4", "ozet.py", 'top.get(k["bolge"], 0) + t', 'top.get(k["bolge"], 0) + 1'),
        ("H5", "okuma.py", "len(p) != 4", "len(p) < 4"),
        ("H6", "bicim.py", "rjust(9)", "rjust(7)")]
KAYITLAR = [{"bolge": "dogu", "endeks": 120}, {"bolge": "yildiz", "endeks": 40},
            {"bolge": "bati", "endeks": 90}]


def yukle(kod):
    ad = {}
    for dosya, kaynak in kod.items():
        alan = {}
        exec(kaynak, alan)
        ad[dosya] = alan
    return ad


def bozuk(kod, hata):
    _, dosya, eski, yeni = hata
    yeni_kod = dict(kod)
    yeni_kod[dosya] = kod[dosya].replace(eski, yeni, 1)
    return yeni_kod


def gozlem(kod):
    a = yukle(kod)
    return {"bedel60": a["tarife.py"]["bedel"](60),
            "donus": a["okuma.py"]["tuketim"](99800, 300),
            "eksi": a["denetim.py"]["gecerli"]({"bolge": "dogu", "endeks": -1}),
            "toplam": a["ozet.py"]["bolge_toplam"](KAYITLAR, [12, 5, 8])["dogu"],
            "fazla": a["okuma.py"]["ayristir"]("A017;dogu;2026-01;41250;ek") is None,
            "boy": len(a["bicim.py"]["satir"]("dogu", 12, 48))}


BOY = {d: len(k.strip().split("\n")) for d, k in KOD.items()}
TEMIZ = gozlem(KOD)
print("kod tabani:", BOY, "toplam", sum(BOY.values()), "satir")
print("temiz gozlem:", TEMIZ)
for h in HATA:
    b = gozlem(bozuk(KOD, h))
    print(h[0], h[1], {k: (TEMIZ[k], b[k]) for k in TEMIZ if TEMIZ[k] != b[k]})
kod tabani: {'okuma.py': 13, 'tarife.py': 13, 'denetim.py': 11, 'ozet.py': 11, 'bicim.py': 5} toplam 53 satir
temiz gozlem: {'bedel60': 630, 'donus': 500, 'eksi': False, 'toplam': 12, 'fazla': True, 'boy': 24}
H1 tarife.py {'bedel60': (630, 530)}
H2 okuma.py {'donus': (500, -99500)}
H3 denetim.py {'eksi': (False, True)}
H4 ozet.py {'toplam': (12, 1)}
H5 okuma.py {'fazla': (True, False)}
H6 bicim.py {'boy': (24, 22)}

Altı kusurun altısı da gözlemlenebilir. Hiçbiri gizli değildir, hiçbiri çalışma zamanında hata fırlatmaz; her biri bir sayının ya da bir doğruluk değerinin yerini değiştirir. Bu, ölçümün adil olmasını sağlar: bir kusur yakalanmıyorsa sebep kusurun görünmezliği değil, sınamanın oraya bakmamasıdır.

Son satır bunun en açık örneğidir. Biçim kusuru yalnız satır boyunda görünür: yirmi dört damgalık satır yirmi ikiye iner. Bölge adı yerindedir, sayılar doğrudur, satır sayısı aynıdır. Bu kusuru yakalayan tek gözlem noktası satırın uzunluğunu ölçendir.

Üretilen Sınamanın Ayırt Etme Gücü

Yardımcıdan sınama istendiğinde ne gelir: yirmi bir gözlem noktasından on dördü ve her biri için koddan okunmuş bir beklenen değer. Okuma çoğunlukla doğrudur, bir payı değildir. Üretilen küme önce geçerlilikten, sonra ayırt etme gücünden geçirilir.

def olcum(kod):
    a = yukle(kod)
    o, t, d, z, b = (a["okuma.py"], a["tarife.py"], a["denetim.py"],
                     a["ozet.py"], a["bicim.py"])
    return {"ayristir-tam": o["ayristir"]("A017;dogu;2026-01;41250")["endeks"],
            "ayristir-bos": o["ayristir"]("") is None,
            "ayristir-fazla": o["ayristir"]("A017;dogu;2026-01;41250;ek") is None,
            "ayristir-harf": o["ayristir"]("A017;dogu;2026-01;xx") is None,
            "tuketim-duz": o["tuketim"](100, 300),
            "tuketim-donus": o["tuketim"](99800, 300),
            "bedel-sifir": t["bedel"](0),
            "bedel-ilk": t["bedel"](10),
            "bedel-orta": t["bedel"](25),
            "bedel-ust": t["bedel"](60),
            "gecerli-yok": d["gecerli"](None),
            "gecerli-eksi": d["gecerli"]({"bolge": "dogu", "endeks": -1}),
            "gecerli-bolge": d["gecerli"]({"bolge": "yildiz", "endeks": 5}),
            "ayikla-bos": d["ayikla"]([]),
            "ayikla-uc": len(d["ayikla"](KAYITLAR)),
            "toplam-bolge": z["bolge_toplam"](KAYITLAR, [12, 5, 8]),
            "sayi-bos": z["bolge_sayi"]([]),
            "sayi-uc": z["bolge_sayi"](KAYITLAR),
            "satir-icerik": "dogu" in b["satir"]("dogu", 12, 48),
            "rapor-satir": b["rapor"]("bas", ["a", "b"]).count("\n"),
            "satir-boy": len(b["satir"]("dogu", 12, 48))}


HAVUZ = [a for a in olcum(KOD) if a != "satir-boy"]     # uretilen kume hizaya bakmaz
SURUM = [("temiz", KOD)] + [(h[0], bozuk(KOD, h)) for h in HATA]
TABLO = {ad: olcum(k) for ad, k in SURUM}


def saptir(v):
    if isinstance(v, bool):
        return not v
    if isinstance(v, int):
        return v + 30
    if isinstance(v, dict):
        return {}
    return [0]


def uret_sinama(tohum=TOHUM, n=14):
    r = uretec(tohum)
    kalan, secili = list(HAVUZ), []
    for _ in range(n):
        secili.append(kalan.pop(int(r() * len(kalan))))
    return [(ad, saptir(TABLO["temiz"][ad]) if r() < 0.14 else TABLO["temiz"][ad])
            for ad in secili]


def kos(kume, surum):
    return [ad for ad, bek in kume if TABLO[surum][ad] != bek]


def degerlendir(kume):
    yanlis = kos(kume, "temiz")
    saglam = [(ad, b) for ad, b in kume if ad not in yanlis]
    yakalanan = [h[0] for h in HATA if kos(saglam, h[0])]
    hep_gecen = [ad for ad, b in saglam
                 if not any(ad in kos(saglam, h[0]) for h in HATA)]
    return len(kume), len(yanlis), len(saglam), yakalanan, hep_gecen


print("surum sayisi:", len(SURUM), "| havuz:", len(HAVUZ), "olcum")
print("kume         | sinama yanlis saglam | yakalanan           | hep gecen")
for etiket, kume in (("tohum 218", uret_sinama(TOHUM)),
                     ("tohum 219", uret_sinama(TOHUM + 1)),
                     ("havuzun tumu", [(a, TABLO["temiz"][a]) for a in olcum(KOD)])):
    n, y, s_, yk, hg = degerlendir(kume)
    print("%-12s | %6d %6d %6d | %d/6 %-15s | %d"
          % (etiket, n, y, s_, len(yk), ",".join(yk), len(hg)))
kume = uret_sinama(TOHUM)
print("yanlis beklentili:", kos(kume, "temiz"))
print("hep gecen:", sorted(degerlendir(kume)[4]))
elle = kume + [("satir-boy", TABLO["temiz"]["satir-boy"])]
print("elle tek madde eklendikten sonra:", degerlendir(elle)[3])


def uret_belge(tohum=TOHUM):
    a = yukle(KOD)
    iddia = []
    for dosya in KOD:
        r = uretec(tohum ^ izno(dosya))
        for f in sorted(x for x in a[dosya]
                        if not x.startswith("__") and callable(a[dosya][x])):
            n = a[dosya][f].__code__.co_argcount
            iddia.append((dosya, f, n + 1 if r() < 0.22 else n))
        if r() < 0.40:
            iddia.append((dosya, "dogrula", 1))
    return iddia


def belge_dene(iddia):
    a = yukle(KOD)
    yok = [i[0] + ":" + i[1] for i in iddia if i[1] not in a[i[0]]]
    yanlis = [i[0] + ":" + i[1] for i in iddia if i[1] in a[i[0]]
              and a[i[0]][i[1]].__code__.co_argcount != i[2]]
    return yok, yanlis


print()
print("tohum     | denetlenir serbest | olmayan islev | yanlis sayi | dogru")
for tohum in (TOHUM, TOHUM + 1):
    ids = uret_belge(tohum)
    yok, yanlis = belge_dene(ids)
    print("%d  | %10d %7d | %13d | %11d | %5d"
          % (tohum, len(ids), len(KOD), len(yok), len(yanlis),
             len(ids) - len(yok) - len(yanlis)))
ids = uret_belge(TOHUM)
yok, yanlis = belge_dene(ids)
print("olmayan islev iddiasi:", yok, "| yanlis parametre sayisi:", yanlis)
surum sayisi: 7 | havuz: 20 olcum
kume         | sinama yanlis saglam | yakalanan           | hep gecen
tohum 218    |     14      2     12 | 3/6 H1,H2,H3        | 9
tohum 219    |     14      1     13 | 3/6 H1,H2,H4        | 10
havuzun tumu |     21      0     21 | 6/6 H1,H2,H3,H4,H5,H6 | 15
yanlis beklentili: ['bedel-sifir', 'ayikla-bos']
hep gecen: ['ayikla-uc', 'ayristir-bos', 'ayristir-harf', 'bedel-orta', 'gecerli-bolge', 'gecerli-yok', 'rapor-satir', 'satir-icerik', 'tuketim-duz']
elle tek madde eklendikten sonra: ['H1', 'H2', 'H3', 'H6']

tohum     | denetlenir serbest | olmayan islev | yanlis sayi | dogru
20260218  |         10       5 |             1 |           3 |     6
20260219  |         11       5 |             2 |           0 |     9
olmayan islev iddiasi: ['bicim.py:dogrula'] | yanlis parametre sayisi: ['denetim.py:ayikla', 'bicim.py:rapor', 'bicim.py:satir']

İlk satır on dört sınamanın ikisinin temiz kodda düştüğünü söylüyor: bedel hesabının sıfır girdisi ve boş listeden süzme. Bunlar kusur bulmuyor, kusur uyduruyor. Kırmızı bir sınamayla karşılaşan geliştirici için asıl tehlike buradadır: sınama yanlışken kodu ona uydurmak, çalışan bir kodu bozmak demektir. Bu iki maddenin ayıklanması ölçümden önce gelen ve bedeli insan zamanıyla ödenen bir iştir.

Kalan on iki sınamadan dokuzu yedi sürümün hepsinde aynı sonucu veriyor. Bunlar hatalı değildir; boş liste süzmek, geçersiz bölge reddetmek ve rapor satır sayısını saymak akla yatkın maddelerdir. Ama bu altı kusurun hiçbirine karşı bilgi taşımıyorlar. Bilgi taşıyan sınama sayısı üçtür ve yakalama 3/6’dır. On dört maddelik bir kümenin ayırt eden payı 0,2143, yani her yedi sınamadan biri buçuk.

İkinci koşum sayının kararlı olduğunu gösteriyor: on dört sınama, bir yanlış beklenti, on üç sağlam, yine 3/6 yakalama ve on hep geçen. Yakalanan kusurların kimliği değişiyor (ilk koşumda H3, ikincisinde H4), toplamı değişmiyor.

Üçüncü satır tavanı veriyor. Havuzun yirmi bir noktasının tamamı sınamaya çevrildiğinde yakalama 6/6 olur ve hep geçen sayısı 15’e çıkar. Bu, hep geçmenin bir kusur olmadığının kanıtıdır: tam kapsamlı bir kümede bile maddelerin dörtte üçü bu altı kusura karşı sessizdir. Ayırt etme gücü madde sayısıyla değil, doğru noktayla gelir.

Son satır bunu tek bir hamlede gösteriyor. Üretilen kümeye elle bir madde eklenir — satırın uzunluğunu ölçen madde — ve yakalama 3/6’dan 4/6’ya çıkar. Bir sınamanın kattığı, on dört sınamanın kattığından fazladır, çünkü o sınama hiçbirinin bakmadığı yere bakar.

Üretilen Belgenin Denetlenebilir Kısmı

Belge tarafında ölçüt farklıdır: doğruluk değil, doğrulanabilirlik önce gelir. Üretilen belge modül başına bir serbest cümle ve işlev başına bir yapısal iddia içeriyor. Serbest cümleler koda bakılarak sınanamaz; yapısal iddialar sınanabilir, çünkü işlevin var olup olmadığı ve kaç parametre aldığı yüklenen ad alanından okunur.

İlk koşumda on denetlenebilir iddia ve beş serbest cümle var: on beş ifadenin 0,6667’si makinenin erişebildiği alanda. Denetlenen onun altısı doğru. Üçü işlevin parametre sayısını bir fazla söylüyor, biri hiç var olmayan bir doğrulama işlevini anlatıyor. İkinci koşumda on bir iddianın dokuzu doğru, ikisi olmayan işlev. İki koşumda da yanlış iddia payı sıfır değildir ve yanlışın iki ayrı türü vardır: var olan bir şeyi yanlış anlatmak ve olmayan bir şeyi anlatmak. İkincisi daha tehlikelidir, çünkü okuyucu belgede gördüğü işlevi çağırmaya kalkar.

Özet

  • Altı kusurun altısı da gözlemlenebilir; biçim kusuru yalnız satır boyunda görünür ve yirmi dört damgalık satırı yirmi ikiye indirir.
  • Üretilen on dört sınamanın ikisi temiz kodda düşer: kusur bulmaz, kusur uydurur ve ayıklanması ölçümden önce gelen bir iştir.
  • Kalan on iki sınamanın dokuzu yedi sürümün hepsinde aynı sonucu verir; bilgi taşıyan üç sınamayla yakalama 3/6’dır ve ikinci koşumda da 3/6 kalır.
  • Havuzun tamamı 6/6 verir ama hep geçen sayısı 15’e çıkar: ayırt etme gücü madde sayısından değil, bakılan noktadan gelir.
  • Elle eklenen tek bir hiza maddesi yakalamayı 4/6’ya çıkarır; bir doğru sınama on dört üretilmiş sınamadan fazla katar.
  • Üretilen belgede on beş ifadenin onu makinece denetlenebilir, altısı doğrudur; yanlışın bir türü var olan işlevi yanlış anlatmak, diğeri olmayan bir işlevi anlatmaktır.

Sonraki Adım

İki derste de ölçülen şey çıktının kalitesiydi: yamanın doğruluğu, sınamanın ayırt etme gücü, belgenin denetlenebilirliği. Bu ölçümlerin hepsi çıktı elde edildikten sonra yapılır ve hepsi kusurun kod tabanında olduğunu varsayar. Oysa devrin iki ucunda daha sayılacak şey vardır. Girdi ucunda modele gönderilen bağlamın içinde ne olduğu hiç sorulmadı: hangi alanlar sır sınıfına giriyor ve bir süzgeç kaçını çıkarıyor. Çıktı ucunda ise kabul kararının kendisi hiç sorgulanmadı: insan bir öneriyi kabul ettiğinde ne kadar sık haklı oluyor. Sonraki ders üç riski tek tek sayar ve konuyu kapatır.

İ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