---
title: 'Test Edilebilir Dönüşümler'
source: 'https://academia.sh/tr/kurslar/veri-hatlari/test-edilebilir-donusumler'
course: 'Veri Hatları ve Orkestrasyon'
language: tr
updated: '2026-08-17T18:08:50+00:00'
license: 'CC BY-SA 4.0'
---

# Test Edilebilir Dönüşümler

Dönüşümün kendi çıktısına konan beklentilerin yakaladığının ve yakalamadığının sayılması: altı beklenti sağlam hatta hiç ateşlemiyor ve altı bozulma sınıfından dördünü yakalıyor, yinelenme ile tür kayması dönüşümün kendisi tarafından yutulduğu için çıktıda hiç görünmüyor. Birim değişimi çıktıya konan beklentiyle üçüncü aşamada yakalanıyor ve bunun için hiçbir taban çizgisi gerekmiyor, oysa sunum birebir aynı kalıyor. Dönem etiketini bir dönem geriye kaydıran kural hatası 2200 satırı, 55 hücreyi ve beş sunum değerini birebir aynı bıraktığı için altı beklentinin hiçbirine takılmıyor; onu yalnız sabit girdi üzerindeki birim testi yakalıyor.

Üç derste dönüşümün **yeri** yazıldı: kaynakta mı hedefte mi yapılacağı, hangi sırayla koşacağı ve
hangi katmanda duracağı. Üç sorunun da yanıtı bir düzenekti ve üçü de aynı şeyi varsaydı — kuralın
kendisi doğru, veri de düzgün geliyor. Hiçbir aşamada dönüşümün **doğru sonucu ürettiği**
sınanmadı.

Bu ders o soruyu sorar ve iki ayrı yanıt aracını yan yana koyar: elle yazılmış **sabit bir girdi**
üzerinde koşan birim testi ile koşumun **gerçek çıktısına** konan beklenti. İkisinin yakaladıkları
farklıdır, ve daha önemlisi, ikisinin birden göremediği bir kalıntı vardır.

- **DN41.** Kaynak, beş aşamalı hat ve bozulma sınıfları **kurgudur**; gerçek bir depo, dönüştürme
  çerçevesi ya da veri testi kitaplığı çağrılmaz. Tohum **20260218**, çıktı koşumdan bağımsızdır.
- **DN42.** Bu derste **dönüşüm**, ilk üç aşamanın bileşkesidir: `ayikla`, `tekille` ve dönem
  farkından tüketim. Girdisi ham satır, çıktısı tüketim satırıdır.
- **DN43.** **Beklenti (expectation)** çıktının taşıması gereken bir değişmezi doğru ya da yanlış
  olarak döndüren yordamdır. Burada `True` **bozuldu** demektir.
- **DN44.** Birim testi yordamı Yazılım Kalitesi ve Test müfredatındaki Birim Testi ve Test Odaklı
  Geliştirme kursunda, kalite boyutlarının denetime çevrilmesi Veri Mühendisliğine Giriş kursunda
  ölçüldü ve **tekrarlanmaz**. Buradaki fark tek bir noktadadır: o denetimler **kaynağa**
  bakıyordu, bunlar **dönüşümün çıktısına** bakar.
- **DN45.** Taban çizgisi **beklentisiz dönüşümdür**: bozulan beklenti **0**, çünkü yazılmış
  beklenti yoktur.
- **DN46.** Bozulma sınıfları saldırı değil üretici tarafı kazalarıdır ve her biri **tek bir
  dönemi** (`2026-07`) bozar.
- **DN47.** Tüketim tavanı **200** fiziksel bir seçimdir; kaynağın üretecinden bilinen 49 değildir.
  Beklentiyi yazan, üreteci değil sayacı bilir.
- **DN48.** Çözünürlük: tüketim satırı oranının adımı **1/2200 = 0,0005**, özet **55 hücre**, sunum
  **5 değer**. **Sunum üzerinde oran basılmaz**, değerin kendisi yazılır.

## Sınanabilir Dönüşümün Koşulu

Bir dönüşüm ancak **saf fonksiyon** olduğunda sınanabilir: aynı girdiye her koşumda aynı çıktıyı verir,
gizli durum taşımaz, saate bakmaz, dışarıdan bir şey okumaz. Bu koşul kursun kuralının doğrudan
karşılığıdır — yeniden koşturulduğunda aynı sonucu vermeyen bir dönüşümün sınanacak bir sonucu
yoktur, çünkü her koşum başka bir cevabı savunulabilir kılar.

Koşul sağlandığında iki ayrı test türü mümkün olur ve ikisi farklı sorulara bakar:

| Tür | Girdi | Kâhin (oracle): beklenen sonuç nereden | Yakaladığı |
|---|---|---|---|
| birim testi | elle yazılmış sabit girdi | elle hesaplanmış çıktı | **kuralın** hatası |
| veri testi (beklenti) | koşumun gerçek çıktısı | çıktının taşıması gereken değişmez | **verinin** bozulması |

Ayrım kâhin sütununda durur. Birim testinde doğru cevap testi yazandan gelir ve girdi küçük
tutulduğu için elle hesaplanabilir. Beklentide böyle bir cevap yoktur; iki bin küçük satırın doğru
toplamını kimse elle bilemez. Beklenti yalnız **çıktının kendisi hakkında her koşumda doğru olması
gereken** şeyi yazabilir: anahtar tekildir, tüketim eksi olamaz, hücre sayısı tamdır.

Aşağıdaki blok kaynağı, beş aşamayı ve altı bozulma sınıfını kurar. Ders boyunca kullanılan tanım
budur.

```python
# KURGUDUR. Olcum aginin sayac okumalari, bes asamali hat ve bozulma siniflari
# ders icinde tanimlanir; gercek bir depo, cerceve ya da orkestrator cagrilmaz.
TOHUM, M32 = 20260218, 0xFFFFFFFF


def uretec(t):
    x = ((t ^ (t >> 16)) * 2246822507) & M32
    x = ((x ^ (x >> 13)) * 3266489909) & M32
    s = [(x ^ (x >> 16)) & M32]

    def sonraki():
        s[0] = (s[0] * 1664525 + 1013904223) & M32
        return s[0] / 4294967296
    return sonraki


BOLGE = ["dogu", "bati", "kuzey", "guney", "merkez"]
TIP = ["mesken", "isyeri"]
DONEM = [f"2026-{a:02d}" for a in range(1, 13)]
ABONE = {}
for i in range(200):
    r = uretec(TOHUM + 37 * i)
    ABONE[f"A{i + 1:03d}"] = {"bolge": BOLGE[int(r() * 5)], "tip": TIP[int(r() * 2)],
                              "baslangic": 10000 + int(r() * 60000)}


def kaynak_uret():
    kayit = []
    for i, (ab, a) in enumerate(ABONE.items()):
        e = a["baslangic"]
        for d in DONEM:
            r = uretec(TOHUM + 101 * i + 7 * DONEM.index(d))
            e += 4 + int(r() * 46)
            kayit.append({"abone": ab, "donem": d, "bolge": a["bolge"], "tip": a["tip"],
                          "endeks": e, "birim": "m3"})
    return kayit


BOZULMA = ["gec_gelen", "yinelenen", "sema_degisti", "birim_degisti", "eksik_bolum", "kayan_tip"]


def boz(kayit, sinif, donem="2026-07"):
    """Tek bir donemi bozar , kalan donemler saglam kalir."""
    yeni, sayac = [], 0
    for k in kayit:
        if k["donem"] != donem:
            yeni.append(k)
            continue
        if sinif == "gec_gelen":
            if k["abone"] <= "A040":                # bu donemde 40 abone hic gelmedi
                sayac += 1
                continue
            yeni.append(k)
        elif sinif == "yinelenen":
            yeni.append(k)
            if k["abone"] <= "A060":                # 60 kayit iki kez geldi
                yeni.append(dict(k))
                sayac += 1
        elif sinif == "sema_degisti":
            y = dict(k)
            y["endeks_degeri"] = y.pop("endeks")    # alan adi degisti
            sayac += 1
            yeni.append(y)
        elif sinif == "birim_degisti":
            y = dict(k)
            y["endeks"] = k["endeks"] * 1000        # m3 yerine litre
            y["birim"] = "m3"                       # ETIKET DEGISMEDI , sessiz bozulma
            sayac += 1
            yeni.append(y)
        elif sinif == "eksik_bolum":
            if k["bolge"] == "kuzey":               # bir bolgenin bolumu hic yazilmadi
                sayac += 1
                continue
            yeni.append(k)
        elif sinif == "kayan_tip":
            y = dict(k)
            y["endeks"] = str(k["endeks"])          # sayi metne dondu
            sayac += 1
            yeni.append(y)
    return yeni, sayac


def a1_ayikla(kayit):
    """Zorunlu alanlari tasimayan satir dusurulur."""
    ZOR = ("abone", "donem", "bolge", "endeks", "birim")
    tut = [k for k in kayit if all(a in k for a in ZOR)]
    return tut, len(kayit) - len(tut)


def a2_tekille(kayit):
    gor, tut = set(), []
    for k in kayit:
        ad = (k["abone"], k["donem"])
        if ad in gor:
            continue
        gor.add(ad)
        tut.append(k)
    return tut, len(kayit) - len(tut)


def a3_tuketim(kayit):
    """Donem farkindan tuketim. Onceki donem yoksa satir hesaplanamaz."""
    tablo = {(k["abone"], k["donem"]): k for k in kayit}
    cikti, dusen = [], 0
    for k in kayit:
        i = DONEM.index(k["donem"])
        if i == 0:
            continue
        onceki = tablo.get((k["abone"], DONEM[i - 1]))
        if onceki is None:
            dusen += 1
            continue
        try:
            f = int(k["endeks"]) - int(onceki["endeks"])
        except (TypeError, ValueError):
            dusen += 1
            continue
        cikti.append(dict(k, m3=f))
    return cikti, dusen


def a4_ozet(kayit):
    top = {}
    for k in kayit:
        ad = (k["bolge"], k["donem"])
        top[ad] = top.get(ad, 0) + k["m3"]
    return top, 0


def a5_sun(ozet):
    """Sunum: bolge basina donem ortalamasi."""
    bolge = {}
    for (b, d), v in ozet.items():
        bolge.setdefault(b, []).append(v)
    return {b: round(sum(v) / len(v), 2) for b, v in bolge.items()}, 0
```

## Sabit Girdi Üzerinde Birim Testi

İkinci blok sekiz satırlık bir girdi yazar ve doğru çıktıyı elle hesaplar. Girdide iki abone,
üçer dönem, bir yinelenen satır ve zorunlu alanı eksik bir satır vardır; dolayısıyla üç aşama da
işini yapmak zorundadır. Yanına aynı hesabı yapan ama sonucu **bir önceki dönemin etiketiyle**
yazan bir kural konur.

```python
def donustur(kayit):
    """Ham satirdan tuketim satirina: ayikla , tekille , donem farki."""
    v, _ = a1_ayikla(kayit)
    v, _ = a2_tekille(v)
    v, _ = a3_tuketim(v)
    return v


def donustur_kaydirmali(kayit):
    """AYNI hesap , tek fark: sonuc bir onceki donemin etiketiyle yaziliyor."""
    return [dict(k, donem=DONEM[DONEM.index(k["donem"]) - 1]) for k in donustur(kayit)]


def satir(ab, d, e, tip="mesken"):
    return {"abone": ab, "donem": d, "bolge": "dogu", "tip": tip, "endeks": e, "birim": "m3"}


# Sabit girdi: sekiz satir , icinde bir yinelenen ve bir eksik alanli satir var.
SABIT = [satir("T1", "2026-01", 100), satir("T1", "2026-02", 112),
         satir("T1", "2026-02", 112), satir("T1", "2026-03", 130),
         satir("T2", "2026-01", 500, "isyeri"), satir("T2", "2026-02", 540, "isyeri"),
         satir("T2", "2026-03", 545, "isyeri"), {"abone": "T3", "donem": "2026-02"}]
# Elle hesaplanmis dogru cikti: testin kahini.
BEKLENEN = [("T1", "2026-02", 12), ("T1", "2026-03", 18),
            ("T2", "2026-02", 40), ("T2", "2026-03", 5)]


def ozu(v):
    return sorted((k["abone"], k["donem"], k["m3"]) for k in v)


def yaz(ad, v):
    print(f"{ad:<11}", " ".join(f"{a}/{d[-2:]}={m}" for a, d, m in ozu(v)),
          " gecti:", ozu(v) == sorted(BEKLENEN))


print("sabit girdi:", len(SABIT), "satir , beklenen cikti:", len(BEKLENEN), "satir")
yaz("dogru", donustur(SABIT))
yaz("kaydirmali", donustur_kaydirmali(SABIT))
print("ayni girdi iki kez kosuldu , cikti ayni mi:",
      ozu(donustur(SABIT)) == ozu(donustur(SABIT)))
```

```
sabit girdi: 8 satir , beklenen cikti: 4 satir
dogru       T1/02=12 T1/03=18 T2/02=40 T2/03=5  gecti: True
kaydirmali  T1/01=12 T1/02=18 T2/01=40 T2/02=5  gecti: False
ayni girdi iki kez kosuldu , cikti ayni mi: True
```

Sekiz satır dörde iniyor: yinelenen satır `tekille` aşamasında, eksik alanlı satır `ayikla`
aşamasında, her abonenin ilk dönemi ise tüketimin tanımı gereği düşüyor. Dört sayının tamamı elle
doğrulanabilir: T1 için 112 − 100 = 12 ve 130 − 112 = 18, T2 için 540 − 500 = 40 ve 545 − 540 = 5.

Kaydırmalı kural **aynı dört sayıyı** üretiyor ve testte hemen düşüyor, çünkü etiketler bir dönem
geriye kaymış. Testin gücü buradadır: hesabın kendisi doğru, yanlış olan yalnızca sonucun hangi
döneme yazıldığıdır, ve bu ayrım sabit girdide gözle görülür. Son satır kursun kuralını sabit girdi
üzerinde doğrular — aynı girdi iki kez koşturulduğunda çıktı birebir aynıdır.

## Çıktıya Konan Beklentiler

Üçüncü blok altı beklenti tanımlar. Hepsi dönüşümün **kendi çıktısına** bakar; hiçbiri kaynağa
dönmez ve hiçbiri önceden bilinen bir doğru sonuca ihtiyaç duymaz. Beklentiler altı bozulma sınıfı
üzerinde tek tek koşturulur.

```python
import statistics

# Beklentilerin hepsi donusumun KENDI ciktisina bakar , kaynaga degil.
SATIR, HUCRE, TAVAN = 2200, 55, 200


def b_anahtar_tekil(v, oz):
    ad = [(k["abone"], k["donem"]) for k in v]
    return len(ad) != len(set(ad))


def b_negatif_yok(v, oz):
    return any(k["m3"] < 0 for k in v)


def b_tavan(v, oz):
    return any(k["m3"] > TAVAN for k in v)


def b_satir_orani(v, oz):
    return abs(len(v) - SATIR) / SATIR > 0.02


def b_hucre_tamligi(v, oz):
    return len(oz) != HUCRE


def b_oynaklik(v, oz):
    """Bir bolgenin donem hucreleri kendi ortancasindan cok uzaklasmamali."""
    for b in BOLGE:
        d = [x for (bb, _), x in oz.items() if bb == b]
        orta = statistics.median(d) if d else 0
        if not d or any(abs(x - orta) / abs(orta) > 0.30 for x in d):
            return True
    return False


BEKLENTI = [("anahtar_tekil", b_anahtar_tekil), ("negatif_yok", b_negatif_yok),
            ("tavan", b_tavan), ("satir_orani", b_satir_orani),
            ("hucre_tamligi", b_hucre_tamligi), ("oynaklik", b_oynaklik)]


def kosut(v):
    """Bozulan beklentilerin adi , ozet ve sunum."""
    oz = a4_ozet(v)[0]
    return [a for a, f in BEKLENTI if f(v, oz)], oz, a5_sun(oz)[0]


KAYNAK = kaynak_uret()
TEMIZ, _, SUNUM = kosut(donustur(KAYNAK))
print("beklenti:", len(BEKLENTI), " saglam hatta bozulan:", len(TEMIZ))
print(f"{'bozulma':<15}{'satir':>6}{'hucre':>7}{'bozulan beklenti':>34}{'sunum':>9}")
for s in BOZULMA:
    v = donustur(boz(KAYNAK, s)[0])
    bz, oz, sn = kosut(v)
    print(f"{s:<15}{len(v):>6}{len(oz):>7}{','.join(bz) or 'yok':>34}"
          f"{'ayni' if sn == SUNUM else 'degisti':>9}")
```

```
beklenti: 6  saglam hatta bozulan: 0
bozulma         satir  hucre                  bozulan beklenti    sunum
gec_gelen        2120     55              satir_orani,oynaklik  degisti
yinelenen        2200     55                               yok     ayni
sema_degisti     1800     45         satir_orani,hucre_tamligi  degisti
birim_degisti    2200     55        negatif_yok,tavan,oynaklik     ayni
eksik_bolum      2108     53         satir_orani,hucre_tamligi  degisti
kayan_tip        2200     55                               yok     ayni
```

İlk satır bedelin olmadığını gösterir: sağlam hatta altı beklentinin hiçbiri ateşlemiyor, yani iyi
satır yanlış engellenmiyor. **Altı beklenti altı sınıfın dördünü yakalıyor.**

`birim_degisti` satırı bu dersin en önemli sayısıdır. Veri Mühendisliğine Giriş kursunda aynı
bozulma yalnızca dördüncü aşamanın özetine bakan dağılım denetimiyle görünüyordu; burada
`negatif_yok` ve `tavan` beklentileri onu **dönüşümün çıktısında**, üçüncü aşamada yakalıyor.
Aradaki fark yalnız bir aşama değildir: dağılım denetimi çalışabilmek için **sağlam bir taban
özetine** ihtiyaç duyar, bu üç beklenti hiçbir tabana ihtiyaç duymaz. Yeni kurulmuş bir hatta
karşılaştırılacak bir geçmiş yoktur, değişmez ise ilk koşumda yazılabilir. Sunum sütunu ise
`birim_degisti` için hâlâ **ayni** diyor — sunulan beş değer birebir yerinde durduğu hâlde iki
dönem çöptür ve bunu söyleyen tek şey beklentidir.

Kalan üç yakalama sıradandır: `gec_gelen` ile `eksik_bolum` satır sayısını düşürüp hücre bandını
kırıyor, `sema_degisti` ise dönemin tamamını `ayikla` aşamasında kaybettiriyor ve 1800 satırla 45
hücre bırakıyor.

## Hiçbir Beklentinin Görmediği Kalıntı

Tabloda iki satır boş: `yinelenen` ve `kayan_tip`. Dördüncü blok bunların yanına iki kural yazar —
biri hatalı, biri kasıtlı — ve aynı altı beklentiyi onlar üzerinde koşturur.

```python
KAYDIRMALI = donustur_kaydirmali(KAYNAK)
KATSAYILI = [dict(k, m3=k["m3"] * (1.05 if k["tip"] == "isyeri" else 1.0))
             for k in donustur(KAYNAK)]
for ad, v in (("kaydirmali kural", KAYDIRMALI), ("katsayili kural", KATSAYILI)):
    bz, oz, sn = kosut(v)
    print(f"{ad:<17} satir={len(v)} hucre={len(oz)} bozulan={','.join(bz) or 'yok'}")
    print("  sunum:", sn)
print("  taban:", SUNUM)
```

```
kaydirmali kural  satir=2200 hucre=55 bozulan=yok
  sunum: {'merkez': 1321.45, 'kuzey': 1212.55, 'dogu': 881.36, 'bati': 833.09, 'guney': 1065.55}
katsayili kural   satir=2200 hucre=55 bozulan=yok
  sunum: {'merkez': 1357.32, 'kuzey': 1235.57, 'dogu': 902.13, 'bati': 859.22, 'guney': 1095.81}
  taban: {'merkez': 1321.45, 'kuzey': 1212.55, 'dogu': 881.36, 'bati': 833.09, 'guney': 1065.55}
```

Kalıntı üç parçadır ve üçünün mekanizması ayrıdır.

**Dönüşümün yuttuğu iki sınıf.** `yinelenen` `tekille` aşamasında, `kayan_tip` ise tür çevriminde
düzeliyor; çıktıya bakan bir beklenti onarılmış bir bozulmayla hiç olmamış bir bozulmayı
birbirinden ayıramaz. Bu iki satırın boş kalması dönüşümün sağlamlığının kanıtıdır, ama üreticinin
hâlâ metin gönderdiğini kimse öğrenmez ve gelecek dönemin metni sayıya çevrilemeyebilir.
**Çıktıya konan beklenti, girdiye konan denetimin yerini tutmaz**; ikisi ayrı sorulara bakar.

**Etiket kaydıran kural.** Kaydırmalı kural 2200 satır, 55 hücre üretiyor, hiçbir beklentiyi
bozmuyor ve sunumu **birebir aynı** bırakıyor. Nedeni yapısaldır: hesaplanan sayılar kümesi hiç
değişmemiş, yalnızca hangi döneme yazıldıkları kaymıştır; bölge başına on bir hücrenin ortalaması
aynı sayıların ortalamasıdır. Çıktının kendisi hakkında yazılabilecek hiçbir değişmez bunu göremez,
çünkü çıktının kendisi kusursuzdur — yanlış olan **hangi gerçeğe karşılık geldiğidir**. Bu hatayı
yakalayan tek şey dersin başındaki sabit girdili testtir.

**Kasıtlı kural değişikliği.** Katsayılı kural işyeri tüketimini 1,05 ile raporluyor. Altı
beklentinin hiçbiri bozulmuyor, çünkü ortada bozulma yok; sunum ise beş değerin hepsinde yukarı
kayıyor. Beklentiler bu farkın **istenen bir değişiklik mi yoksa kaza mı** olduğunu söyleyemez.
Ayrımı yapan tek şey, kural sürümünün kayda yazılmasıdır.

## Özet

- Bir dönüşüm ancak saf fonksiyonse sınanabilir; aynı girdiye her koşumda aynı çıktıyı vermeyen bir
  kuralın sınanacak sonucu yoktur.
- Birim testi kuralın hatasını, beklenti verinin bozulmasını yakalar; ayrım kâhinin nereden
  geldiğindedir ve ikisi birbirinin yerine geçmez.
- Sağlam hatta altı beklentinin hiçbiri ateşlemiyor ve altı bozulma sınıfının dördünü yakalıyorlar;
  `yinelenen` ile `kayan_tip` dönüşümün kendisi tarafından yutulduğu için çıktıda hiç görünmüyor.
- `birim_degisti` çıktıya konan beklentiyle üçüncü aşamada ve **hiçbir taban çizgisi olmadan**
  yakalanıyor, oysa sunulan beş değer birebir aynı kalıyor.
- Dönem etiketini bir dönem geriye kaydıran kural 2200 satır, 55 hücre ve aynı beş sunum değerini
  ürettiği için altı beklentinin hiçbirine takılmıyor; onu yalnız sabit girdili birim testi görüyor.

## Sonraki Adım

Son bloğun katsayılı kuralı bu dersin kapatamadığı soruyu açıyor. Kural değişti ve yeni koşumlar
doğru sonucu üretecek — ama on iki dönemin on birisi **eski kuralla** hesaplanmış olarak duruyor.
Bütün dönemleri yeniden hesaplamak koşum bedeli demektir, yalnız birkaç dönemi yeniden hesaplamak
ise raporun bir kısmını yeni, kalanını eski kuralla bırakır. Sonraki ders geri doldurmayı bu iki
seçenek üzerinden ölçer: kaç dönemin yeniden hesaplandığını, karışık raporun tam uygulamadan ne
kadar saptığını ve o raporun hangi denetimlerden geçtiğini sayar.
