İçeriğe geç
academia.sh

Ders 09 / 14

Kapsam Ölçümü

Üç testlik küme sekiz satırın sekizini kapsar (1,0000), altı dalın altısını alır ve 3/3 geçer; buna karşın dört girdi bileşiminden birinde gerçek davranış kâhinden ayrılır ve test kümesi o çağrıyı çalıştırır — kapsam, testin neye baktığını değil, kodun nerede yürüdüğünü sayar.

İçindekiler

Önceki ders belge iddialarını sınanabilir hale getirdi ve on iki iddianın bayatlayan yedisinden dördünün, sonra altısının sese döndüğünü ölçtü. Oradaki soru “iddia yanlışlandığında duyuluyor mu” sorusuydu. Şimdi ters yönden bakma sırası: testler koştuğunda kodun neresi yürüdü?

Bu soruyu yanıtlayan sayıya kapsam (coverage) denir. Kapsam bir test takımının kaliteli olup olmadığını söylemez; bir koşum boyunca kodun hangi bölümlerinin çalıştığını sayar. Bu dersin işi o sayının tam olarak neyi ölçtüğünü göstermek ve sınırını çizmektir — çünkü sınırın yanlış çizilmesi, bütün konudaki en pahalı okuma hatasıdır.

Kapsam Sayacı Neyi Sayar

Ölçümdeki kapsam sayacı ders içinde modellenmiştir; hiçbir araç adı geçmez. Modelin dayandığı düzenek Python’un izleme kancasıdır: yorumlayıcıya bir işlev verilir ve yorumlayıcı, yürütmenin belli olaylarında o işlevi çağırır. Olaylardan biri line olayıdır ve her yeni kaynak satırına geçildiğinde tetiklenir.

Sayaç bundan ibarettir. Kanca çağrıldığında yürüyen satırın numarası kaydedilir; koşum bittiğinde kaydedilen numaralar kümesi, kapsanan satırları verir. Kancanın gördüğü bilgi yalnızca “hangi satırdayız” bilgisidir — hangi test koşuyor, iddia ne bekliyor, sonuç doğru mu, hiçbiri kancaya gelmez.

Bu, ölçümün bütün sonucunu önceden belirleyen bir olgudur ve bir eksiklik değildir. İzleme kancası testin beklentisini göremez, çünkü beklenti testin içinde bir karşılaştırmadır ve kanca yürütmeyi izler, anlamı değil. Kapsam sayacı, yürütmenin coğrafyasını çıkarır.

İşlevin sekiz satırı, gövdedeki sekiz deyime karşılık gelir ve numaraları kodun kendisinden okunur: tanım satırı ile belge dizgisi dışarıda bırakılır, geriye yürütülebilir sekiz satır kalır.

İki Ölçüt: Satır ve Dal

Satır kapsamı bir satırın en az bir kez yürüyüp yürümediğini sorar. Dal kapsamı bir koşulun hem doğru hem yanlış sonucunun alınıp alınmadığını sorar. İkisi arasındaki fark, koşullu bir deyimin gövdesi hiç çalışmadığında ortaya çıkar: satır kapsamı koşul satırını kapsanmış sayar, dal kapsamı yalnız bir dalın alındığını kaydeder.

Bu ölçütler ve aralarındaki hiyerarşi Yazılım Kalitesi ve Test müfredatının Birim Testi ve Test Odaklı Geliştirme kursunda kuram olarak kurulmuştur; burada tekrarlanmıyor, atıf yapılıyor. Bu derste ikisi birlikte ölçülüyor, çünkü iddianın gücü buna bağlı: eğer yalnız satır kapsamı tam olsaydı “daha güçlü bir ölçüt kullansaydınız görürdünüz” denebilirdi. Ölçüm bu itirazı kapatıyor — iki ölçüt de tamdır ve kusur yine görünmez.

Dal kapsamı modelde de yürütmeden çıkarılır: her koşul için, o koşulun gövdesindeki satırın o çağrıda yürüyüp yürümediğine bakılır. Yürüdüyse doğru dal, yürümediyse yanlış dal alınmıştır. Üç koşul, altı dal.

Ölçütlerin ötesinde bir üçüncüsü de vardır ve burada kullanılmıyor: yol kapsamı, yani koşulların birbirinden bağımsız bileşimlerinin kaçının denendiği. Üç koşulun bu işlevde dört ulaşılabilir bileşimi var ve ölçümün ikinci tablosu tam olarak o dördünü listeliyor. Bu ders yol kapsamını ayrı bir sayı olarak yazmıyor, çünkü kanıtı zayıflatmıyor güçlendiriyor: dördün üçü test kümesinde ve kusurlu olan da onların arasında.

Kâhin Ayrı Yazılmıştır

Ortak kurgunun indirim işlevi üç karar verir: üyelik on puan, kupon on beş puan, ve bir tavan. Kusur üçüncü karardadır — tavan 20 olarak yazılmıştır, oysa sözleşme 25 demektedir. Kâhin, yani doğru davranış, ayrı bir işlev olarak yazılmıştır ve tavanı 25 uygular.

Test kümesinin beklenen değerleri ise kâhinden değil, işlevin gözlenen davranışından yazılmıştır. Bu, ilk derste kurulan durumun aynısıdır ve bir kurgu ayrıntısı değil, gerçek bir yazım alışkanlığıdır: var olan bir işleve test yazarken beklenen değerler çoğu kez işlevi çalıştırıp çıktısına bakarak belirlenir. Böyle yazılmış bir test davranışın değişmediğini doğrular; doğru olduğunu değil.

Ölçümün Varsayımları

  • PT41 — İndirim işlevi ve kâhin ortak tanımdan alınır; ikisinin de davranışı değiştirilmez. Ders yalnız kapsam sayacını ve bir dördüncü testi ekler.
  • PT42 — Kapsam sayacı izleme kancasıyla modellenir. Kanca yalnız line olayını ve yalnız indirim işlevinin çerçevesini sayar; başka hiçbir bilgiye erişmez.
  • PT43 — Sayılan sekiz satır, işlevin gövdesindeki yürütülebilir deyimlerdir ve numaraları kodun kendisinden okunur; tanım satırı ile belge dizgisi dışarıdadır.
  • PT44 — Dal kapsamı üç koşulun her biri için iki sonuçtan oluşur, toplam altı dal. Bir dalın alındığı, o koşulun gövdesindeki satırın yürümesinden çıkarılır.
  • PT45 — Test kümesi ortak tanımın üç testidir ve beklenen değerleri işlevin gözlenen davranışından yazılmıştır.
  • PT46 — Kâhin karşılaştırması ölçümün dışında, ayrı bir tabloda yapılır; test kümesi kâhini hiç çağırmaz.
  • PT47 — Girdi uzayı tutar 100’de sabit tutulur ve iki mantıksal bayrağın dört bileşimi denenir. Tutarın değişmesi kusuru değil, kusurun büyüklüğünü değiştirir.
  • PT48 — Bir çağrının “kümede” olması, test kümesindeki üç girdiden birine eşit olması demektir.
  • PT49 — Dördüncü test aynı girdiyi kullanır ama beklenen değerini kâhinden alır; başka hiçbir farkı yoktur.
  • PT50 — Geçen test sayısı, beklenen değerle gerçekleşen değerin eşit olduğu test sayısıdır; kısmi geçme yoktur.
  • PT51 — Oranlar dört ondalık basamakla yazılır; bu kümede ölçülebilen en küçük satır farkı 1/8, yani 0,1250’dir.
  • PT52 — Süre ölçülmez, depoya dosya bırakılmaz. Sayılan şey kapsanan satır, alınan dal, geçen test ve kâhinden sapan çağrı sayısıdır.

Ölçüm

"""Kapsam sayaci: hangi satir yurudu, hangi dal alindi, hangi cagri kahinden sapti.

Sayac izleme kancasiyla ders icinde modellenir; olculen sey kodun nerede yurudugudur.
"""
import sys


def indirim(tutar, uye, kupon):
    """Uc kararlı islev; kusur ucuncu karardadir."""
    oran = 0
    if uye:
        oran += 10
    if kupon:
        oran += 15
    if oran > 20:
        oran = 20
    return tutar - tutar * oran // 100


def dogru_indirim(tutar, uye, kupon):
    """Kahin: dogru davranis tavani 25 olarak uygular."""
    oran = (10 if uye else 0) + (15 if kupon else 0)
    if oran > 25:
        oran = 25
    return tutar - tutar * oran // 100


AD = ("oran=0", "if uye", "oran+=10", "if kupon", "oran+=15",
      "if oran>20", "oran=20", "return")
GOVDE = indirim.__code__.co_firstlineno + 1   # tanım ve belge dizgisi satırı dışarıda
NO = sorted({s for _, _, s in indirim.__code__.co_lines()
             if s is not None and s > GOVDE})
SATIR = dict(zip(NO, AD))
DALLAR = {"if uye": "oran+=10", "if kupon": "oran+=15", "if oran>20": "oran=20"}

TEST_KUMESI = (((100, True, False), 90),
               ((100, False, True), 85),
               ((100, True, True), 80))
HEPSI = [(100, u, k) for u in (False, True) for k in (False, True)]


def izleyerek(arg):
    """Tek cagriyi izler ve yuruyen satirlarin adini dondurur."""
    gorulen = set()

    def kanca(cerceve, olay, ek):
        if cerceve.f_code is indirim.__code__:
            if olay == "line":
                gorulen.add(SATIR[cerceve.f_lineno])
            return kanca
        return None

    sys.settrace(kanca)
    try:
        deger = indirim(*arg)
    finally:
        sys.settrace(None)
    return deger, gorulen


def kapsam(cagrilar):
    """Satir ve dal kapsami: sayilan sey satirin yuruyup yurumedigidir."""
    satir, dal = set(), set()
    for arg in cagrilar:
        _, gorulen = izleyerek(arg)
        satir |= gorulen
        for kosul, govde in DALLAR.items():
            dal.add((kosul, govde in gorulen))
    return satir, dal


def gecen(kume):
    return sum(1 for arg, beklenen in kume if izleyerek(arg)[0] == beklenen)


cagrilar = [a for a, _ in TEST_KUMESI]
satir, dal = kapsam(cagrilar)
sapan = [a for a in HEPSI if indirim(*a) != dogru_indirim(*a)]

print(f"{'ölçüt':<14s} {'kapsanan':>9s} {'toplam':>7s} {'oran':>7s}")
print(f"{'satır':<14s} {len(satir):9d} {len(AD):7d} {len(satir) / len(AD):7.4f}")
print(f"{'dal':<14s} {len(dal):9d} {2 * len(DALLAR):7d} "
      f"{len(dal) / (2 * len(DALLAR)):7.4f}")
print(f"geçen test {gecen(TEST_KUMESI)}/{len(TEST_KUMESI)}")

print()
print(f"{'girdi':<24s} {'gerçek':>7s} {'kâhin':>7s} {'sapma':>6s} {'kümede':>7s}")
for a in HEPSI:
    g, d = indirim(*a), dogru_indirim(*a)
    print(f"{'tutar=%d üye=%d kupon=%d' % (a[0], a[1], a[2]):<24s} {g:7d} {d:7d} "
          f"{('var' if g != d else 'yok'):>6s} "
          f"{('evet' if a in cagrilar else 'hayır'):>7s}")
print(f"dört bileşimden {len(sapan)} tanesinde gerçek davranış kâhinden ayrılıyor; "
      f"o çağrı kümede {'var' if sapan[0] in cagrilar else 'yok'}")

print()
DORTLU = TEST_KUMESI + (((100, True, True), dogru_indirim(100, True, True)),)
satir4, dal4 = kapsam([a for a, _ in DORTLU])
print(f"{'test kümesi':<16s} {'test':>5s} {'geçen':>6s} {'satır':>7s} {'dal':>7s}")
print(f"{'üç test':<16s} {len(TEST_KUMESI):5d} {gecen(TEST_KUMESI):6d} "
      f"{len(satir) / len(AD):7.4f} {len(dal) / (2 * len(DALLAR)):7.4f}")
print(f"{'kâhinli dördüncü':<16s} {len(DORTLU):5d} {gecen(DORTLU):6d} "
      f"{len(satir4) / len(AD):7.4f} {len(dal4) / (2 * len(DALLAR)):7.4f}")
ORAN = {(gecen(TEST_KUMESI), len(TEST_KUMESI)), (gecen(DORTLU), len(DORTLU))}
print(f"dördüncü test kapsamı {len({len(satir), len(satir4)})} ayrı sonuçta bırakıyor, "
      f"geçen oranını {len(ORAN)} ayrı sonuca çıkarıyor")
ölçüt           kapsanan  toplam    oran
satır                  8       8  1.0000
dal                    6       6  1.0000
geçen test 3/3

girdi                     gerçek   kâhin  sapma  kümede
tutar=100 üye=0 kupon=0      100     100    yok   hayır
tutar=100 üye=0 kupon=1       85      85    yok    evet
tutar=100 üye=1 kupon=0       90      90    yok    evet
tutar=100 üye=1 kupon=1       80      75    var    evet
dört bileşimden 1 tanesinde gerçek davranış kâhinden ayrılıyor; o çağrı kümede var

test kümesi       test  geçen   satır     dal
üç test              3      3  1.0000  1.0000
kâhinli dördüncü     4      3  1.0000  1.0000
dördüncü test kapsamı 1 ayrı sonuçta bırakıyor, geçen oranını 2 ayrı sonuca çıkarıyor

Tam Kapsam ve Kusurlu Davranış

Üstteki tablo üç sayıyı yan yana koyuyor ve üçü de olumlu görünüyor. Üç testlik küme sekiz satırın sekizini kapsıyor: 1,0000. Altı dalın altısını alıyor: 1,0000. Ve 3/3 test geçiyor. Bu üç sayıya bakan biri işlevin sınandığı sonucuna varır.

Ortadaki tablo aynı işlevin bütün girdi uzayını veriyor ve son satırda sapma duruyor. Üyelik ve kupon birlikte geldiğinde işlev 80, kâhin 75 veriyor — oran 25 olması gerekirken tavan onu 20’ye çekiyor. Dört girdi bileşiminden 1’inde gerçek davranış kâhinden ayrılıyor.

İki tablo arasındaki bağ ölçümün asıl bulgusudur. Sapan çağrı — üyelik ve kupon birlikte — test kümesinin içinde. Yani test kümesi o çağrıyı çalıştırıyor. Kusurun bulunduğu satır — tavanı uygulayan satır — yürüyor; kapsam sayacı onu görüyor ve kapsanan olarak kaydediyor. Kapsamın 1,0000 çıkmasının nedeni de zaten bu çağrının kümede olması.

Öyleyse test neden geçiyor? Çünkü o testin beklediği değer 80. Beklenen değer işlevin gözlenen davranışından yazıldığı için, işlev kusurlu değerini üretiyor ve karşılaştırma tutuyor. Geçmesinin nedeni kapsamın eksikliği değil, beklentinin kusurlu değeri yazmış olmasıdır.

Kursun üçüncü iddiası tam olarak budur: kapsam satırı görür, beklentiyi görmez. Ve nedeni bu dersin ilk bölümünde yazılıydı: izleme kancasına gelen bilgi yalnız “hangi satırdayız” bilgisidir. Testin beklediği değer o kancaya hiç ulaşmaz.

Aynı Kapsam, Ayrı Karar

Alttaki tablo bunu bir deneyle kesinleştiriyor. Üç testlik kümeye dördüncü bir test ekleniyor; girdisi aynı — üyelik ve kupon birlikte — ama beklenen değeri kâhinden alınıyor. Yeni test düşüyor ve küme 3/4’e iniyor.

Kapsam ise kılını kıpırdatmıyor: satır 1,0000, dal 1,0000. Aynı girdi zaten kümedeydi, dolayısıyla yürüyen satır kümesi hiç değişmedi. Kapsam 1 ayrı sonuçta kalıyor, geçen oran ise 2 ayrı sonuca çıkıyor.

Bu iki sayının ayrışması, kapsamın ne olduğunun en yalın kanıtıdır. Kusuru bulan değişiklik kapsamı hiç değiştirmedi. Kapsamı büyüterek kusura yaklaşmak bu örnekte imkânsızdır, çünkü kapsam zaten tavandadır. Eksik olan şey bir çağrı değil, bir beklentiydi — ve beklenti kapsamın ölçtüğü şeyin dışındadır.

Tersinin de doğru olduğunu söylemek gerekiyor: kapsamın düşük olması bir bilgidir ve gerçek bir bilgidir. Yürümemiş bir satır hiçbir testin denemediği bir satırdır ve orada her şey olabilir. Kapsam hangi kodun sınanmadığını güvenilir biçimde söyler; hangi kodun doğru olduğunu hiç söylemez. Sayının yönü tek taraflıdır.

Bu asimetrinin bir sonucu daha var. Bir satırın yürümesi ile o satırın sınanması ayrı şeylerdir, ve aradaki farkı kapatan tek şey bir iddiadır. Kapsam sayacına göre bir satır, hiçbir iddiası olmayan bir test onu çalıştırdığında da kapsanmıştır — ilk derste görülen iddiasız test tam olarak bunu yapar. Yani kapsamı yükseltmek için kodu çağırmak yeterlidir ve sonucu hiç kontrol etmek gerekmez; sayı ikisini ayırt edemez.

Sayı Nasıl Okunur

Buradan üç okuma kuralı çıkıyor.

Birincisi: kapsam bir alt sınırdır, bir belge değil. Düşük kapsam bir eksikliğin kanıtıdır; yüksek kapsam bir yeterliliğin kanıtı değildir. Bu asimetri, sayının hedef olarak konulduğunda neden aşındığını da açıklar — sayıyı yükseltmenin en ucuz yolu iddia eklemek değil, çağrı eklemektir.

İkincisi: kapsam yüzdesi tek başına karşılaştırılamaz. Bu ölçümde en küçük ölçülebilir satır farkı 1/8, yani 0,1250’dir; sekiz satırlık bir işlevde 1,0000 ile 0,8750 arasında ara değer yoktur. Başka bir kod tabanının yüzdesiyle yan yana konan bir kapsam sayısı, iki tarafın da neyi saydığı yazılmadan bir şey ifade etmez. Bu, bir önceki kursun ölçüm disiplini dersinde kurulan kuralın bu ölçüme uygulanmasıdır.

Üçüncüsü: kapsam beklentinin kalitesini ölçen bir sayı değildir ve onu ölçen bir sayı kapsam sayacından çıkmaz. Beklentinin doğruluğunu belirleyen şey kâhindir — sözleşme, belirtim, ya da bu kursta olduğu gibi kurgunun kendisi. Kâhin yoksa yazılan her beklenti gözlenen davranışı yeniden yazar ve takım, kodun bugünkü halini kendine kanıt olarak sunar.

Özet

  • Kapsam sayacı izleme kancasının line olayını sayar; gördüğü tek bilgi hangi satırda olunduğudur. Testin beklentisi bu kancaya hiç ulaşmaz.
  • Üç testlik küme sekiz satırın sekizini kapsar (1,0000), altı dalın altısını alır (1,0000) ve 3/3 geçer.
  • Buna karşın dört girdi bileşiminden 1’inde gerçek davranış kâhinden ayrılır ve test kümesi o çağrıyı çalıştırır; geçmesinin nedeni kapsamın eksikliği değil, beklentinin kusurlu değeri yazmış olmasıdır.
  • Kâhinden beklenti alan bir dördüncü test kümeyi 3/4’e indirir ama kapsamı değiştirmez: kapsam 1 ayrı sonuçta kalır, geçen oran 2 ayrı sonuca çıkar.
  • Kapsam tek yönlü bir bilgidir: hangi kodun sınanmadığını söyler, hangi kodun doğru olduğunu söylemez. Kapsam, testin neye baktığını değil, kodun nerede yürüdüğünü sayar.

Sonraki Adım

Bu konu boyunca ölçülen her şey kodun çalışmasıyla ilgiliydi: hangi testler bulundu, hangi sırada koştular, hangi kaynağa baktılar, hangi iddia sese döndü, hangi satır yürüdü. Kapsam bu dizinin sonuncusuydu ve kodun nerede yürüdüğünü saydı.

Geriye kodun bir başka yüzü kalıyor: nasıl yazıldığı. Aynı davranışı üreten bir işlev farklı girintiyle, farklı satır sonlarıyla, farklı tırnak ve boşluk kararlarıyla sayısız biçimde yazılabilir ve bunların hiçbiri kapsam sayacında görünmez — yürüyen satırlar aynı satırlardır. Bu biçim kararları bir insana bırakıldığında her inceleme yeni bir tartışma üretir. Sonraki ders soruyu buraya taşır: aynı kaynağın kaç ayrı yazımı varken kaça iner, ve bu tekleştirmeyi kim yapar?

İ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