---
title: 'Test Çerçeveleri'
source: 'https://academia.sh/tr/kurslar/c-sistem-programlama/test-cerceveleri'
course: 'C ile Sistem Programlama'
language: tr
updated: '2026-09-26T03:02:44+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Bir test tablosunu, onaylama makrosunu ve geçti/kaldı sayımını C ile kurar'
    - 'Süreç yalıtımının bir testin değişikliğini neden sonraki teste taşımadığını ölçer'
    - 'Süreç yalıtımının bellek durumunu kestiğini, dosya sistemi durumunu kesmediğini gösterir'
---

# Test Çerçeveleri

Çatallanmış bir çocukta koşan bir testin değişikliğinin neden sonraki teste sızmadığını, ve bu yalıtımın dosya sistemi durumunu neden kapsamadığını ölçer.

Bir kütüphaneye doğru bağlanmak, onu kullanan kodun doğru çalıştığını göstermez. Bunu
sınamanın kendisi de bir görünürlük sorunu taşır: bir test başka bir testin bıraktığı
durumu görüyorsa, hangi testin gerçekten başarısız olduğunu artık söyleyemezsiniz. Küçük
bir test çerçevesi bu sorunu, her testi kendi kopyasında çalıştırarak çözer.

## Test Tablosu ve Yalıtılmış Koşum

Bir test çerçevesinin çekirdeği bir işlev işaretçisi tablosu, tek bir onaylama makrosu ve
bir geçti/kaldı sayımıdır. Onaylama başarısız olduğunda test işlevi 0 döndürür; onaylanacak
bir şey kalmadan sona ererse 1 döndürür.

```c
// cerceve.h — test tablosu, onaylama makrosu ve paylasilan durumun bildirimi
#ifndef CERCEVE_H
#define CERCEVE_H

typedef int (*TestIslevi)(void);

struct TestGirdisi {
    const char *ad;
    TestIslevi calistir;
};

#define ONAYLA(kosul) do { if (!(kosul)) return 0; } while (0)

extern int ortak_durum;          /* testler arasi sizintiyi gozlemlemek icin */
extern int sahte_cagri_sayaci;   /* sahte bagimliligin kac kez cagrildigi */
extern int duzenek_kurulum_sayaci;  /* duzenegin test oncesi kac kez kuruldugu */
extern int duzenek_temizlik_sayaci; /* duzenegin test sonrasi kac kez sokuldugu */

int test_durumu_ayarla(void);
int test_durumu_sifir_mi(void);
int test_duzenek_degerini_gor(void);
int test_dosyaya_yaz(void);
int test_dosyadan_oku(void);
int sahte_bagimlilik(int girdi);
int guduk_bagimlilik(int girdi);
int gercek_isi_yap(int girdi, int (*bagimlilik)(int));
int duzenek_kur(void);
int duzenek_sok(void);

#endif
```

```c
// cerceve.c — test govdeleri, sahte bagimlilik ve dosya tabanli iki test
#include <stdio.h>
#include "cerceve.h"

int ortak_durum = 0;
int sahte_cagri_sayaci = 0;

int test_durumu_ayarla(void) {
    ortak_durum = 9;
    return 1;
}

int test_durumu_sifir_mi(void) {
    ONAYLA(ortak_durum == 0);
    return 1;
}

int test_duzenek_degerini_gor(void) {
    ONAYLA(ortak_durum == 5);   /* duzenek_kur burayi 5'e getirmis olmali */
    return 1;
}

int test_dosyaya_yaz(void) {
    FILE *f = fopen("iz.dat", "w");
    if (!f) return 0;
    fputc('1', f);
    fclose(f);
    return 1;
}

int test_dosyadan_oku(void) {
    FILE *f = fopen("iz.dat", "r");
    if (!f) return 0;
    int deger = fgetc(f);
    fclose(f);
    return deger == '1';
}

int sahte_bagimlilik(int girdi) {
    sahte_cagri_sayaci++;
    return girdi * 2;
}

int duzenek_kurulum_sayaci = 0;
int duzenek_temizlik_sayaci = 0;

int guduk_bagimlilik(int girdi) {
    (void)girdi;             /* gudugun cevabi sabittir, girdiyi hic okumaz */
    return 42;                /* onceden kararlastirilmis, degismeyen cevap */
}

int gercek_isi_yap(int girdi, int (*bagimlilik)(int)) {
    return bagimlilik(girdi) + 1;
}

int duzenek_kur(void) {
    duzenek_kurulum_sayaci++;
    ortak_durum = 5;    /* duzenegin kurdugu bilinen baslangic durumu */
    return 1;
}

int duzenek_sok(void) {
    duzenek_temizlik_sayaci++;
    ortak_durum = 0;    /* bir sonraki test icin durumu temiz birak */
    return 1;
}
```

**PA5.** Test tablosu yalnız iki girdiyle gösteriliyor; mekanizma herhangi bir sayıda
girdiye genelliyor, çünkü koşum sırası tablo üzerinde bir döngüden ibaret.

Her testi kendi çocuğunda koşturan yardımcı, önceki modülde kurulan çatallama sözleşmesini
kullanıyor: tampon boşaltılıyor, çocuk testi çalıştırıp çıkış durumuyla sonucu bildiriyor,
ebeveyn yalnız o durumu okuyor.

**PA6.** Düzenek burada tek bir çift işlevle — bir kurulum, bir temizlik — gösteriliyor;
gerçek bir çerçevede düzenek testler arasında farklılaşabilir, ama çift olma zorunluluğu
(her kuruluma karşılık bir temizlik) değişmiyor.

Testin kendisinden önce ve sonra çalışması gereken ortak bir hazırlık varsa, bu hazırlığı
her testin gövdesine tek tek yazmak yerine ayrı bir **düzenek** (kurulum/temizlik çifti)
olarak çıkarmak, aynı hazırlığın tekrar tekrar yazılmasını önler ve daha önemlisi, temizliğin
testin sonucundan bağımsız olarak her zaman çalışmasını garanti eder. Bir düzeneğin sözleşmesi
şudur: kurulum testten önce tam olarak bir kez çalışır, temizlik testten sonra tam olarak bir
kez çalışır — test geçse de kalsa da, hatta test bir onaylamada erken dönse bile. Aşağıdaki
`duzenek_kur`/`duzenek_sok` çifti bu sözleşmeyi modelliyor: `duzenek_kur` `ortak_durum`'u
bilinen bir başlangıç değerine (5) getiriyor ve kurulum sayacını artırıyor; `duzenek_sok`
durumu sıfırlıyor ve temizlik sayacını artırıyor. Bu iki sayaç, düzeneğin gerçekten her test
için bir kez kurulup bir kez söküldüğünü doğrulamanın yolu.

Güdük, sahte nesneden farklı bir amaca hizmet eder ve bu ders ikisini ayrı tutuyor. Bir
**güdük** (`guduk_bagimlilik`), kendisine hangi girdi verilirse verilsin sabit, önceden
kararlaştırılmış bir cevap döndürür ve çağrıldığını hiçbir şekilde kaydetmez — amacı yalnız
gerçek bağımlılığın yerini doldurup testin geri kalanının çalışabilmesini sağlamaktır. Bir
**sahte nesne** (`sahte_bagimlilik`) ise buna ek olarak kendi çağrılma biçimini gözlenebilir
kılar; bu derste bunu bir çağrı sayacıyla yapıyor. Ayrım şuna indirgenir: güdük testin
**girdisini** hazırlar, sahte nesne testin **doğrulamasının** bir parçası olur. Bir test
yalnız `gercek_isi_yap`'ın doğru bir sayı ürettiğini görmek istiyorsa güdük yeterlidir ve
daha basittir; test ayrıca bağımlılığın kaç kez çağrıldığını da doğrulamak istiyorsa sahte
nesne gerekir. İkisini karıştırıp her yerde sahte nesne kullanmak, çağrı sayısı hiç
sorulmayan testlere gereksiz bir gözlem yükü bindirir; her yerde güdük kullanmak ise çağrı
davranışını doğrulaması gereken testleri kör bırakır.

```c
// olcum.c — yalitilmis kosum, yalitilmamis sizinti ve dosya sistemi siniri tek kosumda
#define _POSIX_C_SOURCE 200809L
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "cerceve.h"

static int yalitilmis_calistir(TestIslevi islev) {
    fflush(stdout);   /* aksi halde alt surec, ana surecin bosaltilmamis tamponunu da tasir */
    pid_t pid = fork();
    if (pid == 0) {
        _exit(islev() ? 0 : 1);
    }
    int durum = 0;
    waitpid(pid, &durum, 0);
    return WIFEXITED(durum) && WEXITSTATUS(durum) == 0;
}

/* duzenekli_calistir: kurulum ve temizlik ebeveynde, testin kendisi yalitilmis cocukta */
static int duzenekli_calistir(TestIslevi islev) {
    duzenek_kur();
    int sonuc = yalitilmis_calistir(islev);
    duzenek_sok();
    return sonuc;
}

int main(void) {
    printf("yalitilmis durum ayarla: %s\n",
           yalitilmis_calistir(test_durumu_ayarla) ? "gecti" : "kaldi");
    printf("yalitilmis durum sifir mi: %s\n",
           yalitilmis_calistir(test_durumu_sifir_mi) ? "gecti" : "kaldi");

    /* ayni iki test bu kez yalitilmadan, dogrudan ayni surecte cagriliyor */
    test_durumu_ayarla();
    printf("yalitilmamis durum sifir mi: %s\n",
           test_durumu_sifir_mi() ? "gecti" : "kaldi");

    int sonuc = gercek_isi_yap(3, sahte_bagimlilik);
    printf("sahte bagimlilik cagri sayisi: %d\n", sahte_cagri_sayaci);
    printf("gercek_isi_yap sonucu: %d\n", sonuc);

    int guduk_sonuc = gercek_isi_yap(3, guduk_bagimlilik);
    printf("guduk ile gercek_isi_yap sonucu: %d\n", guduk_sonuc);

    printf("duzenekli test degeri gor: %s\n",
           duzenekli_calistir(test_duzenek_degerini_gor) ? "gecti" : "kaldi");
    printf("duzenek kurulum sayaci: %d\n", duzenek_kurulum_sayaci);
    printf("duzenek temizlik sayaci: %d\n", duzenek_temizlik_sayaci);
    printf("duzenekten sonra ana surecte ortak_durum: %d\n", ortak_durum);

    printf("yalitilmis dosyaya yaz: %s\n",
           yalitilmis_calistir(test_dosyaya_yaz) ? "gecti" : "kaldi");
    printf("yalitilmis dosyadan oku: %s\n",
           yalitilmis_calistir(test_dosyadan_oku) ? "gecti" : "kaldi");
    return 0;
}
```

```text
yalitilmis durum ayarla: gecti
yalitilmis durum sifir mi: gecti
yalitilmamis durum sifir mi: kaldi
sahte bagimlilik cagri sayisi: 1
gercek_isi_yap sonucu: 7
guduk ile gercek_isi_yap sonucu: 43
duzenekli test degeri gor: gecti
duzenek kurulum sayaci: 1
duzenek temizlik sayaci: 1
duzenekten sonra ana surecte ortak_durum: 0
yalitilmis dosyaya yaz: gecti
yalitilmis dosyadan oku: gecti
```

İlk iki satır aynı `ortak_durum` değişkenini kullanan iki testin, birbirinden ayrı çocuklarda
koştuğunu gösteriyor: `test_durumu_ayarla` kendi çocuğunda `ortak_durum`'u 9 yapıyor, ama bu
çocuk çıktığında o değişiklik onunla birlikte kayboluyor. `test_durumu_sifir_mi` kendi
çocuğunda çalıştığında, ebeveynin hâlâ 0 olan `ortak_durum` değerinin bir kopyasını devralıyor
ve onaylama geçiyor. Üçüncü satır aynı iki testi yalıtımsız çalıştırıyor: ikisi de tek
süreçte, gerçek `ortak_durum` üzerinde çalışıyor, birincinin bıraktığı 9 ikinciyi doğrudan
etkiliyor ve onaylama kalıyor — bu, süreç yalıtımının önlediği sızıntının kendisi.

Bu sızıntının tehlikeli yanı, sonucun test dosyasındaki **sıraya** bağlı olmasıdır. Aynı iki
test, `test_durumu_sifir_mi` önce çalışacak şekilde yazılsaydı, sızıntı hiç görünmez ve iki
test de yalıtımsız haliyle bile geçerdi — çünkü `ortak_durum` henüz 9'a çekilmemiş olurdu.
Bu, yalıtımsız bir test paketinin en aldatıcı özelliğidir: paket bir sırada tamamen yeşil
görünür, testler yeniden düzenlendiğinde ya da yeni bir test araya girdiğinde aynı paket
kırmızıya döner, oysa hiçbir testin kendi mantığı değişmemiştir. Hatanın kaynağı testlerin
hiçbirinde değil, aralarındaki gizli bağımlılıktadır — ve bu bağımlılık kod incelemesinde
görünmez kalır, çünkü iki test birbirini hiç çağırmaz, yalnız aynı değişkeni paylaşır.
Süreç yalıtımının asıl kazancı burada ortaya çıkıyor: her test kendi çocuğunda çalıştığı
için, testler hangi sırayla yazılırsa yazılsın sonuç aynı kalır — sıra, testin doğruluğunu
etkileyen bir değişken olmaktan çıkar.

`sahte_bagimlilik` gerçek bağımlılığın yerine geçen bir işlev işaretçisi; `gercek_isi_yap`
onu kendi mantığını değiştirmeden çağırıyor ve çağrı sayısı `sahte_cagri_sayaci` üzerinden
ölçülüyor. Bu sayaç da tıpkı `ortak_durum` gibi bir süreç belleğinde yaşıyor — bir test bunu
çatallanmış bir çocukta okusaydı, o çocuğun gördüğü sayım da kendi kopyası olurdu ve
ebeveyne geri dönmezdi; ebeveyne dönen tek şey testin geçip geçmediğidir.

Sahte nesnenin burada değiştirmediği şey `gercek_isi_yap`'ın kendi mantığıdır: işlev hâlâ
kendisine verilen işaretçiyi çağırıyor, yalnız o işaretçinin gösterdiği gövde gerçek
bağımlılık yerine sayaç tutan bir gövdeyle değiştirildi. Bu, testin ölçtüğü şeyi ikiye
ayırıyor — `gercek_isi_yap`'ın kendi hesabının doğruluğu bir yanda, çağırdığı
bağımlılığın kaç kez ve hangi sırayla çağrıldığı öbür yanda. İkinci soru gerçek
bağımlılıkla sınanamaz, çünkü gerçek bağımlılığın kendi çağrı sayısını dışarıdan
gözlemlemenin bir yolu yoktur; sahte nesne bu gözlemi mümkün kılan tek araçtır.

`guduk_bagimlilik` ile alınan sonuç 43 — girdi ne olursa olsun sabit 42 döndüren güdüğün
üzerine `gercek_isi_yap`'ın eklediği 1. Bu satır çağrı sayısı hakkında hiçbir şey söylemiyor,
çünkü güdük zaten hiçbir şey saymıyor; yalnız `gercek_isi_yap`'ın kendi hesabının bağımlılığın
döndürdüğü değeri doğru işlediğini gösteriyor. Sahte nesneyle alınan sonuç 7 ile bu sonuç
karşılaştırıldığında, iki test aynı işlevi iki farklı soru için sınıyor: biri "doğru cevabı
üretiyor mu", öbürü "bağımlılığı doğru sayıda çağırıyor mu".

**PA7.** `duzenek_kur`/`duzenek_sok` çifti burada ebeveyn süreçte, `yalitilmis_calistir`'ın
çağrılmasından hemen önce ve hemen sonra çalıştırılıyor; testin kendisi hâlâ kendi
çocuğunda koşuyor, düzenek çocuğun içine taşınmıyor.

`duzenekli_calistir` satırı, düzeneğin sözleşmesini üç sayıyla doğruluyor. Kurulum sayacı 1,
temizlik sayacı 1 — yani her ikisi de tam olarak bir kez çalıştı, ne eksik ne fazla. Testin
kendisi `ortak_durum == 5` onaylamasını geçti, çünkü `duzenek_kur` bu değeri testin
çocuğundan **önce**, ebeveynde kurdu ve çocuk bu değeri fork ile devraldı. Son satır,
`duzenek_sok` çalıştıktan sonra ana süreçteki `ortak_durum`'un yeniden 0 olduğunu gösteriyor
— düzeneğin temizliği, testin çocuğu çoktan çıktıktan sonra ana süreç üzerinde gerçekleşiyor,
çünkü kurulum ve temizlik hiç çatallanmadan, doğrudan ebeveynde çalışıyor. Bu, düzeneğin
neden yalıtılmış koşumun **dışında** tutulması gerektiğini gösteriyor: düzenek her testten
önce ve sonra tekrarlanan ortak bir hazırlıktır, testin kendisi gibi her seferinde ayrı bir
çocuğa taşınması gerekmez — taşınsaydı, kurulumun etkisi (ortak_durum'un 5 olması) testin
çocuğuna hiç ulaşamazdı, çünkü her çocuk yalnız kendi fork anındaki durumu devralır.

## Sınırlayıcı Ölçüm

Son iki satır, süreç yalıtımının nerede durduğunu gösteriyor. `test_dosyaya_yaz` kendi
çocuğunda `iz.dat` dosyasına bir değer yazıyor ve çocuk çıkıyor; bellek durumu onunla
birlikte kayboluyor. Ama `test_dosyadan_oku`, tamamen ayrı bir çocukta çalıştığı halde, aynı
dosyayı açıp önceki testin yazdığı değeri okuyabiliyor ve onaylama geçiyor. Çatallama yalnız
belleği kopyalar; dosya sistemi, sürecin dışında kalan bir nesnedir ve fork'un yalıttığı
alanın parçası değildir. İki test aynı dosyaya dokunuyorsa, aralarında bellek düzeyinde bir
yalıtım olsa bile birbirini etkileyebilirler.

Bu sınır yalnız dosyalarla sınırlı değil; süreç dışında yaşayan her paylaşılan kaynak aynı
sorunu taşır. Bir ağ soketi, bir kilit dosyası, işletim sisteminin kendisinde tuttuğu bir
ortam değişkeni ya da paylaşılan bellek bölgesi — bunların hiçbiri fork'un kopyaladığı
bellek alanının içinde değildir, dolayısıyla hiçbiri çatallanmayla otomatik olarak
yalıtılmaz. Bir test paketi yazarken bu ayrımı bilmek pratik bir sonuç doğurur: testleri
ayrı süreçlerde çalıştırmak, testlerin birbirinden bağımsız olduğunu **garanti etmez** —
yalnız bellek düzeyindeki bağımsızlığı garanti eder. Süreç dışı bir kaynağa dokunan testler
için ek bir önlem gerekir: ya kaynağın kendisi her test öncesi sıfırlanır, ya da testler
kaynağı hiç paylaşmayacak şekilde tasarlanır — örneğin her test kendi geçici dosya adını
üretir.

Dosya sistemi sınırının bir başka yüzü, testin **temizlenip temizlenmediğidir**. `iz.dat`
dosyası bu koşumdan sonra diskte kalmaya devam eder; bir sonraki koşumda `test_dosyaya_yaz`
zaten var olan bir dosyanın üzerine yazar, bu örnekte sonucu değiştirmez çünkü işlev dosyayı
her seferinde yeniden açıp yazıyor, ama farklı bir test aynı adı başka bir amaçla kullansaydı
eski koşumdan kalan içerik yeni testin ilk okuduğu değer olurdu. Bellekte bu sorun yoktur,
çünkü her koşum kendi sürecini sıfırdan başlatır ve önceki koşumun belleği işletim sistemi
tarafından geri alınır; dosya sistemi ise süreç sona erdikten sonra da değişmeden kalmaya
devam eder. Bu yüzden dosya kullanan testlerin sözleşmesi bellek kullanan testlerden bir
adım daha geniştir: yalnız testler arasında değil, **koşumlar arasında** da temiz bir
başlangıç noktası garanti edilmelidir — düzenek burada da devreye girebilir, temizlik
adımına dosyayı silmek eklenerek.

Bu genişletilmiş düzenek, `duzenek_sok`'un yalnız `ortak_durum`'u sıfırlamakla kalmayıp aynı
zamanda `remove("iz.dat")` çağırmasıyla kurulabilirdi; burada bu adım eklenmedi, çünkü amaç
dosya sisteminin fork'un dışında kaldığını göstermekti, dosya temizliğinin nasıl yapılacağını
değil. Genel kural şudur: bir düzenek yalnız kendi kurduğu kaynağı söker — `duzenek_kur`
`ortak_durum`'a dokunuyorsa `duzenek_sok` da yalnız onu geri alır; bir dosya oluşturan başka
bir düzenek olsaydı, onun temizliği de kendi dosyasını silmekle sınırlı kalırdı. Kurulumun
dokunmadığı bir kaynağı temizlik aşamasında değiştirmek, düzeneğin sözleşmesini genişletir ve
hangi testin hangi kaynaktan sorumlu olduğunu izlemeyi zorlaştırır.

## Özet

- Bir test çerçevesi işlev işaretçisi tablosu, onaylama makrosu ve geçti/kaldı sayımından
  oluşur; her girdi tabloda bir satırdır.
- Her testi ayrı bir çocukta çatallayarak koşturmak, testin bellekte bıraktığı değişikliği
  sonraki teste taşımaz — bu dersin ölçtüğü sınıf **kopya**.
- Aynı testler yalıtımsız, tek süreçte koştuğunda birincinin değişikliği ikinciye sızar ve
  koşum sırası sonucu değiştirebilir.
- Bir bağımlılığı işlev işaretçisiyle bir sahte nesneye çevirmek, çağrı sayısını gerçek
  bağımlılığı değiştirmeden ölçmeyi sağlar; bir güdük ise çağrı saymadan yalnız sabit bir
  cevap döndürerek testin girdisini hazırlar — ikisi farklı amaçlara hizmet eder.
- Bir düzenek, kurulum ve temizliği testin kendisinden ayırır ve temizliğin testin
  sonucundan bağımsız olarak her zaman çalışmasını garanti eder; ebeveyn süreçte çalışır,
  testin çocuğuna taşınmaz.
- Süreç yalıtımı belleği keser, dosya sistemini kesmez; aynı dosyaya dokunan iki test
  çatallama ile birbirinden ayrılmaz. Bu sınır dosyalarla kalmaz — soket, kilit ve ortam
  değişkeni gibi süreç dışı her kaynak aynı şekilde yalıtım dışıdır.

## Sonraki Adım

Bir test kaldığında, neyin hangi adımda beklenenden saptığını görmek gerekir. Bir sonraki
ders, çalışan bir programın belleğindeki ara değerleri, programı durdurup adım adım
ilerleterek nasıl görünür kıldığını ele alıyor.
