İçeriğe geç
academia.sh

Ders 08 / 14

Belge Testleri

On iki belge iddiasının yedisi bir biçim değişikliğiyle bayatlar; düzyazı ağırlıklı yazımda bunların 4'ü sese döner ve 3'ü sessiz kalır, iki iddia örneğe çevrildiğinde sesli sayı 6'ya çıkar ve geriye yalnız gerekçe kalır — aynı bayatlama, 2 ayrı sonuç.

İçindekiler

Önceki ders sahte nesnenin ayrı sonuç sayısını üçten bire indirdiğini, ama gerçek kaynakla uyum hakkında hiçbir şey söylemediğini ölçtü. Oradaki iddialar — beklenen sürüm kümesi, beklenen çağrı sayısı — bir test dosyasında yazılıydı ve her koşumda çalışıyordu. Yanlış olabilirlerdi, ama sessiz kalamazlardı.

Bir projenin doğruluk iddiaları yalnız test dosyalarında durmaz. Bir işlevin belge dizgisinde yazan “bu çağrı şu değeri döndürür” cümlesi de bir iddiadır. Aradaki fark şudur: o cümleyi hiçbir koşum sınamaz. Kod değiştiğinde cümle olduğu yerde kalır, hiçbir şey kırılmaz ve iddia sessizce yanlışlanır. Bu dersin sorusu şudur: aynı belge, aynı değişiklikten sonra kaç bayat iddiayı sese çevirir?

Sessiz Bayatlama ve Sesli Bayatlama

Bir doküman iddiasının bağlandığı yüzey değiştiğinde iddia bayatlar (staleness). Yazılım Geliştirme Pratiği müfredatının Teknik Yazarlık ve Dokümantasyon kursu bunu elli altı iddia üzerinden ölçmüş ve iki sınıfa ayırmıştı: çalıştırılabilir örneğe gömülü bir iddia bayatlayınca ses çıkarır — örnek çalışmaz; gömülü olmayan bir iddia bayatlayınca hiçbir şey olmaz, bu sessiz bayatlamadır. O ölçümde elli altı iddianın on yedisi gömülüydü ve bu sayı bir veriydi, bir seçim değil.

Bu derste gömülülük bir seçimdir. Ölçüm oradaki sayıları tekrarlamaz; sorduğu soru farklıdır: aynı iddia düzyazı olarak da, çalıştırılabilir örnek olarak da yazılabiliyorsa, yazımı değiştirmek sese dönen bayat iddia sayısını ne yapar? Yani orada gömülülük ölçümün girdisiydi, burada ölçümün değişkenidir.

Bunu ölçülebilir kılan düzeneğe belge testi (doctest) denir ve standart kitaplığın doctest modülü tarafından sağlanır. Düzenek basittir: belge metnindeki etkileşimli kabuk istemiyle başlayan satırlar birer ifade, altlarındaki satırlar da beklenen çıktı sayılır. Modül ifadeleri çalıştırır, çıktıyı beklenenle karşılaştırır ve uyuşmayanları düşen örnek olarak sayar. Yani belge, kendisi bir test takımı haline gelir.

Belge testinin bir birim test takımının yerini almadığını baştan yazmak gerekiyor. Sınadığı şey belgede yazan örnektir; belgede yazmayan bir sınır durumu, bir hata yolu ya da bir başarısızlık senaryosu belge testine hiç girmez. Örnekleri kapsayıcı olsun diye çoğaltmak da belgeyi okunmaz hale getirir — belge okunmak için yazılır, sınamak için değil. Belge testinin işi bu yüzden kapsamı büyütmek değil, zaten yazılmış olan iddiaları ayakta tutmaktır.

İddianın İki Yazımı

Aynı bilgi iki türlü yazılabilir. “Sürüm metni noktayla ayrılmış sayılardan oluşur” bir düzyazı iddiadır; okunur, anlaşılır ve hiçbir şey tarafından sınanmaz. Aynı bilgi bir çağrı ile beklenen çıktısı olarak yazıldığında ise sınanabilir hale gelir — ve okunurluğu da genellikle artar, çünkü örnek soyut bir tarifin yerine somut bir değer koyar.

İkisi arasındaki seçim bir üslup tercihi gibi görünür, oysa ölçülebilir bir farkı vardır. Düzyazı iddia bayatladığında bunu ancak bir insan okuyup fark eder; örnek bayatladığında koşum kırmızı olur. Ölçüm bu farkı tam olarak sayıyor.

Belge Testi Neyin Yazılabileceğini Sınırlar

Belge testinin karşılaştırma kuralı katıdır: beklenen çıktı ile gerçekleşen çıktının metin olarak eşit olması gerekir. Bu kural, belgeye hangi örneğin konabileceğini de belirler — çıktısı her koşumda aynı olmayan bir örnek belgeye konamaz.

Python’da bu kısıtın birkaç bilinen kaynağı vardır. Bir nesnenin varsayılan gösterimi kimliğine bağlı bir sayı taşır ve o sayı koşumdan koşuma değişir; kayan noktalı bir sayının gösterimi işlemin sırasına duyarlıdır; bir kümenin ya da sözlük anahtarlarının yazdırılma düzeni, üzerinde durulması gereken ayrı bir konudur. Bu yüzden belge örnekleri ya belirlenimci bir değer döndürecek biçimde yazılır, ya da çıktı sıralanarak, biçimlendirilerek ya da bir karşılaştırmaya indirgenerek belirlenimci hale getirilir.

Ölçümdeki örnekler bunu doğrudan gösteriyor. Fark işlevinin sonucu sorted ile sıralanmış bir listedir; sıralama olmasaydı örnek koşumdan koşuma başka bir düzen verebilirdi. Kilit metnini üreten işlev de paketleri ad sırasına göre yazar. İkisi de belgeyi sınanabilir kılmak için değil, sonucu okunur kılmak için yapılmış tasarım kararlarıdır — ama belge testi bu kararların üzerine oturur.

Buradan çıkan sonuç ilk bakışta ters görünür: belge testini eklemek, belgeyi değil kodu etkiler. Bir işlevin çıktısı belirlenimci değilse, o işlevin belgesi sınanabilir hale gelemez; getirilmek isteniyorsa değişmesi gereken şey işlevin kendisidir.

Örneğe Çevrilemeyen İddia

Her düzyazı iddia örneğe çevrilemez ve bu, belge testinin sınırıdır. Belge testi bir ifadenin değerini karşılaştırır; değeri olmayan bir cümleyi karşılaştıramaz.

Ölçümdeki altı düzyazı iddiadan beşi bir çağrının sonucuna bağlıdır ve örneğe çevrilebilir. Altıncısı bir gerekçedir: “önek yazılmaz, çünkü metin sayı gibi sıralanır.” Bu cümlenin sınanabilir yarısı — önek yazılmaması — zaten başka bir örnekte sınanıyor; sınanamayan yarısı ise cümlenin asıl taşıdığı bilgidir, yani kararın nedeni. Bir nedenin döndürdüğü değer yoktur.

Bu ayrım pratikte şuna karşılık gelir: belge testi ne olduğunu yazan cümleleri koruyabilir, neden öyle olduğunu yazan cümleleri koruyamaz. İkinci sınıf, bayatladığında en pahalı olan sınıftır, çünkü yanlış bir gerekçe okuyan kişiyi yanlış bir kararla baş başa bırakır. Bir örnek düştüğünde okuyan kişi belgeye güvenmeyi bırakır; bir gerekçe bayatladığında ise okuyan kişi belgeye güvenmeye devam eder ve yanlış tarafa yönelir.

Ölçümün Varsayımları

  • PT29 — Belgelenen dört işlev bu dersin kendi işlevleridir: sürüm metni, kilit metni ve iki sözlük arasındaki farkı veren işlev. Ortak tanımın DEPO, BILDIRIM ve indirim tanımlarına dokunulmaz.
  • PT30 — İddia toplamı 12’dir. Altısı belge metnindeki çalıştırılabilir örnek, altısı düzyazı cümledir. İki yazımda da toplam aynıdır; değişen yalnız dağılımdır.
  • PT31 — Değişiklik bir biçim değişikliğidir: sürüm metnine bir önek eklenir ve kilit metninin alan ayracı değişir. Fark işlevi dokunulmadan kalır.
  • PT32 — Kâhin kurgunun kendisidir: her iki gerçekleştirimi de biz yazdığımız için hangi iddianın bayatladığını biliyoruz. Düzyazı iddiaların bayatlığı bu bilgiden işaretlenir.
  • PT33 — Çalıştırılabilir örneklerin bayatlığı işaretlenmez, ölçülür: belge testi koşturulur ve düşen örnek sayısı okunur.
  • PT34 — İkinci yazımda, örneğe çevrilebilen bayat düzyazı iddialar örneğe çevrilir. Bayat olmayanlar düzyazı kalır; ölçüm yalnız bayatlamanın sesini sayar.
  • PT35 — Örneğe çevrilemeyen tek iddia gerekçedir ve her iki yazımda da düzyazı kalır.
  • PT36 — Belge testi belge metnini kaynaktan ayrı bir dizgi olarak alır ve iki ayrı gerçekleştirim kümesiyle koşturulur; koşum çıktısı yutulur, yalnız sayılar okunur.
  • PT37 — Bir örnek, beklenen çıktısı gerçekleşen çıktıya eşit değilse düşer; kısmi eşleşme yoktur.
  • PT38 — Değişiklikten önce her iki yazım da 0 başarısızlık verir; belge yazıldığı gerçekleştirimle uyumludur.
  • PT39 — Süre ölçülmez, depoya dosya bırakılmaz. Sayılan şey iddia, bayat iddia, düşen örnek ve sessiz kalan iddia sayısıdır.
  • PT40 — Ayrı sonuç sayısı, iki belge yazımının verdiği sese dönen bayat iddia sayılarının birbirinden farklı olanlarının sayısıdır.

Ölçüm

"""Belge testi: ayni belge iki yazimda, ayni bayatlama iki ayri sonuc.

Bolum 1 - belgedeki ornekler kosturulur; duzyazi iddialar kahinden isaretlenir.
Bolum 2 - iki duzyazi iddia ornege cevrilir ve olcum yinelenir.
"""
import doctest

SECIM = {"olcum": (1, 2), "ortak": (3, 2), "rapor": (1, 1)}
ONCEKI = {"olcum": (1, 1), "ortak": (3, 2), "rapor": (1, 1)}


def eski_surum(s):
    return ".".join(map(str, s))


def yeni_surum(s):
    return "v" + ".".join(map(str, s))


def eski_kilit(secim):
    return " ".join(f"{p}={eski_surum(secim[p])}" for p in sorted(secim))


def yeni_kilit(secim):
    return ", ".join(f"{p}={yeni_surum(secim[p])}" for p in sorted(secim))


def fark(a, b):
    return sorted(p for p in a if a[p] != b[p])


ESKI = {"surum": eski_surum, "kilit": eski_kilit, "fark": fark,
        "SECIM": SECIM, "ONCEKI": ONCEKI}
YENI = {"surum": yeni_surum, "kilit": yeni_kilit, "fark": fark,
        "SECIM": SECIM, "ONCEKI": ONCEKI}

ORNEKLER = """
>>> surum((1, 2))
'1.2'
>>> surum((3, 0))
'3.0'
>>> kilit(SECIM)
'olcum=1.2 ortak=3.2 rapor=1.1'
>>> kilit(ONCEKI)
'olcum=1.1 ortak=3.2 rapor=1.1'
>>> fark(SECIM, ONCEKI)
['olcum']
>>> fark(SECIM, SECIM)
[]
"""

EK_ORNEKLER = """
>>> set(surum((1, 2))) <= set('0123456789.')
True
>>> kilit(SECIM).count(',')
0
"""

# Duzyazi iddialar: metin, degisiklikten sonra bayat mi, ornege cevrilebilir mi.
DUZYAZI = (("sürüm metni yalnız rakam ve nokta taşır", True, True),
           ("alt sınır dahil, üst sınır dışlanmıştır", False, True),
           ("paketler ad sırasına göre yazılır", False, True),
           ("kilit metninde alanlar virgülsüz ayrılır", True, True),
           ("fark listesi ad sırasına göre verilir", False, True),
           ("önek yazılmaz, çünkü metin sayı gibi sıralanır", True, False))


def kosum(metin, kume):
    test = doctest.DocTestParser().get_doctest(metin, dict(kume), "belge", None, 0)
    sonuc = doctest.DocTestRunner(verbose=False).run(test, out=lambda s: None)
    return sonuc.attempted, sonuc.failed


BAYAT_DUZYAZI = sum(1 for _, b, _ in DUZYAZI if b)
CEVRILEN = sum(1 for _, b, c in DUZYAZI if b and c)
YAZIMLAR = (("düzyazı ağırlıklı", ORNEKLER, 0),
            ("iki iddia örnekte", ORNEKLER + EK_ORNEKLER, CEVRILEN))

print(f"{'belge yazımı':<19s} {'örnek':>6s} {'düzyazı':>8s} {'sesli bayat':>12s} "
      f"{'sessiz bayat':>13s}")
sesli, sessizler = [], []
for ad, metin, cevrilen in YAZIMLAR:
    denenen, dusen = kosum(metin, YENI)
    sessiz = BAYAT_DUZYAZI - cevrilen
    sesli.append(dusen)
    sessizler.append(sessiz)
    print(f"{ad:<19s} {denenen:6d} {len(DUZYAZI) - cevrilen:8d} {dusen:12d} "
          f"{sessiz:13d}")
print(f"iddia toplamı iki yazımda da {len(DUZYAZI) + kosum(ORNEKLER, YENI)[0]}, "
      f"bayatlayan {sesli[0] + BAYAT_DUZYAZI}")
print(f"sese dönen bayat iddia {sesli[0]} ve {sesli[1]}: "
      f"{len(set(sesli))} ayrı sonuç")
print(f"örneğe çevirmek {sessizler[0] - sessizler[1]} sessiz bayatlamayı sese "
      f"çevirdi; {sessizler[1]} tanesi gerekçe olduğu için çevrilemedi")
print(f"aynı belge değişiklikten önce {kosum(ORNEKLER, ESKI)[1]} ve "
      f"{kosum(ORNEKLER + EK_ORNEKLER, ESKI)[1]} başarısızlık verir")
belge yazımı         örnek  düzyazı  sesli bayat  sessiz bayat
düzyazı ağırlıklı        6        6            4             3
iki iddia örnekte        8        4            6             1
iddia toplamı iki yazımda da 12, bayatlayan 7
sese dönen bayat iddia 4 ve 6: 2 ayrı sonuç
örneğe çevirmek 2 sessiz bayatlamayı sese çevirdi; 1 tanesi gerekçe olduğu için çevrilemedi
aynı belge değişiklikten önce 0 ve 0 başarısızlık verir

Aynı Bayatlama, İki Ayrı Sonuç

Son satır ölçümün başlangıç noktasını veriyor: değişiklikten önce her iki yazım da 0 başarısızlık üretiyor. Belge, yazıldığı gerçekleştirimle uyumlu ve iddiaların tamamı doğru. Bayatlama, biçim değiştiğinde doğuyor. Bu satır olmadan ölçümün geri kalanı okunamazdı: düşen örneklerin bayatlamadan geldiğini söyleyebilmek için, değişiklikten önce hiçbirinin düşmediğini göstermek gerekiyordu.

Değişiklikten sonra on iki iddianın 7’si bayatlıyor: dördü çalıştırılabilir örnek, üçü düzyazı. Bayat iddia sayısı iki yazımda da aynı — belgenin nasıl yazıldığı neyin yanlışlandığını değiştirmiyor. Değiştirdiği tek şey, yanlışlanmanın duyulup duyulmadığı.

Düzyazı ağırlıklı yazımda belge testi 4 başarısızlık veriyor ve 3 bayat iddia sessiz kalıyor. İki bayat düzyazı iddia örneğe çevrildiğinde sesli sayı 6’ya çıkıyor, sessiz kalan 1’e iniyor. Aynı bayatlama, iki belge yazımında 2 ayrı sonuç.

Bu iki sayı arasındaki fark tam olarak yazımın kendisidir. Kod aynı, değişiklik aynı, bayatlayan iddia kümesi aynı. Yalnız iki cümle düzyazıdan örneğe geçti ve bayatlamanın görünürlüğü ikiden fazlasına yayıldı. Belge testinin ölçüme kattığı şey budur: bir iddiayı, yanlışlandığında konuşacak biçimde yazmak.

Sesli sayının nereden geldiğine dikkat etmek gerekiyor. Dört çalıştırılabilir örnek bayatladığı için düştü, ve düşmelerini biz işaretlemedik — belge testi koşturuldu ve sayı okundu. Düzyazı iddiaların bayatlığı ise ölçülemedi, kâhinden işaretlendi: iki gerçekleştirimi de biz yazdığımız için hangi cümlenin artık doğru olmadığını biliyoruz. Ölçümün asimetrisi rastlantı değil, konunun kendisidir. Ses çıkaran iddia sayılabilir, sessiz kalan iddia ancak bilinebilir — ve gerçek bir projede o bilgi de yoktur.

Bir başka okuma da şu: iki yazım arasındaki fark 2, bayat düzyazı iddia sayısı ise 3’tü. Yani örneğe çevirme işlemi bayat düzyazı iddiaların hepsini kurtarmadı, üçte ikisini kurtardı. Bu oran kurgunun bir seçimi değil, iddia sınıflarının doğal dağılımının bir örneğidir: bir belgede her zaman değeri olmayan cümleler bulunur.

Geriye Kalan

Sessiz kalan tek iddia gerekçedir ve orada kalmasının nedeni bir eksiklik değil, bir sınırdır. Belge testi bir ifadeyi çalıştırıp değerini karşılaştırır; “çünkü” ile başlayan bir cümlenin çalıştırılacak bir ifadesi yoktur. Bu iddia ancak bir insanın okumasıyla denetlenebilir ve o denetimin nereye oturtulacağı Yazılım Geliştirme Pratiği müfredatının konusudur.

Ölçümün burada söylediği şey, sessiz kalan sayının sıfıra indirilemeyeceğidir. Belge testi sessiz bayatlamayı azaltır, ortadan kaldırmaz; kaldırabilseydi, bütün belge çalıştırılabilir olurdu ve o zaman belge olmaktan çıkıp test olurdu. Bir belgenin değeri de zaten yalnız sınanabilir cümlelerinde değil; okuyanı bir karara götüren cümlelerinde.

Ters yönde bir sınır daha var. Örneğe çevrilen her iddia, belgeyi kırılganlaştırır. İkinci yazımdaki iki yeni örnek, sürüm metninin karakter kümesini ve kilit metnindeki virgül sayısını sınıyor; bunlar davranışın kendisi değil, biçiminin ayrıntısı. Biçim bilerek değiştirildiğinde bu örnekler de düşer ve düştüklerinde söyledikleri şey “kod bozuldu” değil, “belge güncellenmeli” olur. Belge testinin yararı da zaten budur: iki mesajı da aynı kanaldan, aynı anda verir.

Son olarak bir sayının ne olmadığını yazmak gerekiyor. 6/12, belgenin yarısının sınandığı anlamına gelmez; belgedeki iddiaların yarısının yanlışlandığında ses çıkardığı anlamına gelir. Belgede yazmayan bir iddia bu ölçüme hiç girmez, ve bir işlevin belgelenmemiş davranışı bu sayının tamamen dışındadır.

Özet

  • Bir belge dizgisindeki cümle de bir doğruluk iddiasıdır; farkı, hiçbir koşumun onu sınamamasıdır. Bağlandığı yüzey değiştiğinde sessizce yanlışlanır.
  • Belge testi, belge metnindeki çağrıları çalıştırıp beklenen çıktıyla karşılaştırarak belgeyi bir test takımına dönüştürür; iddianın gömülü olması bir veri değil, bir yazım seçimidir.
  • On iki iddianın 7’si bir biçim değişikliğiyle bayatlar ve bu sayı yazımdan bağımsızdır; yazım yalnız bayatlamanın duyulup duyulmadığını belirler.
  • Düzyazı ağırlıklı yazım 4 bayat iddiayı sese çevirir ve 3’ünü sessiz bırakır; iki iddia örneğe çevrildiğinde sesli sayı 6’ya çıkar — 2 ayrı sonuç.
  • Sessiz kalan son iddia bir gerekçedir ve örneğe çevrilemez: belge testi bir ifadenin değerini karşılaştırır, bir nedenin değeri yoktur.

Sonraki Adım

Buraya kadar her ölçüm testin ne gördüğünü saydı: hangi testler keşfedildi, hangi sırada koştular, hangi kaynağa baktılar, hangi iddia sese döndü. Hepsinin ortak yanı, sayılan şeyin testin bakışı olmasıydı. Sonraki ders sayacı öbür tarafa çevirir ve kodun kendisine bakar: üç testlik bir küme koştuğunda işlevin hangi satırları yürüdü, hangi dalları alındı? Bu sayı bir ölçüde yanıltıcıdır ve dersin işi tam olarak o yanılmanın sınırını çizmektir — sekiz satırın sekizi yürüdüğünde ve üç testin üçü geçtiğinde bile işlevin sözleşmeye uymadığı bir çağrı bulunabilir.

İ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