Ders 04 / 09
Sahteleme ve Test Verisi
Saati okuyan test neden yarın düşer? Ders saatin dışarıdan verilmesini, sahte nesneleri ve tohumlu test verisini ölçer; sahtelemenin hangi belirleyeni kaldırdığını ve hangisini bıraktığını ayırır.
Yayın Güncelleme
Bu derste
- Saati okuyan bir testin sonucunu ortamın belirlediğini ve saatin parametre olarak verilmesinin bunu koda çevirdiğini ölçer
- Kayıt tutan sahte nesne, taklit yanıt ve beklenti nesnesini birbirinden ayırır
- Sayaçlı bir test verisi üreticisinin her çağrıda farklı değer verdiğini ve yalıtımın üreticiyi testlere böldüğünü gösterir
- Sahteleme bir belirleyeni kaldırırken tohumlu test verisinin sıra belirleyenini bıraktığını ölçer
İçindekiler
Önceki ders bir test takımının sırayla düştüğünü gösterdi ve yalıtımla düşmeyi ortadan kaldırdı. Şimdi başka bir girdi var: bugünün tarihi. Bir test bugün geçiyor, otuz gün sonra düşüyor ve kodunda tek satır değişmedi. Bu dersin gözlemi saati okuyan test satırıdır: tabanda true, kanıt sütununda B- S- O+. Sonuç, saat değişince değişir; bağımlılık ve sıra değişince değişmez.
M13/K03’te bir değerin hangi isteğe kadar kaldığı soruldu: oturumda tutulan bir bilgi bir sonraki istekte hâlâ oradaydı. Burada aynı soru testlere uygulanır: bir test verisi hangi testten sonra hâlâ oradadır? Cevap, verinin kimin elinde olduğuna bağlıdır, ve bu ders onu ölçer. Çekirdeğin belirleyen.rb dosyası birebir yeniden verilir; blok kaynaktır ve require_relative ile çekilir.
# 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
Saati Okuyan Test
Gözlemin hesabı küçük bir kuraldır: deneme süresi 30 gündür ve süre başlangıçtan beri geçen saniye sayısıyla ölçülür. Hesap saati parametre olarak alır; iki sınır değeri ölçelim.
# saat.rb — deneme suresi 30 gundur def deneme_suresinde?(baslangic, saat) = saat - baslangic < 30 * 86_400 baslangic = Time.utc(2026, 10, 1) p deneme_suresinde?(baslangic, Time.utc(2026, 10, 30, 23, 59)) p deneme_suresinde?(baslangic, Time.utc(2026, 10, 31, 0, 0))
true false
Sınır 30 * 86_400 saniyedir. 30 Ekim 23:59 süre içindedir; 31 Ekim 00:00 tam otuz gün geçmiştir ve false verir. Bu hesap saf bir hesaptır: aynı iki girdi her zaman aynı sonucu verir. Şu ana kadar hiçbir şey ortama bağlı değil. Bağlılık, saatin nereden geldiği sorulduğunda başlar.
Aşağıdaki dosya iki test yazar. Birincisi saati koşum modelinin ortamından okur, yani saati makinenin söylediği gibi alır. İkincisi aynı hesabı, başında verilmiş sabit bir saat kaynağıyla koşturur.
# ortam.rb — saati kimin verdigi sonucu belirler
require_relative "belirleyen"
def deneme_suresinde?(baslangic, saat) = saat - baslangic < 30 * 86_400
baslangic = Time.utc(2026, 10, 1)
p deneme_suresinde?(baslangic, TABAN.ortam[:saat])
p deneme_suresinde?(baslangic, DEGISKENLER[:ortam].ortam[:saat])
p belirleyen(->(k) { deneme_suresinde?(baslangic, k.ortam[:saat]) })
true false ["ortam", "B- S- O+", true]
Tabandaki saatle test geçti, Mart 2027’deki saatle düştü. Kural ortam dedi, kanıt B- S- O+: bağımlılık ve sıra değişkenlerinde sonuç aynı kaldı, ortam değişkeninde değişti. Kodun hiçbir satırı değişmeden aynı test iki sonuç verdi. Bu, saati okuyan bir testin yarın düşecek olan testtir: Time.now ile okunan saat, bugünkü koşumda tabandaki gibi, otuz gün sonra Mart gibi davranır. RK19. Bu ölçüm saati ortam modelinden okur; gerçek saatin değeri Time.now ile okunur ve sandbox’tan sandbox’a değiştiği için basılmaz.
Saati Parametre Yapmak
Düzeltme, saati hesabın içinden çıkarıp dışarıdan vermektir. Hesabın saat kaynağını bir parametre olarak alması, saatin kim tarafından seçildiğini çağıran tarafa taşır. Üretimde kaynak gerçek saattir; testte kaynak sabit bir saattir. Bu sabit saat bir sahte nesnedir: gerçeğinin yerine geçen ve davranışı testin kontrol ettiği bir nesne.
# sahte_saat.rb — saati disaridan veren test
require_relative "belirleyen"
def deneme_suresinde?(baslangic, saat) = saat - baslangic < 30 * 86_400
def kalan_deneme?(baslangic, saat_kaynagi) = deneme_suresinde?(baslangic, saat_kaynagi.call)
baslangic = Time.utc(2026, 10, 1)
sahte_saat = -> { Time.utc(2026, 10, 16, 9, 0) }
p belirleyen(->(k) { kalan_deneme?(baslangic, -> { k.ortam[:saat] }) })
p belirleyen(->(_k) { kalan_deneme?(baslangic, sahte_saat) })
["ortam", "B- S- O+", true] ["kod", "B- S- O-", true]
İlk satır saat kaynağı koşumun ortamından geldiğinde sonucu ortam olarak sınıflandırır. İkinci satır kaynağı sabit bir saatle değiştirir ve sonuç kod olur, kanıt B- S- O-. Hesap hâlâ aynı, test hâlâ aynı iddiada; değişen, saatin test tarafından seçilmesidir. Saat artık ortamın söylediği bir şey değil, testin kendi girdisidir. RK20. Sahte saat bir lambda ile kuruldu; gerçek bir çerçevenin zamanı dondurma yolu, örneğin Time.now yöntemini geçici olarak değiştirmesi, farklı bir kapsam taşır ve burada ölçülmedi.
Parametre yapmanın bir bedeli vardır ve ölçümün dışında kalmaz. Hesap artık bir saat kaynağı bekler; çağıran her yerin bunu geçmesi gerekir, ve üretimde geçilen kaynak gerçek saat olduğu için gerçek saatle çalışan yol testte hiç koşmaz. Sahte saatle yazılan test hesabı doğrular, saatin gerçek kaynaktan doğru okunduğunu doğrulamaz. Bu yüzden saati sahtelemek, saati okuyan testi yarın düşmekten korur ama okuma satırını denetimsiz bırakır. İyi bir ayrım, okumayı tek bir yerde toplamak ve o yeri ayrıca denemektir.
Sabit saatin ikinci bir yararı vardır: sınır değerleri denenebilir olur. Gerçek saatle 30. günün son dakikasını yakalamak için o dakikayı beklemek gerekir; sahte saatle iki çağrı yeter, ve ilk ölçümdeki 23:59 ile 00:00 ayrımı her koşumda aynı çıkar.
Sahte Nesneler
Saat, sahtelenen en basit bağımlılıktır. Testlerde üç tür sahte nesne görülür ve her birinin işi farklıdır. Kayıt tutan sahte nesne gerçeğinin yaptığı işi yapmaz, yalnız kendisine ne söylendiğini kaydeder. Taklit yanıt verilen bir çağrıya hazır bir cevap döner. Beklenti nesnesi ise bir çağrının yapılıp yapılmadığını sonradan denetler.
# bildirici.rb — kayit tutan sahte nesne
class SahteBildirici
attr_reader :gonderilenler
def initialize
@gonderilenler = []
end
def gonder(alici, metin)
@gonderilenler << [alici, metin]
end
end
def fatura_bildir(bildirici, alici) = bildirici.gonder(alici, "fatura hazir")
bildirici = SahteBildirici.new
fatura_bildir(bildirici, "[email protected]")
p bildirici.gonderilenler
[["[email protected]", "fatura hazir"]]
Fatura bildirimi gerçek bir ileti göndermedi; sahte bildirici ne gönderildiğini bir diziye yazdı. Test iddiasını bu diziye kurabilir: [["[email protected]", "fatura hazir"]]. Kayıt tutan sahte nesne, bir çağrının ne yaptığını değil kime ne söylediğini ölçer. Üretimdeki bildirici ağa çıkardı; sahte bildirici ağa çıkmaz, bu yüzden test hem hızlıdır hem ağın durumundan bağımsızdır.
# taklit.rb — taklit yanit
doviz = Object.new
doviz.define_singleton_method(:kur) { |_birim| 42.5 }
p doviz.kur(:usd)
p doviz.singleton_methods
42.5 [:kur]
Taklit yanıt bir hesap yapmadı: ne sorulursa sorulsun 42.5 döndü. Nesnenin yalnız bir çağrısı var ve o da testin yazdığı çağrı. Bu, bir testin dış bir kaynağa, örneğin bir kur servisine bağlılığını koparır; ama aynı zamanda sahte nesnenin gerçeği ne kadar temsil ettiği sorusunu doğurur. Sahte bildirici iki bağımsız değişken alıyordu; gerçek bildirici üçüncü bir değişken, kanal, isteyebilir.
# ayrisma.rb — sahte nesne gercekten ayrisabilir
class SahteBildirici
attr_reader :gonderilenler
def initialize = @gonderilenler = []
def gonder(alici, metin) = @gonderilenler << [alici, metin]
end
class GercekBildirici
def gonder(alici, metin, kanal) = [alici, metin, kanal]
end
sahte = SahteBildirici.new
sahte.gonder("[email protected]", "fatura hazir")
p sahte.gonderilenler
begin
GercekBildirici.new.gonder("[email protected]", "fatura hazir")
rescue ArgumentError => e
p e.class
end
[["[email protected]", "fatura hazir"]] ArgumentError
Sahte nesneyle yazılan çağrı geçti, aynı çağrı gerçek nesnede ArgumentError verdi. Test yeşildi, üretim kırıldı. RK21. Sahte nesne gerçeğin davranışını taklit eder, doğrulamaz; sahte ile gerçek arasındaki ayrışmayı yakalamak için ya gerçek nesneyle yapılan ayrı bir test ya da sahtenin gerçeğin imzasını izlemesini denetleyen bir iddia gerekir. Bu ölçüm yalnız ayrışmanın var olabileceğini gösterir; ayrışmayı otomatik yakalayan bir aracı ölçmez.
Beklenti nesnesi kayıt tutan nesneye bir karar ekler. Neyin çağrıldığını tutar, ama testin sonunda bunu bir iddiaya çevirir: “bu alıcıya tam bir kez bildirim gitmeliydi”.
# beklenti.rb — beklenti nesnesi
class BildirimBeklentisi
def initialize(alici, beklenen_sayi)
@alici = alici
@beklenen_sayi = beklenen_sayi
@cagrilar = []
end
def gonder(alici, _metin) = @cagrilar << alici
def dogrula = @cagrilar.count(@alici) == @beklenen_sayi
end
beklenti = BildirimBeklentisi.new("[email protected]", 1)
beklenti.gonder("[email protected]", "fatura hazir")
p beklenti.dogrula
beklenti.gonder("[email protected]", "fatura hazir")
p beklenti.dogrula
true false
Birinci çağrıdan sonra beklenti tutar, ikinci çağrıdan sonra tutmaz: aynı alıcıya iki bildirim gitmiştir. Ayrım şudur: kayıt tutan nesne bilgiyi toplar, beklenti nesnesi o bilgiye bir karar bağlar. Karar yanlış kurulursa test yanlış şeyi doğrular; beklenti sayısı testin kendi varsayımıdır.
Test Verisi
Test verisi, bir testin girdisi olarak üretilen kayıtlardır: müşteri, fatura, kullanıcı. Veriyi elle yazmak her testte aynı satırları tekrarlar; bir test verisi üreticisi kayıtları bir kuralla üretir. En basit üretici bir sayaç tutar.
# uretici.rb — sayacli test verisi ureticisi
def musteri_uretici
sayac = 0
-> { sayac += 1; "musteri#{sayac}" }
end
uret = musteri_uretici
p uret.call
p uret.call
p musteri_uretici.call
"musteri1" "musteri2" "musteri1"
Aynı üreticiden iki çağrı "musteri1" ve "musteri2" verdi: sayaç her çağrıda bir artar. Yeni bir üretici ise yeniden "musteri1" verdi. Bu iki gözlem yalıtımın cevabıdır. Bir üretici bütün testler arasında paylaşılırsa, bir testin ürettiği kayıt ötekinin sayacını ilerletir ve ikinci test "musteri1" yerine "musteri2" görür. Her testin kendi üreticisini kurması, paylaşılan durumu böler. Bu, önceki dersin ayar tablosundaki kopyalamanın aynısıdır, bir sayaç için. RK22. Ölçüm sayaçlı bir üreticiyi gösterir; veritabanında kalıcı bir kayıt üreten bir üreticinin aynı yalıtımı sağlaması için ayrıca temizlik adımı gerekir ve burada ölçülmedi.
Sınırlayıcı Ölçüm
Sahtelemenin sonucu her şeyi çözmüş gibi görünür: saat sabit, bildirici sahte, sayaç yalıtılmış. Ama bir belirleyeni kaldırmak, ötekini kaldırmaz. Test verisi tohumla üretilirse sonucu tohum belirler.
# veri.rb — tohumlu test verisi
require_relative "belirleyen"
p Array.new(3) { Random.new(1).rand(100) }.uniq.size
p (r = Random.new(1); Array.new(3) { r.rand(100) })
p (r = Random.new(2); Array.new(3) { r.rand(100) })
veri = lambda do |k|
r = Random.new(k.tohum)
Array.new(3) { r.rand(100) }
end
p belirleyen(veri)
sabit = lambda do |_k|
r = Random.new(7)
Array.new(3) { r.rand(100) }
end
p belirleyen(sabit)
1 [37, 12, 72] [40, 15, 72] ["sira", "B- S+ O-", [37, 12, 72]] ["kod", "B- S- O-", [47, 68, 25]]
Birinci satır, her çağrıda yeni bir Random.new(1) kurulursa üç değerin hep aynı olduğunu gösterir: tek bir değer. Tohum 1 ve tohum 2 iki farklı üçlü verdi. belirleyen tohumu koşumdan aldığında sonuç sira: kanıt B- S+ O-, taban tohum 1’in üç değeri. Tohum sabit bir sayı olduğunda, yani Random.new(7), sonuç kod: hesap koşumun tohumuna bakmıyor. Sahteleme bir belirleyeni kaldırır, ötekini kendiliğinden kaldırmaz. Saati sabitlemek ortamı kaldırdı; tohumu koşumdan alan veri üreticisi sıra belirleyenini bıraktı. Hangi belirleyenin kaldığı, bir şeyin sahte olmasından değil, sonucun neye bakmaya devam ettiğinden anlaşılır. RK23. Tohumlu veri burada üç sayıdan ibarettir; gerçek bir veri üreticisinin kayıtları tohuma bağlı olduğunda testin neden bazen düştüğü, bir önceki dersin aynı nedenidir ve ayrıca ölçülmedi.
Bu ayrımın pratik sonucu şudur: bir testi kararlı yapmak için hangi belirleyenin sonucu oynattığı önce ölçülmelidir. Saati sahtelemek, sonucu sıraya bağlı bir testi kurtarmaz; sıralamayı sabitlemek, saate bağlı bir testi kurtarmaz. Önceki dersin tohum tablosu ile bu dersin saat ölçümü aynı soruyu iki ayrı değişkende sorar, ve cevap her seferinde değişkenin kendisine bağlıdır.
Özet
- Saati okuyan bir test ortamın söylediğine bağlıdır:
saati okuyan testsatırında kanıtB- S- O+, Mart 2027’de sonuç değişir. - Bu dersin belirleyeni ortamdır. Saat parametre olarak verilince ve sabit bir saat kaynağı kullanılınca sonuç
kodolur. - Kayıt tutan sahte nesne ne söylendiğini toplar, taklit yanıt hazır bir cevap döner, beklenti nesnesi çağrıya bir karar bağlar.
- Sayaçlı test verisi üreticisi her çağrıda farklı değer verir; her testin kendi üreticisi paylaşılan durumu böler.
- Sahteleme bir belirleyeni kaldırır, ötekini kendiliğinden kaldırmaz: koşumun tohumundan üretilen test verisi
siraolarak kalır.
Sonraki Adım
Bu kursun ilk yarısı paketlerin ve testlerin sonucu neyin belirlediğini ölçtü: dosya adını paket tanımı, kurulan sürümü kilit dosyası, düşen testi sıra, saati okuyan testi ortam. İkinci yarı üretimde çalışan koda geçer. Eşzamanlılık dersi, aynı kodun kilitsiz bir sayaçla koşarken tohuma göre farklı sonuç verdiğini ölçer ve dışlama kilidinin bu farkı neden kaldırdığını gösterir.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.