---
title: 'Python Projeleri: Paketleme ve Test'
source: 'https://academia.sh/tr/kurslar/python-projeleri'
language: tr
updated: '2026-08-17T04:42:25+00:00'
license: 'CC BY-SA 4.0'
lessons: 14
outcomes:
    - 'İzole ortam ve bağımlılık yönetimini yeniden üretilebilir biçimde kurar'
    - 'Test paketi yazar ve kapsamı anlamlı ölçütlerle değerlendirir'
    - 'Kütüphaneyi paketleyip yayımlar'
---

# Python Projeleri: Paketleme ve Test

Bir projenin sayısı çalışıp çalışmadığı değil aynı girdinin kaç ayrı sonuç verdiğidir; kurs bağımlılık çözümlemesini, test sırasını ve kapsamı bu tek soruyla ölçer ve araçları adlandırmadan sınıf düzeyinde modeller.

## Ortam ve Bağımlılık

Yalıtımın gerekçesi ve kurulum akışı, proje meta verisi ile kilitleme, çözümleme stratejilerinin aynı bildirimden ayrı kümeler üretmesi, ve yorumlayıcı sürümleri arasında geçiş.

### Sanal Ortamlar

Source: https://academia.sh/tr/kurslar/python-projeleri/sanal-ortamlar

Aynı çözümleme paylaşılan bir sitede altı kurulum sırasında beş ayrı sonuç verir ve proje kendi çözdüğü kümeyi altı sıranın yalnız ikisinde görür; yalıtılmış sitede sayı bire iner ve altı sıranın altısında aynı küme kurulur.

Önceki kurs on ders boyunca süre yerine adım saydı ve kapanışında bir cümle bıraktı:
ölçmek bir disiplindir; paketlemek ve sınamak da öyle. O kursun sorusu ölçümün **neyi
saydığıydı** — kurulum sayıma dahil mi, sınır nereye çekildi, birim hangi adla yazıldı.

Bu kursun sorusu bir adım geriye gider. Bir sayının sınırını yazmadan önce yanıtlanması
gereken daha temel bir soru vardır: **aynı girdi her seferinde aynı sonucu veriyor mu?**
Bir ölçüm iki koşumda iki ayrı değer veriyorsa, sınırını ne kadar iyi yazarsanız yazın
karşılaştırılamaz. Bu kurs o soruyu bir projenin üç ayrı yerinde sorar: bağımlılıklarında,
testlerinde ve yayımladığı paketinde. İlk yer, kodun hangi paketlerle karşılaşacağını
belirleyen yerdir.

#### Kursun Ölçüsü: Ayrı Sonuç

Bir projenin sayısı çalışıp çalışmadığı değildir. Çalışan bir proje, ikinci makinede
çalışmayabilir ve bu ikinci sonuç birinciyi geçersiz kılmaz — ikisi birden doğrudur, tıpkı
önceki kursun altı bildirimi gibi. Sayılabilir olan şey şudur: **aynı girdi kaç ayrı sonuç
verdi?**

Kursun ölçü adı **ayrı sonuç (distinct outcome)**. Bir kurulumun sonucu, kurulan
sürümlerin kümesidir; iki kurulum aynı kümeyi verdiyse tek sonuç sayılır, ayrı küme
verdiyse iki. Bir sayının iki koşumda değişmemesi ise **yeniden üretilebilirlik
(reproducibility)** olarak anılır ve bu kursun bütün ölçümleri onu arar.

Kurgu üç paketten oluşur: `olcum`, `rapor` ve `ortak`. Her birinin depoda yayımlanmış
sürümleri vardır ve proje bunları bir **bağımlılık bildirimi (dependency declaration)**
ile ister: kabul edilen en küçük ve en büyük sürüm, üst sınır dahil değil. Bildirim bir
sürüm söylemez, bir aralık söyler — bu ayrım sonraki dersin konusudur.

Bu kursun hiçbir dersi araç adı yazmaz. Sanal ortam aracı,
**paket yöneticisi (package manager)**, test çerçevesi ve derleme sistemi yalnız
**sınıf adıyla** anılır; davranışları
derste **modellenir** ve modellendiği yazılır. Standart kitaplığın parçası olan `venv` ve
`sysconfig` modülleri dilin kendisine aittir ve adlarıyla anılabilir. Ölçümde **gerçek
kurulum yapılmaz, gerçek paket indirilmez**; kurulum bir sözlüğe yazma işlemi olarak
modellenmiştir.

#### Yalıtım Neyi Ayırır

**Sanal ortam (virtual environment)**, yorumlayıcının paket arayacağı yerleşimi projeye
özel kılan yalıtım sınırıdır. Kurulan paketlerin yerleştiği dizine bu derste **site**
denir; yorumlayıcı içe aktarma sırasında oraya bakar. Standart kitaplıkta `sysconfig`
modülü bu yerleşimi şema adlarıyla verir, `venv` modülü ise yeni bir yerleşim üretir.
Gerçek yol yazılmaz, çünkü yol makineye bağlıdır ve yeniden üretilemez.

Yalıtımın sağladığı şey bir gizlilik sınırı değildir. Sağladığı tek şey şudur: **aynı adlı
paket iki projede iki ayrı sürümde bulunabilir.** Tek bir site varken bu olanaksızdır,
çünkü bir ad bir yerde bir sürüme karşılık gelir.

Buradan doğrudan bir sonuç çıkar. Tek site paylaşıldığında bir projenin gördüğü sürümler
yalnız kendi bildirimine değil, **siteyi paylaşan öbür projelere ve kurulum sırasına** da
bağlıdır. Ölçülecek olan budur.

#### Sınır Nerede Çizilir

Sanal ortamın ne olduğu kadar ne olmadığı da ölçümü belirler, çünkü sınırın dışında kalan
her şey ayrı sonuç üretmeye devam eder.

Bir sanal ortam **yorumlayıcıyı çoğaltmaz**; var olan bir yorumlayıcıya bağlanır ve ona
ayrı bir site verir. Ortamı kuran işlem, o yorumlayıcının içe aktarma sırasında bakacağı
yerleşimi projeye özel kılmaktan ibarettir. Etkinleştirme de bir düzenek değil, aynı şeyin
kabuk tarafındaki karşılığıdır: hangi yorumlayıcının çağrılacağı ve onun hangi siteyi
göreceği belirlenir. Standart kitaplıkta bu yerleşimin adları `sysconfig` şemalarında
durur; yeni bir yerleşim üretmek `venv` modülünün işidir.

Bunun bir sonucu doğrudan bu konunun son dersine bağlanır: yalıtım **paketleri** ayırır,
**yorumlayıcıyı** ayırmaz. Ortam hangi yorumlayıcıya bağlandıysa onunla anlamlıdır;
yorumlayıcı değiştiğinde ortamın yeniden kurulması gerekir, çünkü sitedeki paketlerin bir
bölümü o yorumlayıcı için üretilmemiş olabilir.

İkinci bir dışarıda kalan, işletim sistemi düzeyidir. Paylaşılan sistem kitaplıkları,
derleyici, ortam değişkenleri ve dosya sistemi sanal ortamın sınırının dışındadır; bir
paket bunlardan birine dayanıyorsa iki makinede iki ayrı sonuç yine mümkündür. Bu daha
geniş sınırı çizen araçlar başka bir sınıfa aittir ve bu kursun konusu değildir. Burada
ölçülen sınır tek bir düzeyde durur: **aynı makinede, aynı yorumlayıcıyla, iki projenin
paket kümesi.**

#### Ortamın Yaşam Döngüsü

Kurulum akışı dört adımdır: ortam kurulur, bildirim çözümlenir, seçilen sürümler siteye
kurulur, proje o site üzerinden koşar. Dördüncü adımdan sonra ortam artık bir **yapıttır**
— üretilmiş bir sonuç, yazılmış bir kaynak değil.

Bu ayrımın pratik karşılığı nettir: ortam sürüm denetimine konmaz. Konmamasının nedeni
boyutu değil, **kaynağının başka bir yerde olmasıdır**. Ortamı yeniden üretecek olan şey
bildirimdir; ortamın kendisi o bildirimin bir koşumudur. İki koşum aynı kümeyi veriyorsa
ortamı saklamanın bir karşılığı yoktur.

Buradan bir sınama doğar. Ortamı silip aynı bildirimden yeniden kurduğunuzda kaybolan bir
şey varsa, o şey bildirime hiç yazılmamış demektir — elle kurulmuş bir paket, elle
düzeltilmiş bir sürüm, ortamın içinde biriken bir artık. Silme işlemi bu yüzden bir bakım
işi değil, **bir ölçümdür**: bildirimin eksiksiz olup olmadığını başka hiçbir şey bu kadar
doğrudan söylemez.

Ölçüm sorusunun ikinci yarısı da buradan çıkar. Ortam silinip yeniden kurulduğunda **aynı
küme** mi geliyor? Yalıtılmış bir sitede komşu ve sıra elendiğine göre geriye tek bir
değişken kalır: çözümlemenin kendisi. Bu dersin ölçümü o değişkeni sabit tutar ve yalnız
yalıtımın payını sayar.

#### Paylaşılan Sitede Son Kuran Kazanır

Üç proje aynı siteyi kullanıyor. **A** bizim projemiz: bildirimini çözümleyip en yeni uygun
sürümleri seçiyor. **B** ve **C** komşu projeler; ikisi de kendi kilitleriyle geliyor, yani
kuracakları sürümler önceden yazılmış ve çözümleme yapmıyorlar. Üç projenin paket kümeleri
kısmen örtüşüyor.

Kurulumun modeldeki davranışı tek satırlıktır: bir paketi kurmak, sitedeki sürümün üzerine
yazmaktır. **Son kuran kazanır.** Bu, tek siteli bir yerleşimin kaçınılmaz davranışıdır; bir
ad bir sürüme bağlanabildiği için kuran son proje bağı kendi lehine çevirir.

A'nın çözümlemesi altı sıranın hepsinde aynıdır ve ölçümde bu ayrıca yazdırılır. Değişen
şey A'nın çözdüğü küme değil, **koşum anında gördüğü** kümedir.

Ölçümün varsayımları:

- **OB1** — Kâhin kurgunun kendisidir: depodaki sürümleri ve üç projenin isteklerini biz
  yazdık, bu yüzden her sıranın doğru sonucu ölçmeden bilinir.
- **OB2** — Depo ve bildirim ortak tanımdan birebir alınır; ders ikisini de değiştirmez.
- **OB3** — A bildirimini **en yeni uygun sürüm** stratejisiyle çözer. Strateji bu derste
  sabittir; değiştiğinde ne olduğu sonraki iki dersin konusudur.
- **OB4** — B ve C çözümleme yapmaz; kuracakları sürümler kilitlerinde yazılıdır ve
  kurgunun parçasıdır.
- **OB5** — Kurulum sitedeki sürümün üzerine yazar. Sürüm karşılaştırması ya da "zaten
  kurulu" denetimi yoktur; modelin tek kuralı son kuranın kazanmasıdır.
- **OB6** — Paylaşılan rejimde tek site vardır ve üçü de oraya kurar. Yalıtılmış rejimde
  her projenin kendi sitesi vardır.
- **OB7** — Ölçülen şey A'nın sitesinde koşum anında duran `olcum`, `ortak` ve `rapor`
  sürümleridir; öbür projelerin ne gördüğü sayılmaz.
- **OB8** — Altı sıra üç projenin bütün kurulum sıralarıdır; hiçbiri ayrıcalıklı değildir.
  Gerçek bir makinede sırayı belirleyen şey kurulumların takvimidir ve proje onu seçmez.
- **OB9** — Kurulum bir sözlüğe yazma işlemidir. **Gerçek kurulum yapılmaz, gerçek paket
  indirilmez, depoya dosya bırakılmaz.**
- **OB10** — Süre ölçülmez. Sayılan şey **ayrı sonuç** ve ayrı sonucu üreten sıra sayısıdır.
- **OB11** — Ölçüm tek koşumdur ve belirlenimcidir; kurguda rastgelelik yoktur.

#### Ölçüm

```python
"""Yalitim: ayni cozumleme, ayri site; kurulum sirasi kac ayri sonuc veriyor."""
from itertools import permutations

DEPO = {
    "olcum": [(1, 0), (1, 1), (1, 2), (2, 0)],
    "rapor": [(0, 9), (1, 0), (1, 1)],
    "ortak": [(3, 0), (3, 1), (3, 2), (4, 0)],
}
BILDIRIM = {"olcum": ((1, 0), (2, 0)), "rapor": ((1, 0), (2, 0)),
            "ortak": ((3, 0), (4, 0))}

# Komsu projeler kendi kilitleriyle geliyor; ayni depoyu ayri surumlerde kullaniyorlar.
KOMSU = {"B": {"ortak": (3, 0), "rapor": (1, 0)},
         "C": {"olcum": (1, 1), "ortak": (3, 1)}}
PAKETLER = ("olcum", "ortak", "rapor")


def uygun(paket):
    alt, ust = BILDIRIM[paket]
    return [s for s in DEPO[paket] if alt <= s < ust]


def coz(strateji="en yeni"):
    secim = {}
    for paket in sorted(BILDIRIM):
        adaylar = uygun(paket)
        secim[paket] = adaylar[-1] if strateji == "en yeni" else adaylar[0]
    return secim


def yaz(site, secim):
    """Kurulum sitedeki surumun uzerine yazar: son kuran kazanir."""
    site.update(secim)
    return site


def secimi(ad):
    return coz() if ad == "A" else KOMSU[ad]


def paylasilan(sira):
    site = {}
    for ad in sira:
        yaz(site, secimi(ad))
    return tuple(site[p] for p in PAKETLER)


def yalitilmis(sira):
    siteler = {"A": {}, "B": {}, "C": {}}
    for ad in sira:
        yaz(siteler[ad], secimi(ad))
    return tuple(siteler["A"][p] for p in PAKETLER)


def yazim(goru):
    return " / ".join(".".join(map(str, s)) for s in goru)


SIRALAR = list(permutations(("A", "B", "C")))
BEKLENEN = tuple(coz()[p] for p in PAKETLER)

print(f"{'kurulum sırası':<16s} {'paylaşılan sitede A':>21s} {'yalıtılmış sitede A':>21s}")
p_goru, y_goru = [], []
for sira in SIRALAR:
    a, b = paylasilan(sira), yalitilmis(sira)
    p_goru.append(a)
    y_goru.append(b)
    print(f"{' '.join(sira):<16s} {yazim(a):>21s} {yazim(b):>21s}")

print(f"\nA'nın çözümlemesi altı sıranın hepsinde {yazim(BEKLENEN)}")
print(f"altı kurulum sırası, paylaşılan sitede {len(set(p_goru))} ayrı sonuç, "
      f"yalıtılmış sitede {len(set(y_goru))}")
print(f"çözdüğü kümeyi gören sıra: paylaşılan {p_goru.count(BEKLENEN)}/{len(SIRALAR)}, "
      f"yalıtılmış {y_goru.count(BEKLENEN)}/{len(SIRALAR)}")
```

```
kurulum sırası     paylaşılan sitede A   yalıtılmış sitede A
A B C                  1.1 / 3.1 / 1.0       1.2 / 3.2 / 1.1
A C B                  1.1 / 3.0 / 1.0       1.2 / 3.2 / 1.1
B A C                  1.1 / 3.1 / 1.1       1.2 / 3.2 / 1.1
B C A                  1.2 / 3.2 / 1.1       1.2 / 3.2 / 1.1
C A B                  1.2 / 3.0 / 1.0       1.2 / 3.2 / 1.1
C B A                  1.2 / 3.2 / 1.1       1.2 / 3.2 / 1.1

A'nın çözümlemesi altı sıranın hepsinde 1.2 / 3.2 / 1.1
altı kurulum sırası, paylaşılan sitede 5 ayrı sonuç, yalıtılmış sitede 1
çözdüğü kümeyi gören sıra: paylaşılan 2/6, yalıtılmış 6/6
```

#### Altı Sıra, Beş Sonuç

Sol sütun altı satırda beş ayrı küme veriyor. Yalnız iki sıra aynı sonucu üretiyor
(`B C A` ile `C B A`) ve ikisinin ortak özelliği A'nın **son** kurmuş olmasıdır. Kalan
dört sıranın her biri kendi kümesini veriyor.

İlk satır uç durumu veriyor. `A B C` sırasında A önce kuruyor ve üç paketini de kendi
seçtiği sürümlerle yazıyor; sonra B `ortak` ile `rapor` üzerine, sonra C `olcum` ile
`ortak` üzerine yazıyor. Sonuçta A'nın gördüğü üç sürümün **üçü de** başka projelerden
geliyor — kendi kararlarından ayakta kalan yok. `B A C` sırasında yalnız `rapor` ayakta
kalıyor, çünkü C o paketi hiç kurmuyor. `C A B` sırasında ayakta kalan tek paket `olcum`;
`ortak` ile `rapor` en son B'den geliyor.

Bu beş sonucun hiçbiri bildirimi çiğnemiyor. A'nın bildirimi `ortak` için üç sürümü de
kabul ediyor, `olcum` için üçünü de, `rapor` için ikisini de. Yani A hangi sırada kurulursa
kurulsun kabul ettiği bir sürümle karşılaşıyor ve hiçbir uyarı doğmuyor. Kusurun **sessiz**
olması buradan geliyor: bildirim bir aralık olduğu için, aralığın içindeki her sapma
meşrudur.

Beş kümeden yalnız biri A'nın kendi seçimidir. Kalan dördünü A hiç seçmedi, hiç istemedi ve
hiçbir yerde yazmadı; yine de kodu o dördünün üzerinde koştu. Bu, ayrı sonucun neden
"çalıştı" ya da "çalışmadı"dan ayrı bir ölçü olduğunu gösteriyor: altı sıranın altısında da
proje koşabilir, hiçbiri hata vermeyebilir ve arkada beş ayrı kurulum durur.

Sağ sütun aynı altı sırayı yalıtılmış rejimde gösteriyor ve tek bir küme veriyor:
**1 ayrı sonuç**. Altı sıranın altısında A, kendi çözdüğü kümeyi görüyor.

Alt satır farkı en keskin biçimde yazıyor. Paylaşılan sitede A, çözümlemesinin sonucunu
**altı sıranın ikisinde** görüyor; yalıtılmış sitede **altısında**. Dörtte üçlük bir
sapmanın kaynağı A'nın kendi kararlarında değil, tamamen dışarıdadır.

#### Yalıtımın Tekleştirmediği

Yalıtım bir şeyi çözer ve bir şeyi çözmez. Bu ayrım kursun geri kalanının çatısıdır.

**Çözdüğü:** komşu projelerin kararları ve kurulum sırası artık sonucu değiştirmiyor. Beş
ayrı sonuç bire indi ve indiren şey daha iyi bir çözümleme değil, **sınırın kendisi** oldu.
A'nın çözümlemesi ölçümün başında da sonunda da aynıydı; değişen, çözümlemenin sonucunun
korunup korunmadığıydı.

**Çözmediği:** A'nın çözümlemesinin **kendisi**. Yalıtılmış sitede bile kurulan küme, o
çözümlemenin hangi kuralla yapıldığına bağlıdır. Ölçümde bu kural sabit tutuldu — en yeni
uygun sürüm — ve bu yüzden sağ sütun tek bir küme verdi. Kural değişseydi sağ sütun da
değişirdi, çünkü bildirim bir sürüm değil bir aralık söylüyor.

İkinci sınır daha da dışarıdadır: aynı bildirim, depo değiştiğinde de ayrı bir küme
verebilir. Bugün en yeni olan sürüm yarın en yeni olmayabilir ve bildirim yeni olanı da
kabul eder. Yalıtım bu ikisine karşı hiçbir şey yapmaz; sınır, sitenin duvarında biter.

Bir sanal ortam bu yüzden **yeniden üretilebilirliğin tamamı değil, ilk koşuludur.** Sınırı
çizmek, sınırın içinde ne olacağına karar vermekle aynı şey değildir.

#### Özet

- Kursun ölçüsü **ayrı sonuçtur**: bir projenin sayısı çalışıp çalışmadığı değil, aynı
  girdinin kaç ayrı sonuç verdiğidir.
- **Sanal ortam** bir yalıtım sınırıdır; sağladığı tek şey aynı adlı paketin iki projede iki
  ayrı sürümde bulunabilmesidir.
- Tek site paylaşıldığında kurulum sırası sonucu belirler: altı sıra **5 ayrı sonuç** verir
  ve proje kendi çözdüğü kümeyi yalnız **2/6** sırada görür.
- Yalıtılmış sitede aynı altı sıra **1 ayrı sonuç** verir ve proje kümesini **6/6** sırada
  görür; A'nın çözümlemesi iki rejimde de aynıdır, korunan şey çözümlemenin **sonucudur**.
- Beş sonucun hiçbiri bildirimi çiğnemez; bildirim bir aralık olduğu için aralık içindeki
  sapma sessizdir ve uyarı üretmez.
- Yalıtım komşuyu ve sırayı eler, ama çözümlemenin kendi kuralını ve deponun zaman içinde
  değişmesini elemez.

#### Sonraki Adım

Yalıtılmış sitedeki tek sonuç, ölçümde bir kuralın sabit tutulmasıyla elde edildi: en yeni
uygun sürüm. O kural bir seçimdi ve başka seçimler de bildirime uygundur. Sonraki ders
sınırın içine bakar ve aynı soruyu bir kez daha sorar: **aynı bildirim, tek başına kaç ayrı
sürüm kümesi belirtiyor?** Ölçüm iki kuralı yan yana koyar, çıkan sayıyı bir dosyaya
yazdırır ve o dosyanın çözümlemeyi ortadan kaldırıp kaldırmadığını sınar.

### Bağımlılık Bildirimi

Source: https://academia.sh/tr/kurslar/python-projeleri/bagimlilik-bildirimi

Aynı bildirim çözümleme stratejisi değiştiğinde 2 ayrı sürüm kümesi verir ve kilit dosyası sayıyı 1'e indirir; depo görüntüsü de değiştiğinde bildirimle yapılan dört kurulum 3 ayrı küme verirken kilitli dördü tek küme kalır.

Önceki ders bir sınır çizdi ve sınırın payını ölçtü: yalıtılmış bir sitede kurulum sırası
ile komşu projeler elendi, beş ayrı sonuç bire indi. O ölçüm bir şeyi sabit tutarak elde
edildi — çözümleme kuralı hep aynıydı, hep en yeni uygun sürüm seçildi.

Bu ders o sabiti serbest bırakır. Sorusu şudur: **aynı bildirim, tek başına kaç ayrı sürüm
kümesi belirtiyor?** Bildirim projenin yazdığı tek şeydir ve değişmez; değişen, onu okuyan
tarafın kuralıdır. Yanıt ölçülecek, sonra bir dosyaya yazılıp ölçüm yinelenecek.

#### Bildirim Bir Aralık Söyler

**Bağımlılık bildirimi**, projenin meta verisinde duran ve hangi paketlere hangi sürüm
aralığında ihtiyaç duyulduğunu söyleyen listedir. Meta verinin kendisi bundan fazlasını
taşır: projenin adı, kendi sürümü, giriş noktaları, isteğe bağlı bağımlılık grupları. Bu
dersin ilgilendiği alan yalnız bağımlılık listesidir.

Bildirimin yazılış biçimi kritik bir ayrım taşır. Bir paket için yazılan şey neredeyse hiç
tek bir sürüm değildir; bir **aralıktır** — en küçük kabul edilen sürüm ve üstünde artık
kabul edilmeyen sürüm. Bunun bir nedeni var: tek sürüme sabitlenmiş bir bildirim, o paketi
kullanan başka bir paketle bir araya gelemez. Aralık, bir araya gelebilmenin koşuludur.

Bedeli de aynı yerden gelir. Bir aralık, içine düşen her sürümü meşru kılar. Bildirimi
okuyan taraf aralıktan **bir** sürüm seçmek zorundadır ve bu seçimi yapan kural bildirimde
yazmaz. Kural, aralığı okuyan **paket yöneticisinin** kendi **çözümleme stratejisidir
(resolution strategy)**.

Bu derste iki strateji karşılaştırılır: **en yeni** ve **en eski uyumlu**. İkisi de
bildirime uyar; ikisi de meşrudur. Stratejiler ve davranışları derste **modellenmiştir**;
hiçbir ürün adı yazılmaz ve gerçek kurulum yapılmaz.

#### Kilit Nedir, Ne Değildir

**Kilit dosyası (lock file)**, bir çözümlemenin sonucunu satır satır yazan belgedir:
aralık değil, seçilen tam sürümler. Bildirim insanın yazdığı istektir, kilit makinenin
ürettiği karardır. İkisi ayrı belgelerdir ve ikisi de sürüm denetimine konur — önceki
dersteki ortamın tersine, çünkü ortam bir yapıt, kilit ise bir karardır.

Kilidin ne olmadığını söylemek daha önemli. Kilit, çözümlemeyi ortadan kaldırmaz. Kilit
dosyasının kendisi **bir çözümlemenin çıktısıdır**; üretilmesi için aralıkların bir kez
okunması, bir stratejinin uygulanması ve bir kümenin seçilmesi gerekir. Kilidin yaptığı şey
o işi bir kez yaptırıp sonucunu **yazıya geçirmektir**. Sonraki kurulumlar çözümleme
yapmaz, yazılanı okur.

Bu ayrım ölçümde iki satır olarak görünür: kilit dosyasıyla yapılan iki kurulum, iki ayrı
strateji istense bile aynı kümeyi verir. Strateji artık okunmuyordur.

Ölçümün varsayımları:

- **OB12** — Depo, bildirim ve çözümleme işlevi ortak tanımdan birebir alınır; ders
  ikisinin de davranışını değiştirmez.
- **OB13** — Depo bir işlev parametresi haline getirilmiştir ve öntanımlı değeri ortak
  tanımın deposudur. Bağlayıcı sayılar öntanımlı depoyla üretilir.
- **OB14** — Bildirim bir aralıktır: en küçük sürüm dahil, en büyük sürüm hariç.
- **OB15** — İki strateji vardır: **en yeni**, uygun adayların sonuncusunu; **en eski
  uyumlu**, ilkini seçer. İkisi de bildirime uyar.
- **OB16** — Kâhin kurgudur: hangi sürümlerin yayımlandığını ve bildirimin ne kabul ettiğini
  biz yazdık; her satırın doğru sonucu ölçmeden bilinir.
- **OB17** — Kilit, bir çözümlemenin sonucunun kopyasıdır. Kilit varken çözümleme
  çağrılmaz; bu, modelde `kur` işlevinin tek dallanmasıdır.
- **OB18** — Ölçümde kilit **en yeni** stratejisinin sonucundan üretilir. Başka bir
  stratejiden üretilseydi kilitli satırlar o kümeyi gösterirdi; değişmeyecek olan, kilitli
  kurulumların **kaç ayrı sonuç** verdiğidir.
- **OB19** — İkinci depo görüntüsü, ilk depoya sonradan yayımlanmış sürümler eklenerek
  türetilir. Bildirim değişmez, depo değişir; eklenen sürümler bildirimin aralığına düşer.
- **OB20** — Bu derste paketler birbirini gerektirmez; her paket bağımsız seçilir. Geçişli
  gereklerin ve çakışmanın ne yaptığı sonraki dersin konusudur.
- **OB21** — Sürüm numaraları kurgu paketlerin kurgu sürümleridir; gerçek bir paketin ya da
  aracın sürümü değildir.
- **OB22** — **Gerçek kurulum yapılmaz, gerçek paket indirilmez.** Kurulum, seçilen sözlüğün
  döndürülmesidir.
- **OB23** — Süre ölçülmez. Sayılan şey **ayrı sürüm kümesi**, yani ayrı sonuçtur.

#### Ölçüm

```python
"""Bildirim bir aralik belirtir: strateji ve depo goruntusu kac ayri kume veriyor."""

DEPO = {
    "olcum": [(1, 0), (1, 1), (1, 2), (2, 0)],
    "rapor": [(0, 9), (1, 0), (1, 1)],
    "ortak": [(3, 0), (3, 1), (3, 2), (4, 0)],
}
BILDIRIM = {"olcum": ((1, 0), (2, 0)), "rapor": ((1, 0), (2, 0)),
            "ortak": ((3, 0), (4, 0))}
# Ayni depo, sonradan yayimlanmis surumlerle: bildirim degismedi, depo degisti.
YENI = {"olcum": [(1, 3)], "rapor": [(1, 2)], "ortak": [(3, 3)]}
SONRA = {p: sorted(DEPO[p] + YENI[p]) for p in DEPO}
PAKETLER = ("olcum", "ortak", "rapor")


def uygun(paket, depo=DEPO):
    alt, ust = BILDIRIM[paket]
    return [s for s in depo[paket] if alt <= s < ust]


def coz(strateji, depo=DEPO):
    """Ayni bildirimi iki ayri strateji cozer."""
    secim = {}
    for paket in sorted(BILDIRIM):
        adaylar = uygun(paket, depo)
        secim[paket] = adaylar[-1] if strateji == "en yeni" else adaylar[0]
    return secim


def kilit(secim):
    """Kilit, cozumlemenin sonucunu sabitler."""
    return dict(secim)


def kur(kilit_dosyasi=None, strateji="en yeni", depo=DEPO):
    return kilit_dosyasi if kilit_dosyasi else coz(strateji, depo)


def yazim(secim):
    return " ".join(f"{'.'.join(map(str, secim[p])):>7s}" for p in PAKETLER)


yeni, eski = coz("en yeni"), coz("en eski")
k = kilit(yeni)
print(f"{'kurulum':<26s} {'olcum':>7s} {'ortak':>7s} {'rapor':>7s}")
for ad, secim in (("strateji: en yeni", yeni), ("strateji: en eski", eski),
                  ("kilit dosyasıyla", kur(k)), ("kilit, başka strateji", kur(k, "en eski"))):
    print(f"{ad:<26s} " + yazim(secim))
ayri = len({tuple(sorted(s.items())) for s in (yeni, eski)})
ayri_kilitli = len({tuple(sorted(kur(k, st).items())) for st in ("en yeni", "en eski")})
print(f"aynı bildirim, stratejiye göre {ayri} ayrı küme; kilitle {ayri_kilitli}")

print()
print(f"{'depo görüntüsü / rejim':<26s} {'olcum':>7s} {'ortak':>7s} {'rapor':>7s}")
kurulumlar = []
for depo_ad, depo in (("ilk depo", DEPO), ("sonraki depo", SONRA)):
    for st in ("en yeni", "en eski"):
        secim = kur(None, st, depo)
        kurulumlar.append(secim)
        print(f"{depo_ad + ', ' + st:<26s} " + yazim(secim))
kilitli = [kur(k, st, depo) for depo in (DEPO, SONRA) for st in ("en yeni", "en eski")]
print(f"{'kilit dosyasıyla, dördü de':<26s} " + yazim(kilitli[0]))
print(f"iki depo görüntüsü ve iki strateji, dört kurulum: bildirimle "
      f"{len({tuple(sorted(s.items())) for s in kurulumlar})} ayrı küme, "
      f"kilitle {len({tuple(sorted(s.items())) for s in kilitli})}")

print()
uretim = coz("en yeni")
print(f"kilit çözümlemeden üretilir: kilit == en yeni çözümlemesi -> "
      f"{kilit(uretim) == uretim}")
print("kilit sonraki depoda yeniden üretilirse: " + yazim(kilit(coz("en yeni", SONRA))))
```

```
kurulum                      olcum   ortak   rapor
strateji: en yeni              1.2     3.2     1.1
strateji: en eski              1.0     3.0     1.0
kilit dosyasıyla               1.2     3.2     1.1
kilit, başka strateji          1.2     3.2     1.1
aynı bildirim, stratejiye göre 2 ayrı küme; kilitle 1

depo görüntüsü / rejim       olcum   ortak   rapor
ilk depo, en yeni              1.2     3.2     1.1
ilk depo, en eski              1.0     3.0     1.0
sonraki depo, en yeni          1.3     3.3     1.2
sonraki depo, en eski          1.0     3.0     1.0
kilit dosyasıyla, dördü de     1.2     3.2     1.1
iki depo görüntüsü ve iki strateji, dört kurulum: bildirimle 3 ayrı küme, kilitle 1

kilit çözümlemeden üretilir: kilit == en yeni çözümlemesi -> True
kilit sonraki depoda yeniden üretilirse:     1.3     3.3     1.2
```

#### İki Küme, Sonra Bir

Üst tablonun ilk iki satırı kursun ikinci iddiasını taşıyor. Aynı bildirim, aynı depo, aynı
üç paket: strateji **en yeni** olduğunda `1.2 / 3.2 / 1.1`, **en eski uyumlu** olduğunda
`1.0 / 3.0 / 1.0`. **2 ayrı sürüm kümesi.**

İki küme arasında hiçbir yerde bir hata yok. Bildirim ikisini de kabul ediyor; ikisi de
projenin yazdığı isteğe uyuyor. Ama bunlar aynı kurulum değil. Üç paketin üçü de ayrı
sürümde; kodun karşılaşacağı davranış, düzeltilmiş kusurlar ve eklenmiş yöntemler ayrı.
"Bildirim aynı, o hâlde kurulum aynı" cümlesi ölçümün çürüttüğü cümledir.

İkisi arasında bir üstünlük sıralaması da yok, çünkü ikisi ayrı şey sınıyor. **En yeni**
düzeltmeleri getirir, ama bildirimin üst sınırına en yakın davranışı getirdiği için
projenin en az sınadığı sürümleri seçer. **En eski uyumlu** tam tersini yapar: bildirimin
alt ucunun gerçekten çalıştığını sınar. Bir aralığın alt sınırı çoğu zaman kopyalanarak
yazılmıştır ve hiç denenmemiştir; onu seçen bir kurulum, bildirimin yalan söyleyip
söylemediğini ortaya çıkarır. Ölçümün söylediği şey hangisinin iyi olduğu değil, **ikisinin
aynı kurulum olmadığıdır.**

Alt iki satır kilidi devreye sokuyor. Kilit dosyasıyla yapılan kurulum `1.2 / 3.2 / 1.1`
veriyor; kilit dosyası **ve** başka bir strateji istendiğinde de aynı küme geliyor.
**Kilitle 1 ayrı sonuç.** Dördüncü satır tek başına şunu söylüyor: strateji artık okunmuyor,
çünkü okunacak bir aralık kalmadı. Karar zaten verilmiş ve yazılmış.

#### Depo Zamanla Değişir

İkinci tablo değişkeni bir tane artırıyor. Bildirim yine aynı, ama depo bir sonraki
görüntüsünde: aralığa düşen üç yeni sürüm yayımlanmış.

Bildirimle yapılan dört kurulum **3 ayrı küme** veriyor. `en yeni` stratejisi iki depo
görüntüsünde iki ayrı küme üretiyor — `1.2 / 3.2 / 1.1` ve `1.3 / 3.3 / 1.2` — çünkü "en
yeni" sabit bir sürüm değil, **deponun o anki durumuna bağlı bir işlevdir**. `en eski uyumlu`
ise iki görüntüde de aynı kümeyi veriyor; aralığın alt ucu deponun büyümesinden etkilenmiyor.

Bu satır, bildirimin taşıdığı belirsizliğin iki eksenli olduğunu gösteriyor: **kim çözüyor**
ve **ne zaman çözüyor**. Aynı bildirim aynı stratejiyle bile iki ayrı zamanda iki ayrı küme
verebilir. Kurulumun tarihi, çıktının bir parçasıdır.

Kilitli dört kurulum yine **1 ayrı sonuç** veriyor. Kilit her iki ekseni birden kapatıyor,
çünkü yazılan şey bir kural ya da bir aralık değil, sürümlerin kendisidir. Bu, iki tablonun
birlikte okunmasını gerektiren bir sonuçtur: bildirimle yapılan kurulumların sayısı
arttıkça ayrı sonuç sayısı da artıyor — iki kurulumda iki, dört kurulumda üç — kilitli
tarafta ise kurulum sayısı ne olursa olsun sayı **1**'de duruyor.

Son iki satır kilidin nereden geldiğini gösteriyor. Kilit, `en yeni` çözümlemesinin
sonucuna eşittir — üretilmesi için çözümleme koşmuştur. Ve aynı kilit sonraki depoda
yeniden üretilirse `1.3 / 3.3 / 1.2` çıkar. Yani kilit dondurulmuş bir çözümlemedir;
çözümleme yeniden koşturulduğunda dondurduğu şey de değişir.

#### Kilidin Kapsamı

Bildirim ile kilidin uzunlukları neredeyse hiç aynı değildir ve nedeni ölçümün dışında
kalan bir yerdedir.

Bildirim projenin **doğrudan** istediklerini yazar. Kurulan küme ise çoğu zaman daha
büyüktür, çünkü istenen her paketin kendi bildirimi vardır ve o bildirim de başka paketler
getirir. Bunlara **geçişli bağımlılık** denir. Projenin kodu onları hiç anmaz, ama koşum
anında sitede dururlar ve davranışa katılırlar. Kilit dosyası kurulan kümenin **tamamını**
yazar; doğrudan istenenleri değil, siteye giren her paketi. Bu yüzden kilit bildirimden çok
daha uzundur ve elle yazılacak bir belge değildir.

Bu dersin kurgusunda paketler birbirini gerektirmiyor, bu yüzden iki küme çakışıyor ve
tablolar üç satırla okunabiliyor. Farkın açıldığı yer sonraki dersin ölçümüdür.

Kilidin ikinci alanı sürüm adının altına bakar. Bir sürüm numarası bir **etikettir**; o
etiketin altındaki içeriğin zaman içinde değişmediğini kendi başına söylemez. Kilit
dosyaları bu yüzden sürümün yanına bir **içerik özeti** yazabilir ve kurulum sırasında
indirileni özetle karşılaştırabilir. Sürüm adı eşleşip özet eşleşmediğinde ortaya çıkan
şey bir sürüm sorunu değil, aynı adın iki ayrı içerik taşıması sorunudur — ve bu, sürüm
numarasının hiçbir zaman göremeyeceği bir ayrı sonuçtur.

Üçüncüsü kapsamın kendisidir. Bir projenin bildiriminde çoğu zaman birden çok grup bulunur:
koşum için gerekenler, sınama için gerekenler, belge üretimi için gerekenler. Bunlar ayrı
kümelerdir ve ayrı kurulur. Bir kilidin hangi grupları kapsadığı yazılı değilse, aynı kilit
iki ayrı kurulumda iki ayrı küme kurdurur — hem de her iki kurulum da kilide uymuş olur.
Kilit yalnız sürümleri değil, **hangi soruya yanıt verdiğini** de sabitlemek zorundadır.

#### Kilit Çözümlemeyi Ortadan Kaldırmaz

Ölçümün bütün satırları tek bir cümlede toplanır: **kilit çözümlemeyi ortadan kaldırmaz;
kararı bir kez verdirir ve yazıya geçirir.**

Bunun üç doğrudan sonucu var. Birincisi, kilit bir güvence değil bir **kayıttır**; ne kadar
iyi bir kümenin dondurulduğunu söylemez, yalnız hangi kümenin dondurulduğunu söyler. Kötü
bir çözümleme de aynı sadakatle kilitlenir.

İkincisi, kilidin yenilenmesi kaçınılmaz bir iştir. Aralık yeni sürümleri kabul ediyor ama
kilit onları getirmiyorsa, proje bildiriminin izin verdiği bir yerde değil, geçmişte bir
noktada duruyordur. Bu geçmiş nokta bilinçli seçilmişse bir karardır; farkında olunmadan
kalınmışsa bir birikmedir. İkisini ayıran tek şey, kilidin ne zaman ve neden yenilendiğinin
yazılı olmasıdır.

Üçüncüsü, bildirim ile kilidin **birbirinden sapabilmesidir**. Kilit elle düzenlenirse ya da
bildirim değiştirilip kilit yenilenmezse, iki belge iki ayrı şey söyler ve kurulum
hangisinin okunduğuna göre ayrı sonuç verir. Ölçümün son satırı bunun sınamasını veriyor:
kilit, aynı bildirimden aynı stratejiyle yeniden üretildiğinde kendisiyle aynı çıkmalıdır.
Çıkmıyorsa ya depo değişmiştir ya da belgelerden biri elle dokunulmuştur — ve hangisi
olduğu, deponun görüntüsüne bakılarak ayırt edilir.

#### Özet

- **Bağımlılık bildirimi** bir sürüm değil bir **aralık** söyler; aralık, başka paketlerle
  bir araya gelebilmenin koşuludur ve bedeli seçimin bildirimin dışında yapılmasıdır.
- Aynı bildirim, çözümleme stratejisi değiştiğinde **2 ayrı sürüm kümesi** verir:
  `1.2 / 3.2 / 1.1` ile `1.0 / 3.0 / 1.0`. İkisi de bildirime uyar.
- **Kilit dosyası** çözümlemenin sonucunu tam sürümlerle yazar; kilitli kurulum strateji
  değişse bile **1 ayrı sonuç** verir, çünkü okunacak aralık kalmamıştır.
- Depo görüntüsü de değiştiğinde bildirimle yapılan dört kurulum **3 ayrı küme** verir;
  belirsizlik iki eksenlidir: kim çözüyor ve ne zaman çözüyor.
- Kilit, doğrudan istenenleri değil **kurulan kümenin tamamını** yazar; hangi grupları
  kapsadığını da yazmazsa aynı kilit iki ayrı küme kurdurabilir.
- **Kilit çözümlemeyi ortadan kaldırmaz; kararı bir kez verdirir ve yazıya geçirir.** Kilit
  bir güvence değil bir kayıttır ve yenilendiğinde dondurduğu küme de değişir.

#### Sonraki Adım

Bu dersin ölçümünde paketler birbirinden bağımsız seçildi: her paket kendi aralığından
alındı ve seçimler birbirine hiç bakmadı. Gerçek bir kümede paketlerin kendi bildirimleri
vardır ve bunlar birbiriyle çelişebilir. Sonraki ders o durumu ölçer: aynı bildirim, paket
gerekleri de hesaba katıldığında hangi kümeleri veriyor, bir strateji tutarsız bir küme
seçtiğinde bu nereden görülüyor ve hiçbir tutarlı küme yoksa çözümleme ne döndürüyor.

### Paket Yöneticileri

Source: https://academia.sh/tr/kurslar/python-projeleri/paket-yoneticileri

Paketler birbirini gerektirdiğinde aynı bildirim dört strateji altında 4 ayrı küme verir ve bunların yalnız 3'ü tutarlıdır; kısıtlar sıkılaştığında iki geri izlemeli strateji çözüm bulamaz ve tutarlı küme sayısı 0'a iner, kilit dosyası ise dördünü de 1'e indirir.

Önceki dersin ölçümünde her paket kendi aralığından bağımsız seçildi; seçimler birbirine
hiç bakmadı. O varsayım tabloyu okunur kıldı ama gerçeği eksik bıraktı: kurulan paketlerin
de kendi bildirimleri vardır ve bunlar birbiriyle çelişebilir.

Bu ders o kısıtları tabloya koyar. Sorusu iki katmanlıdır: **aynı bildirim, paket gerekleri
de hesaba katıldığında hangi kümeleri veriyor** ve bir strateji çelişkili bir küme
seçtiğinde bu nereden görülüyor? Yanıtın ikinci yarısı ilkinden önemli çıkacak.

#### Paket Yöneticisi Ne Yapar

**Paket yöneticisi** bir araç sınıfının adıdır ve bu kurs hiçbir ürün adı yazmaz;
davranışları burada **modellenmiştir**. Yaptığı iş dört adıma ayrılır: bildirimi okumak,
depodan aday sürümleri toplamak, adaylar arasından bir küme seçmek, seçilen kümeyi siteye
kurmak. Bu dersin ilgilendiği adım üçüncüsüdür.

Dördüncü adımın nereye yazdığı ilk derste kuruldu: yalıtılmış bir sitede kurulum yalnız o
projenin gördüğünü değiştirir, paylaşılan bir sitede ise komşuların gördüğünü de. Bu ders
sitenin yalıtılmış olduğunu varsayar ve o eksene bir daha dönmez; buradaki bütün ayrı
sonuçlar tek bir sitenin içinde doğar.

Üçüncü adımın tek başına bir adı vardır: **çözümleme (resolution)**. Girdisi bir aralık
listesi ve bir aday havuzudur; çıktısı her paket için tam bir sürümdür. Arada duran şey,
paket yöneticisinin **çözümleme stratejisidir** ve önceki derste gösterildiği gibi bu
strateji bildirimde yazmaz.

Sürüm numarasının bölümlerinin ne anlattığı ve anlamlı sürümleme, **Sürüm Kontrolüne
Giriş** kursundaki etiketleme dersinde kuruldu; burada tekrarlanmaz. Buradaki soru sürüm
numarasının ne söylediği değil, **aralıkların birlikte hangi kümeleri mümkün kıldığıdır**.

#### Gerek Tablosu ve Tutarlılık

Kurguya tek bir ekleme yapılır: bir **gerek tablosu**. Tablo, her paket sürümünün hangi
paketten hangi aralığı istediğini yazar. Depo ve projenin bildirimi ortak tanımdan birebir
gelir ve değişmez; eklenen şey paketlerin kendi istekleridir.

Tablonun kurguda kurduğu gerilim tek cümleyle okunur: `olcum`'un yeni sürümleri `ortak`'ın
yeni sürümlerini ister, `rapor`'un yeni sürümü ise `ortak`'ın eski sürümlerini. İkisini de
en yeni haliyle isteyen bir kurulum, `ortak` için aynı anda iki ayrı şey istemiş olur.

Buradan ölçümün ikinci kavramı doğar. Bir küme **tutarlıdır** ancak ve ancak seçilen her
sürümün gereği, kümedeki öbür seçimlerce karşılanıyorsa. Tutarlılık kurulumun başarısıyla
aynı şey değildir: tutarsız bir küme de siteye kurulabilir, çünkü kurulum bir dosya
yazmadır ve gerek tablosunu okumak zorunda değildir.

#### Dört Strateji

Ölçüm dört strateji karşılaştırır. İlk ikisi **bağımsız** seçicidir: her paketi kendi
aralığının bir ucundan alır ve gerek tablosuna hiç bakmaz — önceki dersin iki stratejisi
budur. Kalan ikisi **geri izlemelidir**: bir sürüm seçer, kısıtları sınar, tutmadığında geri
döner ve başka bir sürüm dener.

Geri izlemeli iki stratejinin farkı yalnız **arama sırasıdır**: hangi paketin sürümüne önce
karar verildiği. Aynı bildirim, aynı gerek tablosu, aynı depo — değişen tek şey sıradır. Bu
fark ölçümün en önemli satırını üretecek.

Ölçümün varsayımları:

- **OB24** — Depo, bildirim ve aralık kuralı ortak tanımdan birebir alınır; ders ikisinin de
  davranışını değiştirmez. Eklenen tek şey gerek tablosudur.
- **OB25** — Gerek tablosu kurgudur ve kâhinin parçasıdır: hangi sürümün neyi istediğini biz
  yazdık, bu yüzden her kümenin tutarlı olup olmadığı ölçmeden bilinir.
- **OB26** — Bir paket sürümü en çok bir gerek taşır ve gerek bir aralıktır. Zincirin
  derinliği bir düzeydir; daha derin zincirler sayıyı büyütür, kuralı değiştirmez.
- **OB27** — Bağımsız stratejiler gerek tablosunu **okumaz**. Kurdukları küme tutarsız
  olabilir ve kurulum yine tamamlanır.
- **OB28** — Geri izleme, arama sırasındaki her pakete adayları strateji yönünde dener ve
  kısmi kümeyi her adımda sınar. İlk tutarlı tam kümeyi döndürür.
- **OB29** — Kısmi kümenin sınanmasında yalnız **iki ucu da seçilmiş** gerekler denetlenir;
  hedefi henüz seçilmemiş bir gerek o adımda ihlal sayılmaz.
- **OB30** — İki arama sırası denenir: `olcum, ortak, rapor` ve `rapor, ortak, olcum`.
  Sıra bir yapılandırma ayrıntısıdır; proje onu bildirimde yazmaz.
- **OB31** — Sıkı gerek tablosu, aynı depoda `olcum`'un bütün uygun sürümlerinin `ortak`'ın
  en yeni sürümünü, `rapor`'un bütün uygun sürümlerinin ise daha eskisini istemesiyle
  türetilir. Kesişimi boş olan bir bildirim kümesini modeller.
- **OB32** — Çözüm bulunamadığında geri izleme `None` döndürür ve bu bir **hata değil,
  ölçümün bir sonucudur**: tutarlı küme sayısı sıfırdır.
- **OB33** — Kilit, tutarlı kümelerden birinin — geri izlemeli en yeni stratejisinin
  sonucunun — kopyasıdır. Kilit varken çözümleme çağrılmaz.
- **OB34** — Sürüm numaraları kurgu paketlerin kurgu sürümleridir.
- **OB35** — **Gerçek kurulum yapılmaz, gerçek paket indirilmez;** çözümleme bir sözlük
  döndürür.
- **OB36** — Süre ölçülmez. Sayılan şey **ayrı küme**, **ayrı tutarlı küme** ve kilitli
  kurulumun verdiği **ayrı sonuç** sayısıdır.

#### Ölçüm

```python
"""Cozumleme stratejileri: ayni bildirim, ayri kume; cakisma dogdugunda ne oluyor."""

DEPO = {
    "olcum": [(1, 0), (1, 1), (1, 2), (2, 0)],
    "rapor": [(0, 9), (1, 0), (1, 1)],
    "ortak": [(3, 0), (3, 1), (3, 2), (4, 0)],
}
BILDIRIM = {"olcum": ((1, 0), (2, 0)), "rapor": ((1, 0), (2, 0)),
            "ortak": ((3, 0), (4, 0))}
PAKETLER = ("olcum", "ortak", "rapor")

# Paketlerin kendi bagimliliklari: surum -> (hedef paket, alt, ust)
GEREK = {
    ("olcum", (1, 0)): ("ortak", (3, 0), (4, 0)),
    ("olcum", (1, 1)): ("ortak", (3, 1), (4, 0)),
    ("olcum", (1, 2)): ("ortak", (3, 2), (4, 0)),
    ("rapor", (0, 9)): ("ortak", (3, 0), (3, 1)),
    ("rapor", (1, 0)): ("ortak", (3, 0), (4, 0)),
    ("rapor", (1, 1)): ("ortak", (3, 0), (3, 2)),
}
# Ayni depo, daha sikici bir gerek tablosu: olcum'un uc surumu de yeni ortak istiyor.
SIKI = dict(GEREK)
SIKI[("olcum", (1, 0))] = ("ortak", (3, 2), (4, 0))
SIKI[("olcum", (1, 1))] = ("ortak", (3, 2), (4, 0))
SIKI[("rapor", (1, 0))] = ("ortak", (3, 0), (3, 2))


def uygun(paket):
    alt, ust = BILDIRIM[paket]
    return [s for s in DEPO[paket] if alt <= s < ust]


def coz(strateji="en yeni"):
    """Bagimsiz secim: her paket kendi araliginin ucundan alinir."""
    secim = {}
    for paket in sorted(BILDIRIM):
        adaylar = uygun(paket)
        secim[paket] = adaylar[-1] if strateji == "en yeni" else adaylar[0]
    return secim


def tutarli(secim, gerek):
    """Secilen surumlerin gerekleri, secilmis olan paketlerce karsilaniyor mu."""
    for paket, surum in secim.items():
        k = gerek.get((paket, surum))
        if k is None:
            continue
        hedef, alt, ust = k
        if hedef in secim and not alt <= secim[hedef] < ust:
            return False
    return True


def geri_izle(sira, gerek, strateji="en yeni"):
    """Cakisma dogdugunda geri donup baska surum dener; sira arama sirasidir."""
    def ilerle(i, secim):
        if i == len(sira):
            return dict(secim)
        paket = sira[i]
        adaylar = uygun(paket)
        if strateji == "en yeni":
            adaylar = adaylar[::-1]
        for surum in adaylar:
            secim[paket] = surum
            if tutarli(secim, gerek):
                sonuc = ilerle(i + 1, secim)
                if sonuc is not None:
                    return sonuc
            del secim[paket]
        return None
    return ilerle(0, {})


def yazim(secim):
    if secim is None:
        return f"{'çözüm yok':>23s}"
    return " ".join(f"{'.'.join(map(str, secim[p])):>7s}" for p in PAKETLER)


def kur(kilit_dosyasi, islev, gerek):
    """Kilit varsa cozumleme yapilmaz; yoksa karari strateji verir."""
    return dict(kilit_dosyasi) if kilit_dosyasi else islev(gerek)


STRATEJILER = (
    ("bağımsız, en yeni", lambda g: coz("en yeni")),
    ("bağımsız, en eski", lambda g: coz("en eski")),
    ("geri izlemeli, en yeni", lambda g: geri_izle(("olcum", "ortak", "rapor"), g)),
    ("geri izlemeli, rapor önce", lambda g: geri_izle(("rapor", "ortak", "olcum"), g)),
)


def tablo(baslik, gerek):
    print(f"{baslik:<26s} {'olcum':>7s} {'ortak':>7s} {'rapor':>7s}  tutarlı")
    kumeler, saglam = [], []
    for ad, islev in STRATEJILER:
        secim = islev(gerek)
        durum = secim is not None and tutarli(secim, gerek)
        if secim is not None:
            kumeler.append(tuple(sorted(secim.items())))
            if durum:
                saglam.append(tuple(sorted(secim.items())))
        print(f"{ad:<26s} {yazim(secim)}  {'evet' if durum else 'hayır':>7s}")
    print(f"dört strateji: {len(set(kumeler))} ayrı küme, "
          f"{len(set(saglam))} ayrı tutarlı küme")


tablo("gerek tablosu", GEREK)
print()
tablo("sıkı gerek tablosu", SIKI)
print()
kilit_dosyasi = geri_izle(("olcum", "ortak", "rapor"), GEREK)
kilitli = {tuple(sorted(kur(kilit_dosyasi, islev, GEREK).items()))
           for _, islev in STRATEJILER}
print("kilide yazılan küme:      " + yazim(kilit_dosyasi))
print(f"kilit dosyası varken dört strateji: {len(kilitli)} ayrı küme")
```

```
gerek tablosu                olcum   ortak   rapor  tutarlı
bağımsız, en yeni              1.2     3.2     1.1    hayır
bağımsız, en eski              1.0     3.0     1.0     evet
geri izlemeli, en yeni         1.2     3.2     1.0     evet
geri izlemeli, rapor önce      1.1     3.1     1.1     evet
dört strateji: 4 ayrı küme, 3 ayrı tutarlı küme

sıkı gerek tablosu           olcum   ortak   rapor  tutarlı
bağımsız, en yeni              1.2     3.2     1.1    hayır
bağımsız, en eski              1.0     3.0     1.0    hayır
geri izlemeli, en yeni                   çözüm yok    hayır
geri izlemeli, rapor önce                çözüm yok    hayır
dört strateji: 2 ayrı küme, 0 ayrı tutarlı küme

kilide yazılan küme:          1.2     3.2     1.0
kilit dosyası varken dört strateji: 1 ayrı küme
```

#### Dört Strateji, Dört Küme

Üst tablonun dört satırı **4 ayrı küme** veriyor ve dördü de aynı bildirimden çıkıyor. Bu,
önceki dersin iki kümesinin üstüne iki tane daha ekliyor; eklenen ikisi bağımsız seçimin
ulaşamayacağı kümeler.

İlk satır kursun bu dersteki asıl bulgusudur. Bağımsız `en yeni` stratejisi
`1.2 / 3.2 / 1.1` kümesini seçiyor ve bu küme **tutarsız**: `rapor 1.1`, `ortak` için
`3.2`'den eskisini istiyor, ama kümede `ortak 3.2` var. Buna karşın kurulum tamamlanıyor. Hiçbir hata iletisi doğmuyor,
çünkü bağımsız strateji gerek tablosunu hiç okumadı. Sonuç, sitede duran ve birbirini
istemeyen üç paket.

İkinci satır bağımsız `en eski` stratejisinin tesadüfen tutarlı bir küme kurduğunu
gösteriyor. Tesadüf sözcüğü burada tam yerindedir: strateji yine gerek tablosunu okumadı,
yalnızca aralıkların alt uçları çakışmadı. Aynı strateji başka bir tabloyla tutarsız bir
küme kurardı — nitekim alt tabloda kuruyor.

Son iki satır geri izlemeli stratejileri veriyor ve ikisi de tutarlı, ama **ayrı**:
`1.2 / 3.2 / 1.0` ile `1.1 / 3.1 / 1.1`. İkisi arasında tek fark arama sırasıdır — hangi
paketin sürümüne önce karar verildiği. Bildirim aynı, depo aynı, kısıtlar aynı; yine de iki
ayrı meşru kurulum. Bu satır, **çözümleyicinin kendisinin bir ayrı sonuç ekseni olduğunu**
söylüyor: aracı değiştirmek, bildirimi değiştirmeden kurulumu değiştirebilir.

Toplam: dört strateji, **4 ayrı küme**, bunların **3'ü tutarlı**.

#### Çakışma Nereye Yazılır

Alt tablo kısıtları sıkılaştırıyor: bu tabloda `olcum`'un bütün uygun sürümleri `ortak`'ın
en yenisini, `rapor`'un bütün uygun sürümleri ise daha eskisini istiyor. İki isteğin
kesişimi boş.

Geri izlemeli iki strateji de **çözüm yok** döndürüyor. Tutarlı küme sayısı **0**. Bu bir
başarısızlık gibi görünür ve bir anlamda öyledir — ama ölçümün asıl bulgusu üstteki iki
satırdadır: bağımsız stratejiler yine bir küme döndürüyor. Aynı çelişki, okuyan araca göre
ya bir ret ya sessiz bir kurulum üretiyor.

İkisi arasındaki fark, hatanın **nerede görüneceğidir**. Ret, çözümleme anında görünür ve
elde bir bilgi bırakır: bu bildirim kümesi çelişkilidir, ya bir aralık gevşetilecek ya bir
paket değiştirilecektir. Sessiz kurulum ise hiçbir şey bırakmaz; çelişki siteye kurulur ve
ancak çalışma zamanında, çelişen iki paketin karşılaştığı satırda ortaya çıkar. Ve o satıra
her koşumda uğranmaz.

Ölçünün diliyle: ret **0 ayrı sonuç** üretir ve sıfır, sayılabilir bir yanıttır. Sessiz
kurulum **1 ayrı sonuç** üretir ama o sonucun geçerli olup olmadığını ölçmez. Bir
çözümleyicinin çözemediğini söylemesi, çözemediğini söylememesinden bilgi açısından
üstündür.

#### Çözümleyicinin Kendisi Yeniden Üretilebilir mi

Ölçüm dört stratejiyi karşılaştırdı, ama her stratejinin **kendi içinde** kaç sonuç
verdiğini de sormak gerekir. Modeldeki dört stratejinin dördü de belirlenimcidir: aday
listesi depodan sıralı gelir, arama sırası sabittir, hiçbir yerde rastgelelik yoktur. Aynı
strateji aynı girdiyle çağrıldığında **1 ayrı sonuç** verir.

Bu, bir çözümleyiciden istenecek en temel özelliktir ve kendiliğinden gelmez. Aday havuzu
birden çok kaynaktan toplanıyorsa hangi kaynağın önce yanıtladığı sıraya karışabilir; bir
önbellek ısındığında aynı sorgu ayrı bir liste döndürebilir; paralel indirme kurulum
sırasını değiştirebilir. Bunların hiçbiri stratejinin kuralını değiştirmez, ama hepsi
**girdisini** değiştirir — ve girdisi değişen bir kural ayrı bir küme verir.

Bu yüzden bir çözümleyicinin sözü iki katmanlıdır. Birinci katman kuraldır: hangi aday
seçilir. İkinci katman kuralın girdisidir: adayların hangi düzende, hangi kaynaktan ve
hangi anda toplandığı. Yeniden üretilebilir bir kurulum ikisini birden sabitlemeyi
gerektirir ve ikinci katmanı yazıya geçiren belge kilit dosyasıdır.

#### Kilidin Bu Tablodaki Yeri

Son iki satır önceki dersin sonucunu bu tablonun üstüne koyuyor. Tutarlı kümelerden biri —
geri izlemeli en yeni stratejisinin bulduğu `1.2 / 3.2 / 1.0` — kilide yazıldığında dört
strateji de aynı kümeyi kuruyor: **1 ayrı sonuç**.

Kilidin burada kapattığı eksen yenidir. Önceki derste kapatılanlar strateji yönü ve deponun
zamanıydı; burada kapatılan **çözümleyicinin kendisi**. Kilit yazıldıktan sonra hangi paket
yöneticisinin kullanıldığı sonucu değiştirmiyor, çünkü çözümlenecek bir şey kalmadı.

Bu, kilidin ne kadar iş yaptığını da sınırlıyor. Kilit, kilidi üreten çözümleyicinin
kararını taşır; iyi bir kararı da kötü bir kararı da aynı sadakatle. Üstteki tabloda üç
tutarlı kümeden hangisinin kilide yazıldığı, kilide bakılarak anlaşılmaz — kilit yalnız
seçileni yazar, seçme gerekçesini değil.

#### Özet

- **Paket yöneticisi** bir araç sınıfıdır; işi bildirimi okumak, aday toplamak, bir küme
  seçmek ve kurmaktır. Ayrı sonucu üreten adım seçme adımıdır.
- Paketlerin kendi gerekleri hesaba katıldığında aynı bildirim dört strateji altında
  **4 ayrı küme** verir ve bunların **3'ü tutarlıdır**.
- Bağımsız seçim gerek tablosunu okumaz: `1.2 / 3.2 / 1.1` kümesi **tutarsız** olduğu halde
  kurulur ve hiçbir uyarı üretmez.
- İki geri izlemeli strateji ayrı **arama sırası** yüzünden iki ayrı tutarlı küme verir;
  çözümleyicinin kendisi bir ayrı sonuç eksenidir.
- Kesişimi boş kısıtlarda geri izleme **0 tutarlı küme** döndürür; ret çözümleme anında
  bilgi bırakır, sessiz kurulum ise çelişkiyi çalışma zamanına erteler.
- Kilit dosyası dört stratejiyi **1 ayrı sonuca** indirir, ama yazdığı kararın hangi
  gerekçeyle seçildiğini taşımaz.

#### Sonraki Adım

Bu dersin bütün kümeleri tek bir yorumlayıcı varsayıldığı için karşılaştırılabildi. O
varsayım da tıpkı önceki iki varsayım gibi yazılı değildi. Sonraki ders onu ölçüme sokar:
aynı proje, aynı bildirim, ayrı yorumlayıcı sürümleri. Sorulacak olan şudur — çözümleme
yorumlayıcıya göre kaç ayrı küme veriyor ve bir yorumlayıcıda üretilmiş kilit dosyası bir
başkasında ne yapıyor?

### Sürüm Yönetimi

Source: https://academia.sh/tr/kurslar/python-projeleri/surum-yonetimi

Aynı bildirim üç kurgu yorumlayıcı sürümünde 2 ayrı sürüm kümesi verir; bir yorumlayıcıda üretilmiş kilit dosyalarının dokuz kurulumundan 6'sı kabul edilip 3'ü reddedilir ve aynı kilit sabit yorumlayıcıda 1 ayrı sonuç verirken üç yorumlayıcıda bir ret üretir.

Önceki üç ders aynı bildirimin kaç ayrı sürüm kümesi verdiğini üç ayrı eksende ölçtü:
yalıtım sınırı, çözümleme stratejisi ve çözümleyicinin kendisi. Üçünde de yazılmamış bir
varsayım vardı — tek bir yorumlayıcı. Paket sürümleri karşılaştırılırken hepsinin aynı
yorumlayıcıda kurulabildiği kabul edildi.

Bu ders o varsayımı ölçüme sokar. Sorusu iki parçalıdır: **aynı proje, ayrı yorumlayıcı
sürümlerinde kaç ayrı sonuç veriyor** ve bir yorumlayıcıda üretilmiş kilit dosyası bir
başkasında ne yapıyor? İkinci parçanın yanıtı, kilidin ne tür bir güvence olduğunu
kesinleştirecek.

#### Yorumlayıcı da Bir Bağımlılıktır

Bir proje kendi bildirimini yazarken paketlerini sayar; yorumlayıcıyı saymayı çoğu zaman
atlar. Oysa yorumlayıcı da kurulan bir şeydir, sürümü vardır ve o sürüm değiştiğinde
projenin karşılaştığı davranış değişebilir.

Bu ders **gerçek sürüm numarası yazmaz.** Yorumlayıcılar kurgu etiketlerle anılır: `Y1`,
`Y2`, `Y3`. Etiketlerin sırası bir zaman sırasıdır — `Y1` en eski, `Y3` en yeni — ve bunun
dışında hiçbir şey ima etmez. Paketlerin sürümleri de önceki üç dersteki gibi kurgudur.

Ölçümün kurduğu ilişki basit bir tablodur: her paket sürümü, hangi yorumlayıcı sürümlerinde
kurulabildiğini bildirir. Bu bir kurgu değil, gerçek bir kısıttır; bir paket sürümü belli
bir dil düzeyine göre yazılmıştır ve o düzeyi karşılamayan bir yorumlayıcıda ya kurulamaz
ya çalışmaz. Kurguda bu ilişki bir küme olarak yazılmıştır ve **modellenmiştir**; hiçbir
ürün adı geçmez.

Bunun doğrudan sonucu şudur: yorumlayıcı, aday havuzunu **daraltır**. Bildirim
değişmemiştir, depo değişmemiştir, strateji değişmemiştir; ama aynı aralıktan seçilebilecek
sürümlerin listesi yorumlayıcıya göre kısalır. Çözümleme aynı kuralı ayrı bir havuza
uygular ve ayrı bir küme döndürür.

#### Ortamın Yorumlayıcıya Bağlılığı

İlk derste sanal ortamın yorumlayıcıyı çoğaltmadığı, ona bağlandığı söylenmişti. Buradaki
ölçüm o cümlenin bedelini gösterir.

Bir ortam hangi yorumlayıcı üzerinde kurulduysa onunla anlamlıdır. Yorumlayıcı sürümü
değiştiğinde ortam taşınmaz: sitedeki paketlerin bir bölümü yeni yorumlayıcı için
üretilmemiş olabilir ve içe aktarma yolu da yorumlayıcı sürümüne göre adlandırılır. Geçiş
işlemi bu yüzden bir yükseltme değil, **yeni bir ortam kurmaktır** — ve yeni ortamda ne
kurulacağı yeniden çözümlenir.

Aynı şey kilit dosyası için de geçerli midir? Kilit tam sürümler yazar ve çözümleme
yapmaz; öyleyse yeni yorumlayıcıda da aynı kümeyi kurmalıdır. Ölçümün sınadığı sav budur ve
yanıt tek kelimeyle verilemez.

Ölçümün varsayımları:

- **OB37** — Depo, bildirim ve aralık kuralı ortak tanımdan birebir alınır; ders ikisinin de
  davranışını değiştirmez. Eklenen tek şey uyum tablosudur.
- **OB38** — Üç yorumlayıcı sürümü kurgudur ve `Y1`, `Y2`, `Y3` etiketleriyle anılır.
  **Gerçek sürüm numarası yazılmaz.**
- **OB39** — Uyum tablosu kurgudur ve kâhinin parçasıdır: her paket sürümünün hangi
  yorumlayıcılarda kurulabildiğini biz yazdık, bu yüzden her satırın doğru sonucu ölçmeden
  bilinir.
- **OB40** — Uyum bir ikili karardır: bir sürüm bir yorumlayıcıda ya kurulabilir ya
  kurulamaz. Kısmi çalışma ya da uyarıyla kurulum modellenmez.
- **OB41** — Çözümleme aday havuzunu önce bildirimle, sonra uyum tablosuyla daraltır. Sıra
  sonucu değiştirmez; iki süzgeç de kesişimi alır.
- **OB42** — Strateji bu derste sabittir: **en yeni uygun sürüm**. Strateji ekseni önceki iki
  derste ölçüldü ve burada tekrarlanmaz.
- **OB43** — Paketler bu derste birbirini gerektirmez; gerek ekseni önceki derste ölçüldü.
  Ölçülen tek yeni eksen yorumlayıcıdır.
- **OB44** — Kilit dosyası tam sürümler yazar ve çözümleme yapmaz. Kilitle kurulum, yazılan
  her sürümün hedef yorumlayıcıda kurulabilmesini gerektirir.
- **OB45** — Kurulum reddi bir hata değil, **ölçümün bir sonucudur** ve tabloda `ret` olarak
  sayılır. Reddedilen kurulum hiçbir ayrı sonuç üretmez.
- **OB46** — Her yorumlayıcının kilidi kendi çözümlemesinden üretilir; dokuz kurulum, üç
  kilidin üç yorumlayıcıya uygulanmasıdır.
- **OB47** — Yorumlayıcı sürümleri arasında geçiş, ortamın yeniden kurulmasıdır; eski site
  taşınmaz ve modelde de taşınmaz.
- **OB48** — **Gerçek kurulum yapılmaz, gerçek paket indirilmez, gerçek yorumlayıcı
  çağrılmaz.** Yorumlayıcı bir etiket, kurulum bir sözlüktür.
- **OB49** — Süre ölçülmez. Sayılan şey **ayrı sürüm kümesi**, **kabul** ve **ret** sayısıdır.
- **OB50** — Ölçüm tek koşumdur, belirlenimcidir ve kurguda rastgelelik yoktur.

#### Ölçüm

```python
"""Yorumlayici surumleri: ayni proje, ayni bildirim, kac ayri sonuc."""

DEPO = {
    "olcum": [(1, 0), (1, 1), (1, 2), (2, 0)],
    "rapor": [(0, 9), (1, 0), (1, 1)],
    "ortak": [(3, 0), (3, 1), (3, 2), (4, 0)],
}
BILDIRIM = {"olcum": ((1, 0), (2, 0)), "rapor": ((1, 0), (2, 0)),
            "ortak": ((3, 0), (4, 0))}
PAKETLER = ("olcum", "ortak", "rapor")

# Kurgu yorumlayici surumleri; gercek surum numarasi degildir.
YORUMLAYICILAR = ("Y1", "Y2", "Y3")

# Paket surumu -> destekledigi yorumlayicilar
UYUM = {
    ("olcum", (1, 0)): {"Y1", "Y2"}, ("olcum", (1, 1)): {"Y1", "Y2"},
    ("olcum", (1, 2)): {"Y2", "Y3"}, ("olcum", (2, 0)): {"Y3"},
    ("ortak", (3, 0)): {"Y1", "Y2"}, ("ortak", (3, 1)): {"Y1", "Y2", "Y3"},
    ("ortak", (3, 2)): {"Y2", "Y3"}, ("ortak", (4, 0)): {"Y3"},
    ("rapor", (0, 9)): {"Y1"}, ("rapor", (1, 0)): {"Y1", "Y2"},
    ("rapor", (1, 1)): {"Y2", "Y3"},
}


def uygun(paket, yorumlayici):
    alt, ust = BILDIRIM[paket]
    return [s for s in DEPO[paket]
            if alt <= s < ust and yorumlayici in UYUM[(paket, s)]]


def coz(yorumlayici, strateji="en yeni"):
    secim = {}
    for paket in sorted(BILDIRIM):
        adaylar = uygun(paket, yorumlayici)
        if not adaylar:
            return None
        secim[paket] = adaylar[-1] if strateji == "en yeni" else adaylar[0]
    return secim


def kilit(secim):
    return dict(secim)


def kur(kilit_dosyasi, yorumlayici):
    """Kilit her surumu tam yazar; yorumlayici desteklemiyorsa kurulum reddedilir."""
    if all(yorumlayici in UYUM[(p, s)] for p, s in kilit_dosyasi.items()):
        return dict(kilit_dosyasi)
    return None


def yazim(secim):
    if secim is None:
        return f"{'reddedildi':>23s}"
    return " ".join(f"{'.'.join(map(str, secim[p])):>7s}" for p in PAKETLER)


def kisa(secim):
    if secim is None:
        return f"{'ret':>12s}"
    return f"{'/'.join('.'.join(map(str, secim[p])) for p in PAKETLER):>12s}"


def anahtar(secim):
    return tuple(sorted(secim.items()))


print(f"{'bildirimle çözümleme':<26s} {'olcum':>7s} {'ortak':>7s} {'rapor':>7s}")
cozumler = {}
for y in YORUMLAYICILAR:
    cozumler[y] = coz(y)
    print(f"{'yorumlayıcı ' + y:<26s} " + yazim(cozumler[y]))
print(f"aynı bildirim, üç yorumlayıcı: {len({anahtar(s) for s in cozumler.values()})} "
      f"ayrı sürüm kümesi")

print()
print(f"{'kilit üretildiği yorumlayıcı':<30s} " +
      " ".join(f"{'kurulum ' + y:>12s}" for y in YORUMLAYICILAR))
kabul, kumeler = 0, []
for uretim in YORUMLAYICILAR:
    k = kilit(cozumler[uretim])
    hucre = []
    for hedef in YORUMLAYICILAR:
        s = kur(k, hedef)
        kabul += s is not None
        if s is not None:
            kumeler.append(anahtar(s))
        hucre.append(kisa(s))
    print(f"{'kilit ' + uretim + ' üzerinde üretildi':<30s} " + " ".join(hucre))
print(f"dokuz kurulum: {kabul} kabul, {9 - kabul} ret; kabul edilenlerde "
      f"{len(set(kumeler))} ayrı sürüm kümesi")

print()
k2 = kilit(cozumler["Y2"])
sabit = [kur(k2, "Y2") for _ in range(3)]
gezen = [kur(k2, y) for y in YORUMLAYICILAR]
for ad, kurulumlar in (("aynı kilit, Y2'de üç kurulum", sabit),
                       ("aynı kilit, üç yorumlayıcıda", gezen)):
    olan = [s for s in kurulumlar if s is not None]
    print(f"{ad:<30s} {len({anahtar(s) for s in olan})} ayrı sonuç, "
          f"{len(kurulumlar) - len(olan)} ret")
```

```
bildirimle çözümleme         olcum   ortak   rapor
yorumlayıcı Y1                 1.1     3.1     1.0
yorumlayıcı Y2                 1.2     3.2     1.1
yorumlayıcı Y3                 1.2     3.2     1.1
aynı bildirim, üç yorumlayıcı: 2 ayrı sürüm kümesi

kilit üretildiği yorumlayıcı     kurulum Y1   kurulum Y2   kurulum Y3
kilit Y1 üzerinde üretildi      1.1/3.1/1.0  1.1/3.1/1.0          ret
kilit Y2 üzerinde üretildi              ret  1.2/3.2/1.1  1.2/3.2/1.1
kilit Y3 üzerinde üretildi              ret  1.2/3.2/1.1  1.2/3.2/1.1
dokuz kurulum: 6 kabul, 3 ret; kabul edilenlerde 2 ayrı sürüm kümesi

aynı kilit, Y2'de üç kurulum   1 ayrı sonuç, 0 ret
aynı kilit, üç yorumlayıcıda   1 ayrı sonuç, 1 ret
```

#### İki Küme, Üç Yorumlayıcı

Üst tablo aynı bildirimi üç yorumlayıcıda çözüyor ve **2 ayrı sürüm kümesi** veriyor.

`Y1` satırı `1.1 / 3.1 / 1.0` veriyor. Üçünün de en yenisi değil bunlar; `olcum 1.2`,
`ortak 3.2` ve `rapor 1.1` bu yorumlayıcıda kurulamıyor ve havuzdan düşüyor. Strateji hâlâ
"en yeni uygun sürüm"; değişen, neyin uygun sayıldığı. `Y2` ve `Y3` ise aynı kümeyi veriyor:
`1.2 / 3.2 / 1.1`.

İki gözlem buradan çıkar. Birincisi, yorumlayıcı ekseni **kendi başına** bir ayrı sonuç
üretir: bildirime, stratejiye ve depoya dokunmadan iki kurulum elde edildi. İkincisi, sayı
yorumlayıcı sayısıyla aynı değil. Üç yorumlayıcı iki küme verdi çünkü `Y2` ile `Y3` aynı
adaylara izin veriyor. Ayrı sonuç sayısı, ortam sayısının değil **uyum tablosunun**
işlevidir.

`Y1` satırının bir yan sonucu daha var. O yorumlayıcıda çalışan bir proje, sonraki
sürümlerde düzeltilmiş kusurları taşıyan paketleri hiç görmez; bildirimi onları kabul etse
bile. Yorumlayıcıyı yerinde tutmak, paket sürümlerini de yerinde tutmaktır.

#### Kilit Bir Yorumlayıcı İçin Üretilir

Orta tablo dokuz kurulum veriyor: üç kilit, üç hedef yorumlayıcı. **6 kabul, 3 ret.**

Her satırın kendi deseni var. `Y1` üzerinde üretilen kilit `Y1` ve `Y2`'de kuruluyor,
`Y3`'te **reddediliyor** — `olcum 1.1` ile `rapor 1.0` o yorumlayıcıda kurulamıyor. `Y2` ve
`Y3` üzerinde üretilen kilitler aynı kümeyi yazıyor ve ikisi de `Y1`'de reddediliyor.

Reddin ne olduğunu doğru okumak gerekir. Bu bir yükseltme hatası değildir; kilidin doğru
davranışıdır. Kilit tam sürümler yazar ve pazarlık yapmaz; yazdığı sürüm hedef ortamda
kurulamıyorsa yapabileceği tek şey durmaktır. Alternatifi, kilidi sessizce esnetip başka
bir sürüm kurmak olurdu — ve o durumda kilit, kilit olmaktan çıkardı.

Kabul edilen altı kurulum **2 ayrı sürüm kümesi** veriyor. Bu sayı üstteki tablonunkiyle
aynı ve tesadüf değil: kilitler de o çözümlemelerden üretildi. Kilit yeni bir küme
yaratmaz, var olan bir kümeyi taşır.

#### Kilidin Güvence Verdiği Eksen

Alt iki satır dersin sonucudur ve kilidin ne tür bir güvence olduğunu tam olarak yazar.

Aynı kilit, **sabit** bir yorumlayıcıda üç kez kurulduğunda **1 ayrı sonuç**, **0 ret**
veriyor. Aynı kilit üç ayrı yorumlayıcıda kurulduğunda yine **1 ayrı sonuç** ama **1 ret**
veriyor. Kilit hiçbir yerde ikinci bir küme üretmedi — sözünü tuttu. Tutamadığı şey başkaydı:
sözünü **her ortamda** tutamadı.

Buradan kilidin sınırı çıkıyor: **kilit yeniden üretilebilirliği bir yorumlayıcı içinde
sağlar; yorumlayıcı ekseninde sağlamaz.** O eksende kilit bir güvence değil, bir
**kısıttır** — ve kısıt olması kusur değil, işlevidir. Bir kilidin taşınamaması, taşınmaması
gerektiğinin bildirimidir.

Pratik karşılığı doğrudan bir kuraldır: kilit dosyası, üretildiği yorumlayıcı sürümünü de
yazmalıdır. Yazmadığında elde iki belge kalır — biri hangi sürümlerin kurulacağını söyler,
öbürü hangi ortamda geçerli olduğunu söylemez — ve ret, ancak kurulum anında görünür.
Bildirimin yorumlayıcı için de bir aralık taşıması aynı gerekçeyledir: proje hangi
yorumlayıcılarda çalıştığını söylemezse, uyum tablosu ancak kurulum denenerek keşfedilir.

Bir üçüncü sonuç da sayıların dışında durur. Yorumlayıcı geçişi bir bakım işi gibi
görünür, ama ölçüm onun bir **çözümleme** işi olduğunu gösteriyor: geçişte kilit yeniden
üretilir, yeni küme eskisinden ayrı çıkabilir ve proje o yeni kümede sınanmamıştır. Geçişin
maliyeti yorumlayıcıyı kurmak değil, **yeni kümeyi doğrulamaktır**.

#### Yan Yana Duran Yorumlayıcılar

Ölçümün üç satırı bir makinede üç yorumlayıcının aynı anda bulunabileceğini varsayar ve bu
varsayım gerçeğe uyar. Birden çok yorumlayıcı sürümünü yan yana tutan araçlar bir sınıf
oluşturur — **sürüm yöneticisi** — ve yaptıkları iş bir seçimdir: hangi dizinde hangi
yorumlayıcının çağrılacağı. Ürün adları burada da yazılmaz.

Seçimin nerede yapıldığı önemlidir. Sanal ortam kurulduğu anda bir yorumlayıcıya bağlanır;
o noktadan sonra projenin hangi yorumlayıcıyı kullandığı bir yapılandırma ayarı değil,
**ortamın kimliğidir**. Sürüm yöneticisinin seçtiği şey, yeni bir ortam kurulurken hangi
yorumlayıcının kullanılacağıdır — var olan bir ortamı başka bir yorumlayıcıya
devretmez.

Geçişin akışı bu yüzden beş adımdır ve hiçbiri atlanamaz: yeni yorumlayıcı kurulur, onun
üzerinde yeni bir ortam kurulur, bildirim yeniden çözümlenir, yeni kilit üretilir, proje
yeni kümede sınanır. Eski ortam ancak bundan sonra atılır. Ölçümün orta tablosu üçüncü ve
dördüncü adımın neden atlanamayacağını gösteriyor: yeni yorumlayıcıda eski kilidi kurmayı
denemek, üç durumdan birinde ret üretiyor.

Bir projenin aynı anda birden çok yorumlayıcıyı desteklemesi de aynı tablodan okunur.
Ölçümde her yorumlayıcının kendi kilidi var: **üç kilit, 2 ayrı küme.** İki yorumlayıcıyı
birden desteklemek, tek bir kilidi ikisine birden uydurmak değil, **iki kilit tutmak**
demektir. Tek kilitle desteklenebilen yorumlayıcılar yalnız o kilitteki bütün sürümlerin
ortak uyum kümesidir — tabloda `Y2` ile `Y3` böyle bir çifttir, `Y1` ile `Y3` değildir.

Yan yana durmanın bedeli de aynı tabloda duruyor: desteklenen her yorumlayıcı için ayrı bir
site, ayrı bir kurulum ve ayrı bir kilit. Üçü de projenin verdiği her söz için ayrı ayrı
ödenir; desteklenen sürüm sayısı arttıkça sabitlenmesi gereken küme sayısı da artar ve her
küme kendi başına doğrulanmayı bekler.

#### Özet

- Yorumlayıcı da bir bağımlılıktır ve aday havuzunu daraltır; bildirim, depo ve strateji
  değişmeden **2 ayrı sürüm kümesi** doğar.
- Ayrı sonuç sayısı ortam sayısına değil **uyum tablosuna** bağlıdır: üç yorumlayıcı iki
  küme verir, çünkü ikisi aynı adaylara izin verir.
- Bir yorumlayıcıda üretilen kilit başka birinde reddedilebilir: dokuz kurulumun **6'sı
  kabul, 3'ü rettir**. Ret bir hata değil, kilidin doğru davranışıdır.
- **Kilit yeniden üretilebilirliği bir yorumlayıcı içinde sağlar, yorumlayıcı ekseninde
  sağlamaz;** o eksende bir güvence değil bir kısıttır.
- İki yorumlayıcıyı birden desteklemek tek kilidi ikisine uydurmak değil, **iki kilit
  tutmaktır**; tek kilit ancak sürümlerinin ortak uyum kümesindeki yorumlayıcıları kapsar.
- Yorumlayıcı geçişi ortamın yeniden kurulmasıdır ve kilit yeniden üretilir; maliyeti
  kurulum değil, yeni kümenin doğrulanmasıdır.

#### Sonraki Adım

Bu konu boyunca dört eksen ölçüldü — yalıtım sınırı, çözümleme stratejisi, çözümleyicinin
kendisi ve yorumlayıcı sürümü — ve dördü de aynı yöntemle tekleştirildi: kararı bir kez
verdir, yazıya geçir, sonraki koşumlara okut. Kurulumun kaç ayrı sonuç verdiği artık
sayılabiliyor.

Geriye kurulumun içindeki asıl şey kalıyor: **kod**. Ortam tek bir kümeye indirildiğinde,
o kümede koşan kodun kendisi her koşumda aynı kararı veriyor mu? Sonraki konu bu soruyu
sorar ve yanıtı ölçmek için testin ne olduğunu, bir takımın ne bildirdiğini ve aynı
takımın kaç ayrı karar verebildiğini ele alır.

## Test

Birim test yazım biçimleri, tekrarın düzenek ve parametrelemeyle testten çıkarılması, dış bağımlılıkların yalıtılması, örneklerin sınanabilir hale gelmesi, ve kapsamın ne söyleyip ne söylemediği.

### Test Çerçeveleri

Source: https://academia.sh/tr/kurslar/python-projeleri/test-cerceveleri

Çerçeve dört iş yapar ve bunlardan yalnız biri kararı üretir: aynı üç durum iki yazım biçiminde 1 ayrı sonuç verirken, aynı dosya üç keşif kuralında bulunan test bakımından 3 ayrı sonuç verir ve rapor üçünde de aynı kalır.

Önceki ders ortamı ve bağımlılığı tekleştirdi: yorumlayıcı sürümü sabitlendi, kilit
dosyası çözümlemenin kararını yazıya geçirdi ve aynı bildirim artık tek bir sürüm kümesi
veriyor. Kurulum tarafında sorulacak soru kalmadı. Ama kurulumun tekleşmesi, o kurulumun
üzerinde koşan kodun da her seferinde aynı kararı verdiği anlamına gelmiyor.

Bu dersin sorusu şudur: **kodun kendisi her koşumda aynı sonucu mu veriyor ve bunu bize
kim söylüyor?** Söyleyen şey bir **test çerçevesidir (test framework)**. Çerçeve bir ürün
değil, bir **araç sınıfıdır**; bu derste hiçbir ürün adı geçmez. Çerçevenin yaptığı işler
ders içinde modellenir, standart kitaplığın sunduğu çerçeve ise doğrudan kullanılır.

#### Çerçevenin Dört İşi

Bir test çerçevesi tek bir iş yapıyormuş gibi görünür — "testleri koşturur" — ama aslında
dört ayrı iş yapar ve bunların yalnız biri sonucu belirler.

**Keşif.** Hangi çağrılabilirlerin test olduğuna karar verir. Bu karar bir adlandırma
kuralına, bir dizin desenine ya da bir işaretlemeye dayanır ve **testin içeriğine
bakmaz**. Kuralın kaynağı çerçevenin varsayılanı olabilir, projenin ayarı da olabilir;
her iki durumda da kural testin dışındadır.

**Koşum.** Keşfedilen her testi çağırır, sırayı belirler, gerekirse hazırlık ve toplama
adımlarını araya yerleştirir. Sıranın kendisi de bir karardır ve sonraki dersin ölçtüğü
şeydir.

**İddia.** Testin içinde beklenen ile gerçekleşeni karşılaştırır. Python'da bunun en yalın
biçimi `assert` deyimidir; deyim yanlışlanınca `AssertionError` fırlatır ve çerçeve bu
istisnayı düşen test olarak sayar. İstisnanın akış denetimi olarak kullanılması Python
Temelleri kursunda kurulmuştu; burada aynı düzenek raporlama için kullanılıyor.

**Rapor.** Kaç testin koştuğunu, kaçının geçtiğini ve düşenlerin adını yazar; okuyan kişi
kararı bu satırlardan alır.

Bu dört işten yalnız **iddia** doğruluk hakkında bir şey söyler. Keşif, koşum ve rapor
yapılandırma kararlarıdır — ve bu dersin ölçtüğü şey, yapılandırma kararlarının sonuca ne
yaptığıdır.

Dördü de bir arada durduğu için tek bir iş gibi görünürler. Oysa yalnız iddia projenin
kaynak dosyalarında yazılıdır; keşif kuralı ve koşum düzeni çoğu kurulumda ayrı bir
yapılandırma dosyasında ya da koşturma komutunun seçeneklerinde durur. Yani kararın bir
parçası kodun içinde, bir parçası dışındadır — ve dışarıda duran parça değiştiğinde kod
dosyalarında hiçbir iz kalmaz. Bu kursun sorusu bakımından bu ayrım tam olarak aranan
şeydir: aynı kaynak, ayrı yapılandırma, kaç ayrı sonuç.

#### İddianın Kâhine İhtiyacı

Bir iddia iki şeyi karşılaştırır: kodun ürettiği değer ile **beklenen** değer. Beklenen
değerin nereden geldiği testin kendi içinde görünmez; onu yazan kişi bir yerden almıştır.
Bu kaynağa **kâhin (oracle)** denir ve kavram Yazılım Kalitesi ve Test müfredatının Kalite
ve Test Temelleri kursunda kurulmuştur. Burada kuram tekrarlanmıyor; kâhinin **ölçüme ne
kattığı** ölçülüyor.

Ortak kurgumuzda kâhin bizizdir: doğru indirim davranışını, temiz ortamı ve doğru sürüm
kümesini biz yazdık. Bu dersin test durumları ise kâhinden değil, **işlevin gözlenen
davranışından** yazılmıştır. Yani beklenen değerler, işlev bugün ne veriyorsa o yazılarak
elde edildi. Böyle yazılmış bir test, işlevin davranışının **değişmediğini** doğrular;
davranışın **doğru olduğunu** doğrulamaz. Aradaki farkın ne ödettiği bu konunun son
dersinde ölçülecek; şimdilik şunu saptamak yeterli: **bir testin geçmesi, beklenen değerin
doğru olduğunu söylemez.**

İddianın yazılışı da bir seçimdir. `assert` deyimi bir tümce yanlışlanınca `AssertionError`
fırlatır ve raporda yalnız "bu satırdaki deyim yanlışlandı" bilgisi kalır. Çerçevenin
karşılaştırma yöntemleri ise iki değeri de yakalar ve rapora yazar. Bu fark tanı süresini
kısaltır ama kararı değiştirmez; ölçümün üst tablosu tam olarak bunu gösteriyor.

#### İddiasız Test Sessizce Geçer

Modellenen koşucunun kuralı basit bir kuraldır: testi çağır, `AssertionError` gelmezse
geçmiş say. Bu kuralın doğrudan bir sonucu vardır — **hiçbir iddia içermeyen bir test her
zaman geçer.** Koşucu iddiayı saymaz, istisnayı sayar; iddia hiç yazılmamışsa fırlatılacak
istisna da yoktur.

Bu, çerçevenin bir kusuru değil, sınırıdır. Çerçeve testin gövdesine bakmaz; baksaydı,
hangi çağrının iddia sayılacağını bilmesi gerekirdi ve bu bilgi uygulamaya özgüdür.
Ölçümdeki `yardimci_tutar` bu sınırın somut örneğidir: bir iddia taşımaz, dolayısıyla
keşfedilseydi de geçmiş sayılırdı. Aynı şey, iddiası yanlışlıkla yorum satırına alınmış ya
da bir koşulun içinde kalıp hiç değerlendirilmeyen bir test için de geçerlidir.

Sonuç şudur: **geçen bir test, en az iki ayrı nedenden geçmiş olabilir** — iddia doğrulandı
ya da hiç iddia yoktu. Rapor bu ikisini ayırmaz. Ayrım için bakılacak yer rapor değil,
testin kendisidir; ve bunu kodun üzerinden ölçen düzenek bu konunun son dersinde kurulacak.

#### Aynı Üç Durum, İki Yazım Biçimi

Birim testin iki yaygın yazım biçimi vardır. Birincisinde her test bir işlevdir ve iddia
`assert` deyimiyle yazılır; koşucu işlevi çağırır, istisna yakalarsa testi düşmüş sayar.
İkincisinde testler bir sınıfın yöntemleridir ve iddia, çerçevenin sunduğu karşılaştırma
yöntemleriyle yazılır; standart kitaplığın `unittest` çerçevesi bu biçimi kullanır.

Aradaki fark bir **yazım** farkıdır. İkinci biçim düşen bir iddiada iki değeri de rapora
yazar, birincisi yalnız deyimin yanlışlandığını söyler. Bu bir rapor ayrıntısıdır ve
kararın kendisini değiştirmez — ölçüm bunu gösteriyor.

Sınıf tabanlı biçimin ikinci bir katkısı vardır: her testin öncesinde ve sonrasında
çalışan kancalar tanımlanabilir. Bu kancalar tekrarı testten çıkarmanın bir yoludur ve
sonraki dersin konusudur; burada yalnız üç yalın test yazıldığı için kanca kullanılmıyor
ve iki biçim gerçekten aynı işi yapıyor. Karşılaştırmanın anlamlı olması için de bu
gerekiyordu: iki biçim ancak aynı üç durumu aynı hazırlıkla koşturduğunda yan yana konabilir.

#### Keşif Kuralı Neyi Belirler

Keşif kuralı bir dosyadaki hangi çağrılabilirlerin test sayılacağını belirler. Kural
değiştiğinde dosyanın içeriği değişmez, **koşan test kümesi** değişir. Ölçümdeki dosya beş
çağrılabilir taşır: üçü `t_` önekli, biri `kontrol_` önekli, biri de yardımcı bir işlev.

Bu üç kural gerçek bir durumu modelliyor. Bir takım büyüdükçe testler birden çok kişi
tarafından yazılır ve herkes aynı adlandırma alışkanlığını taşımaz; bir kısmı `test_`,
bir kısmı `t_`, bir kısmı da `kontrol_` gibi bir önek kullanır. Keşif kuralı bunlardan
yalnız birini arıyorsa öbürleri dosyada durur, gözle görünür, sürüm denetiminde
izlenir — ve hiç koşmaz.

Burada kritik olan şudur: koşmayan bir test **düşmüş sayılmaz**. Çerçeve onu hiç görmediği
için raporda yeri yoktur. Rapor "başarısız yok" der ve bu cümle hem beş testin koştuğu
durumda hem de hiçbirinin koşmadığı durumda aynıdır. Keşif kuralı bu yüzden bir ayrıntı
değil, kararın bir parçasıdır.

#### Ölçümün Varsayımları

- **PT1** — Koşucu ders içinde modellenir: her testi çağırır, `AssertionError` yakalarsa
  testi düşmüş sayar, başka bir şey yapmaz. Ürün adı taşıyan hiçbir çerçeve kullanılmaz.
- **PT2** — Sınıf tabanlı takım standart kitaplığın `unittest` çerçevesiyle koşturulur;
  rapor akışı yutulur ve yalnız sayılar okunur.
- **PT3** — Üç test durumu ortak tanımın test kümesidir ve beklenen değerler işlevin
  **gözlenen davranışından** yazılmıştır, kâhinden değil.
- **PT4** — İndirim işlevinin davranışı değiştirilmez; ders yalnız test ve keşif kuralı
  ekler.
- **PT5** — Keşif kuralı bir ad önekidir ve testin gövdesine bakmaz. Üç kural denenir:
  `t_` öneki, `test_` öneki ve `t_` ile `kontrol_` öneklerinin birleşimi.
- **PT6** — Dosyadaki `yardimci_tutar` bir test değil, bir yardımcıdır; hiçbir kural onu
  toplamaz ve toplasaydı iddia içermediği için geçmiş sayılırdı.
- **PT7** — Ayrı sonuç sayısı, bir ölçüm boyunca gözlenen **birbirinden farklı** sonuç
  demetlerinin sayısıdır; koşumda süre ölçülmez.
- **PT8** — Rapor yalnız düşen test sayısını taşır; koşmayan testin adı raporda yoktur ve
  raporu okuyan bunu göremez.

#### Ölçüm

```python
"""Cerceve dort is yapar: kesif, kosum, iddia ve rapor. Kosucu ders icinde modellenir.

Bolum 1 - ayni uc durum, iki yazim bicimi.
Bolum 2 - ayni dosya, uc kesif kurali.
"""
import io
import unittest


def indirim(tutar, uye, kupon):
    """Ortak tanimin uc kararli islevi; davranisi degistirilmez."""
    oran = 0
    if uye:
        oran += 10
    if kupon:
        oran += 15
    if oran > 20:
        oran = 20
    return tutar - tutar * oran // 100


DURUMLAR = (("yalniz_uye", (100, True, False), 90),
            ("yalniz_kupon", (100, False, True), 85),
            ("ikisi_birden", (100, True, True), 80))


def kosucu(testler):
    """Modellenmis kosucu: her testi cagirir, gecen ve duseni sayar."""
    gecen, dusen = 0, []
    for ad, islev in testler:
        try:
            islev()
        except AssertionError:
            dusen.append(ad)
        else:
            gecen += 1
    return gecen, dusen


def duz_takim():
    def uret(arg, beklenen):
        def test():
            assert indirim(*arg) == beklenen
        return test
    return [(ad, uret(arg, bek)) for ad, arg, bek in DURUMLAR]


class SinifTakimi(unittest.TestCase):
    def test_yalniz_uye(self):
        self.assertEqual(indirim(100, True, False), 90)

    def test_yalniz_kupon(self):
        self.assertEqual(indirim(100, False, True), 85)

    def test_ikisi_birden(self):
        self.assertEqual(indirim(100, True, True), 80)


def sinif_kosumu():
    takim = unittest.TestLoader().loadTestsFromTestCase(SinifTakimi)
    sonuc = unittest.TextTestRunner(stream=io.StringIO(), verbosity=0).run(takim)
    return sonuc.testsRun - len(sonuc.failures) - len(sonuc.errors), len(sonuc.failures)


def t_yalniz_uye():
    assert indirim(100, True, False) == 90


def t_yalniz_kupon():
    assert indirim(100, False, True) == 85


def t_ikisi_birden():
    assert indirim(100, True, True) == 80


def kontrol_iki_yuz():
    assert indirim(200, True, True) == 160


def yardimci_tutar(tutar):
    return tutar


DOSYA = {"t_yalniz_uye": t_yalniz_uye, "t_yalniz_kupon": t_yalniz_kupon,
         "t_ikisi_birden": t_ikisi_birden, "kontrol_iki_yuz": kontrol_iki_yuz,
         "yardimci_tutar": yardimci_tutar}

KURALLAR = {"t_ öneki": ("t_",), "test_ öneki": ("test_",),
            "t_ ya da kontrol_": ("t_", "kontrol_")}


def kesif(kural):
    return [(ad, i) for ad, i in DOSYA.items() if ad.startswith(kural)]


print(f"{'yazım biçimi':<22s} {'test':>5s} {'geçen':>6s} {'düşen':>6s}")
duz_gecen, duz_dusen = kosucu(duz_takim())
print(f"{'düz işlev takımı':<22s} {len(DURUMLAR):5d} {duz_gecen:6d} {len(duz_dusen):6d}")
sinif_gecen, sinif_dusen = sinif_kosumu()
print(f"{'sınıf tabanlı takım':<22s} {len(DURUMLAR):5d} {sinif_gecen:6d} {sinif_dusen:6d}")
print(f"aynı üç durum, iki yazım biçimi: "
      f"{len({(duz_gecen, len(duz_dusen)), (sinif_gecen, sinif_dusen)})} ayrı sonuç")

print()
print(f"{'keşif kuralı':<18s} {'bulunan':>8s} {'geçen':>6s} {'düşen':>6s} {'rapor':>16s}")
bulunan, rapor = [], []
for ad, kural in KURALLAR.items():
    testler = kesif(kural)
    gecen, dusen = kosucu(testler)
    bulunan.append(len(testler))
    rapor.append(len(dusen))
    print(f"{ad:<18s} {len(testler):8d} {gecen:6d} {len(dusen):6d} "
          f"{'başarısız ' + str(len(dusen)):>16s}")
print(f"aynı dosya, üç keşif kuralı: bulunan test {len(set(bulunan))} ayrı sonuç, "
      f"rapor {len(set(rapor))} ayrı sonuç")
print(f"en dar kuralda hiç koşmayan test {max(bulunan) - min(bulunan)} tane; "
      f"rapor bunu söylemiyor")
```

```
yazım biçimi            test  geçen  düşen
düz işlev takımı           3      3      0
sınıf tabanlı takım        3      3      0
aynı üç durum, iki yazım biçimi: 1 ayrı sonuç

keşif kuralı        bulunan  geçen  düşen            rapor
t_ öneki                  3      3      0      başarısız 0
test_ öneki               0      0      0      başarısız 0
t_ ya da kontrol_         4      4      0      başarısız 0
aynı dosya, üç keşif kuralı: bulunan test 3 ayrı sonuç, rapor 1 ayrı sonuç
en dar kuralda hiç koşmayan test 4 tane; rapor bunu söylemiyor
```

#### Sayıların Okunması

Üstteki tablo yazım biçiminin ödettiğini veriyor: aynı üç durum, iki ayrı yazımda
**1 ayrı sonuç**. Düz işlev takımı da sınıf tabanlı takım da 3 testin 3'ünü geçiriyor.
Yazım biçimi bir tercihtir ve kararı değiştirmez. Bu, çerçeveler arasında seçim yapmanın
önemsiz olduğu anlamına gelmez — ama seçimin ödettiği şey **sonuç değil, raporun
ayrıntısıdır.**

Alttaki tablo yapılandırmanın ödettiğini veriyor ve sayı değişiyor. Aynı dosya, üç keşif
kuralı: bulunan test **3 ayrı sonuç** (3, 0 ve 4), rapor ise **1 ayrı sonuç**. Üç kuralın
üçünde de rapor "başarısız 0" diyor.

İkinci satır bu ölçümün asıl bulgusudur. `test_` önekini arayan kural bu dosyada **hiçbir
test bulamıyor**, sıfır test koşuyor ve raporu "başarısız 0" oluyor. Bu rapor, üç testin
geçtiği rapordan **ayırt edilemez**. Bir takımın yeşil olması iki ayrı durumda doğrudur:
testler koştu ve geçti, ya da hiç test koşmadı.

Üçüncü satır aynı olguyu öbür yönden gösteriyor. `kontrol_iki_yuz` adlı test dosyada
duruyor, iddiası doğru ve geçiyor — ama yalnız `t_` önekini arayan varsayılan kural onu
hiç toplamıyor. En dar kural ile en geniş kural arasındaki fark **4 test** ve bu farkın
tamamı raporun dışında kalıyor.

Buradan çıkan kural şudur: **bir test takımının kararı, testlerin kendisi kadar keşif
kuralına da bağlıdır.** Rapor koşan testleri sayar, koşmayanları değil; bu yüzden
raporun "başarısız 0" satırı tek başına bir kanıt değildir. Kanıtın tamamı için **bulunan
test sayısının** da okunması gerekir — ve bu sayı, ölçümün gösterdiği gibi, kodun kendisi
hiç değişmeden üç ayrı değer alabilir.

İki tablo birlikte okunduğunda kursun ölçü ekseni burada ilk kez görünür hale geliyor.
Aynı girdiye — aynı üç durum, aynı işlev, aynı dosya — iki ayrı soru soruldu. Birincisi
"nasıl yazdın" sorusuydu ve sonucu değiştirmedi: **1 ayrı sonuç**. İkincisi "neyi
koşturuyorsun" sorusuydu ve sonucu değiştirdi: **3 ayrı sonuç**. Yeniden üretilebilirliği
tehdit eden şey birincisi değil ikincisidir, çünkü ikincisi bir dosyada değil, koşturmayı
yapılandıran ayarda yazılıdır ve kodla birlikte okunmaz.

Bu ayrımın pratikteki karşılığı da açıktır. Bir takımın yeşil raporu, ancak koşan test
sayısı da bilindiğinde bir bilgi taşır; sayı bilinmeden okunan yeşil rapor, hiçbir testin
koşmadığı bir kurulumda da aynı görünür. Bu yüzden koşan test sayısı raporun yanında
tutulur ve düşmesi, düşen test kadar dikkat çeken bir olay sayılır.

#### Özet

- Bir test çerçevesi dört iş yapar: keşif, koşum, iddia ve rapor. Bunlardan yalnız
  **iddia** doğruluk hakkında bir şey söyler; öbür üçü yapılandırma kararıdır.
- İddia, beklenen değeri bir kâhinden alır. Testin geçmesi, o beklenen değerin doğru
  olduğunu göstermez.
- Aynı üç durum düz işlev takımıyla da sınıf tabanlı takımla da **3/3** geçer; iki yazım
  biçimi **1 ayrı sonuç** verir ve fark yalnız raporun ayrıntısındadır.
- Aynı dosya üç keşif kuralında **3, 0 ve 4** test bulur — **3 ayrı sonuç**; rapor
  üçünde de "başarısız 0" der ve **1 ayrı sonuçta** kalır.
- Hiç koşmayan test düşmüş sayılmaz; en dar kuralda **4 test** raporun dışında kalır ve
  yeşil bir rapor bunu söylemez.

#### Sonraki Adım

Keşif kuralı hangi testlerin koşacağını belirledi; ama koşan testler tek tek bağımsız
değil. Ortak kurgunun beş testlik takımında biri paylaşılan durumu kirletiyor, biri de
kirlenmemiş bir durum varsayıyor. Böyle bir takımda testleri hangi sırayla çağırdığınız
bir yapılandırma ayrıntısıdır — peki bu ayrıntı kararı değiştiriyor mu? Sonraki ders aynı
takımı üç ayrı sırada koşturur, çıkan ayrı karar sayısını okur ve tekrarı testten çıkaran
iki düzeneği ölçer: paylaşılan durumu her testte yeniden kuran **düzenek** ve aynı gövdeyi
birden çok girdiyle koşturan **parametreleme**.

### Düzenek ve Parametreleme

Source: https://academia.sh/tr/kurslar/python-projeleri/duzenek-ve-parametreleme

Beş testlik takım yazım sırasında 4/5, öbür iki sırada 5/5 geçer ve sıraya göre 2 ayrı karar verir; düzenek paylaşılan durumu her testte yeniden kurduğunda sayı 1'e iner, oturum düzeyinde kurulduğunda yine 1'e iner ama bu kez 4/5'te.

Önceki ders keşif kuralının hangi testlerin koşacağını belirlediğini ölçtü: aynı dosya üç
kuralda 3, 0 ve 4 test buldu ve rapor üçünde de aynı kaldı. Orada testler birbirinden
bağımsızdı; her biri işlevi çağırıyor, dönen değeri karşılaştırıyor ve arkasında hiçbir iz
bırakmıyordu.

Gerçek takımlar böyle değildir. Testlerin çoğu bir hazırlık ister: bir nesne kurulur, bir
dizin açılır, bir sayaç sıfırlanır. Bu hazırlık paylaşıldığında testler birbirinin
komşusu olur — ve komşuluk bir sıra doğurur. Bu dersin sorusu şudur: **aynı takım, koşum
sırası değiştiğinde kaç ayrı karar verir ve bu sayıyı ne tekleştirir?**

#### Paylaşılan Durum Sırayı Anlamlı Kılar

Ortak kurgunun beş testlik takımı bunun en yalın modelidir. Beş test tek bir ortam nesnesi
üzerinde çalışır. Üçü ortama dokunmaz ve her koşulda geçer. Biri ortamı **kirletir**: bir
sayacı bir artırır ve yine de geçer. Biri ise ortamın **kirlenmemiş olduğunu varsayar**;
sayaç sıfırsa geçer, değilse düşer.

Beş testin hiçbiri tek başına kusurlu değildir. Kirleten test kendi işini yapar, bağımlı
test de kendi işini yapar. Kusur ikisinin **birlikte ve belli bir sırada** koşmasındadır.
Yazım sırasında kirleten test bağımlı testten önce çağrılır ve bağımlı test düşer; bağımlı
testi öne alan sırada ve ters sırada ise bağımlı test önce çağrılır ve geçer.

Bu, Yazılım Kalitesi ve Test müfredatının Birim Testi ve Test Odaklı Geliştirme kursunda
"hızlı ve bağımsız testler" başlığı altında kuram olarak kurulmuştu. Burada kuram
tekrarlanmıyor; ölçülen şey **aynı takımın kaç ayrı karar verdiğidir.**

#### Sırayı Kim Belirler

Sıranın hiçbir teste yazılı olmaması bu ölçümün çıkış noktasıdır. Testlerin çağrılma
düzeni koşucunun bir kararıdır ve bu karar en az dört yerden gelir.

Birincisi **keşif düzenidir**: koşucu dosyaları hangi sırayla topladıysa testler de o
sırayla koşar ve dosya adlarının sıralanışı bir dosyanın yeniden adlandırılmasıyla
değişir. İkincisi **seçim**: yalnız düşen testleri ya da yalnız belli bir etiketi
koşturmak, kalanları aradan çıkarır ve geri kalanların komşuluğunu değiştirir. Üçüncüsü
**dağıtım**: testler birden çok işçiye bölündüğünde her işçi kendi alt kümesini kendi
sırasıyla koşturur ve bölünmenin nasıl yapıldığı işçi sayısına bağlıdır. Dördüncüsü ise
**bilerek karıştırma**: kimi kurulum sırayı her koşumda yeniden karar verilen bir değere
bağlar, çünkü sıra bağımlılığını erken görmek ister.

Dördünün ortak yanı şudur: hiçbiri projenin kaynak dosyalarında yazılı değildir. Sıra,
önceki dersteki keşif kuralıyla aynı sınıfa girer — kararın kodun dışında duran parçası.
Bu dersin ölçümü de tam olarak o parçayı değiştirip sonucu okuyor: üç sıra, aynı beş test.

#### Düzenek: Hazırlığı Testten Çıkarmak

**Düzenek (fixture)**, bir testin ihtiyaç duyduğu hazırlığı testin gövdesinden çıkaran ve
çerçevenin çağırdığı bir birimdir. Testin kendisi ortamı kurmaz; düzenek kurar ve testin
eline hazır verir. Bu, tekrarı azaltmanın yanında ikinci ve daha önemli bir şey yapar:
**hazırlığın ne zaman yapılacağını** açık bir karara dönüştürür.

Bu karara düzeneğin **kapsamı** denir ve ölçümde üç değer alıyor.

**Düzeneksiz.** Ortam bir kez kurulur ve beş test onu paylaşır. Kirleten testin bıraktığı
iz, kendisinden sonra koşan her teste görünür.

**Her testte.** Düzenek her testin öncesinde ortamı yeniden kurar. Kirleten testin izi bir
sonraki teste ulaşmaz.

**Oturum düzeyinde.** Düzenek koşum oturumunun başında bir kez kurulur ve bütün testler,
bütün koşumlar boyunca aynı nesneyi paylaşır. Bu kapsam pahalı hazırlıklar için seçilir —
kurulumu ağır olan bir kaynak her testte yeniden kurulmasın diye.

Üç kapsamın da geçerli kullanımı vardır; ölçüm hangisinin "doğru" olduğunu değil, her
birinin **ayrı karar sayısına** ne yaptığını veriyor.

Düzeneğin ikinci yarısı **toplamadır**: kurulan şeyin geri alınması. Bellekteki bir nesne
için toplama gerekmez, yeni nesne eskisinin yerini alır ve eskisi kendiliğinden düşer. Bir
dosya, bir dizin ya da bir bağlantı için ise toplama açıkça yazılır ve testin düşmesi
durumunda da çalışması gerekir; bu, Python Temelleri kursunda kurulan bağlam yöneticisi
protokolünün doğrudan uygulandığı yerdir. Toplaması eksik bir düzenek, paylaşımı ortadan
kaldırdığını sanırken kalıntı bırakır ve ölçüm yine sıraya bağlı hale gelir.

#### Parametreleme: Aynı Gövde, Ayrı Girdi

Aynı iddiayı birden çok girdiyle sınamanın iki yolu vardır. Birincisinde girdiler tek bir
testin içinde bir döngüye konur; ikincisinde her girdi **ayrı bir test** olur ve gövde
paylaşılır. İkinciye **parametreleme (parametrisation)** denir.

Parametrelemenin düzenekle ortak bir yanı vardır: ikisi de tekrarı testin gövdesinden
çıkarır. Ayrıldıkları nokta ise neyi çıkardıklarıdır. Düzenek **hazırlığı** çıkarır ve
testler arasındaki paylaşımı yönetir; parametreleme **girdiyi** çıkarır ve tek bir gövdeyi
birden çok duruma çoğaltır. Bu yüzden ikisi birbirinin yerine geçmez, birlikte kullanılır:
parametrelenmiş her durum kendi düzeneğiyle koşar.

Fark testler geçerken görünmez. İkisi de aynı iddiayı aynı girdilerle sınar ve ikisi de
yeşil rapor verir. Fark bir girdi düştüğünde ortaya çıkar: döngülü yazımda ilk sapma
testi düşürür ve döngü orada durur, dolayısıyla kalan girdiler **hiç denenmez**. Rapor tek
bir satır taşır ve o satırda testin adı yazar — hangi girdinin düştüğü değil.
Parametrelenmiş yazımda ise her girdi kendi adıyla koşar; düşen girdi raporda adıyla
görünür ve öbürleri koşmaya devam eder.

#### Ölçümün Varsayımları

- **PT9** — Beş testlik takım ve üç sıra ortak tanımdan alınır; `TESTLER` listesinin ve
  testlerin davranışı değiştirilmez. Ders yalnız düzenek kapsamı ekler.
- **PT10** — Kâhin kurgunun kendisidir: beş testin beşinin de temiz bir ortamda geçtiğini
  biz yazdığımız için biliyoruz. Doğru karar her sırada **5/5**'tir.
- **PT11** — Üç sıra denenir: yazım sırası, bağımlı testi öne alan sıra ve ters sıra.
  Sıra listesi testlerin dizinlerinden oluşur ve hiçbir teste yazılı değildir.
- **PT12** — Düzeneksiz kapsamda ortam her koşumun başında bir kez kurulur; koşum içinde
  yeniden kurulmaz.
- **PT13** — Her testte kurulan kapsamda ortam, sıradaki her testin çağrılmasından hemen
  önce yeniden kurulur.
- **PT14** — Oturum düzeyinde kurulan kapsamda ortam üç koşumun tamamı için bir kez
  kurulur; koşumlar arasında da taşınır.
- **PT15** — Ayrı karar sayısı, üç sıranın verdiği **geçen test sayılarının** birbirinden
  farklı olanlarının sayısıdır.
- **PT16** — Parametreleme ölçümünde sınanan iddia şudur: her sıra 5/5 vermelidir. Bu
  iddia kâhinden gelir ve düzeneksiz takımda yazım sırasında yanlışlanır.
- **PT17** — Döngülü yazım ilk sapmada döner; parametrelenmiş yazım üç sırayı da koşturur.
  Süre ölçülmez, sayılan şey koşan sıra ve rapor satırı sayısıdır.
- **PT18** — Rapor satırı, çerçevenin sonuç listesine yazdığı kayıt sayısıdır; koşmayan
  girdi rapora hiç girmez.

#### Ölçüm

```python
"""Duzenek ve parametreleme: paylasilan durumu kim, ne zaman kuruyor.

Bolum 1 - uc sira, uc duzenek rejimi.
Bolum 2 - ayni uc sira tek testte ve parametrelenmis testte.
"""


class Ortam:
    """Testlerin paylastigi durum."""

    def __init__(self):
        self.sayac = 0


def t_temiz_a(o):
    return True


def t_kirleten(o):
    o.sayac += 1
    return True


def t_bagimli(o):
    return o.sayac == 0


def t_temiz_b(o):
    return True


def t_temiz_c(o):
    return True


TESTLER = [("temiz_a", t_temiz_a), ("kirleten", t_kirleten),
           ("bagimli", t_bagimli), ("temiz_b", t_temiz_b), ("temiz_c", t_temiz_c)]

SIRALAR = {"yazım sırası": [0, 1, 2, 3, 4],
           "bağımlı önce": [0, 2, 1, 3, 4],
           "ters": [4, 3, 2, 1, 0]}


def kosum(sira, rejim="düzeneksiz", oturum=None):
    """Duzenek kapsami: hic, her testte yeniden, ya da oturumda bir kez."""
    o = oturum if rejim == "oturum düzeyinde" else Ortam()
    gecen = 0
    for i in sira:
        if rejim == "her testte":
            o = Ortam()
        _, islev = TESTLER[i]
        if islev(o):
            gecen += 1
    return gecen, len(sira)


print(f"{'düzenek kapsamı':<18s} " + " ".join(f"{ad:>13s}" for ad in SIRALAR)
      + "  ayrı karar")
for rejim in ("düzeneksiz", "her testte", "oturum düzeyinde"):
    oturum, gecenler = Ortam(), []
    for sira in SIRALAR.values():
        gecenler.append(kosum(sira, rejim, oturum)[0])
    print(f"{rejim:<18s} " + " ".join(f"{g:>11d}/5" for g in gecenler)
          + f" {len(set(gecenler)):12d}")

print()


def tek_test():
    """Uc sirayi tek govdede, tek dongude dener; ilk sapmada doner."""
    kosulan = 0
    for ad, sira in SIRALAR.items():
        kosulan += 1
        gecen, toplam = kosum(sira)
        if gecen != toplam:
            return [("üç sıra", "düşen")], kosulan
    return [("üç sıra", "geçen")], kosulan


def parametreli_test():
    """Her sira ayri bir testtir; ucu de kosar."""
    rapor, kosulan = [], 0
    for ad, sira in SIRALAR.items():
        kosulan += 1
        gecen, toplam = kosum(sira)
        rapor.append((ad, "geçen" if gecen == toplam else "düşen"))
    return rapor, kosulan


print(f"{'yazım':<16s} {'rapor satırı':>12s} {'koşan sıra':>11s} "
      f"{'düşen olarak adlandırılan':>26s}")
satir, kosan = [], []
for ad, islev in (("tek test", tek_test), ("parametrelenmiş", parametreli_test)):
    rapor, kosulan = islev()
    dusen = [r[0] for r in rapor if r[1] == "düşen"]
    satir.append(len(rapor))
    kosan.append(kosulan)
    print(f"{ad:<16s} {len(rapor):12d} {kosulan:11d} "
          f"{(dusen[0] if dusen else '-'):>26s}")
print(f"aynı üç sıra: rapor satırı {len(set(satir))}, koşan sıra {len(set(kosan))} "
      f"ayrı sonuç; düşen karar ikisinde de 1")
```

```
düzenek kapsamı     yazım sırası  bağımlı önce          ters  ayrı karar
düzeneksiz                   4/5           5/5           5/5            2
her testte                   5/5           5/5           5/5            1
oturum düzeyinde             4/5           4/5           4/5            1

yazım            rapor satırı  koşan sıra  düşen olarak adlandırılan
tek test                    1           1                    üç sıra
parametrelenmiş             3           3               yazım sırası
aynı üç sıra: rapor satırı 2, koşan sıra 2 ayrı sonuç; düşen karar ikisinde de 1
```

#### İki Ayrı Karar ve Tekleşmenin İki Yolu

İlk satır kursun dördüncü okumasını ödüyor. Düzeneksiz takım yazım sırasında **4/5**,
bağımlı testi öne alan sırada **5/5**, ters sırada **5/5** geçiyor — **2 ayrı karar**. Beş
testin kodu hiç değişmedi; değişen tek şey çağrılma sırası oldu. Aynı girdi, aynı kod, iki
ayrı sonuç.

Bu sayının pratikteki karşılığı şudur: böyle bir takım koşumların bir kısmında yeşil, bir
kısmında kırmızı olur ve kırmızı olduğunda düşen test **kusurun bulunduğu test değildir**.
Rapor `bagimli` testini gösterir, oysa ortamı kirleten `kirleten` testidir. Sıra bağımlılığı
tanıyı yanlış yere taşır.

İkinci satır düzeneğin ödediğini veriyor. Ortam her testin öncesinde yeniden kurulduğunda
üç sıra da **5/5** veriyor ve sayı **1 ayrı karara** iniyor. Kirleten test yine sayacı
artırıyor, ama artırdığı nesne bir sonraki teste ulaşmıyor. Düzenek kusuru düzeltmedi —
kusur zaten testlerde değildi — **paylaşımı ortadan kaldırdı.**

Üçüncü satır ölçümün asıl uyarısıdır. Oturum düzeyinde kurulan düzenek de sayıyı **1 ayrı
karara** indiriyor, ama bu kez **4/5**'te. Kirlilik ilk koşumda birikiyor ve sonraki
koşumlara da taşınıyor; bağımlı test artık hiçbir sırada geçmiyor. Sonuç kararlı hale
geldi ve kararlı hale geldiği değer yanlış.

Buradan çıkan cümle bu dersin ölçü ekseninin en keskin ifadesidir: **ayrı sonuç sayısının
1'e inmesi, inilen sonucun doğru olduğu anlamına gelmez.** Yeniden üretilebilirlik
doğruluğun yerine geçmez; yalnız doğruluğun tartışılabilir olmasının önkoşuludur. Bir takım
her koşumda aynı yanlış cevabı veriyorsa yeniden üretilebilirdir ve yine de kusurludur.

İkinci ve üçüncü satırı ayıran şey de bu yüzden kapsamın adı değil, kurulumun **hangi
kararı verdiğidir**. İkisi de tek bir sayı üretir; biri kirliliği hiç doğmadan keser,
öbürü kirliliği kalıcı hale getirir. Bir takımın sıra bağımlılığından kurtulduğunu
gösteren şey, ayrı karar sayısının 1 olması değil, o tek kararın **kâhinle uyuşmasıdır** —
ve kâhin, ortak kurguda beş testin beşinin temiz ortamda geçtiğini söylüyor.

Bir uyarı da tersi yönde geçerlidir. Üç sıra denendiği için burada 2 ayrı karar
görülebildi; tek bir sıra koşturulsaydı takım ya hep 4/5 ya hep 5/5 verirdi ve sıra
bağımlılığı hiç görünmezdi. Sıra bağımlılığının ölçülebilmesinin koşulu, sıranın **birden
çok değer alacak biçimde** koşturulmasıdır.

#### Rapor Satırı ve Koşan Girdi

Alt tablo parametrelemenin ödediğini veriyor. Üç sıra tek bir testin içinde denendiğinde
koşucu **1 rapor satırı** yazıyor ve o satırda testin adı — "üç sıra" — duruyor. Hangi
sıranın düştüğü raporda yok; ayrıca ilk sapmada döndüğü için **1 sıra** koşuyor ve kalan
ikisi hiç denenmiyor. Bu ikinci nokta ilkinden ağırdır: rapor yalnız eksik bilgi vermiyor,
ölçüm de eksik yapılıyor.

Parametrelenmiş yazımda aynı üç sıra **3 rapor satırı** üretiyor ve **3 sıra** koşuyor.
Düşen satır adıyla görünüyor: "yazım sırası". Öbür iki sıra koşmaya devam ettiği için
raporu okuyan kişi kusurun üç sıranın hepsinde mi yoksa yalnız birinde mi olduğunu
görebiliyor — ve bu ayrım tanıyı doğrudan `kirleten` testine götürüyor.

İki yazımın **düşen karar sayısı aynı**: ikisi de takımı kırmızı yapıyor. Ayrı olan şey
raporun taşıdığı bilgi ve gerçekten koşan girdi sayısı. Parametreleme testin kararını
değiştirmez, kararın **çözünürlüğünü** artırır.

Bu ayrımın bir bedeli de vardır ve saklanmamalıdır. Parametrelenmiş bir takımda test
sayısı girdi sayısıyla birlikte büyür; on girdi on test demektir ve bu on test raporda on
satır kaplar. Döngülü yazım tek satırla yetinir. Yani parametreleme, tanı bilgisini rapor
hacmiyle satın alır. Ölçümün söylediği şey hangisinin seçileceği değil, seçimin neyi
değiştirdiğidir: **rapor satırı 2 ayrı sonuç, koşan sıra 2 ayrı sonuç, düşen karar 1.**

#### Özet

- Paylaşılan durum, testin doğruluğunu komşusuna bağlar; beş testin hiçbiri tek başına
  kusurlu değilken takım sıraya göre **2 ayrı karar** verir: yazım sırasında **4/5**, öbür
  iki sırada **5/5**.
- Düzenek hazırlığı testten çıkarır ve asıl kararı görünür kılar: hazırlık **ne zaman**
  yapılacak. Bu karara düzeneğin kapsamı denir.
- Ortam her testte yeniden kurulduğunda üç sıra da **5/5** verir ve sayı **1 ayrı karara**
  iner; düzenek kusuru düzeltmez, paylaşımı ortadan kaldırır.
- Oturum düzeyinde kurulan düzenek de sayıyı **1'e** indirir ama **4/5**'te; ayrı sonuç
  sayısının tekleşmesi, tekleşilen sonucun doğru olduğunu söylemez.
- Parametreleme kararı değiştirmez, çözünürlüğünü artırır: aynı üç sıra tek testte
  **1 rapor satırı** ve **1 koşan sıra**, parametrelenmiş yazımda **3** ve **3** verir.

#### Sonraki Adım

Düzenek paylaşılan durumu her testte yeniden kurarak sayıyı tekleştirdi — ama bunu
yapabildi, çünkü kurduğu şey kendi elindeydi: bellekteki bir nesne. Düzeneğin kurduğu şey
bir ağ üzerindeki paket deposu, bir saat ya da bir dosya sistemi olsaydı, "yeniden kurmak"
bir seçenek olmazdı. Sonraki ders testin dışarıya uzanan bu ayağına bakar: aynı test dış
kaynağın üç ayrı kipinde kaç ayrı sonuç verir ve dış kaynağın yerine konan **sahte nesne**
bu sayıyı neye indirir — indirdiği sonucun doğru olduğunu neye dayanarak söyleriz?

### Sahte Nesneler

Source: https://academia.sh/tr/kurslar/python-projeleri/sahte-nesneler

Dış kaynağa uzanan aynı test üç kipte 3 ayrı sonuç verir, sahte nesneyle 1'e iner; ama depodan sapmış bir sahte de 1 ayrı sonuç verir ve beklenti onun yanında yazıldığı için geçer, gerçek kaynakla arasında 2 ayrı sonuç durur ve sözleşme sınaması sapmayı tek pakette bulur.

Önceki ders düzeneğin paylaşılan durumu her testte yeniden kurarak ayrı karar sayısını
ikiden bire indirdiğini ölçtü. Bunu yapabildi, çünkü kurduğu şey kendi elindeydi:
bellekteki bir nesne, bir sayaç. Yeniden kurmak bir atama kadar ucuzdu ve sonucu tam
olarak belirliyordu.

Testin dokunduğu her şey böyle değildir. Bir çözümleyici, kabul edilen sürümleri öğrenmek
için bir **paket deposuna** sorar; depo bir ağın öbür ucundadır, içeriği bizim
denetimimizde değildir ve aynı soruya her zaman aynı yanıtı vermez. Bu dersin sorusu
şudur: **böyle bir kaynağa uzanan test kaç ayrı sonuç verir ve yerine bir sahte nesne
konduğunda bu sayı neye iner?**

#### Dış Kaynağın Üç Kipi

Ölçümdeki dış kaynak bir **depo istemcisidir** ve ders içinde modellenmiştir; hiçbir ürün
adı geçmez, gerçek bir istek yapılmaz. İstemcinin tek yöntemi vardır: bir paketin
yayımlanmış sürümlerini döndürür. Aynı çağrı üç ayrı kipte üç ayrı şey yapar.

**Tam.** Depo bilinen bütün sürümleri döndürür. Çözümleme ortak tanımın sonucunu verir.

**Gecikmiş yansıma.** Deponun bir kopyası henüz güncellenmemiştir ve en son sürümü
taşımaz. İstek başarılıdır, yanıt geçerlidir, ama eksiktir — ve eksiklik yanıtın içinde
görünmez.

**Kesinti.** Depoya erişilemez ve çağrı bir istisna fırlatır. Çözümleme hiç sonuç
üretmez.

Üç kip de gerçek bir kurulumda görülür ve hiçbiri bir kusur değildir; deponun normal
işleyişinin parçasıdır. Testin bakış açısından ise üçü tek bir şeydir: **aynı test, üç
ayrı sonuç.**

Üç sayısı ölçümün seçimidir, kaynağın sınırı değil. Gerçek bir depoda kip sayısı daha
büyüktür: yeni bir sürüm yayımlanır ve liste uzar, bir sürüm geri çekilir ve liste
kısalır, istek sınırlanır ve yanıt gecikir. Bu dersin ölçtüğü sayının anlamı "kaynak tam
üç şey yapar" değil, **kaynağın kip sayısı ne olursa olsun testin sonuç sayısı ona eşit
olur** demektir. Test dış kaynağa bağlıysa, kaynağın her ayrı davranışı testin bir ayrı
sonucudur.

#### Sahtenin Konabileceği Yer

Bir sahte nesnenin gerçeğin yerine geçebilmesi bir tasarım koşuluna bağlıdır: gerçeğin
**dışarıdan verilmiş** olması. Ölçümdeki çözümleme işlevi sürüm listesini kendi içinde
üretmiyor; kendisine verilen istemciden istiyor. Bu yüzden istemcinin yerine başka bir
nesne koymak bir argüman değiştirmekten ibaret.

Aynı işlev depoya doğrudan kendisi sorsaydı — istemciyi kendi içinde kurup kullansaydı —
sahte nesnenin konabileceği bir yer kalmazdı. O durumda testin elinde iki seçenek olurdu:
gerçek depoya sormak ve üç kipe açık kalmak, ya da çalışma zamanında adı yeniden bağlayıp
işlevin gördüğü nesneyi değiştirmek. İkincisi işe yarar ama testi işlevin **iç
gerçekleştirimine** bağlar: işlev istemciyi başka bir adla kurmaya başladığında test,
davranış hiç değişmeden düşer.

Bağımlılığın dışarıdan verilmesi Yazılım Kalitesi ve Test müfredatının Birim Testi ve Test
Odaklı Geliştirme kursunda "test edilebilir tasarım" olarak kurulmuştu; burada
tekrarlanmıyor. Kaydedilecek tek şey şu: **sahte nesnenin ayrı sonuç sayısını
indirebilmesi, kodun onu kabul edecek biçimde yazılmış olmasına bağlıdır.**

#### Sahte Nesne Neyi Değiştirir

**Sahte nesne (mock / stub)**, gerçek bir bağımlılığın yerine konan ve aynı çağrılara
yanıt veren bir nesnedir. Yanıtı testin kendisi belirler; ağ yoktur, gecikme yoktur,
kesinti yoktur. Bunun ölçüme etkisi doğrudandır: kaynağın kip sayısı kaç olursa olsun,
sahte nesne tek bir yanıt verir ve test **1 ayrı sonuç** üretir.

Sahte nesnenin türleri ve hangi durumda hangisinin kullanılacağı Yazılım Kalitesi ve Test
müfredatının Birim Testi ve Test Odaklı Geliştirme kursunda kurulmuştur; **yordam burada
tekrarlanmıyor**, atıf yapılıyor. Bu derste ölçülen tek şey **yalıtımın kaç ayrı sonucu
tekleştirdiğidir.**

Ölçümdeki sahte nesne iki iş yapar. Birincisi, yanıtı sabitler: kendisine verilen sürüm
sözlüğünü döndürür. İkincisi, aldığı çağrıları kaydeder — kaç kez ve hangi paket için
çağrıldığını saklar. Bu ikinci iş, testin yalnız dönen değeri değil, **yapılan çağrıyı**
da sınamasına izin verir; ölçümün son satırı bu kaydı yazıyor.

#### Yalıtımın Ödettiği

Sahte nesne testi kaynağın kiplerinden kurtarır, ama bir bedel karşılığında: test artık
**gerçek kaynağa dokunmuyor.** Sabitlenen yanıtın gerçek deponun verdiği yanıtla aynı
olduğunu söyleyen hiçbir şey kalmaz. Sahte nesne bir kez yazılır, depo ise değişmeye
devam eder.

Buna **sözleşme sapması** denir ve ölçümün ikinci bölümü onu sayıyor. Sahtenin sürüm
listesine gerçek depoda bulunmayan bir sürüm eklendiğinde test **yine geçer** — çünkü
testin beklentisi sahtenin yanında, sahteye bakılarak yazılmıştır. Beklenti ile sahte
birbirini doğrular ve ikisi birlikte gerçekten uzaklaşır.

Sapmanın iki yönü de olur ve ikisi de sessizdir. Sahte, gerçeğin artık vermediği bir yanıtı
vermeye devam edebilir; ya da gerçek, sahtenin hiç öngörmediği bir yanıt vermeye
başlayabilir. Birinci durumda test yeşil kalır ve kurulum kırılır; ikinci durumda test
yine yeşil kalır ve kurulum, sahtenin hiç denemediği bir yolu izler.

Bu sapmayı görünür kılan düzeneğe **sözleşme sınaması** denir: aynı çağrılar hem sahteye
hem gerçek kaynağa yapılır ve yanıtlar karşılaştırılır. Böyle bir sınama yalıtımı bozar —
gerçek kaynağa dokunur, dolayısıyla kaynağın üç kipine yeniden açıktır. Bu yüzden birim
testlerin yanında değil, ayrı ve seyrek koşan bir küme olarak tutulur.

#### Ölçümün Varsayımları

- **PT19** — Depo istemcisi ders içinde modellenir; gerçek bir ağ isteği yapılmaz, gerçek
  paket indirilmez, hiçbir ürün adı yazılmaz.
- **PT20** — Kâhin ortak tanımın deposudur: hangi paketin hangi sürümleri taşıdığını biz
  yazdığımız için biliyoruz. `DEPO` ve `BILDIRIM` değiştirilmez.
- **PT21** — İstemcinin üç kipi vardır: tam, gecikmiş yansıma ve kesinti. Gecikmiş
  yansıma her paketin **en son sürümünü** düşürür; kesinti bir istisna fırlatır.
- **PT22** — Çözümleme "en yeni" stratejisiyle yapılır ve sürüm listesini yalnız
  istemciden alır; başka hiçbir kaynağa bakmaz.
- **PT23** — Kesinti sonucu "sonuç yok" olarak yazılır ve ayrı bir sonuç sayılır; hata
  da bir sonuçtur.
- **PT24** — Sahte nesne yanıtı sabitler ve çağrıları kaydeder. Kip kavramı sahtede
  yoktur; üç koşumda da aynı yanıtı verir.
- **PT25** — Sapmış sahte, `olcum` paketine gerçek depoda bulunmayan bir sürüm ekler.
  Öbür iki paket dokunulmadan kalır.
- **PT26** — Testin beklentisi, kullanılan sahtenin yanında ve ona bakılarak yazılır. Bu,
  sahte nesneyle çalışan bir takımda olağan yazım biçimidir.
- **PT27** — Sözleşme sınaması aynı üç çağrıyı iki kaynağa sorar ve yanıtları paket paket
  karşılaştırır; sapma bulunan paket sayısını verir.
- **PT28** — Süre ölçülmez. Sayılan şey ayrı sonuç, sapma bulunan paket ve sahteye giden
  çağrı sayısıdır.

#### Ölçüm

```python
"""Sahte nesne: dis kaynagin uc yaniti ile tek yanit. Istemci ders icinde modellenir.

Bolum 1 - gercek istemci uc kipte, sahte nesne tek kipte.
Bolum 2 - sahte nesne depodan sapinca ne oluyor.
"""

DEPO = {"olcum": [(1, 0), (1, 1), (1, 2), (2, 0)],
        "rapor": [(0, 9), (1, 0), (1, 1)],
        "ortak": [(3, 0), (3, 1), (3, 2), (4, 0)]}
BILDIRIM = {"olcum": ((1, 0), (2, 0)), "rapor": ((1, 0), (2, 0)),
            "ortak": ((3, 0), (4, 0))}


class Kesinti(Exception):
    """Dis kaynaga erisilemedigini bildirir."""


class GercekIstemci:
    """Dis kaynak: ayni cagri kipe gore ayri yanit verir."""

    def __init__(self, kip):
        self.kip = kip
        self.cagri = 0

    def surumler(self, paket):
        self.cagri += 1
        if self.kip == "kesinti":
            raise Kesinti(paket)
        if self.kip == "gecikmiş yansıma":
            return DEPO[paket][:-1]
        return list(DEPO[paket])


class SahteIstemci:
    """Yanitı sabitleyen sahte nesne; cagriyi da sayar."""

    def __init__(self, kaynak=None):
        self.kaynak = kaynak if kaynak is not None else DEPO
        self.cagri = 0
        self.istenen = []

    def surumler(self, paket):
        self.cagri += 1
        self.istenen.append(paket)
        return list(self.kaynak[paket])


def coz(istemci, strateji="en yeni"):
    """Ayni bildirimi cozer; surum listesini istemciden alir."""
    secim = {}
    for paket in sorted(BILDIRIM):
        alt, ust = BILDIRIM[paket]
        adaylar = [s for s in istemci.surumler(paket) if alt <= s < ust]
        secim[paket] = adaylar[-1] if strateji == "en yeni" else adaylar[0]
    return secim


def metin(secim):
    return " ".join(".".join(map(str, secim[p])) for p in ("olcum", "ortak", "rapor"))


def dene(istemci):
    try:
        return metin(coz(istemci))
    except Kesinti:
        return "sonuç yok"


KIPLER = ("tam", "gecikmiş yansıma", "kesinti")

print(f"{'koşum':<22s} {'gerçek istemci':>16s} {'sahte nesne':>16s}")
gercek, sahte = [], []
for kip in KIPLER:
    g, s = dene(GercekIstemci(kip)), dene(SahteIstemci())
    gercek.append(g)
    sahte.append(s)
    print(f"{'kip: ' + kip:<22s} {g:>16s} {s:>16s}")
print(f"aynı test, gerçek kaynakla {len(set(gercek))} ayrı sonuç; "
      f"sahte nesneyle {len(set(sahte))}")

print()
KAYAN = dict(DEPO, olcum=DEPO["olcum"] + [(1, 3)])
kayan_sahte = SahteIstemci(KAYAN)
kayan = dene(kayan_sahte)          # beklenti sahtenin yanindan yazilir
print(f"{'koşum':<31s} {'sonuç':>12s} {'beklenti':>12s} {'karar':>7s}")
for ad, sonuc, beklenti in (("sahte, depoyla aynı", dene(SahteIstemci()),
                             dene(SahteIstemci())),
                            ("sahte, depodan sapmış", kayan, kayan),
                            ("gerçek kaynak, sapmış beklenti",
                             dene(GercekIstemci("tam")), kayan)):
    print(f"{ad:<31s} {sonuc:>12s} {beklenti:>12s} "
          f"{('geçer' if sonuc == beklenti else 'düşer'):>7s}")
print(f"sapmış sahte ile gerçek kaynak arasında "
      f"{len({kayan, dene(GercekIstemci('tam'))})} ayrı sonuç var")

sozlesme = [p for p in sorted(BILDIRIM)
            if SahteIstemci(KAYAN).surumler(p) != GercekIstemci("tam").surumler(p)]
print(f"sözleşme sınaması aynı üç çağrıyı iki kaynağa sorar ve "
      f"{len(sozlesme)} pakette sapma bulur: {', '.join(sozlesme)}")
print(f"sahte nesne {kayan_sahte.cagri} çağrı aldı, istenen paketler "
      f"{', '.join(kayan_sahte.istenen)}")
```

```
koşum                    gerçek istemci      sahte nesne
kip: tam                    1.2 3.2 1.1      1.2 3.2 1.1
kip: gecikmiş yansıma       1.2 3.2 1.0      1.2 3.2 1.1
kip: kesinti                  sonuç yok      1.2 3.2 1.1
aynı test, gerçek kaynakla 3 ayrı sonuç; sahte nesneyle 1

koşum                                  sonuç     beklenti   karar
sahte, depoyla aynı              1.2 3.2 1.1  1.2 3.2 1.1   geçer
sahte, depodan sapmış            1.3 3.2 1.1  1.3 3.2 1.1   geçer
gerçek kaynak, sapmış beklenti   1.2 3.2 1.1  1.3 3.2 1.1   düşer
sapmış sahte ile gerçek kaynak arasında 2 ayrı sonuç var
sözleşme sınaması aynı üç çağrıyı iki kaynağa sorar ve 1 pakette sapma bulur: olcum
sahte nesne 3 çağrı aldı, istenen paketler olcum, ortak, rapor
```

#### Üçten Bire

Üst tablo yalıtımın ödediğini veriyor. Gerçek istemciyle aynı test **3 ayrı sonuç**
üretiyor: tam kipte `1.2 3.2 1.1`, gecikmiş yansımada `1.2 3.2 1.0`, kesintide sonuç yok.
Sahte nesneyle üç koşumun üçü de `1.2 3.2 1.1` veriyor — **1 ayrı sonuç.**

Ortadaki satır bu üçlünün en tehlikelisidir ve ayrıca bakılmayı hak eder. Kesinti kendini
duyurur: bir istisna fırlar, koşum kırmızı olur ve nedeni ortadadır. Gecikmiş yansıma
hiçbir şey duyurmaz. İstek başarılıdır, yanıt geçerli bir listedir, çözümleme sorunsuz
tamamlanır ve **yalnız `rapor` paketinin sürümü değişir.** Böyle bir farkı yakalayan tek
şey, beklentinin tam olarak yazılmış olmasıdır — testin "bir sonuç döndü" demesi yetmez.

Sayının üçten bire inmesinin anlamı şudur: sahte nesne testi **kaynağın kiplerinden**
kurtardı. Test artık deponun gecikmesine, kesintisine ve içeriğinin değişmesine karşı
duyarsız. Kurtardığı şey budur ve azımsanacak bir şey değildir; sıra bağımlılığı gibi,
kaynak kipi de kararı kodun dışından değiştiren bir etkendir.

#### Tekleşen Sonucun Doğruluğu

Alt tablo sahte nesnenin **kurtaramadığını** veriyor ve önceki dersin uyarısını başka bir
yüzeyde tekrarlıyor.

İlk iki satır aynı kararı veriyor: geçer. İkisinin de sahtesi tek bir yanıt döndürüyor,
ikisi de **1 ayrı sonuç** üretiyor. Ama biri deponun içeriğini taşıyor, öbürü depoda
olmayan bir sürüm uyduruyor. Testin geçmesinin nedeni ikisinde de aynı: **beklenti,
sahteye bakılarak yazıldı.** Sahte ne derse beklenti onu yazar ve karşılaştırma
kendiliğinden tutar.

Üçüncü satır ise ancak biri gerçek kaynağı çağırırsa var olur. Sapmış beklenti gerçek
kaynağa sorulduğunda düşüyor, çünkü aralarında **2 ayrı sonuç** duruyor. Bu iki sonuç
takımın hiçbir koşumunda karşı karşıya gelmiyor; birim testler sahteyle koşuyor, gerçek
kaynak yalnız kurulum sırasında devreye giriyor.

Sözleşme sınaması tam bu boşluğu kapatıyor: aynı üç çağrıyı iki kaynağa soruyor ve
**1 pakette** sapma buluyor — `olcum`. Sapmanın tek pakette olması ölçümün ölçeğini de
veriyor: üç paketin ikisinde sahte hâlâ doğru, biri kaymış. Sözleşme sınaması olmadan bu
oran hiç bilinmez, çünkü bilinmesi için gerçek kaynağa sorulması gerekir.

Son satır sahte nesnenin ikinci işini gösteriyor. Sahte **3 çağrı** almış ve üç paketi de
sormuş. Bu kayıt, dönen değerin ötesinde bir şey sınamayı sağlar: çözümleyici gerçekten
her paket için deposuna sordu mu, yoksa bir kısmını önbellekten mi aldı? Dönen değer bu
sorunun yanıtını taşımaz, çağrı kaydı taşır.

Çağrı kaydının kendi bedeli de vardır. Çağrı sayısını sınayan bir test, çözümleyicinin
**nasıl çalıştığına** bağlanır: bir gün önbellek eklenir, çağrı sayısı üçten bire iner ve
test düşer — oysa çözümleme aynı sonucu vermeye devam etmektedir. Dönen değeri sınayan
test böyle bir değişiklikten etkilenmez. Kayıt bu yüzden ancak çağrının kendisi bir
sözleşme parçasıysa sınanır: örneğin her paketin ayrı ayrı sorulması bir gereklilikse.

Üç bölümü bir arada okuduğumuzda ölçüm şunu söylüyor. Sahte nesne **kaynağın
değişkenliğini** ortadan kaldırır ve ayrı sonuç sayısını üçten bire indirir; buna karşılık
**kaynağın kendisiyle uyumu** hakkında hiçbir şey söylemez, çünkü gerçek kaynağa hiç
sormaz. Yalıtımın tekleştirdiği şey sonuçtur, doğruluk değildir — ve tekleşen sonucun
gerçekle bağını kuran tek düzenek, o bağı **ayrıca ölçen** bir sınamadır.

#### Özet

- Dış kaynağa uzanan aynı test, kaynağın üç kipinde **3 ayrı sonuç** verir: tam yanıt,
  eksik yanıt ve hata. Üçü de kaynağın olağan işleyişindedir.
- Gecikmiş yansıma üç kipin en sessizidir: istek başarılıdır, yanıt geçerlidir ve yalnız
  bir paketin seçilen sürümü değişir.
- Sahte nesne yanıtı sabitleyerek sayıyı **1 ayrı sonuca** indirir ve testi kaynağın
  kiplerinden kurtarır; ayrıca aldığı çağrıları kaydederek dönen değerin ötesinde bir
  sınamaya izin verir.
- Depodan sapmış bir sahte de **1 ayrı sonuç** verir ve testi geçirir, çünkü beklenti
  sahteye bakılarak yazılmıştır; sahte ile gerçek kaynak arasında **2 ayrı sonuç** durur.
- Sözleşme sınaması aynı çağrıları iki kaynağa sorar ve sapmayı **1 pakette** bulur;
  yalıtımı bozduğu için birim testlerin yanında değil, ayrı bir küme olarak tutulur.

#### Sonraki Adım

Buraya kadar sınanan her şey bir test dosyasında yazılıydı: iddia da, düzenek de, sahte
nesne de. Oysa bir projenin doğruluk iddiaları yalnız orada durmaz — belgelerde de durur.
Bir işlevin belge dizgisinde yazan "şu çağrı şunu döndürür" cümlesi bir iddiadır ve
hiçbir koşum onu sınamaz; kod değiştiğinde sessizce yanlışlanır. Sonraki ders bu iddiaları
sınanabilir hale getirir ve tek bir şeyi sayar: aynı belge, aynı değişiklikten sonra kaç
bayat iddiayı sese çevirir, kaçını sessiz bırakır.

### Belge Testleri

Source: https://academia.sh/tr/kurslar/python-projeleri/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ç.

Ö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

```python
"""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.

### Kapsam Ölçümü

Source: https://academia.sh/tr/kurslar/python-projeleri/kapsam-olcumu

Üç 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.

Ö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

```python
"""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?

## Kalite ve Yayım

Otomatik biçim ve kural denetimi, birden çok sürümde doğrulama, dağıtım biçimleri ve derleme sistemi, paket kaydına gönderim ile sürüm işaretleme, ve kaynaktan belge üretimi.

### Biçimlendirici ve Tüy Toplayıcılar

Source: https://academia.sh/tr/kurslar/python-projeleri/bicimlendirici-ve-tuy-toplayicilar

Aynı işlevin altı ayrı yazımı altı ayrı kaynak metni ama tek bir ayrıştırma ağacı verir; biçimlendirici altı metni 1 ayrı sonuca indirir, tüy toplayıcının bulgu kümesini 5'ten 1'e düşürür ama kalan 6 bulguyu ve kâhinden sapan tek çağrıyı yerinde bırakır.

Önceki ders kapsamı ölçtü ve kapsamın neyi saymadığını gösterdi: sekiz satırın sekizi
yürüdü, üç testin üçü geçti, buna karşın dört girdi bileşiminden birinde gerçek davranış
kâhinden ayrıldı. Kapsam **kodun nerede yürüdüğünü** sayar.

Bu ders bir adım yana gider: peki **kodun nasıl yazıldığını** kim tekleştirir? Aynı işlev
bir ekipte kaç ayrı biçimde yazılabilir ve bu sayı bir araca devredildiğinde kaça iner?
Sorunun ölçülebilir olması için "biçim" tanımının kesin olması gerekir; bu ders o tanımı
kurup sayıyı okur.

#### İki Ayrı Araç Sınıfı

İki araç sınıfı sık sık aynı cümlede anılır ve karıştırılır.

**Biçimlendirici (formatter)** kaynağı **yeniden yazar**. Girdisi bir kaynak metni,
çıktısı yine bir kaynak metnidir; ikisinin ayrıştırma ağacı aynıdır, düzeni ayrıdır.
Biçimlendiricinin ürettiği sonuç **tektir**: aynı ağaçtan hangi metinle girilirse
girilsin aynı metin çıkar.

**Tüy toplayıcı (linter)** kaynağı **bildirir**. Girdisi bir kaynak metni, çıktısı bir
bulgu listesidir; kaynağa dokunmaz. Bulguların bir bölümü düzenle ilgilidir, bir bölümü
değildir — ve bu ayrım dersin ölçtüğü şeyin merkezinde durur.

İki sınıfın çıktısı da ayrı türdendir ve bu, aralarındaki en somut farktır. Biçimlendiricinin
çıktısı kaynağın **yerine geçer**; tüy toplayıcının çıktısı kaynağın **yanında durur**.
Birincisi bir dönüşümdür ve tersi alınamaz, çünkü silinen düzen bilgisi geri getirilemez.
İkincisi bir gözlemdir ve kaynağı olduğu gibi bırakır; bulguyu okuyup ne yapılacağına karar
veren yine yazan kişidir.

Biçim tartışmasının bir araca devredilmesi **Kod İnceleme ve Ekip Süreci** kursunda,
inceleme ekseninin bir maddesi olarak ölçülmüştü; orada sorulan soru, incelemenin kaç
yorumunun biçim üzerine olduğuydu. Burada soru başkadır ve tartışmanın kendisiyle ilgilenmez:
**aynı kaynağın kaç ayrı yazımı var ve bu sayı kaça iniyor?**

#### Modelin Kurulumu

Bu kursun hiçbir dersi araç adı yazmaz; araçların davranışı ders içinde modellenir ve
modellendiği açıkça söylenir. Buradaki model üç parçadan oluşur.

**Yazımlar.** Ortak kurgunun üç kararlı `indirim` işlevi tek bir kanonik metinle yazılır.
Beş yazım daha, bu metne üç düzen dönüşümü uygulanarak **üretilir**: girinti adımını
değiştirmek, `if` gövdesini aynı satıra almak, işleç çevresindeki boşlukları silmek. Hiçbir
dönüşüm işlevin davranışına dokunmaz.

**Biçimlendirici.** Model, kaynağı ayrıştırıp ağaçtan yeniden yazan standart kitaplık
işlevidir. Gerçek bir biçimlendirici yorumları korur, satır genişliği kuralı uygular ve
uzun ifadeleri böler; bu model bunların hiçbirini yapmaz. Modelin tekleştirdiği şey yalnız
**düzendir** ve ölçüm de yalnız düzeni sorar.

**Tüy toplayıcı.** İki kural ailesi vardır. **Metne bakan kurallar** kaynağın satırlarını
tarar: girinti adımı dört boşluk mu, bir satırda iki deyim var mı, işlecin iki yanında
boşluk var mı. **Ağaca bakan kurallar** ayrıştırma ağacında dolaşır: adlandırılmamış
sayısal sabit var mı, işlevin karar sayısı eşiği aşıyor mu. İkinci aile kaynağın nasıl
yazıldığını hiç görmez, çünkü ağaç düzeni taşımaz.

Ölçümün varsayımları:

- **KY1** — Altı yazım tek kanonik metinden üretilir; aralarındaki fark yalnızca girinti
  adımı, gövdenin satıra yerleşimi ve işleç çevresindeki boşluktur.
- **KY2** — Yazımların özdeşliği iki ayrı yolla sınanır: ayrıştırma ağacının dökümü ve
  dört girdi bileşimindeki dönüş değerleri. İkisi de tabloda ayrı sütun olarak durur.
- **KY3** — Biçimlendirici modeli yorum ve boş satır tutmaz; ölçüm bu yüzden yorumsuz bir
  kaynak üzerinde yapılır ve modelin sınırı burada bildirilir.
- **KY4** — Metne bakan üç kural ile ağaca bakan iki kural, tüy toplayıcının tamamıdır.
  Bunlar bir kural kataloğu değil, iki aileyi ayırmaya yeten en küçük kümedir.
- **KY5** — Bulgu kümesi sıralı bir demet olarak karşılaştırılır; iki yazımın bulgu kümesi
  aynıysa **bir** ayrı sonuç sayılır, metinleri ayrı olsa bile.
- **KY6** — `indirim` işlevinin gövdesi ortak tanımdaki gibidir ve değiştirilmez; kusurlu
  tavan yerinde durur. Kâhin ayrı yazılmıştır ve tavanı 25 olarak uygular.
- **KY7** — Hiçbir süre ölçülmez, hiçbir dosya yazılmaz. Sayılan şey ayrı metin, ayrı ağaç,
  ayrı davranış ve ayrı bulgu kümesidir.

#### Ölçüm

```python
"""Bicimin tek sonuca indirgenmesi: alti yazim, kac ayri sonuc."""

import ast
import re

KANONIK = (
    "def indirim(tutar, uye, kupon):\n"
    '    """Uc kararli bir islev."""\n'
    "    oran = 0\n"
    "    if uye:\n"
    "        oran += 10\n"
    "    if kupon:\n"
    "        oran += 15\n"
    "    if oran > 20:\n"
    "        oran = 20\n"
    "    return tutar - tutar * oran // 100\n"
)
ISLECLER = ("+=", "//", ">", "-", "*")


def girintile(k, adim):
    return "\n".join(" " * (adim * ((len(s) - len(s.lstrip())) // 4))
                     + s.lstrip() for s in k.split("\n"))


def tek_satira(k):
    return re.sub(r"(?m)^(\s*)(if .+:)\n\s+(.+)$", r"\1\2 \3", k)


def bosluksuzlastir(k):
    for op in ISLECLER:
        k = k.replace(f" {op} ", op)
    return k


YAZIMLAR = {
    "dört boşluk": KANONIK,
    "iki boşluk": girintile(KANONIK, 2),
    "sekiz boşluk": girintile(KANONIK, 8),
    "tek satır gövde": tek_satira(KANONIK),
    "boşluksuz işleç": bosluksuzlastir(KANONIK),
    "ikisi birden": bosluksuzlastir(tek_satira(girintile(KANONIK, 2))),
}


def duzen_bulgulari(kaynak):
    """Metne bakan kurallar; bicimlendirici bunlari susturur."""
    bulgu = []
    girinti = [len(s) - len(s.lstrip()) for s in kaynak.split("\n") if s.strip()]
    if min((g for g in girinti if g > 0), default=4) != 4:
        bulgu.append("girinti adımı dört boşluk değil")
    for no, s in enumerate(kaynak.split("\n"), 1):
        if re.match(r"\s*(if|for|while) .*:\s*\S", s):
            bulgu.append(f"satır {no}: aynı satırda iki deyim")
        for op in ISLECLER:
            if op in s and f" {op} " not in s:
                bulgu.append(f"satır {no}: {op} çevresinde boşluk yok")
    return bulgu


def yapi_bulgulari(kaynak):
    """Agaca bakan kurallar; bicimlendirici bunlari susturmaz."""
    agac = ast.parse(kaynak)
    bulgu = [f"adlandırılmamış sabit {d.value}" for d in ast.walk(agac)
             if isinstance(d, ast.Constant) and isinstance(d.value, int)
             and d.value not in (0, 1)]
    karar = sum(1 for d in ast.walk(agac) if isinstance(d, ast.If))
    return sorted(bulgu + ([f"karar sayısı {karar}, eşik 2"] if karar > 2 else []))


def bicimlendir(kaynak):
    """Bicimlendirici modeli: kaynak, agactan tek kurala gore yeniden yazilir."""
    return ast.unparse(ast.parse(kaynak)) + "\n"


def davranis(kaynak):
    ad = {}
    exec(compile(kaynak, "<yazım>", "exec"), ad)
    return tuple(ad["indirim"](100, u, k)
                 for u in (False, True) for k in (False, True))


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


print(f"{'yazım':<17s} {'satır':>5s} {'düzen':>6s} {'yapı':>5s} {'davranış':>22s}")
for ad, k in YAZIMLAR.items():
    print(f"{ad:<17s} {len(k.strip().split(chr(10))):5d} "
          f"{len(duzen_bulgulari(k)):6d} {len(yapi_bulgulari(k)):5d} "
          f"{str(davranis(k)):>22s}")

K = list(YAZIMLAR.values())
B = [bicimlendir(k) for k in K]
once = {tuple(sorted(duzen_bulgulari(k) + yapi_bulgulari(k))) for k in K}
sonra = {tuple(sorted(duzen_bulgulari(b) + yapi_bulgulari(b))) for b in B}
print()
print(f"ayrı kaynak metni {len(set(K))} | ayrı ayrıştırma ağacı "
      f"{len({ast.dump(ast.parse(k)) for k in K})} | ayrı davranış "
      f"{len({davranis(k) for k in K})} | biçimlendirmeden sonra ayrı metin "
      f"{len(set(B))}")
print(f"tüy toplayıcı: biçimlendirmeden önce {len(once)} ayrı bulgu kümesi, "
      f"sonra {len(sonra)}")
print(f"kalan bulgu {len(sorted(sonra)[0])}: {', '.join(sorted(sonra)[0])}")
print()
sapan = [(u, k) for u in (False, True) for k in (False, True)
         if davranis(KANONIK)[2 * u + k] != dogru(100, u, k)]
print(f"dört girdi bileşiminden {len(sapan)} tanesinde davranış kâhinden "
      f"ayrılıyor: {sapan}")
```

```
yazım             satır  düzen  yapı               davranış
dört boşluk          10      0     6      (100, 85, 90, 80)
iki boşluk           10      1     6      (100, 85, 90, 80)
sekiz boşluk         10      1     6      (100, 85, 90, 80)
tek satır gövde       7      3     6      (100, 85, 90, 80)
boşluksuz işleç      10      6     6      (100, 85, 90, 80)
ikisi birden          7     10     6      (100, 85, 90, 80)

ayrı kaynak metni 6 | ayrı ayrıştırma ağacı 1 | ayrı davranış 1 | biçimlendirmeden sonra ayrı metin 1
tüy toplayıcı: biçimlendirmeden önce 5 ayrı bulgu kümesi, sonra 1
kalan bulgu 6: adlandırılmamış sabit 10, adlandırılmamış sabit 100, adlandırılmamış sabit 15, adlandırılmamış sabit 20, adlandırılmamış sabit 20, karar sayısı 3, eşik 2

dört girdi bileşiminden 1 tanesinde davranış kâhinden ayrılıyor: [(True, True)]
```

#### Altı Yazım, Bir Sonuç

Alt satır dersin ana sayısını veriyor: **6 ayrı kaynak metni, 1 ayrı ayrıştırma ağacı, 1
ayrı davranış, biçimlendirmeden sonra 1 ayrı metin.**

Üç sayının aynı anda okunması gerekir. Altı metin gerçekten ayrıdır — dosya olarak
karşılaştırılsalar altısı da birbirinden farklı çıkar ve bir değişiklik kaydında altı ayrı
satır olarak görünürler. Buna karşın ağaç **tektir**: yorumlayıcının gördüğü şey altısında
da aynıdır. Davranış sütunu bunu bağımsız bir yoldan doğruluyor; altı yazımın altısı da
dört girdi bileşiminde `(100, 85, 90, 80)` veriyor.

Biçimlendiricinin yaptığı iş tam olarak bu üç sayı arasındaki boşluğu kapatmaktır. Ağaç
zaten tekti; biçimlendirici metni de tekleştirir. Altıya bir kural uygulanır ve geriye
**1** metin kalır. Bu, aracın verdiği tek güvencedir: aynı ağaçtan aynı metin.

Üst tablonun `satır` sütunu bedelin nerede olduğunu gösteriyor. Tek satır gövdeli yazımlar
işlevi **7** satıra sıkıştırıyor, öbürleri **10** satır tutuyor. Biçimlendirilmiş sonuç bu
seçimlerin hiçbirini korumaz; yazanın satır düzeni üzerindeki tercihi ortadan kalkar.
Aracın devraldığı şey tartışma değil, **karardır**.

Bu sayının pratik karşılığı, kaynağın **karşılaştırılabilir** hale gelmesidir. İki yazım
arasındaki fark ağaca yansımıyorsa, o fark bir değişiklik kaydında yer kaplar ama hiçbir
şey anlatmaz. Altı yazım biçimlendiriciden geçirildiğinde aradaki farkın tamamı sıfırlanır;
geriye kalan her fark **ağaca yansıyan** bir farktır. Ölçüm bunu doğrudan söylüyor: ayrı
metin sayısı 6'dan 1'e inerken ayrı ağaç sayısı zaten 1'di ve değişmedi.

#### Kuralın Tek Yerde Yazılması

Biçimlendiricinin verdiği güvence koşullu bir güvencedir: **aynı kurala** göre çalışan iki
koşum aynı metni üretir. Kural değişirse çıktı da değişir. Bu yüzden aracın tek başına
kullanılması yetmez; kuralın nerede yazıldığı sorusu ölçümün parçasıdır.

Kural üç ayrı yerde yaşayabilir. **Yazan kişide** yaşarsa, sonuç kişi sayısı kadar ayrı
metindir — ölçümün başlangıç durumu tam olarak budur, altı yazım altı ayrı metin. **Aracın
öntanımlısında** yaşarsa, sonuç aracın yapılandırmasına bağlı olur ve iki geliştirme
ortamı ayrı öntanımlı taşıdığında sayı yine birden büyür. **Projenin kendi dosyasında**
yaşarsa, kural kaynakla birlikte taşınır ve her koşum onu okur.

Üçüncüsü bu kursun bağımlılık tarafında kurduğu düzenle aynı biçimdedir. Kilit dosyası
çözümlemeyi ortadan kaldırmıyordu; kararı **bir kez** verdirip yazıya geçiriyordu. Biçim
kuralı da tartışmayı ortadan kaldırmaz; tartışmayı bir kez yaptırır, sonucu dosyaya yazar
ve sonraki her koşumu o dosyaya bağlar. İkisinde de tekleşen şey işin kendisi değil,
**kararın kaynağıdır**.

Aradaki fark, kararın ne kadar geri alınabilir olduğudur. Kilit dosyası değiştirildiğinde
kurulan sürümler değişir ve program başka kodla çalışır. Biçim kuralı değiştirildiğinde
üretilen metin değişir ama ağaç değişmez; ölçümün `davranış` sütunu bunu altı yazımın
altısında da gösteriyor. Biçim kararı bu yüzden **ucuz** bir karardır: yanlış verilirse
bedeli okunabilirliktir, doğruluk değil.

#### Tüy Toplayıcının Sustuğu Yer

`düzen` sütunu altı yazımda **0, 1, 1, 3, 6, 10** bulgu veriyor. Bu sayı yazımdan yazıma
değişiyor çünkü kural metne bakıyor. `yapı` sütunu altı yazımda da **6**: ağaca bakan
kurallar düzeni hiç görmediği için yazımdan etkilenmiyor.

Sayıların nereden geldiği tek tek okunabilir. Tek satır gövdeli yazımda üç `if` satırı
"aynı satırda iki deyim" bulgusu üretiyor: **3**. Boşluksuz işleç yazımında iki artırma
satırı, bir karşılaştırma satırı ve `return` satırındaki üç işleç bulgu veriyor: **6**.
İkisi birden uygulandığında bunlar toplanıyor ve girinti adımı bulgusu ekleniyor: **10**.
Bulgu sayısı, kaynağın kalitesiyle değil, kaynağın **kanonik yazımdan ne kadar uzak
olduğuyla** artıyor.

İkinci satır önemli bir ayrıntı taşıyor. Altı yazım **5** ayrı bulgu kümesi veriyor, 6
değil — çünkü iki boşluklu ve sekiz boşluklu yazımlar aynı tek bulguyu üretiyor: "girinti
adımı dört boşluk değil". **Ayrı metin olmak ayrı bulgu kümesi olmayı gerektirmez.** Bulgu
kümesi kaynağın kaba bir özetidir ve iki ayrı kusuru aynı adla anabilir.

Biçimlendirmeden sonra bulgu kümesi sayısı **1**'e iniyor ve o kümede **6** bulgu kalıyor:
beş adlandırılmamış sabit (`10`, `15`, `20`, `20`, `100`) ve bir karar sayısı bulgusu
(`3`, eşik `2`). Biçimlendirici bunların hiçbirini susturmadı, çünkü hiçbiri düzenle ilgili
değil. **Biçimlendirici bir kalite aracı değil, bir tekleştirme aracıdır.**

Kalan altı bulgunun neden susturulamadığı da tek cümlede toplanır: **hiçbirinin tek bir
doğru düzeltmesi yoktur.** `20` sabitine bir ad vermek, adı seçmeyi gerektirir ve doğru adı
bilen şey kaynağın kendisi değil, alanın kuralıdır. Karar sayısını üçten ikiye indirmek
gövdeyi bölmeyi gerektirir ve bölmenin nereden yapılacağı ağaçtan okunamaz. Düzen
bulgularının hepsinde ise doğru sonuç tektir ve kuraldan çıkar — bu yüzden onlar bir araca
devredilebiliyor, öbürleri devredilemiyor.

Son satır ölçümün ucunu bağlıyor. Dört girdi bileşiminden **1**'inde gerçek davranış
kâhinden ayrılıyor: hem üyelik hem kupon verildiğinde işlev **80** döndürüyor, kâhin ise
tavanı 25 uyguladığı için **75** bekliyor. Tüy toplayıcının bildirdiği bulgu sayısı **6**,
bu sapmayı bildiren bulgu sayısı **0**.

Bu, önceki dersin sonucuyla aynı biçimdedir ve başka bir eksende durur. Kapsam kodun
**nerede yürüdüğünü** saydı ve kusuru görmedi; tüy toplayıcı kodun **nasıl yazıldığını**
denetliyor ve aynı kusuru yine görmüyor. İkisi de kâhini bilmez. Kâhini bilen tek şey
testin **beklentisidir** ve o beklenti bu kurguda kusurlu değeri yazmıştır.

#### Özet

- Biçimlendirici kaynağı yeniden yazar ve ayrı yazımları tek metne indirir; tüy toplayıcı
  kaynağa dokunmaz, bulgu bildirir.
- Aynı işlevin **6** ayrı yazımı **1** ayrıştırma ağacı ve **1** davranış verir; biçimlendirme
  sonrası ayrı metin sayısı **1**'e iner.
- Metne bakan kurallar yazımdan yazıma **0** ile **10** arasında bulgu üretir; ağaca bakan
  kurallar altı yazımda da **6** bulguda sabit kalır.
- Bulgu kümesi sayısı biçimlendirmeden önce **5**, sonra **1**; ayrı metin olmak ayrı bulgu
  kümesi olmayı gerektirmez.
- Biçimlendirmeden sonra kalan **6** bulgunun hiçbiri kâhinden sapan tek çağrıyı bildirmez;
  biçim aracı bir kalite aracı değildir.

#### Sonraki Adım

Bu ders tek bir ortamda çalıştı: tek yorumlayıcı, tek bağımlılık kümesi, tek koşum. Ölçülen
şey kaynağın kendisiydi ve kaynak ortamdan bağımsızdır. Testler için aynı şey söylenemez.
Aynı takım, ortak tanımın gösterdiği gibi, yalnız sıraya göre bile ayrı karar verebiliyordu;
sıra bir yapılandırma ayrıntısıydı. Yorumlayıcı sürümü ve çözümlenen bağımlılık kümesi de
yapılandırma ayrıntısıdır. Sonraki ders bu ekseni açar: aynı takım kaç ortamda koşturulunca
kaç ayrı karar veriyor ve bu sayıyı hangi kararlar geri indiriyor?

### Çoklu Ortam Testi

Source: https://academia.sh/tr/kurslar/python-projeleri/coklu-ortam-testi

Aynı yedi testlik takım üç yorumlayıcı, iki çözümleme ve üç sıra ekseninde 18 koşumda 4 ayrı karar verir; kilit dosyası koşumu 9'a ve kararı 3'e, düzenek 3'e ve 2'ye indirir, kalan iki karar ise gerçek ortam farkının kendisidir.

Önceki ders tek bir ortamda çalıştı. Ölçtüğü şey kaynağın kendisiydi: altı yazım, tek ağaç,
tek davranış. Kaynak metni hangi yorumlayıcıda okunursa okunsun aynı ağacı verir, bu yüzden
o ölçüm ortamdan bağımsızdı.

Testler için aynı şey söylenemez. Bir test yalnız kodu değil, kodun **içinde bulunduğu
düzeneği** çalıştırır: hangi yorumlayıcı, hangi bağımlılık kümesi, hangi sıra. Ortak
kurgunun beş testlik takımı yalnız sıraya göre bile ayrı karar veriyordu. Bu dersin sorusu o
gözlemi genişletir: **aynı takım kaç ortamda kaç ayrı karar verir ve bu sayı hangi
kararlarla iner?**

#### Ortam Kaç Eksenden Kurulur

"Ortam" tek bir şey değil, bir **eksenler çarpımıdır**. Bu ölçümde üç eksen vardır.

**Yorumlayıcı ekseni.** Üç kurgu yorumlayıcı — `Y1`, `Y2`, `Y3` — birer **yetenek
kümesiyle** modellenir. Gerçek sürüm numarası yazılmaz; yorumlayıcılar arasındaki fark,
bir yeteneğin bulunup bulunmamasına indirgenir. `Y1` `birlesim` yeteneğini taşımaz,
öbür ikisi taşır.

**Çözümleme ekseni.** Aynı bağımlılık bildirimi iki strateji ile çözülür: **en yeni** ve
**en eski uyumlu**. Ortak tanımın gösterdiği gibi bu, aynı bildirimden iki ayrı sürüm
kümesi çıkarır. Üçüncü bir seçenek kilit dosyasıdır ve stratejinin sonucu değiştirmesini
durdurur.

**Sıra ekseni.** Takım üç ayrı sırada koşturulur. Paylaşılan durumu kirleten bir test ile
kirlenmemiş ortam varsayan bir test aynı takımdadır; hangisinin önce koştuğu sonucu
değiştirir.

Takım yedi testliktir: ortak kurgunun beş testi, artı ortamı okuyan iki test. `yetenek`
testi yorumlayıcının yetenek kümesine bakar, `surum` testi çözümlenen `rapor` sürümünün en
az `1.1` olmasını ister. Beş testin davranışına dokunulmaz.

Bir hattın bu matrisi nasıl tanımlayacağı, hangi işin hangi çalıştırıcıda koşacağı ve
sonucun nereye raporlanacağı **Sürekli Tümleştirme ve Teslimat** kursunda işletim tarafında
kuruldu ve burada tekrarlanmaz. Bu derste hat yoktur; ölçülen tek şey **kaç ortamda kaç
ayrı karar** çıktığıdır.

#### Matrisin Maliyeti

Eksenler çarpılır: üç yorumlayıcı, iki çözümleme, üç sıra — **18** koşum. Yedi testlik takım
her koşumda baştan çalıştığı için toplam **126** test çağrısı yapılır. Eksen sayısı
arttıkça bu sayı çarpımsal büyür; bir eksen daha eklemek koşum sayısını ikiye ya da üçe
katlar.

Buna karşın matrisin **verdiği bilgi** aynı hızda büyümez. Matris, ayrı karar üretmeyen
eksen noktaları da içerebilir; `Y2` ile `Y3` bu ölçümde aynı yetenek kümesini taşır ve
hiçbir koşumda birbirinden ayrılmaz. Ölçüm bunu doğrudan gösterir.

Bir eksenin matriste yer alması için iki koşul gerekir. **Kararı değiştirebilmelidir** —
değiştiremiyorsa koşum sayısını büyütür, bilgi eklemez. **Elenemez olmalıdır** — bir
yapılandırma kararıyla sabitlenebiliyorsa, matriste tutulması yerine sabitlenmesi gerekir.
Bu iki koşul bu dersin ölçtüğü şeyin tanımıdır: sıra ve çözümleme eksenleri birinci koşulu
sağlar ama ikinci koşulda düşer; yorumlayıcı ekseni ikisini de sağlar ve matriste kalır.

Süre burada da ölçülmez. Bir matrisin maliyeti, koşum ve çağrı sayısı olarak yazıldığında
yeniden üretilebilir kalır; aynı matris başka bir makinede aynı **126** çağrıyı yapar.
Ölçmenin neyi saydığını yazma kuralı bir önceki kursun kapanışında kurulmuştu ve bu kursta
da sürer.

Ölçümün varsayımları:

- **KY8** — Yorumlayıcılar kurgudur ve yalnız yetenek kümeleriyle ayrılır; gerçek sürüm
  numarası yazılmaz, yorumlayıcı gerçekleştirimi adlandırılmaz.
- **KY9** — Ortak tanımın beş testi aynen alınır; `kirleten` ve `bagimli` testlerinin
  davranışı değiştirilmez. Eklenen iki test paylaşılan duruma dokunmaz, yalnız ortamı okur.
- **KY10** — `surum` testi çözümlemeyi her çağrıda yeniden yapar; kilit seçildiğinde
  çözümleme yerine kilitteki küme okunur.
- **KY11** — Düzenek açıkken paylaşılan ortam **her testten önce** yeniden kurulur. Düzeneğin
  bu etkisi bu kursun düzenek ve parametreleme dersinde ölçüldü; burada bir **eleme aracı**
  olarak kullanılır, yeniden ölçülmez.
- **KY12** — Bir koşumun kararı, geçen test sayısıdır. İki koşum aynı sayıyı veriyorsa
  **bir** ayrı karar sayılır, düşen testlerin adları farklı olsa bile.
- **KY13** — Hiçbir süre ölçülmez. Matrisin maliyeti **koşum sayısı** ve **test çağrısı**
  olarak yazılır.
- **KY14** — Gerçek kurulum yapılmaz, gerçek paket indirilmez; yorumlayıcı ve bağımlılık
  kümesi tümüyle ders içinde modellenir.

#### Ölçüm

```python
"""Coklu ortam testi: ayni takim kac ortamda kac ayri karar veriyor."""

DEPO = {"olcum": [(1, 0), (1, 1), (1, 2), (2, 0)],
        "rapor": [(0, 9), (1, 0), (1, 1)],
        "ortak": [(3, 0), (3, 1), (3, 2), (4, 0)]}
BILDIRIM = {"olcum": ((1, 0), (2, 0)), "rapor": ((1, 0), (2, 0)),
            "ortak": ((3, 0), (4, 0))}


def coz(strateji):
    secim = {}
    for paket in sorted(BILDIRIM):
        alt, ust = BILDIRIM[paket]
        adaylar = [s for s in DEPO[paket] if alt <= s < ust]
        secim[paket] = adaylar[-1] if strateji == "en yeni" else adaylar[0]
    return secim


KILIT = dict(coz("en yeni"))


class Ortam:
    def __init__(self):
        self.sayac = 0


def t_temiz_a(o, y, s):
    return True


def t_kirleten(o, y, s):
    o.sayac += 1
    return True


def t_bagimli(o, y, s):
    return o.sayac == 0


def t_temiz_b(o, y, s):
    return True


def t_temiz_c(o, y, s):
    return True


def t_yetenek(o, y, s):
    """Yorumlayicinin birlesim yetenegini ister."""
    return "birlesim" in YORUMLAYICI[y]


def t_surum(o, y, s):
    """Cozumlenen rapor surumunun en az 1.1 olmasini ister."""
    return (KILIT if s == "kilit" else coz(s))["rapor"] >= (1, 1)


TESTLER = [("temiz_a", t_temiz_a), ("kirleten", t_kirleten),
           ("bagimli", t_bagimli), ("temiz_b", t_temiz_b),
           ("temiz_c", t_temiz_c), ("yetenek", t_yetenek),
           ("surum", t_surum)]
SIRALAR = {"yazım sırası": [0, 1, 2, 3, 4, 5, 6],
           "bağımlı önce": [0, 2, 1, 3, 4, 5, 6],
           "ters": [6, 5, 4, 3, 2, 1, 0]}
YORUMLAYICI = {"Y1": {"a"}, "Y2": {"a", "birlesim"}, "Y3": {"a", "birlesim"}}
CAGRI = 0


def kosum(y, s, sira, duzenek=False):
    """Duzenek acikken paylasilan ortam her testten once yeniden kurulur."""
    global CAGRI
    o, gecen, dusen = Ortam(), 0, []
    for i in sira:
        ad, islev = TESTLER[i]
        if duzenek:
            o = Ortam()
        CAGRI += 1
        if islev(o, y, s):
            gecen += 1
        else:
            dusen.append(ad)
    return gecen, dusen


print(f"{'yorumlayıcı':<12s} {'çözümleme':<10s} {'sıra':<14s} {'geçen':>6s}  düşen")
karar = []
for y in YORUMLAYICI:
    for s in ("en yeni", "en eski"):
        for sad, sira in SIRALAR.items():
            gecen, dusen = kosum(y, s, sira)
            karar.append(gecen)
            print(f"{y:<12s} {s:<10s} {sad:<14s} {gecen:6d}  "
                  f"{','.join(dusen) or '-'}")
print(f"\n{len(karar)} koşum, {len(set(karar))} ayrı karar: {sorted(set(karar))}")
print(f"test çağrısı {CAGRI}")

print()
print(f"{'aşama':<34s} {'koşum':>6s} {'ayrı karar':>11s} {'çağrı':>6s}")
for ad, stratejiler, siralar, duzenek in (
        ("ham matris", ("en yeni", "en eski"), SIRALAR, False),
        ("kilit dosyasıyla", ("kilit",), SIRALAR, False),
        ("kilit ve düzenekle", ("kilit",), {"tek": SIRALAR["yazım sırası"]}, True)):
    CAGRI, sonuc = 0, []
    for y in YORUMLAYICI:
        for s in stratejiler:
            for sira in siralar.values():
                sonuc.append(kosum(y, s, sira, duzenek)[0])
    print(f"{ad:<34s} {len(sonuc):6d} {len(set(sonuc)):11d} {CAGRI:6d}  "
          f"{sorted(set(sonuc))}")

print()
for y in YORUMLAYICI:
    gecen, dusen = kosum(y, "kilit", SIRALAR["yazım sırası"], True)
    print(f"{y}: {gecen}/7 geçti, düşen: {','.join(dusen) or '-'}")
```

```
yorumlayıcı  çözümleme  sıra            geçen  düşen
Y1           en yeni    yazım sırası        5  bagimli,yetenek
Y1           en yeni    bağımlı önce        6  yetenek
Y1           en yeni    ters                6  yetenek
Y1           en eski    yazım sırası        4  bagimli,yetenek,surum
Y1           en eski    bağımlı önce        5  yetenek,surum
Y1           en eski    ters                5  surum,yetenek
Y2           en yeni    yazım sırası        6  bagimli
Y2           en yeni    bağımlı önce        7  -
Y2           en yeni    ters                7  -
Y2           en eski    yazım sırası        5  bagimli,surum
Y2           en eski    bağımlı önce        6  surum
Y2           en eski    ters                6  surum
Y3           en yeni    yazım sırası        6  bagimli
Y3           en yeni    bağımlı önce        7  -
Y3           en yeni    ters                7  -
Y3           en eski    yazım sırası        5  bagimli,surum
Y3           en eski    bağımlı önce        6  surum
Y3           en eski    ters                6  surum

18 koşum, 4 ayrı karar: [4, 5, 6, 7]
test çağrısı 126

aşama                               koşum  ayrı karar  çağrı
ham matris                             18           4    126  [4, 5, 6, 7]
kilit dosyasıyla                        9           3     63  [5, 6, 7]
kilit ve düzenekle                      3           2     21  [6, 7]

Y1: 6/7 geçti, düşen: yetenek
Y2: 7/7 geçti, düşen: -
Y3: 7/7 geçti, düşen: -
```

#### On Sekiz Koşum, Dört Karar

Üst tablo aynı takımın on sekiz koşumunu yan yana koyuyor. Geçen test sayısı **4** ile **7**
arasında değişiyor; **4 ayrı karar** var.

Dördünü tek tek okumak, sayının nereden geldiğini gösterir. En kötü satır `Y1` /
`en eski` / `yazım sırası`: **4/7**, düşen üç test `bagimli`, `yetenek` ve `surum`. En iyi
satırlar `Y2` ve `Y3`'ün `en yeni` / `bağımlı önce` ve `ters` koşumları: **7/7**, düşen
yok.

Dikkat edilmesi gereken şey, aradaki farkın **üç ayrı kaynaktan** gelmesidir. `bagimli`
testinin düşmesi sıra ekseninden, `surum` testinin düşmesi çözümleme ekseninden, `yetenek`
testinin düşmesi yorumlayıcı ekseninden geliyor. Üçü aynı sütunda toplanıyor ve tek bir
sayıya — geçen test sayısına — karışıyor.

Bir raporda yalnız bu sayı görünseydi elde dört ayrı değer olurdu ve hiçbiri hangi eksenin
sorumlu olduğunu söylemezdi. `düşen` sütunu tam da bu yüzden var: **karar sayısı farkın
varlığını, düşen testin adı farkın kaynağını verir.** Matrisin bir eksenle daha büyütülmesi
ilk sayıyı büyütür, ikinciyi vermez.

#### Aynı Sayı, Ayrı Neden

Üst tabloda **5** değeri beş ayrı satırda geçiyor ve düşen testler bu beş satırda üç ayrı
küme oluşturuyor: `bagimli` ile `yetenek`, `yetenek` ile `surum`, `bagimli` ile `surum`.
Aynı sayı, üç ayrı nedenle çıkıyor.

Bu, ölçümün en kolay yanlış okunan yeridir. İki koşumun aynı sayıyı vermesi, aynı durumda
olduklarını göstermez; yalnız **düşen test sayısının** eşit olduğunu gösterir. Karar
sayısını sayarken bu iki satırı bir sayıyoruz — varsayım bloğu bunu açıkça yazıyor — çünkü
ölçülen şey ayrı sonuç sayısıdır, ayrı neden sayısı değil. Ama bir arıza incelenirken
soru tersine döner ve nedeni ayırmak gerekir.

`ters` sırasındaki iki satır aynı ayrımın küçük bir örneğini taşıyor: `Y1` / `en eski` /
`ters` koşumunda düşen testler `surum,yetenek` sırasıyla, `bağımlı önce` koşumunda
`yetenek,surum` sırasıyla yazılıyor. Küme aynı, sıra ayrı — çünkü liste testlerin
koştuğu sırayla doluyor. Sayıya hiçbir etkisi yok, ama iki dökümü metin olarak
karşılaştıran bir araç bunları ayrı sayardı.

Buradan pratik bir sonuç çıkar: **koşum raporu yalnız sayıyı taşırsa, ayrı nedenler aynı
satırda toplanır.** Düşen testlerin adı raporda duruyorsa üç neden birbirinden ayrılabilir;
durmuyorsa matris on sekiz koşum yapmış olur ve elde dört sayı kalır. Matrisi genişletmenin
maliyeti çarpımsaldır, ondan alınan bilgi ise raporun ne taşıdığına bağlıdır.

#### Eksenleri Elemek

Orta tablo elemenin üç aşamasını ölçüyor.

**Ham matris**: **18** koşum, **4** ayrı karar, **126** test çağrısı. Bu, hiçbir eksenin
sabitlenmediği durumdur.

**Kilit dosyasıyla**: çözümleme ekseni düşüyor, çünkü kilit stratejinin sonucu değiştirmesini
durduruyor. Koşum **9**'a, çağrı **63**'e, ayrı karar **3**'e iniyor. Kaybolan karar `4`;
`surum` testi artık hiçbir koşumda düşmüyor. Kilit çözümlemeyi ortadan kaldırmıyor —
kararı bir kez verdirip yazıya geçiriyor — ama ortam matrisinden bir ekseni **tümüyle**
çıkarıyor.

**Kilit ve düzenekle**: sıra ekseni de düşüyor. Koşum **3**'e, çağrı **21**'e, ayrı karar
**2**'ye iniyor. `bagimli` testi artık hiçbir sırada düşmüyor, çünkü paylaşılan ortam her
testten önce yeniden kuruluyor.

Geriye kalan **2** ayrı karar burada durur ve indirilemez. Alt satırlar bunu gösteriyor:
`Y1` **6/7**, `Y2` ve `Y3` **7/7**. Düşen tek test `yetenek` ve düşmesinin nedeni bir
yapılandırma ayrıntısı değil, yorumlayıcının gerçekten bir yeteneği taşımamasıdır. Çoklu
ortam koşumunun aradığı fark tam olarak budur.

Elemenin sırası ara sayıları belirler, son sayıyı belirlemez. Düzenek kilitten önce
uygulansaydı orta satırdaki **3** yerine başka bir sayı görülürdü, çünkü o aşamada elenmiş
olan eksen başka olurdu. Elemenin her adımı bir ekseni tümüyle düşürür ve o eksenin karara
katkısını da beraberinde götürür; iki eksen de düşürüldüğünde hangi sırayla gelinirse
gelinsin geriye aynı **2** karar kalır. Ara sayılar yöntemin, son sayı ise ortamın
özelliğidir.

Buradan dersin kuralı çıkar: **çoklu ortam koşumu ortam kaynaklı farkı bulmak içindir;
yapılandırma kaynaklı fark önce elenmezse hangi eksenin sorumlu olduğu bilinemez.** On sekiz
koşumluk matris dört karar veriyordu ve bunların ikisi ortamla ilgili bile değildi. Eleme,
koşum sayısını altıda bire indirirken kaybettiği tek bilgi, zaten bu ölçümün konusu olmayan
bilgidir.

Son bir okuma matrisin genişliğiyle ilgilidir. Üç yorumlayıcı koşturuldu ama ayrı karar
**2** çıktı; `Y2` ile `Y3` hiçbir koşumda ayrılmadı, çünkü yetenek kümeleri aynı. Matrisin
bir noktası daha eklemek koşum ve çağrı sayısını doğrusal büyütür ama ayrı karar sayısını
**büyütmeyebilir**. Matrisin genişliği bir maliyet, ayrı karar sayısı ise bir kazançtır ve
ikisi aynı hızda artmaz.

#### Özet

- Ortam tek bir şey değil, eksenler çarpımıdır; burada üç eksen **18** koşum ve **126** test
  çağrısı üretir.
- Aynı yedi testlik takım bu matriste **4 ayrı karar** verir (**4**, **5**, **6**, **7**);
  fark üç ayrı eksenden gelir ve tek bir sayıya karışır.
- Düşen testin adı farkın kaynağını verir: `bagimli` sıradan, `surum` çözümlemeden,
  `yetenek` yorumlayıcıdan.
- Kilit dosyası çözümleme eksenini düşürür — **9** koşum, **3** ayrı karar; düzenek sıra
  eksenini düşürür — **3** koşum, **2** ayrı karar.
- Kalan **2** karar indirilemez ve gerçek ortam farkıdır: `Y1` **6/7**, `Y2` ve `Y3`
  **7/7**. Üç yorumlayıcı koşturulmasına karşın ayrı karar sayısı ikidir; matrisin
  genişlemesi ayrı sonuç sayısını büyütmek zorunda değildir.

#### Sonraki Adım

Bu ders takımı ortamdan ortama taşıdı ve her ortamda aynı kaynağın koştuğunu varsaydı. Bu
varsayım sessizdir ve sınanmadı: koşan şey gerçekten aynı kaynak mıydı? Bir proje
başkasının kullanımına verildiğinde kaynak doğrudan gitmez; bir **dağıtım biçimine**
paketlenir ve o paket açılarak kurulur. Sonraki ders bu adımı ölçer: aynı kaynaktan üretilen
dağıtım kaç ayrı içerik verir, hangi girdiler bu sayıyı birden büyütür ve neyi sabitlemek
onu bire indirir?

### Paketleme

Source: https://academia.sh/tr/kurslar/python-projeleri/paketleme

Aynı kaynaktan üç dizin durumu ve üç dahil etme kuralıyla yapılan dokuz üretim 5 ayrı içerik verir; açık liste bu sayıyı 1'e indirir, arşiv özetini yalnız sıralı girdi ile kaynaktan türetilmiş damganın birlikte kullanılması tekleştirir ve derleme adımının ne zaman çalıştığı kurulumu 3 ayrı sonuçtan 1'e düşürür.

Önceki ders aynı takımı üç eksenli bir matriste koşturdu ve kalan farkın gerçek ortam farkı
olduğunu gösterdi. O ölçümün sessiz bir varsayımı vardı: her ortamda koşan şey **aynı
kaynaktı**. Testler doğrudan çalışma dizininden okundu.

Bir proje başkasının kullanımına verildiğinde bu varsayım kendiliğinden sağlanmaz. Kaynak
dizini olduğu gibi gitmez; bir **dağıtım biçimine (distribution format)** paketlenir, o
paket taşınır ve karşı tarafta açılır. Bu dersin sorusu paketlemenin nasıl yapıldığı değil:
**aynı kaynaktan üretilen dağıtım kaç ayrı içerik verir ve neyi sabitlemek bu sayıyı bire
indirir?**

#### Dağıtım Biçimi ve Derleme Sistemi

İki dağıtım biçimi ayrılır ve aradaki fark bu dersin son ölçümünün konusudur.

**Kaynak dağıtımı** kaynak dosyaları ve proje bildirimini taşır. Kurulan tarafta bir
**derleme adımı** çalışır ve kurulacak ağacı üretir. **Kurulmuş dağıtım** ise bu adımın
sonucunu taşır; kurulan tarafta yalnız açma işi kalır.

Bu iki biçimi üreten araç sınıfı **derleme sistemidir (build system)**. Görevi üç sorunun
yanıtını üretmektir: hangi dosyalar dağıtıma girecek, dağıtım hangi düzende yazılacak ve
derleme adımı ne zaman çalışacak. Bu kurs araç adı yazmaz; derleme sistemi burada üç
işlevle **modellenir** — bir dahil etme kuralı, bir arşiv yazıcısı ve bir derleme adımı.
Model gerçek bir arşiv üretmez, hiçbir dosya diske yazmaz ve hiçbir paket indirmez; ürettiği
tek şey içerik özetleridir.

#### Üç Dizin Durumu

Dahil etme kuralının ne yapacağı, kaynağın yanında **ne durduğuna** bağlıdır. Bir çalışma
dizini kaynaktan ibaret değildir: derleme artıkları, önbellek dosyaları ve yerel ayar
dosyaları da orada birikir. Ölçüm üç durum tanımlar. **Temiz** dizin yalnız kaynağı taşır.
**Önbellekli** dizinde bir üretilmiş dosya vardır. **Artıklı** dizinde ayrıca bir kayıt
dosyası ve bir yerel ayar dosyası bulunur.

Üç dahil etme kuralı denenir. **Her şeyi topla** dizindeki her dosyayı alır. **Desenle ele**
belirli uzantıları dışarıda bırakır. **Açık liste** yalnız bildirimde adı geçen dosyaları
alır.

Ölçümün varsayımları:

- **KY15** — Kaynak ağacı sabittir ve üç durumda da aynıdır; durumlar yalnız kaynağın
  yanında duran dosyalarla ayrılır.
- **KY16** — Dağıtımın içeriği, dosya adları ve gövdelerinin ada göre sıralanmış özetidir;
  dizin listeleme sırası bu özete girmez.
- **KY17** — Arşiv özeti ise sıraya ve damgaya duyarlıdır; iki koşumun listeleme sırası ve
  koşum damgası **kurgu** değerlerle modellenir, gerçek bir saat okunmaz.
- **KY18** — Derleme adımı kaynaktan tek bir dosya üretir ve içine çalıştığı ortamın adını
  yazar. Gerçek bir derleme adımı bundan çok daha fazlasını yapar; modelin tuttuğu tek
  özellik, **çıktısının çalıştığı ortama bağlı olmasıdır**.
- **KY19** — Özetler kısaltılmış sekiz onaltılık basamaktır; iki özetin eşitliği içeriğin
  eşitliği olarak okunur.
- **KY20** — Hiçbir dosya diske yazılmaz, hiçbir arşiv üretilmez, hiçbir paket indirilmez;
  bütün üretim bellekte modellenir.
- **KY21** — Süre ölçülmez. Sayılan şey **ayrı içerik**, **ayrı özet** ve **ayrı kurulum**
  sayısıdır.

#### Ölçüm

```python
"""Paketleme: ayni kaynaktan uretilen dagitim kac ayri icerik veriyor."""

import hashlib

KAYNAK = {
    "olcum/__init__.py": "from .indirim import indirim\n",
    "olcum/indirim.py": "def indirim(tutar, uye, kupon):\n    return tutar\n",
    "olcum/oran.json": '{"tavan": 20}\n',
    "testler/test_indirim.py": "def test_tavan():\n    pass\n",
    "BENIOKU.md": "# olcum\n",
    "proje.toml": '[proje]\nad = "olcum"\n',
}
ARTIK = {
    "temiz": {},
    "önbellekli": {"olcum/__onbellek__/indirim.onb": "derlenmis\n"},
    "artıklı": {"olcum/__onbellek__/indirim.onb": "derlenmis\n",
                "olcum/gecici.log": "kosum kaydi\n",
                "yerel_ayar.txt": "yol = /ev/kullanici\n"},
}
LISTE = ("olcum/__init__.py", "olcum/indirim.py", "olcum/oran.json",
         "proje.toml")
KURALLAR = {
    "her şeyi topla": lambda ad: True,
    "desenle ele": lambda ad: not ad.endswith((".onb", ".log")),
    "açık liste": lambda ad: ad in LISTE,
}


def dizin(durum):
    return {**KAYNAK, **ARTIK[durum]}


def ozet(metin):
    return hashlib.sha256(metin.encode()).hexdigest()[:8]


def icerik_ozeti(dosyalar):
    """Dagitimin icerigi: ad ve govde, adina gore sirali."""
    return ozet("".join(f"{a}\n{dosyalar[a]}" for a in sorted(dosyalar)))


def sec(durum, kural):
    d = dizin(durum)
    return {a: g for a, g in d.items() if KURALLAR[kural](a)}


print(f"{'dahil etme':<15s} " + " ".join(f"{d:>14s}" for d in ARTIK)
      + "  ayrı içerik")
tum = []
for kural in KURALLAR:
    satir, ozetler = [], []
    for durum in ARTIK:
        secim = sec(durum, kural)
        o = icerik_ozeti(secim)
        ozetler.append(o)
        tum.append(o)
        satir.append(f"{len(secim)}·{o}")
    print(f"{kural:<15s} " + " ".join(f"{s:>14s}" for s in satir)
          + f"{len(set(ozetler)):11d}")
print(f"dokuz üretim, {len(set(tum))} ayrı içerik")

print()
print(f"{'sıra kuralı':<18s} {'damga kuralı':<20s} {'koşum A':>9s} "
      f"{'koşum B':>9s}  ayrı")


def listele(dosyalar, kosum):
    return sorted(dosyalar, reverse=(kosum == "koşum B"))


def arsiv(dosyalar, sirali, kaynaktan_damga, kosum, damga):
    adlar = sorted(dosyalar) if sirali else listele(dosyalar, kosum)
    d = icerik_ozeti(dosyalar) if kaynaktan_damga else damga
    return ozet("".join(f"{a}\n{dosyalar[a]}" for a in adlar) + f"damga={d}\n")


SECIM = sec("artıklı", "açık liste")
DAMGA = {"koşum A": "d-101", "koşum B": "d-207"}
for s_ad, sirali in (("listeleme sırası", False), ("adına göre sıralı", True)):
    for d_ad, kaynaktan in (("koşum damgası", False),
                            ("kaynaktan türetilmiş", True)):
        a = [arsiv(SECIM, sirali, kaynaktan, k, DAMGA[k]) for k in DAMGA]
        print(f"{s_ad:<18s} {d_ad:<20s} {a[0]:>9s} {a[1]:>9s}"
              f"{len(set(a)):6d}")

print()


def derle(dosyalar, ortam):
    """Derleme adimi: kaynaktan bir dosya uretir."""
    return {**dosyalar, "olcum/_yapi.py": f'YAPILDI = "{ortam}"\n'}


kaynak_dagitimi = SECIM
kurulmus_dagitim = derle(SECIM, "üretim ortamı")
print(f"{'dağıtım biçimi':<20s} " + " ".join(f"{d:>10s}" for d in ARTIK)
      + "  ayrı kurulum")
for ad, kurulum in (("kaynak dağıtımı",
                     lambda o: icerik_ozeti(derle(kaynak_dagitimi, o))),
                    ("kurulmuş dağıtım",
                     lambda o: icerik_ozeti(kurulmus_dagitim))):
    o = [kurulum(d) for d in ARTIK]
    print(f"{ad:<20s} " + " ".join(f"{x:>10s}" for x in o)
          + f"{len(set(o)):13d}")
```

```
dahil etme               temiz     önbellekli        artıklı  ayrı içerik
her şeyi topla      6·3f5fd540     7·c6e0e7fa     9·5bd4ca54          3
desenle ele         6·3f5fd540     6·3f5fd540     7·f43474cf          2
açık liste          4·3f6d7475     4·3f6d7475     4·3f6d7475          1
dokuz üretim, 5 ayrı içerik

sıra kuralı        damga kuralı           koşum A   koşum B  ayrı
listeleme sırası   koşum damgası         0ef2d766  ed542e63     2
listeleme sırası   kaynaktan türetilmiş  f2347fb6  153d3f8d     2
adına göre sıralı  koşum damgası         0ef2d766  94813185     2
adına göre sıralı  kaynaktan türetilmiş  f2347fb6  f2347fb6     1

dağıtım biçimi            temiz önbellekli    artıklı  ayrı kurulum
kaynak dağıtımı        ca94ea4c   c836cc41   8cd3c4ae            3
kurulmuş dağıtım       47abe500   47abe500   47abe500            1
```

#### Dokuz Üretim, Beş İçerik

Üst tablo dersin ilk sayısını veriyor: aynı kaynaktan yapılan **9** üretim **5 ayrı
içerik** üretiyor.

Satırlar tek tek okunduğunda sayının nereden geldiği görülür. **Her şeyi topla** kuralı üç
dizin durumunda **6**, **7** ve **9** dosya alıyor ve **3 ayrı içerik** veriyor. Bu kuralın
sonucu kaynağa değil, **kaynağın yanında ne durduğuna** bağlıdır; aynı depodan çekilmiş iki
çalışma dizini ayrı paket üretir.

**Desenle ele** kuralı iki durumda aynı sonucu veriyor — `temiz` ile `önbellekli` aynı
özeti, `3f5fd540`, taşıyor — çünkü elediği uzantı önbellek dosyasını yakalıyor. Üçüncü
durumda ise `yerel_ayar.txt` dosyası desene takılmıyor ve içeriğe giriyor: **7** dosya,
ayrı özet. Kural, saydığı şeyi **bildiği** kadar eleyebilir.

**Açık liste** üç durumda da **4** dosya ve tek bir özet, `3f6d7475`, veriyor: **1 ayrı
içerik**. Aradaki fark yöntemseldir. İlk iki kural "neyin dışarıda kalacağını" söyler ve
listelenmemiş her yeni dosya sessizce içeri girer. Üçüncü kural "neyin içeri gireceğini"
söyler; listelenmemiş her dosya sessizce dışarıda kalır. **Sessizliğin yönü değişir ve
tekleşme bu yön değişiminden gelir.**

İki özet dokuz üretimin ikisinde birden geçiyor: `3f5fd540` hem `her şeyi topla`/`temiz`
hem `desenle ele`/`temiz` hücrelerinde. Dokuz hücre, beş ayrı içerik; kuralların çakıştığı
yerde sayı düşüyor.

Dosya sayıları bir ayrımı daha görünür kılıyor. Açık liste **4** dosya alıyor, oysa temiz
dizinde **6** kaynak dosyası var. Dışarıda kalan ikisi `testler/test_indirim.py` ve
`BENIOKU.md`. Bu bir kusur değil, bir karardır: dağıtım **kurulan** ağacı taşır, depodaki
her şeyi değil. Test dosyası depoda kalır ve orada koşar; dağıtıma girdiğinde kurulan
tarafın disk alanını ve içe aktarma ad uzayını kaplar. Aynı karar belge dosyası için de
verilebilir ve tersi de savunulabilir — savunulamaz olan, kararın **verilmemiş** olmasıdır.
İlk iki kuralda bu karar hiç verilmiyor; dosyanın dağıtıma girip girmeyeceğini adının
uzantısı belirliyor.

#### Bildirimin İki İşi

Üç dahil etme kuralı da bir yerde yazılıdır ve o yer proje bildirimidir. Bildirim iki ayrı
iş yapar ve ikisi sık sık aynı dosyada durduğu için karıştırılır.

**Birinci iş: paketin kimliği.** Ad, sürüm, bağımlılık bildirimi ve dağıtıma girecek
dosyalar. Bunlar veriyi tanımlar; hangi araçla okundukları sonucu değiştirmez.

**İkinci iş: derleme sisteminin seçimi.** Dağıtımı hangi derleme sisteminin üreteceği ve o
sistemin hangi arayüzle çağrılacağı. Bu bir yönergedir; sonucu doğrudan belirler.

Ayrımın pratik karşılığı şudur: birinci iş **bildirimseldir** ve iki ayrı araç aynı bildirimi
okuyup aynı dosya kümesini seçer. İkinci iş bir **seçimdir** ve seçilen sisteme göre üretilen
arşivin düzeni, damgası ve derleme adımının davranışı değişir. Ölçümün üst tablosu birinci
işi, orta tablosu ikinci işi sınıyor.

Bu yüzden bir bildirimin "eksiksiz" olması iki ayrı anlama gelir. Dosya listesi eksiksizse
dağıtımın içeriği tekleşir. Derleme sistemi ve çağrı arayüzü de yazılıysa, dağıtımı üreten
her koşum aynı sistemi aynı biçimde çağırır. İkincisi yazılmadığında üretim aracın
öntanımlısına düşer ve ortak tanımın çözümleme ölçümündeki durum tekrarlanır: **karar
verilmemiştir, o yüzden ortam verir.**

#### Arşivi Sabitleyen İki Karar

İçeriğin tekleşmesi arşivin tekleşmesi anlamına gelmez. Orta tablo aynı dosya kümesini —
`açık liste` kuralının seçtiği dört dosyayı — iki koşumda arşivliyor ve dört kural
bileşimini yan yana koyuyor.

Üç bileşimde iki koşum **2 ayrı özet** veriyor. Bir bileşimde **1**.

**Listeleme sırası** kullanıldığında koşum A ve koşum B dosyaları ayrı düzende yazıyor;
gövde aynı, bayt dizisi ayrı. Damga kaynaktan türetilse bile bu fark kalıyor: ikinci satır
`f2347fb6` ile `153d3f8d` veriyor. **Koşum damgası** kullanıldığında ise sıra sabitlense
bile fark kalıyor; üçüncü satır `0ef2d766` ile `94813185` veriyor.

Yalnız dördüncü satır **1** veriyor: sıra adına göre sabitlenmiş **ve** damga kaynaktan
türetilmiş. İkisi birden gerekiyor, biri yetmiyor. Bu, **yeniden üretilebilirliğin**
tanımıdır: aynı girdiden aynı çıktı, koşumdan koşuma.

Arşiv özetinin neden ayrıca ölçüldüğü de buradan anlaşılır. Kurulan taraf içeriği kendi
hesaplayamaz; eline geçen şey bir arşivdir ve onu ancak **baytları üzerinden** doğrulayabilir.
İçerik tek olsa bile arşiv baytları koşumdan koşuma değişiyorsa, iki arşivin aynı kaynaktan
geldiği doğrulanamaz. Yeniden üretilebilirlik iddiası bu yüzden içerik düzeyinde değil,
**bayt düzeyinde** kurulur.

Üçüncü satırdaki `0ef2d766` ile birinci satırdaki `0ef2d766` aynı olduğu için bir ayrıntı
görülebiliyor: koşum A'nın listeleme sırası zaten adına göre sıralıdır, bu yüzden o koşumda
iki sıra kuralı aynı sonucu verir. Fark yalnız koşum B'de ortaya çıkar. **Bir yapılandırma
kusuru her koşumda görünmez; görünmediği koşumda yok olduğu sanılır.**

#### Derlemenin Ne Zaman Çalıştığı

Alt tablo son ölçümü veriyor ve iki dağıtım biçimini ayırıyor.

**Kaynak dağıtımı** üç dizin durumunda **3 ayrı kurulum** veriyor. Nedeni tabloda görünmüyor
ama modelde açıktır: derleme adımı kurulan tarafta çalışıyor ve ürettiği dosyaya kurulduğu
ortamın adını yazıyor. Kurulan ağaç bu yüzden ortamdan ortama değişiyor. Dağıtımın kendisi
tekti — dört dosya, tek özet — ama kurulum sonucu tek değil.

**Kurulmuş dağıtım** üç durumda da **1 ayrı kurulum** veriyor: `47abe500`. Derleme adımı bir
kez, üretim tarafında çalıştı; kurulan taraf yalnız açıyor.

İkisi arasındaki fark **derleme adımının nerede çalıştığıdır** ve bu bir dağıtım biçimi
kararıdır. Kaynak dağıtımı derlemeyi kurulan tarafa taşır; karşılığında kurulan taraf
kendi ortamına uygun bir çıktı üretebilir. Kurulmuş dağıtım derlemeyi bir kez yapar;
karşılığında ortam farkını dağıtıma yansıtamaz.

Bu, kurulmuş dağıtımın bedelsiz olduğu anlamına gelmez. Derleme adımı ortamdan bir şey
okuyorsa, kurulmuş dağıtım okuduğu değeri **içine gömer** ve artık yalnız o ortama uyar. O
zaman dağıtımın hangi ortama uyduğunun paketin kendisinde yazılı olması gerekir; yazılı
değilse kurulan taraf uymayan bir paketi açar ve hata kurulumdan çok sonra, çalışma
sırasında görünür. Kaynak dağıtımının ürettiği **3** ayrı kurulum bir kusur değil, bu
uyarlamanın kendisidir; kusur, üç ayrı sonucun **beklenmiyor** olmasıdır.

Bir kez üretilip her yere dağıtılan yapı çıktısı ilkesi **DevOps Kültürü ve Temelleri**
kursunda işletim tarafında kurulmuştu. Buradaki ölçüm o ilkenin dil tarafındaki karşılığını
sayıyla veriyor: ilke uygulandığında kurulum sonucu **1**, uygulanmadığında ortam sayısı
kadar.

Üç ölçümü tek cümlede birleştirmek gerekirse: **aynı kaynaktan çıkan dağıtımın tek içerik
vermesi için üç şeyin birden sabitlenmesi gerekir** — dahil edilecek dosyaların açık
listesi, arşivin yazım sırası ve damgası ve derleme adımının hangi tarafta çalışacağı.
Üçünden biri açık bırakıldığında sayı birden büyüktür ve büyüklüğü, açık bırakılan yerin
ortamdan ne kadar etkilendiğine bağlıdır.

#### Özet

- Dağıtım biçimi ikiye ayrılır: kaynak dağıtımı derleme adımını kurulan tarafa taşır,
  kurulmuş dağıtım o adımın sonucunu taşır.
- Aynı kaynaktan yapılan **9** üretim **5 ayrı içerik** verir; `her şeyi topla` kuralı
  **3**, `desenle ele` **2**, `açık liste` **1** ayrı içerik üretir.
- Eleyen kurallar listelenmemiş dosyayı sessizce içeri alır; açık liste sessizce dışarıda
  bırakır. Tekleşme bu yön değişiminden gelir.
- Arşiv özetinin tekleşmesi için sıranın **ve** damganın birlikte sabitlenmesi gerekir; dört
  bileşimden yalnız biri **1** ayrı özet verir, üçü **2**.
- Kaynak dağıtımı üç ortamda **3 ayrı kurulum**, kurulmuş dağıtım **1** verir; dağıtımın
  kendisi tek olsa bile kurulum sonucu tek olmayabilir.

#### Sonraki Adım

Bu ders dağıtımı üretti ve içeriğini bir özetle adlandırdı. Özet, üretim tarafında hesaplanan
bir sayıdır ve orada kalır. Paket bir **paket kaydına** gönderildiğinde ise ona bir ad ve bir
sürüm verilir; kurulan taraf artık içeriği değil, **adı** ister. Sonraki dersin sorusu
buradan doğar: yayımlanmış bir sürümün içeriği sonradan değişebilir mi, aynı ad iki ayrı
içeriği taşıyabilir mi ve taşıyabiliyorsa kilit dosyasının yazdığı sürüm numarası neyi
garanti eder?

### Yayımlama

Source: https://academia.sh/tr/kurslar/python-projeleri/yayimlama

Dokuz yayım girişimi üzerine yazılabilir bir kayıtta 9 kabul ve 2 sessiz değişiklik, değiştirilemez bir kayıtta 7 kabul ve 2 red üretir; aynı kilit dosyası ilk kayıtta 2 ayrı kurulum verir ve özet yazan bir kilit bu iki uyuşmazlığın ikisini de yakalayarak kurulumu durdurur.

Önceki ders dağıtımı üretti ve içeriğini bir özetle adlandırdı. O özet üretim tarafında
hesaplanan bir sayıydı ve orada kaldı; kurulan tarafın ondan haberi yoktu.

Paket bir **paket kaydına (package registry)** gönderildiğinde bu düzen değişir. Kurulan
taraf artık içeriği değil, **adı** ister: "`olcum` paketinin `1.2` sürümü". Adın karşılığını
kayıt verir. Bu dersin sorusu buradan doğar: **aynı ad iki ayrı içeriği taşıyabilir mi ve
taşıyabiliyorsa kilit dosyasının yazdığı sürüm numarası neyi garanti eder?**

#### Kayıt Ne Saklar

Paket kaydı, `(ad, sürüm)` çiftinden bir dağıtıma giden bir eşlemedir. Bu kurs kayıt ürün
adı yazmaz; kayıt burada tek bir sözlükle **modellenir** ve üzerinde tek bir işlem
tanımlanır: yayımla. Model gerçek bir kayda bağlanmaz, hiçbir şey gönderilmez ve hiçbir
paket indirilmez.

Sürüm işaretlemenin kendisi — etiketleme ve anlamlı sürümleme — **Sürüm Kontrolüne Giriş**
kursunda kurulmuştu ve burada tekrarlanmaz. Bu ders sürüm numarasının nasıl seçileceğini
sormaz; **seçilmiş bir numaranın ne kadar bağlayıcı olduğunu** sorar.

#### İki İlke

Yayımla işleminin `(ad, sürüm)` çifti daha önce kullanılmışsa ne yapacağı bir ilke
sorusudur ve iki yanıtı vardır.

**Üzerine yazılabilir** kayıt, yeni gövdeyi eskisinin yerine koyar. Gönderim her zaman
başarılıdır. **Değiştirilemez** kayıt, aynı çifte **ayrı** bir gövde geldiğinde gönderimi
reddeder. Aynı gövdenin yeniden gönderilmesini reddetmez — o gönderim kaydı zaten
değiştirmez.

Ayrımın tam yeri budur: değiştirilemezlik **yeniden gönderimi** değil, **ayrı içerikle
yeniden gönderimi** yasaklar. Bir yayım akışında ikisi yan yana bulunabilir ve ölçüm
ikisini de içerir.

Ölçüm dokuz yayım girişimi tanımlar. Üçü ortak kurgunun kilit dosyasındaki sürümleri ilk kez
yayımlar, biri daha eski bir sürümü yayımlar, biri aynı gövdeyi yeniden gönderir, ikisi aynı
sürüme ayrı gövde gönderir, biri aynı gövdeyi bir kez daha gönderir, biri yeni bir sürüm
açar. İki zaman noktası okunur: beşinci girişimden sonra `t1`, dokuzuncudan sonra `t2`.

Ölçümün varsayımları:

- **KY22** — Kilit dosyası ortak tanımın "en yeni" çözümlemesinin sonucudur: `olcum 1.2`,
  `ortak 3.2`, `rapor 1.1`. Kilidin içeriği ölçüm boyunca değişmez.
- **KY23** — Dağıtımın içeriği tek bir gövde dizgisiyle temsil edilir; özeti kısaltılmış
  sekiz onaltılık basamaktır ve iki özetin eşitliği içeriğin eşitliği olarak okunur.
- **KY24** — Kurulum, kilit dosyasındaki üç sürümün kayıttaki özetlerini okumaktır. Kurulumun
  başka hiçbir yan etkisi modellenmez.
- **KY25** — `t1` ve `t2`, aynı kilit dosyasıyla yapılan iki ayrı kurulumdur. Aradaki tek
  fark, kayda bu sürede gelen yayım girişimleridir.
- **KY26** — Özet yazan kilit, kurulum anında beklenen ve gelen özeti karşılaştırır;
  uyuşmazlık bulursa kurulum **durur**. Uyuşmazlığı düzeltmeye çalışmaz.
- **KY27** — Geri çekme, sürümü **çözümlemeye kapatır** ama kayıttaki içeriğine dokunmaz;
  kilit dosyası sürümü doğrudan istediği için çözümlemeyi hiç çalıştırmaz.
- **KY28** — Süre ölçülmez, gerçek kayda gönderim yapılmaz. Sayılan şey **kabul**, **red**,
  **uyuşmazlık** ve **ayrı kurulum** sayısıdır.

#### Ölçüm

```python
"""Yayimlama: ayni ad iki ayri icerigi tasiyabilir mi."""

import hashlib

KILIT = {"olcum": (1, 2), "ortak": (3, 2), "rapor": (1, 1)}
DEPO = {"rapor": [(0, 9), (1, 0), (1, 1)]}
BILDIRIM = {"rapor": ((1, 0), (2, 0))}
GERI_CEKILEN = {("rapor", (1, 1))}

GIRISIMLER = [
    ("rapor", (1, 0), "rapor-govde-0"),
    ("olcum", (1, 2), "olcum-govde-1"),
    ("ortak", (3, 2), "ortak-govde-1"),
    ("rapor", (1, 1), "rapor-govde-1"),
    ("olcum", (1, 2), "olcum-govde-1"),
    ("rapor", (1, 1), "rapor-govde-2"),
    ("olcum", (1, 2), "olcum-govde-2"),
    ("ortak", (3, 2), "ortak-govde-1"),
    ("olcum", (2, 0), "olcum-govde-3"),
]
T1 = 5
ILKELER = (("üzerine yazılabilir", False), ("değiştirilemez", True))


def ozet(metin):
    return hashlib.sha256(metin.encode()).hexdigest()[:8]


def yayimla(girisimler, degistirilemez):
    """Paket kaydi modeli: (ad, surum) -> dagitim ozeti."""
    kayit, kabul, red, cakisma = {}, 0, 0, []
    for paket, surum, govde in girisimler:
        anahtar, o = (paket, surum), ozet(govde)
        varsa = kayit.get(anahtar)
        if varsa is not None and varsa != o:
            cakisma.append(f"{paket} {surum[0]}.{surum[1]}")
            if degistirilemez:
                red += 1
                continue
        kayit[anahtar] = o
        kabul += 1
    return kayit, kabul, red, cakisma


def kurulum(kayit):
    """Kilit dosyasindaki surumler kayittan okunur."""
    return {p: kayit[(p, s)] for p, s in KILIT.items()}


print(f"{'ilke':<20s} {'kabul':>5s} {'red':>4s} {'t1 kurulum':>28s}"
      f" {'t2 kurulum':>28s} {'ayrı':>5s}")
for ad, kural in ILKELER:
    b = kurulum(yayimla(GIRISIMLER[:T1], kural)[0])
    kayit, kabul, red, cakisma = yayimla(GIRISIMLER, kural)
    g = kurulum(kayit)
    d1 = " ".join(b[p] for p in sorted(KILIT))
    d2 = " ".join(g[p] for p in sorted(KILIT))
    print(f"{ad:<20s} {kabul:5d} {red:4d} {d1:>28s} {d2:>28s}"
          f" {len({d1, d2}):5d}")
print(f"çakışan gönderim: {', '.join(yayimla(GIRISIMLER, True)[3])}")

print()
print(f"{'kilit biçimi':<15s} {'ilke':<20s} {'uyuşmazlık':>10s} "
      f"{'yakalanan':>9s} {'kurulum':>9s} {'ayrı içerik':>12s}")
for kb, ozetli in (("yalnız sürüm", False), ("sürüm ve özet", True)):
    for ad, kural in ILKELER:
        b = kurulum(yayimla(GIRISIMLER[:T1], kural)[0])
        g = kurulum(yayimla(GIRISIMLER, kural)[0])
        uyusmaz = sum(1 for p in KILIT if b[p] != g[p])
        yakalanan = uyusmaz if ozetli else 0
        durdu = yakalanan > 0
        kurulan = ({tuple(sorted(b.items()))} if durdu
                   else {tuple(sorted(b.items())), tuple(sorted(g.items()))})
        print(f"{kb:<15s} {ad:<20s} {uyusmaz:10d} {yakalanan:9d} "
              f"{'duruyor' if durdu else 'sürüyor':>9s} {len(kurulan):12d}")

print()


def coz(paket, geri_cekmeyi_uygula):
    alt, ust = BILDIRIM[paket]
    return [s for s in DEPO[paket] if alt <= s < ust
            and not (geri_cekmeyi_uygula and (paket, s) in GERI_CEKILEN)][-1]


kayit = yayimla(GIRISIMLER, True)[0]
print(f"{'kurulum yolu':<18s} {'geri çekmeden önce':>20s} {'sonra':>20s} "
      f"{'ayrı':>5s}")
for ad, secici in (("çözümleme ile", lambda gc: coz("rapor", gc)),
                   ("kilit dosyasıyla", lambda gc: KILIT["rapor"])):
    o = [f"{'.'.join(map(str, secici(gc)))}·{kayit[('rapor', secici(gc))]}"
         for gc in (False, True)]
    print(f"{ad:<18s} {o[0]:>20s} {o[1]:>20s} {len(set(o)):5d}")
```

```
ilke                 kabul  red                   t1 kurulum                   t2 kurulum  ayrı
üzerine yazılabilir      9    0   3d3f4bcd 69832227 acf4bf32   d032978b 69832227 3c8049dd     2
değiştirilemez           7    2   3d3f4bcd 69832227 acf4bf32   3d3f4bcd 69832227 acf4bf32     1
çakışan gönderim: rapor 1.1, olcum 1.2

kilit biçimi    ilke                 uyuşmazlık yakalanan   kurulum  ayrı içerik
yalnız sürüm    üzerine yazılabilir           2         0   sürüyor            2
yalnız sürüm    değiştirilemez                0         0   sürüyor            1
sürüm ve özet   üzerine yazılabilir           2         2   duruyor            1
sürüm ve özet   değiştirilemez                0         0   sürüyor            1

kurulum yolu         geri çekmeden önce                sonra  ayrı
çözümleme ile              1.1·acf4bf32         1.0·660f7f84     2
kilit dosyasıyla           1.1·acf4bf32         1.1·acf4bf32     1
```

#### Dokuz Girişim, İki Red

Üst tablo iki ilkeyi aynı akış üzerinde karşılaştırıyor.

**Üzerine yazılabilir** kayıt dokuz girişimin dokuzunu da kabul ediyor: **9 kabul, 0 red**.
Hiçbir gönderim başarısız olmuyor, hiçbir uyarı çıkmıyor. Buna karşın kaydın içeriği
değişiyor ve bu değişim `t1` ile `t2` sütunlarında görünüyor: `olcum 1.2` özeti `3d3f4bcd`
iken `d032978b` oluyor, `rapor 1.1` özeti `acf4bf32` iken `3c8049dd` oluyor. Aynı kilit
dosyası **2 ayrı kurulum** veriyor.

**Değiştirilemez** kayıt aynı akışta **7 kabul, 2 red** üretiyor. Reddedilen ikisi
`rapor 1.1` ve `olcum 1.2`; ikisi de daha önce başka bir gövdeyle yayımlanmış çiftler. Aynı
gövdenin yeniden gönderildiği iki girişim ise kabul ediliyor — beşinci ve sekizinci
girişimler kaydı değiştirmedikleri için reddedilecek bir şey yok. `t1` ve `t2` sütunları
birebir aynı: **1 ayrı kurulum**.

Sayının okunuşu şudur: **9 kabul, 0 red** görüntüsü bir sağlık göstergesi değildir. İki
kayıt da aynı dokuz girişimi aldı; biri iki çakışmayı **sesli**, öbürü **sessiz** işledi.
Sessiz olanın raporu daha temiz görünüyor ve tam olarak bu yüzden daha az bilgi taşıyor.

Değiştirilemezliğin verdiği güvence tek cümleyle yazılabilir: **bir ad ve sürüm çifti,
ömrü boyunca en fazla bir içeriği adlandırır.** Bu güvence olmadan sürüm numarası bir
tanımlayıcı değil, yalnızca bir etikettir; aynı etiket iki ayrı kutuya yapıştırılabilir.

Aynı gövdenin iki kez kabul edilmesi bu güvenceyle çelişmez ve pratikte gereklidir. Bir
gönderim yarıda kesilebilir, sonucu gönderen tarafa ulaşmayabilir; gönderen o zaman aynı
gövdeyi yeniden gönderir. Değiştirilemez kayıt bunu reddetseydi, gönderen tarafın
gönderimin gerçekleşip gerçekleşmediğini önce sorması gerekirdi. Kural içeriğe bakarak
kurulduğu için ikinci gönderim kaydı değiştirmez ve reddedilecek bir şey doğmaz —
ölçümdeki **7 kabul** sayısının içinde bu iki gönderim de vardır. Yasaklanan işlem
"yeniden göndermek" değil, **kaydı değiştirmek**tir.

#### Sürüm Numarasının Taşıdığı İki Söz

Bir sürüm numarası iki ayrı söz verir ve ikisi sık sık tek bir söz sanılır.

**Birinci söz uyumlulukla ilgilidir:** numaranın hangi bölümünün arttığı, değişikliğin
geriye dönük uyumlu olup olmadığını bildirir. Bu söz **Sürüm Kontrolüne Giriş** kursunun
etiketleme dersinde kuruldu ve burada tekrarlanmaz.

**İkinci söz kimlikle ilgilidir:** numara **tek bir içeriği** adlandırır. Bu ders yalnız
ikinci sözü ölçer ve ölçüm gösteriyor ki bu söz numaranın kendisinden gelmez; kaydın
ilkesinden gelir. Üzerine yazılabilir bir kayıtta numara birinci sözü verebilir ama ikincisini
veremez.

İkisinin bağımsız olduğu, ölçümün üst tablosunda doğrudan görülüyor. Reddedilen iki gönderim
de mevcut bir çifte ayrı gövde göndermeye çalışıyor; ikisinin de uyumluluk sözüyle ilgisi
yok. Gövde tümüyle uyumlu bir düzeltme bile olsa red aynı olurdu, çünkü denetlenen şey
uyumluluk değil **kimliktir**.

Reddin yayımlayan tarafa ödettiği bedel de bellidir: değişiklik yayımlanacaksa **yeni bir
sürüm numarası açılmak zorundadır**. Ölçümün dokuzuncu girişimi tam olarak bunu yapıyor ve
`olcum 2.0` olarak kabul ediliyor. Değiştirilemezlik bir yasak değil, bir yönlendirmedir:
düzeltmeyi engellemez, düzeltmenin **adlandırılmasını** zorunlu kılar. Bunun karşılığında
kurulan taraf, elindeki numaranın anlamının zamanla değişmediğini bilir.

#### Kilidin Yazdığı Şey

Orta tablo, kurulan tarafın kendini bu duruma karşı ne kadar koruyabildiğini ölçüyor.

**Yalnız sürüm yazan kilit**, üzerine yazılabilir kayıtta **2 uyuşmazlık** karşısında **0**
yakalıyor. Kurulum sürüyor ve **2 ayrı içerik** kuruluyor. Kilit dosyası burada işini
yapmış sayılır — istenen sürümler kuruldu — ama kurduğu şey iki ayrı koşumda iki ayrı
şeydi. Kilit çözümlemeyi tekleştirdi, **içeriği tekleştirmedi.**

**Sürüm ve özet yazan kilit**, aynı kayıtta **2 uyuşmazlığın 2'sini** yakalıyor ve kurulum
**duruyor**. Kurulan ayrı içerik sayısı **1**'e iniyor, çünkü ikinci kurulum hiç
tamamlanmıyor. Burada kazanılan şey bir kurulumun başarısı değil, **başarısızlığın
görünürlüğüdür**: kayıt sessiz kaldığında kilit sesli olur.

Alt iki satır ilkenin doğru kurulduğu durumu gösteriyor: değiştirilemez kayıtta uyuşmazlık
**0**, yakalanan **0**, kurulum sürüyor, ayrı içerik **1**. Özet yazmanın burada bir
maliyeti yok ve bir kazancı da görünmüyor — çünkü yakalayacak bir şey yok.

Buradan iki ayrı güvencenin bağımsız olduğu çıkar. Değiştirilemezlik **kaydın** verdiği bir
güvencedir ve kurulan taraf onu doğrulayamaz. Özet ise **kurulan tarafın** kendi
doğrulamasıdır ve kaydın ilkesinden bağımsız çalışır. İkisi birlikte kurulduğunda kurulan
taraf kayda güvenmek zorunda kalmaz; yalnız birincisi varsa güvenmek zorundadır, yalnız
ikincisi varsa güvenmez ama sık sık durur.

#### Geri Çekme Silme Değildir

Alt tablo üçüncü bir işlemi ölçüyor. Bir sürüm yayımlandıktan sonra kullanılmaması
gerektiği anlaşılabilir. **Geri çekme**, sürümü çözümlemeye kapatır ama kayıttaki içeriğine
dokunmaz.

Ölçüm ikisini ayırıyor. **Çözümleme ile** kurulan taraf, geri çekmeden önce `rapor 1.1`
sürümünü ve `acf4bf32` özetini alıyor; sonra `rapor 1.0` sürümünü ve `660f7f84` özetini
alıyor. **2 ayrı sürüm.** **Kilit dosyasıyla** kurulan taraf ise iki durumda da
`1.1·acf4bf32` alıyor: **1**.

Fark, kilit dosyasının çözümlemeyi hiç çalıştırmamasından gelir. Geri çekme çözümlemenin
aday listesini değiştirir; kilit dosyası aday listesi kullanmaz, doğrudan sürümü ister ve
kayıt onu vermeye devam eder. Bu, kilidin hem gücü hem sınırıdır: geri çekilmiş bir sürümü
kullanmayı **sürdürür** ve bunu sessizce yapar.

Silme ayrı bir işlemdir ve ayrı bir sonuç verir: silinmiş bir sürümde kilit dosyasıyla
kurulum da başarısız olur. Üç işlem — üzerine yazma, geri çekme, silme — üç ayrı sonuç
üretir ve yalnız ikisi kurulan tarafa haber verir.

Üçünü yan yana koymak, kaydın kurulan tarafa ne borçlu olduğunu tanımlar. **Üzerine yazma**
içeriği değiştirir ve hiçbir şey söylemez; kurulan taraf ancak kendi tuttuğu özetle fark
eder. **Geri çekme** içeriği değiştirmez ve yalnız yeni çözümlemelere görünür; kilitli
kurulum etkilenmez. **Silme** içeriği ortadan kaldırır ve her iki yolu da durdurur.
Sıralama bir sertlik sırası değil, bir **görünürlük** sırasıdır: en sessiz işlem en çok
zarar veren işlemdir, çünkü ötekilerin ikisinde de kurulan taraf bir yanıt alır.

Geri çekmenin sessiz kaldığı yerin de bir adı vardır. Kilit dosyasını yenilemeyen bir proje
geri çekilmiş sürümü kullanmayı sürdürür ve bunu bir hata iletisi olmadan yapar; geri
çekme kararı ancak kilit yenilendiğinde o projeye ulaşır. Kilit, çözümlemenin sonucunu
donduran bir karardır — ve dondurduğu şeye sonradan verilen kararları da dışarıda bırakır.

#### Özet

- Paket kaydı `(ad, sürüm)` çiftinden bir dağıtıma giden eşlemedir; üzerine yazılabilir ve
  değiştirilemez olmak üzere iki ilkeyle kurulabilir.
- Aynı dokuz girişim ilk kayıtta **9 kabul, 0 red**, ikincisinde **7 kabul, 2 red** verir;
  değiştirilemezlik yeniden gönderimi değil, **ayrı içerikle** yeniden gönderimi yasaklar.
- Aynı kilit dosyası üzerine yazılabilir kayıtta **2 ayrı kurulum**, değiştirilemez kayıtta
  **1** verir; sürüm numarası tek başına içeriği belirlemez.
- Yalnız sürüm yazan kilit **2 uyuşmazlığın 0'ını**, özet de yazan kilit **2'sini** yakalar
  ve kurulumu durdurur; kaydın ilkesi ile kurulan tarafın doğrulaması bağımsız güvencelerdir.
- Geri çekme çözümlemeyi etkiler, kilidi etkilemez: çözümleme ile kurulan taraf **2 ayrı
  sürüm**, kilitle kurulan taraf **1** görür.

#### Sonraki Adım

Yayımlanmış paket kurulan tarafa kodu taşır. Kodun **nasıl kullanılacağını** taşımaz. O
bilgi belgeden gelir ve belge iki yoldan üretilebilir: elle yazılarak ya da kaynaktan
üretilerek. İki yol arasındaki fark bu kursun bütün derslerinde sorulan soruyla ölçülebilir.
Son ders bunu sorar: kaynaktan belge üretimi hangi sonucu tekleştirir, hangisini
tekleştirmez ve tekleştirmediği yerde neyin yerini alamaz?

### Belgelendirme

Source: https://academia.sh/tr/kurslar/python-projeleri/belgelendirme

Dört kaynak sürümünden üretilen belge 2 ayrı sonuç verir ve bayat imza sayısı dördünde de 0 kalırken elle yazılan belgede 2 ile 3 arasında durur; üretim imzayı tekleştirir, özet metnindeki iddiayı tekleştirmez ve o iddiayı ancak çalıştırılabilir örnek yakalar.

Önceki ders yayımlanmış bir sürümün içeriğini adına bağladı: değiştirilemez bir kayıtta
`(ad, sürüm)` çifti tek bir dağıtımı adlandırır. Paket kurulan tarafa kodu taşır.

Kodun **nasıl kullanılacağını** taşımaz. O bilgi belgeden gelir ve belge iki yoldan
üretilebilir: elle yazılarak ya da kaynaktan üretilerek. Bu kursun son dersi ikisini aynı
soruyla ölçer: **üretim hangi sonucu tekleştirir, hangisini tekleştirmez?**

#### Üretimin Girdisi ve Çıktısı

Kaynaktan belge üretiminin yaklaşımları **Teknik Yazarlık ve Dokümantasyon** kursunda
kuruldu; bayatlama ve denetim de orada ele alındı. Burada o tartışma tekrarlanmaz. Ölçülen
tek şey, üretimin **kaç ayrı sonucu bire indirdiğidir**.

Bu kurs araç adı yazmaz. **Belge üreticisi** burada tek bir işlevle **modellenir**: kaynağı
ayrıştırır, adı alt çizgiyle başlamayan işlevleri seçer, her biri için imzayı ve özet
metninin ilk satırını okur, sonucu ada göre sıralar. Gerçek bir üretici çok daha fazlasını
yapar — bağlantı kurar, tip bilgisini işler, sayfa düzeni üretir; modelin tuttuğu tek
özellik, **çıktısının tümüyle kaynaktan türetilmiş olmasıdır**.

Ölçüm dört kaynak sürümü tanımlar: kanonik yazım, işlev tanımlarının sırası değişmiş yazım,
özel bir yardımcı işlevin eklendiği yazım ve iki imzanın değiştiği yazım. Bunların yanında
bir de **elle yazılmış belge** durur; eski imzalarla yazılmıştır ve o günden beri elle
güncellenmemiştir.

Ölçümün varsayımları:

- **KY29** — Üç işlev ortak kurgudan alınır; `indirim` işlevinin gövdesi değiştirilmez ve
  kusurlu tavan yerinde durur.
- **KY30** — `indirim` işlevinin özet metni tavanın yüzde 25 olduğunu **yazar**; gövde ise
  20 uygular. İddia ile davranış arasındaki bu ayrım kurgunun kendisidir.
- **KY31** — Belge üreticisi yalnız kaynaktan okur ve hiçbir iddiayı davranışla
  karşılaştırmaz; yaptığı denetim sayısı **0**'dır.
- **KY32** — Elle yazılan belge dört kaynak sürümünde de aynıdır; kaynağı hiç izlemez.
- **KY33** — Çalıştırılabilir örneklerin koşumu standart kitaplığın belge testi
  düzeneğiyle yapılır. Belge testinin ne olduğu bu kursun belge testleri dersinde kuruldu;
  burada bir **denetim aracı** olarak kullanılır, yeniden kurulmaz.
- **KY34** — Süre ölçülmez, dosya yazılmaz. Sayılan şey **ayrı belge**, **bayat imza** ve
  **düşen örnek** sayısıdır.

#### Ölçüm

```python
"""Belgelendirme: uretim neyi tekleştirir, neyi tekleştirmez."""

import ast
import doctest
import hashlib
import types

GOVDE = {
    "indirim": (
        "(tutar, uye, kupon)",
        '"""Uye ve kupon indirimini uygular; tavan oran yuzde 25\'tir.\n\n'
        "    >>> indirim(100, True, True)\n    75\n    \"\"\"\n"
        "    oran = 0\n    if uye:\n        oran += 10\n"
        "    if kupon:\n        oran += 15\n"
        "    if oran > 20:\n        oran = 20\n"
        "    return tutar - tutar * oran // 100\n"),
    "uygun": (
        "(paket)",
        '"""Bildirimin araligina giren surumleri verir.\n\n'
        '    >>> uygun("rapor")\n    [(1, 0), (1, 1)]\n    """\n'
        "    alt, ust = BILDIRIM[paket]\n"
        "    return [s for s in DEPO[paket] if alt <= s < ust]\n"),
    "kilit": (
        "(secim)",
        '"""Cozumlemenin sonucunu sabitler.\n\n'
        "    >>> kilit({\"rapor\": (1, 1)})\n    {'rapor': (1, 1)}\n    \"\"\"\n"
        "    return dict(secim)\n"),
}
BASLIK = ('DEPO = {"rapor": [(0, 9), (1, 0), (1, 1)]}\n'
          'BILDIRIM = {"rapor": ((1, 0), (2, 0))}\n\n\n')
YARDIMCI = 'def _normalle(s):\n    """Ic kullanim."""\n    return tuple(s)\n\n\n'
YENI_IMZA = {"indirim": "(tutar, uye, kupon, tavan)", "kilit": "(secim, dogrula)"}


def kur(adlar, yardimci=False, imza_degisti=False):
    p = BASLIK + (YARDIMCI if yardimci else "")
    for ad in adlar:
        im, govde = GOVDE[ad]
        if imza_degisti and ad in YENI_IMZA:
            im = YENI_IMZA[ad]
        p += f"def {ad}{im}:\n    {govde}\n\n"
    return p


SURUMLER = {
    "kanonik": kur(["indirim", "uygun", "kilit"]),
    "sırası değişik": kur(["kilit", "uygun", "indirim"]),
    "özel yardımcı eklenmiş": kur(["indirim", "uygun", "kilit"], yardimci=True),
    "imzası değişmiş": kur(["indirim", "uygun", "kilit"], imza_degisti=True),
}
ELLE = {"indirim": "indirim(tutar, uye)", "uygun": "uygun(paket, bildirim)",
        "kilit": "kilit(secim)"}


def uret(kaynak):
    """Belge ureticisi modeli: genel adlarin imzasi ve ozeti kaynaktan okunur."""
    kayit = {}
    for d in ast.parse(kaynak).body:
        if isinstance(d, ast.FunctionDef) and not d.name.startswith("_"):
            im = ", ".join(a.arg for a in d.args.args)
            ilk = (ast.get_docstring(d) or "").split("\n")[0]
            kayit[d.name] = f"{d.name}({im}) — {ilk}"
    return "\n".join(kayit[a] for a in sorted(kayit))


def imza(kaynak, ad):
    for d in ast.parse(kaynak).body:
        if isinstance(d, ast.FunctionDef) and d.name == ad:
            return f"{ad}({', '.join(a.arg for a in d.args.args)})"
    return ""


def belgedeki_imza(belge, ad):
    for s in belge.splitlines():
        if s.startswith(ad + "("):
            return s.split(" — ")[0]
    return ""


def ozet(metin):
    return hashlib.sha256(metin.encode()).hexdigest()[:8]


print(f"{'kaynak sürümü':<24s} {'genel ad':>8s} {'belge özeti':>12s} "
      f"{'üretilende bayat':>16s} {'elle yazılanda bayat':>21s}")
belgeler = []
for ad, k in SURUMLER.items():
    b = uret(k)
    belgeler.append(ozet(b))
    bu = sum(1 for a in ELLE if belgedeki_imza(b, a) != imza(k, a))
    be = sum(1 for a in ELLE if ELLE[a] != imza(k, a))
    print(f"{ad:<24s} {len(b.splitlines()):8d} {ozet(b):>12s} "
          f"{bu:>13d}/{len(ELLE)} {be:>18d}/{len(ELLE)}")
print(f"dört kaynak sürümü, {len(set(belgeler))} ayrı üretilmiş belge; "
      f"elle yazılan belge dördünde de aynı")

print()
modul = types.ModuleType("olcum")
exec(compile(SURUMLER["kanonik"], "<olcum>", "exec"), modul.__dict__)
denenen, dusen = 0, []
for t in doctest.DocTestFinder().find(modul):
    kosucu = doctest.DocTestRunner(verbose=False)
    kosucu.run(t, out=lambda s: None)
    denenen += kosucu.tries
    if kosucu.failures:
        dusen.append(t.name.split(".")[-1])
print(f"belge testi {denenen} örnek denedi, {len(dusen)} tanesi düştü: "
      f"{', '.join(dusen)}")
print("belge üreticisinin iddia için yaptığı denetim: 0")
```

```
kaynak sürümü            genel ad  belge özeti üretilende bayat  elle yazılanda bayat
kanonik                         3     0aab9512             0/3                  2/3
sırası değişik                  3     0aab9512             0/3                  2/3
özel yardımcı eklenmiş          3     0aab9512             0/3                  2/3
imzası değişmiş                 3     65a37bdf             0/3                  3/3
dört kaynak sürümü, 2 ayrı üretilmiş belge; elle yazılan belge dördünde de aynı

belge testi 3 örnek denedi, 1 tanesi düştü: indirim
belge üreticisinin iddia için yaptığı denetim: 0
```

#### Tekleşen: İmza

Tablonun ilk üç satırı aynı belge özetini veriyor: `0aab9512`. Üç kaynak sürümü ayrı
metinlerdir — biri işlev tanımlarını başka sırada yazar, biri özel bir yardımcı işlev
taşır — ama ürettikleri belge tektir. Üretici çıktısını ada göre sıraladığı için tanım
sırası düşer; alt çizgiyle başlayan adı atladığı için özel yardımcı düşer.

Dördüncü satır ayrı bir özet veriyor: `65a37bdf`. İki imza değişti, belge değişti. Bu bir
kusur değil, üretimin tanımıdır: **belge kaynağın genel arayüzünü izler ve yalnız onu
izler.**

Bayat imza sütunları ayrımı sayıyla veriyor. Üretilen belgede bayat imza dört sürümde de
**0/3**. Elle yazılan belgede ilk üç sürümde **2/3**, dördüncüde **3/3** — üstelik elle
yazılan belge hiç değişmedi. Kaynak ilerledikçe bayatlık kendiliğinden büyüyor ve bunu
haber veren hiçbir şey yok.

Aradaki fark bir emek farkı değil, bir **bağ** farkıdır. Üretilen belgede imza ile kaynak
arasında bir kopya yoktur; imza her üretimde yeniden okunur. Elle yazılan belgede kopya
vardır ve kopyanın aslıyla eşit kalması bir insanın hatırlamasına bağlıdır. Üretimin
tekleştirdiği sonuç tam olarak budur: **aynı kaynaktan tek belge ve o belgede sıfır bayat
imza.**

#### Tekleşmeyen: İddia

Alt satırlar ölçümün ikinci yarısını veriyor ve tekleşmenin nerede durduğunu gösteriyor.

`indirim` işlevinin özet metni tavanın yüzde 25 olduğunu yazıyor; gövde 20 uyguluyor.
Üretici bu satırı belgeye **olduğu gibi** taşıyor. Yaptığı denetim sayısı **0**, çünkü
üretici bir kopyalayıcıdır: kaynakta ne yazıyorsa belgede o yazar. Kaynağın kendisi yanlış
bir şey söylüyorsa, üretim o yanlışı **daha da yaygınlaştırır**.

Belge testi ise **3** örnek deniyor ve **1** tanesi düşüyor: `indirim`. Düşmesinin nedeni
örneğin yanlış olması değil, örneğin **çalıştırılabilir** olmasıdır. Aynı iddia düzyazı
olarak yazılsaydı hiçbir şey düşmezdi.

Buradan dersin sonucu çıkar. Üretim **iki sonucu** tekleştirir: belgenin kaynakla aynı
arayüzü göstermesini ve aynı kaynaktan aynı belgenin çıkmasını. **Bir sonucu
tekleştirmez:** belgedeki iddianın davranışla uyuşmasını. O uyuşma ancak iddia
çalıştırılabilir bir biçimde yazıldığında sınanır ve sınandığında da denetleyen şey belge
üreticisi değil, testtir.

Bu, kursun kapsam ölçümüyle aynı biçimdedir. Kapsam kodun nerede yürüdüğünü sayar, testin
neye baktığını değil. Üretim belgenin neyi gösterdiğini belirler, doğru olup olmadığını
değil. **Her araç kendi girdisine bakar; kâhini olan tek şey beklentidir.**

#### Özet

- Belge üreticisi çıktısını tümüyle kaynaktan türetir; tanım sırası ve özel adlar sonuca
  girmez.
- Dört kaynak sürümü **2 ayrı üretilmiş belge** verir; ilk üçü tek bir özette birleşir,
  dördüncüsü imza değiştiği için ayrılır.
- Üretilen belgede bayat imza dört sürümde de **0/3**; elle yazılan belgede **2/3** ve
  **3/3**, üstelik belge hiç değişmeden.
- Üretim iddiayı tekleştirmez: `indirim` işlevinin özet metnindeki yüzde 25 iddiası
  belgeye olduğu gibi taşınır ve üreticinin yaptığı denetim **0**'dır.
- Aynı iddia çalıştırılabilir örnek olarak yazıldığında **3** örnekten **1**'i düşer;
  iddiayı sınayan şey üretici değil, testtir.

#### Kurs Kapanışı

Bu kurs on dört ders boyunca tek bir soru sordu: **aynı girdi kaç ayrı sonuç verdi?** Soru
hiçbir derste "çalışıyor mu" biçimine dönüşmedi, çünkü çalışan bir şeyin kaç ayrı biçimde
çalıştığı ayrı bir sorudur ve yeniden üretilebilirlik o soruyla ölçülür.

| ders | ölçülen | ayrı sonuç sayısı |
|---|---|---|
| Sanal Ortamlar | altı kurulum sırası, paylaşılan ve yalıtılmış site | paylaşılanda **5**, yalıtılmışta **1** |
| Bağımlılık Bildirimi | aynı bildirim, iki strateji ve iki depo görüntüsü | **2**, kilitle **1**; dört kurulumda **3**, kilitle **1** |
| Paket Yöneticileri | dört çözümleme stratejisi, gevşek ve sıkı kısıt | **4** küme (**3** tutarlı); sıkıda **2** küme (**0** tutarlı); kilitle **1** |
| Sürüm Yönetimi | aynı bildirim üç kurgu yorumlayıcıda | **2**; dokuz kurulumda **6** kabul, **3** ret |
| Test Çerçeveleri | iki yazım biçimi, üç keşif kuralı | yazımda **1**; bulunan testte **3**, raporda **1** |
| Düzenek ve Parametreleme | aynı beş testlik takım üç sırada | **2**, düzenekle **1** |
| Sahte Nesneler | aynı test üç kipte, sahte ve gerçek kaynak | gerçekle **3**, sahteyle **1**; sapmış sahteyle **2** |
| Belge Testleri | on iki iddianın biçim değişikliğiyle bayatlaması | sese dönen bayat **4** ve **6**: **2** |
| Kapsam Ölçümü | üç testlik kümenin kapsamı ve kâhinden sapan çağrı | satır ve dal **1,0000**, geçen **3/3**; sapan çağrı **1**; dördüncü testle kapsam **1**, geçen oranı **2** |
| Biçimlendirici ve Tüy Toplayıcılar | aynı işlevin altı ayrı yazımı | metin **6** → **1**; bulgu kümesi **5** → **1** |
| Çoklu Ortam Testi | aynı takım üç eksenli matriste, 18 koşum | **4**; kilitle **3**, düzenekle **2** |
| Paketleme | aynı kaynaktan dokuz üretim, üç dahil etme kuralı | **5**; açık listeyle **1**; kurulumda **3** ve **1** |
| Yayımlama | aynı kilit dosyasıyla iki kurulum, iki kayıt ilkesi | **2**, değiştirilemez kayıtta **1** |
| Belgelendirme | dört kaynak sürümünden üretilen belge | **2**; bayat imza **0**'a karşı **2** ve **3** |

Tablonun okunuşu tek cümlede toplanır: **hemen her satırda ilk sayı birden büyük, ikinci
sayı bir.** Aradaki fark bir araç farkı değil, bir **karar** farkıdır. Kilit dosyası
çözümlemeyi ortadan kaldırmadı; kararı bir kez verdirip yazıya geçirdi. Yalıtım kurulumu
kolaylaştırmadı; kurulum sırasının sonucu değiştirmesini durdurdu. Düzenek testi
hızlandırmadı; sıranın kararı belirlemesini durdurdu. Açık liste dosya seçmedi; seçimin
dizinin durumuna bağlı olmasını durdurdu. Her durumda tekleşen şey işin kendisi değil,
**kararın kaynağıydı**.

İkinci bir örüntü de tabloda duruyor: **tekleşen sayı doğruluk vermez.** Kapsam bire
indirdiği yerde bile kusurlu beklentiyi görmedi; biçimlendirici altı yazımı bire indirdi ama
kalan altı bulgunun hiçbiri kusuru bildirmedi; belge üretimi belgeyi tekleştirdi ama
belgedeki yanlış iddiayı taşıdı. Tekleşme **karşılaştırılabilirliğin** koşuludur, doğruluğun
değil. Doğruluğu veren tek şey kâhindir ve kâhin her zaman ölçümün dışından gelir.

| kurs | ölçü ekseni |
|---|---|
| Python Temelleri | sözdizimin çağırdığı protokol |
| Veri Yapıları ve Fonksiyonel Araçlar | var edilen, tutulan ve paylaşılan nesne |
| Nesneye Dayalı Python ve Tipler | çağrıyı kim yanıtladı, sözü kim denetledi |
| Eşzamanlılık ve Başarım | örtüşen adım |
| Python Projeleri: Paketleme ve Test | aynı girdi kaç ayrı sonuç verdi |

Beş eksen bir dizi oluşturuyor. İlk kurs sözdizimin altına bakıp her biçimin hangi özel
yöntemi çağırdığını saydı. İkincisi nesnenin nasıl var edildiğini, nerede tutulduğunu ve
kimlerle paylaşıldığını ölçtü. Üçüncüsü çağrının hangi sınıfta yanıtlandığını ve verilen
sözün ne zaman denetlendiğini ayırdı. Dördüncüsü işin kaç adımının örtüştüğünü saydı ve
süre yerine adım saymanın gerekçesini kurdu. Beşincisi bunların hepsini bir projeye koydu ve
aynı girdinin kaç ayrı sonuç verdiğini sordu.

Beşi birlikte **bir dilin kendi düzeneğini** kuruyor: dil neyi çağırıyor, ne tutuyor, neyi
denetliyor, işi nasıl bölüyor ve bir proje olarak nasıl tekrarlanabilir hale geliyor. Bu
düzenek dile özgüdür ve başka bir dile olduğu gibi taşınmaz.

Taşınan şey **sorulardır**. M08 ile başlayan dil müfredatları aynı beş soruyu başka bir
dile sorar ve yanıtları başka çıkar: bir dilde özel yöntemle verilen yanıt öbüründe
arayüzle, bir dilde çalışma zamanında denetlenen söz öbüründe derleme anında, bir dilde
yorumlayıcı kilidiyle sınırlanan örtüşme öbüründe başka bir düzenekle karşılanır. **Bir
dilin kendi düzeneği kuruldu; sıra başka bir dilin aynı soruları nasıl yanıtladığında.**

