---
title: 'Test Çerçeveleri'
source: 'https://academia.sh/tr/kurslar/ruby-uretim/test-cerceveleri'
course: 'Ruby Ekosistemi ve Üretim'
language: tr
updated: '2026-10-21T05:03:44+00:00'
license: 'CC BY-SA 4.0'
objectives:
    - 'Test çerçevesinin üç parçasını (tanım, iddia, koşucu) ayırır ve test sınıfı ile tanım bloğu biçiminin koşucuya aynı tabloyu verdiğini gösterir'
    - 'Koşucunun testleri tohumla karıştırdığını ve düşen testin tohumla yeniden üretildiğini gösterir'
    - 'Kararsız testin kaynağının paylaşılan durum olduğunu ölçer'
    - 'Yalıtımın sınıfı sıradan koda geçirdiğini ve kuralın yalnız iki tohumu denediğini ayırır'
---

# Test Çerçeveleri

Aynı testler neden bazen düşer? Ders bir test çerçevesinin üç parçasını saf Ruby ile kurar, tohumun sırayı seçtiğini ve paylaşılan durumun testi düşürdüğünü ölçer.

Bağımlılık kilitlendi ve kurulan sürüm artık sabit. Yine de aynı kod, aynı kilit dosyası ve aynı testler bir koşumda geçip ötekinde düşebilir. Bu dersin gözlemi `test sonucu` satırıdır: tabanda `[]`, yani hiçbir test düşmez, ama kanıt sütununda `B- S+ O-` görünür. Sonuç, sıra değişince değişir.

M10/K05'te bir güvencenin sınırı soruldu: bir test neyi yakalar, neyi kaçırır. Burada soru başkadır. Bir testin geçip geçmediğini ne belirliyor? Orada sınır güvencenin kapsamıydı; burada sınır, testin kendi dışındaki bir girdidir. Çekirdeğin `belirleyen.rb` dosyası birebir yeniden verilir; blok kaynaktır ve `require_relative` ile çekilir.

```ruby
# belirleyen.rb — bir kosum modeli ve bir sonucu neyin belirledigini siniflandiran yardimci

# Kosum: sonucun bagli olabilecegi uc girdi. Bu uc girdinin disinda kalan her sey koddur.
Kosum = Data.define(:kilit, :tohum, :ortam)

TABAN = Kosum.new(
  kilit: { "bicimleyici" => "1.4.2", "ayristirici" => "2.0.1" },
  tohum: 1,
  ortam: { saat: Time.utc(2026, 10, 16, 9, 0), cekirdek: 4, degiskenler: {} }
)

# Her degisken tek bir girdiyi degistirir; otekiler tabandaki gibi kalir.
DEGISKENLER = {
  bagimlilik: TABAN.with(kilit: { "bicimleyici" => "1.5.0", "ayristirici" => "2.1.0" }),
  sira: TABAN.with(tohum: 2),
  ortam: TABAN.with(ortam: { saat: Time.utc(2027, 3, 1, 9, 0), cekirdek: 8,
                             degiskenler: { "ISCI_SAYISI" => "3" } })
}

# Kural: hesap once tabanda, sonra her degiskende bir kez kosar. Sonucu degistiren ilk
# degisken (bagimlilik, sira, ortam sirasiyla) belirleyendir; hicbiri degistirmiyorsa kod.
def belirleyen(hesap)
  taban = hesap.call(TABAN)
  degisti = DEGISKENLER.to_h { |ad, kosum| [ad, hesap.call(kosum) != taban] }
  tur = degisti.key(true)&.to_s || "kod"
  kanit = degisti.map { |ad, d| "#{ad[0].upcase}#{d ? '+' : '-'}" }.join(" ")
  [tur, kanit, taban]
end
```

## Çerçevenin Üç Parçası

Bir **test çerçevesi** üç parçadan oluşur. **Test tanımı**, çalıştırılabilir bir deneme olarak adlandırılmış bir bloktur. **İddia**, beklenen ile gerçeği karşılaştırır ve tutmuyorsa hata yükseltir. **Test koşucusu**, tanımları çalıştırır, iddia hatasını yakalar ve sonucu bir tabloya yazar. Yaygın test çerçeveleri de bu üç parça üzerine kuruludur. Burada üçünü de saf Ruby ile kuruyoruz, çünkü gözlem koşucunun içindedir ve koşucu görünür olmalıdır.

```ruby
# cerceve.rb — test cercevesinin uc parcasi: tanim, iddia, kosucu
class IddiaHatasi < StandardError; end

# Iddia: beklenen ile gercek ayniysa sessiz kalir, degilse hata yukseltir.
def iddia(beklenen, gercek)
  raise IddiaHatasi, "iddia tutmadi" unless beklenen == gercek
end

# Kosucu: her testi calistirir, iddia hatasini yakalir, sonucu tabloya yazar.
def testleri_kos(testler)
  testler.to_h do |ad, test|
    test.call
    [ad, :gecti]
  rescue IddiaHatasi
    [ad, :dustu]
  end
end
```

Dosya kaynaktır. Aşağıdaki dosya üç testi koşturur. İkincisi tam sayı bölmesini yanlış bekler.

```ruby
# kucuk.rb — uc parca bir araya gelir
require_relative "cerceve"

testler = {
  "toplama" => -> { iddia(5, 2 + 3) },
  "bolme" => -> { iddia(3.5, 7 / 2) },
  "ondalik" => -> { iddia(3.5, 7 / 2.0) }
}
p testleri_kos(testler)
```
```
{"toplama" => :gecti, "bolme" => :dustu, "ondalik" => :gecti}
```

`7 / 2` tam sayı bölmesidir ve `3` verir; ikinci iddia tutmadı, koşucu hatayı yakaladı ve `bolme` testini `:dustu` olarak yazdı. Öteki ikisi geçti. Koşucunun yalnız `IddiaHatasi`'nı yakaladığına dikkat edin. Başka bir hata, örneğin sıfıra bölme, bir iddia sonucu değildir ve tabloya girmez.

```ruby
# hata.rb — iddia hatasi olmayan hata tabloya girmez
require_relative "cerceve"

begin
  testleri_kos("sifira bolme" => -> { 1 / 0 })
rescue => e
  p e.class
end
```
```
ZeroDivisionError
```

Koşucu `ZeroDivisionError`'u yakalamadı, hata dışarı çıktı. Bu ayrım önemlidir: bir testin **düşmesi** ile bir testin **patlaması** farklı olaylardır, ve bu derste yalnız ilkini ölçüyoruz. **RK13.** Burada kurulan çerçeve üç parçayı gösterir; gerçek bir çerçevenin beklenti kütüphanesi, ön hazırlık ve temizlik adımları bu modelin dışındadır.

## İki Yazım Biçimi

Yaygın çerçeveler aynı üç parçayı iki ayrı yazım biçimiyle sunar. Birincisinde testler bir sınıfın `test_` ile başlayan yöntemleridir ve iddia bir yöntem çağrısıdır. İkincisinde testler bir tanım bloğunun içinde adlandırılan örneklerdir ve iddia gerçek değerden başlayan bir beklenti zinciridir. Aşağıdaki dosya aynı iki testi iki biçimde yazar ve ikisini de aynı koşucuya verir.

```ruby
# bicimler.rb — ayni iki test, iki yazim bicimi, tek kosucu
require_relative "cerceve"

# Birinci bicim: test sinifi; test_ ile baslayan her yontem bir testtir.
class BolmeTestleri
  def test_tam
    iddia(3, 7 / 2)
  end

  def test_ondalik
    iddia(3.5, 7 / 2)
  end
end

sinif_testleri = BolmeTestleri.public_instance_methods(false).grep(/\Atest_/).sort.to_h do |ad|
  [ad.to_s, -> { BolmeTestleri.new.public_send(ad) }]
end

# Ikinci bicim: tanim blogu ve beklenti zinciri.
Beklenti = Struct.new(:gercek) do
  def olsun(beklenen)
    iddia(beklenen, gercek)
  end
end

def bekle(gercek)
  Beklenti.new(gercek)
end

class Tanim
  attr_reader :testler

  def initialize(konu)
    @konu = konu
    @testler = {}
  end

  def ornek(ad, &blok)
    @testler["#{@konu} #{ad}"] = blok
  end
end

def tanimla(konu, &govde)
  tanim = Tanim.new(konu)
  tanim.instance_eval(&govde)
  tanim.testler
end

blok_testleri = tanimla("bolme") do
  ornek("tam") { bekle(7 / 2).olsun(3) }
  ornek("ondalik") { bekle(7 / 2).olsun(3.5) }
end

p testleri_kos(sinif_testleri)
p testleri_kos(blok_testleri)
```
```
{"test_ondalik" => :dustu, "test_tam" => :gecti}
{"bolme tam" => :gecti, "bolme ondalik" => :dustu}
```

İki tablo aynı sonucu verir: tam sayı bölmesini `3.5` bekleyen test iki biçimde de düştü. Fark yazımdadır. Birinci biçimde test adı yöntem adıdır ve testler bir sınıfta toplanır. İkinci biçimde test adı, konu ile örnek adından kurulan bir dizedir. Koşucu iki durumda da aynı ad → blok tablosunu görür; bu yüzden sonraki bölümlerde ölçülecek sıra ve tohum etkisi koşucuya aittir. **RK13a.** İki biçim burada tek bir örnekte, iki testle karşılaştırıldı; gerçek çerçevelerin ön hazırlık, iç içe gruplama ve raporlama farkları bu karşılaştırmanın dışındadır.

## Gözlemin Koşucusu

Gözlem satırındaki koşucu daha küçüktür ama bir şey daha yapar: testleri tohumla karıştırır ve hepsine aynı ayar tablosunu verir. Aşağıdaki dosya `gozlemler.rb`'den alınan `TESTLER` ve `test_kos` tanımlarını birebir taşır.

```ruby
# test.rb — gozlem satirinin testleri ve kosucusu
# Test kosucusu: testler tohumla karistirilan sirada kosar ve ayni ayarlari paylasir.
TESTLER = {
  "fiyat_bicimi" => ->(ayar) { "12 #{ayar[:para_birimi]}" == "12 TRY" },
  "doviz_cevirimi" => ->(ayar) { ayar[:para_birimi] = "USD"; true },
  "fatura_toplami" => ->(ayar) { ayar[:para_birimi] == "TRY" }
}

def test_kos(kosum, testler = TESTLER)
  ayar = { para_birimi: "TRY" }
  sira = testler.keys.shuffle(random: Random.new(kosum.tohum))
  sira.reject { |ad| testler[ad].call(ayar) }
end
```

Üç test var. `fiyat_bicimi` ayardaki para birimine bakar ve `TRY` bekler. `doviz_cevirimi` ayardaki para birimini `USD` yapar ve doğru döner. `fatura_toplami` yine `TRY` bekler. Koşucu düşen testlerin adlarını döndürür. Aşağıdaki dosya altı tohum için karışık sırayı ve düşen testleri yan yana basar.

```ruby
# tohumlar.rb — alti tohum, alti sira
require_relative "belirleyen"
require_relative "test"

(1..6).each do |tohum|
  sira = TESTLER.keys.shuffle(random: Random.new(tohum))
  puts "#{tohum} #{sira.inspect} #{test_kos(TABAN.with(tohum: tohum)).inspect}"
end
```
```
1 ["fiyat_bicimi", "fatura_toplami", "doviz_cevirimi"] []
2 ["fatura_toplami", "doviz_cevirimi", "fiyat_bicimi"] ["fiyat_bicimi"]
3 ["doviz_cevirimi", "fiyat_bicimi", "fatura_toplami"] ["fiyat_bicimi", "fatura_toplami"]
4 ["doviz_cevirimi", "fiyat_bicimi", "fatura_toplami"] ["fiyat_bicimi", "fatura_toplami"]
5 ["fiyat_bicimi", "doviz_cevirimi", "fatura_toplami"] ["fatura_toplami"]
6 ["fiyat_bicimi", "doviz_cevirimi", "fatura_toplami"] ["fatura_toplami"]
```

Tohum 1 sıralaması `doviz_cevirimi`'ni sona koyar; ondan sonra koşan test olmadığı için hiçbir test düşmez. Tohum 2'de `doviz_cevirimi` ortadadır ve ondan sonra gelen `fiyat_bicimi` düşer. Tohum 3 ve 4 aynı sırayı verdi: `doviz_cevirimi` başta, ondan sonra gelen iki test de düştü. Tohum 5 ve 6 de aynı sırayı paylaşır; `doviz_cevirimi` ortadadır ve yalnız ondan sonra gelen `fatura_toplami` düşer. Altı tohumdan dört farklı sıra çıktı, ve her sıranın sonucu tohum sabit kaldıkça aynıdır.

## Paylaşılan Durum

Kusur sırada değil, sırayı kusur olarak görünür kılan şeydedir. `doviz_cevirimi` ortak ayar tablosunu değiştirir. Bu değişiklik, ondan sonra koşan her testin girdisidir. Tablonun testlerin arasında paylaşıldığını doğrudan göstermek mümkündür.

```ruby
# paylasilan.rb — bir testin yazdigi ayar, ondan sonra gelen testin girdisi
require_relative "test"

ayar = { para_birimi: "TRY" }
TESTLER["doviz_cevirimi"].call(ayar)
p ayar
p TESTLER["fiyat_bicimi"].call(ayar)
p TESTLER["fiyat_bicimi"].call({ para_birimi: "TRY" })
```
```
{para_birimi: "USD"}
false
true
```

`doviz_cevirimi`'nden sonra ayar `USD` oldu. Aynı `fiyat_bicimi` testi bu ayarla `false` döndü, taze bir ayarla `true`. Test kodu aynıdır; değişen, testin başlangıç durumudur. Bu **paylaşılan durum**dur ve **kararsız test**in en yaygın kaynağıdır: bir test iyidir, ama kendisinden önce kimin koştuğuna bağlıdır. **RK14.** Ölçüm yalnız ayar tablosunu paylaşan bir model için geçerlidir; dosya sistemi, ağ ya da küresel değişken üzerinden paylaşılan durum aynı biçimde davranır, ama burada ölçülmedi.

## Hangi Test, Kaç Tohumda Düşüyor

Tek bir tohumun sonucu bir tanıdır; altı tohumun sayımı ise bir profil verir. Aşağıdaki dosya her tohumun düşürdüğü test sayısını, her testin kaç tohumda düştüğünü ve en az bir testin düştüğü tohum sayısını sayar.

```ruby
# sayim.rb — dusenlerin tohuma gore dagilimi
require_relative "belirleyen"
require_relative "test"

sonuclar = (1..6).map { |t| test_kos(TABAN.with(tohum: t)) }
p sonuclar.map(&:size)
p sonuclar.flatten.tally
p sonuclar.count { |d| !d.empty? }
```
```
[0, 1, 2, 2, 1, 1]
{"fiyat_bicimi" => 3, "fatura_toplami" => 4}
5
```

Altı koşumun beşinde en az bir test düştü; yalnız tohum 1 temiz geçti. `doviz_cevirimi` hiç düşmedi, ama o, öteki ikisini düşüren testtir. `fatura_toplami` daha çok tohumda düştü çünkü `doviz_cevirimi` onun önüne daha sık geçti; `fiyat_bicimi` daha az düştü. Bu tablo suçluyu göstermez, mağduru gösterir. Düşen test, kusurlu testin kendisi değildir; kusurlu test sessizce geçiyor ve başkasını düşürüyor. **Bir testin düşme sıklığı, onun kendi sağlamlığını değil, önüne kimin geçtiğini ölçer.** Bu yüzden düşen testin kodu okunurken ilk soru, düşen testin neyi paylaştığı olmalıdır. **RK15.** Sayım tek bir takımın altı tohumu içindir; düşme sıklığının gerçek bir takımda aynı oranlarda olacağı bu ölçümden çıkmaz.

Sırayı sabitlemek bu sorunu çözmez, saklar. Tohum 1 her koşumda kullanılsaydı bu takım hiç düşmezdi ve paylaşılan durum fark edilmeden kalırdı. Karıştırma bir hata üretmez; var olan bağımlılığı görünür kılar. Ve görünür kılabildiği ölçüde, düşen testin tohumunu basmak, karıştırmayı bir kuşkudan bir tanıya çevirir.

## Tohumla Yeniden Üretmek

Kararsız test, düştüğü anda yeniden üretilebilirse bir hata raporudur; üretilemiyorsa bir söylentidir. Koşucu tohumu sabitleyince düşen test her seferinde aynı sırada düşer. Bu yüzden iyi bir koşucu tohumu **basar**: bir koşum düştüğünde tohum, yeniden üretimin anahtarıdır.

```ruby
# tekrar.rb — dusen testi tohumla yeniden uret
require_relative "belirleyen"
require_relative "test"

sonuclar = (1..6).map { |t| test_kos(TABAN.with(tohum: t)) }
p sonuclar.count([])
p test_kos(DEGISKENLER[:sira])
p test_kos(DEGISKENLER[:sira]) == test_kos(TABAN.with(tohum: 2))
p belirleyen(->(k) { test_kos(k) })
```
```
1
["fiyat_bicimi"]
true
["sira", "B- S+ O-", []]
```

Altı tohumdan yalnız birinde hiçbir test düşmedi: tohum 1 geçerken tohum 2 düştü. `DEGISKENLER[:sira]` tohum 2 olduğu için `["fiyat_bicimi"]` verdi ve aynı tohumla ikinci çağrı birincinin aynısını verdi. Son satır sınıflandırmayı gösterir: sonuç `sira`dan geliyor, kanıt `B- S+ O-`, taban `[]`. Kural tabandaki sonucu `[]` olarak yazdı; düşen test taban koşumunda görünmez, yalnız `sira` değişkeninde görünür. Bu dersin belirleyeni **sıra**dır. **RK16.** Tohum 1'in "geçti" göstermesi testin sağlam olduğunu göstermez; yalnız o sırada ayar tablosunu bozan test sona düştüğünü gösterir.

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

Sınıf iki yönden sınanır. Birinci yön: her test kendi ayar kopyasıyla koşarsa ne olur? İkinci yön: kural yalnız iki tohumu dener, bu iki tohum her kararsızlığı yakalar mı?

```ruby
# yalitim.rb — her test kendi ayar kopyasini alir
require_relative "belirleyen"
require_relative "test"

def yalitilmis_test_kos(kosum, testler = TESTLER)
  ayar = { para_birimi: "TRY" }
  sira = testler.keys.shuffle(random: Random.new(kosum.tohum))
  sira.reject { |ad| testler[ad].call(ayar.dup) }
end

p (1..6).map { |t| yalitilmis_test_kos(TABAN.with(tohum: t)) }
p belirleyen(->(k) { yalitilmis_test_kos(k) })
```
```
[[], [], [], [], [], []]
["kod", "B- S- O-", []]
```

Altı tohumun hepsi `[]` verdi ve kural bu kez `kod` dedi, kanıt `B- S- O-`. Aynı üç test, aynı altı tohum; tek fark her testin kendi kopyasını alması. Sınıf **sıra**dan **kod**a geçti: **yalıtım sıranın etkisini ortadan kaldırır**, çünkü testler artık birbirinin girdisi değil. Yalıtımın sınırı kopyanın derinliğidir.

```ruby
# yuzeysel.rb — dup yuzeyseldir
ayar = { liste: [] }
kopya = ayar.dup
kopya[:liste] << 1
p ayar
p ayar[:liste].equal?(kopya[:liste])
```
```
{liste: [1]}
true
```

`dup` tabloyu kopyaladı, içindeki diziyi kopyalamadı: iki tablo aynı diziyi tutuyor ve kopyadaki değişiklik aslına geçti. Ayar tablosu yalnız basit değerler taşıdığı için bu dersin yalıtımı çalıştı. İç içe bir ayar için `dup` yetmez. **RK17.** Yalıtım ölçümü yalnız düz bir tablo için geçerlidir.

İkinci yön, kuralın kendi kör noktasıdır. Kural tabanı ve `sira` değişkenini, yani tohum 1 ve tohum 2'yi dener. Tohum 2'nin geçtiği ama tohum 5'in düştüğü bir test takımı kurulabilir. `fiyat_bicimi` testini ayara hiç bakmayan bir testle değiştirmek yeter.

```ruby
# kor.rb — kuralin yalniz tohum 1 ve 2'yi denedigi yer
require_relative "belirleyen"
require_relative "test"

TESTLER_KOR = TESTLER.merge("fiyat_bicimi" => ->(_ayar) { true })

p belirleyen(->(k) { test_kos(k, TESTLER_KOR) })
sonuclar = (1..6).map { |t| test_kos(TABAN.with(tohum: t), TESTLER_KOR) }
p sonuclar
```
```
["kod", "B- S- O-", []]
[[], [], ["fatura_toplami"], ["fatura_toplami"], ["fatura_toplami"], ["fatura_toplami"]]
```

Kural bu takımı `kod` olarak sınıflandırdı, çünkü tohum 1 ve 2 ikisi de `[]` verdi. Ama altı tohumdan dördünde `fatura_toplami` düştü. **"Sıra değil" sonucu yalnız denenen iki tohum için söylenmiştir.** Takım hâlâ paylaşılan duruma bağlı; kural onu görmedi çünkü bakmadığı sıralarda düşüyor. Bunun çaresi kuralı değiştirmek değil, ölçümü yorumlamaktır: kuralın `kod` dediği bir test takımı, tohum aralığı genişletilmeden yalıtılmış sayılmaz. Yukarıdaki iki ölçüm birlikte okunmalıdır. Yalıtılmış koşucu altı tohumun hepsinde `[]` verdi; sınanan takım ise yalnız tohum 1 ve 2'de `[]` verdi. Birincide `[]` altı tohumun tümünde görüldü, ikincisinde yalnız ikisinde. Aynı çıktı, farklı sayıda tohumla desteklenir, ve ölçümün değeri o sayıdan gelir. **RK18.** Altı tohum altı olası sıranın yalnız dördünü verdi; testlerin sayısı arttıkça olası sıra sayısı hızla büyür ve birkaç tohum onları tüketemez.

## Özet

- Bir test çerçevesi üç parçadır: test tanımı, iddia ve koşucu. Düşen test iddia hatasıdır, patlayan test başka bir hatadır.
- İki yazım biçimi, yani `test_` yöntemli test sınıfı ve beklenti zincirli tanım bloğu, koşucuya aynı ad → blok tablosunu verir; aynı iki test iki biçimde de aynı sonucu verdi.
- Bu dersin belirleyeni **sıra**dır: `test sonucu` satırında kanıt `B- S+ O-`, tabanda `[]`, tohum 2'de `["fiyat_bicimi"]`.
- Kararsız testin kaynağı paylaşılan durumdur: `doviz_cevirimi` ortak ayarı değiştirir ve kendisinden sonra koşan testi düşürür. Tohum basılırsa düşen test yeniden üretilir.
- Her test kendi ayar kopyasını alınca altı tohumun hepsi `[]` verir ve kural `kod` der; `dup` yüzeyseldir.
- Kural yalnız tohum 1 ve 2'yi dener; tohum 2'nin geçtiği ama tohum 5'in düştüğü bir takımı `kod` diye sınıflandırır.

## Sonraki Adım

Sırayı tohum seçiyor ve yalıtım onu kaldırıyor. Ama bir test başka bir girdiye de bağlı olabilir: bugünün saati. Bir sonraki ders saati okuyan bir testin neden yarın düşeceğini, ve sahteleme ile test verisinin bu bağımlılığı nasıl kaldırdığını ölçer.
