Ders 04 / 16
Sahteleme ve Arayüz Ayrımı
Aynı StokAzalt gövdesi hem kayıtlı hem casus bir Bildirici gerçekleştirmesiyle koşturuluyor: normal bir azaltmada ikisi de aynı kararı veriyor ve casus nesnenin çağrı sayısı 1 okunuyor. Sınırlayıcı ölçüm, stoktan fazlasını azaltan bir çağrıda gerçek gerçekleştirmenin negatif kalanı reddettiğini ama casus nesnenin aynı çağrıyı sessizce kaydettiğini gösteriyor.
Yayın Güncelleme
Bu derste
- Bildirici arayüzünün StokAzalt'ı somut bir gerçekleştirmeye değil bir sözleşmeye bağladığını kaynaktan okur
- Aynı test gövdesini gerçek ve sahte bir gerçekleştirmeyle koşturup casus nesnenin çağrı sayısını okur
- Gerçek gerçekleştirmede tutan bir varsayımın sahte gerçekleştirmede hiç sınanmadığını, stoktan fazla azaltma durumuyla sınırlar
İçindekiler
Önceki ders bir HTTP işleyicisinin gerçek bir bağlantı hiç kurulmadan sınanabildiğini ölçtü, ama işleyicinin kendisi hâlâ elle kurulan sabit bir depoya bakıyordu. Bu ders o depoyu, işleyicinin arkasındaki bir başka bağımlılığa — bir bildirim gönderen taraf — taşıyor ve aynı soruyu soruyor: bu bağımlılık gerçek dünyada yavaş, kararsız ya da yan etkili bir şeyse (bir dış sisteme yazmak gibi), bu bağımlılığa dokunmadan aynı mantığı nasıl sınarsınız?
Önceki derste bu soru bir bakıma zaten çözülmüştü — httptest.NewRecorder, standart
kitaplığın kendi http.ResponseWriter arayüzünü gerçekleştiren, önceden yazılmış bir
sahteydi. Bu ders aynı deseni, standart kitaplığın hazır bir sahtesi olmayan bir
bağımlılığa uyguluyor: Bildirici’yi ve onun iki gerçekleştirmesini bu paketin kendisi
yazıyor.
Sahte bağımlılık ailesi — sahte nesne, güdük, taklit nesne, casus nesne, kukla nesne — Birim Testi ve Test Odaklı Geliştirme kursunda taksonomisiyle birlikte kuruldu ve burada yeniden tanıtılmıyor; ne taksonomi yeniden anlatılıyor ne de her üye bu derste ayrı ayrı gerçekleştiriliyor. Bu dersin eklediği soru dar: sahtelemeyi mümkün kılan şey nedir ve bu şey kaynakta nerede görünüyor?
Üretim kod tabanlarında bir işlevin çağırdığı her şeyi gerçek hâliyle sınamak çoğu zaman mümkün değil: bir bildirim gönderen taraf gerçek bir dış sisteme yazabilir, bir ödeme sağlayıcısı gerçek parayı hareket ettirebilir, bir depolama katmanı gerçek bir sunucuya bağlanabilir. Bu bağımlılıkların yerine geçen bir sahte, sınamayı bu maliyetlerden kurtarıyor — ama bu kurtuluşun kendi bedeli var ve bu dersin sınırlayıcı ölçümü tam olarak bu bedeli sayıyor. Bedel görünmez kaldığı sürece bir risk taşımıyor; bu dersin amacı bedeli görünür kılmak, sahtelemeyi terk ettirmek değil.
Sahteyi Mümkün Kılan Şey Arayüz Ayrımı
envanter paketine bir işlev daha ekleniyor: StokAzalt, bir kalemin adedini azaltıyor
ve kalan miktar belirli bir eşiğin altına düştüğünde bir bildirim gönderiyor — eşik bu
derste sabit olarak 2 seçildi. Bildirimi gönderen tarafı StokAzalt doğrudan çağırmıyor
— bir Bildirici arayüzü üzerinden çağırıyor.
// envanter.go — StokAzalt somut bir bildirim gonderen tarafa degil bir arayuze bagli
package main
import (
"errors"
"fmt"
)
type Kalem struct {
Ad string
Adet int
Fiyat int
}
type Bildirici interface {
Bildir(ad string, kalanAdet int) error
}
func StokAzalt(depo map[string]Kalem, ad string, miktar int, b Bildirici) error {
k := depo[ad]
k.Adet -= miktar
depo[ad] = k
if k.Adet < 2 {
return b.Bildir(ad, k.Adet)
}
return nil
}
// kayitliBildirici gercek gerceklestirim: negatif kalani reddediyor.
type kayitliBildirici struct {
kayitlar []string
}
func (k *kayitliBildirici) Bildir(ad string, kalanAdet int) error {
if kalanAdet < 0 {
return errors.New("negatif kalan bildirilemez")
}
k.kayitlar = append(k.kayitlar, fmt.Sprintf("%s:%d", ad, kalanAdet))
return nil
}
// casusBildirici sahte gerceklestirim: dogrulama yapmadan cagriyi kaydediyor.
type casusBildirici struct {
cagrilar int
}
func (c *casusBildirici) Bildir(ad string, kalanAdet int) error {
c.cagrilar++
return nil
}
TS10. StokAzalt’ın son parametresi Bildirici — bir yöntem imzasından ibaret bir
arayüz, kayitliBildirici ya da casusBildirici gibi somut bir tip değil. Bu, kaynaktan
doğrudan okunabilen bir karar: StokAzalt’ı çağıran kod, Bildir yöntemini taşıyan
herhangi bir tipi geçirebiliyor ve StokAzalt’ın gövdesi hangi somut tipin
geçirildiğini hiç bilmiyor. Sahtelemeyi mümkün kılan tam olarak bu: eğer StokAzalt
*kayitliBildirici’yi doğrudan alsaydı, onun yerine başka bir tip geçirmenin hiçbir
yolu olmazdı.
envanter paketi bu ders boyunca tek bir yeni bağımlılık kazandı ve bu bağımlılık
paketin kendi içinde tanımlandı — Bildirici gerçek bir dış paketten içe aktarılan bir
tip değil, StokAzalt’ın ihtiyaç duyduğu tek yöntemi tanımlayan, envanter paketinin
kendi arayüzü. Bu, arayüzü tüketen tarafın (burada StokAzalt) kendi ihtiyacına göre
küçük bir arayüz tanımlamasının bir örneği: arayüz, bir kitaplığın dışarıya sunduğu geniş
bir sözleşme değil, çağıranın gerçekten kullandığı kadarını kapsayan dar bir sözleşme.
Bildirici arayüzü tek bir yöntem taşıyor ve bu darlık kasıtlı: arayüz ne kadar dar
olursa, onu gerçekleştiren bir tip yazmak o kadar az iş gerektiriyor. kayitliBildirici
ve casusBildirici ikisi de yalnız bu tek yöntemi (Bildir) tanımlayarak arayüzü
sağlıyor — Go’da bir tipin bir arayüzü sağladığını ayrıca bildirmek gerekmiyor, yöntem
kümesi arayüzün istediği kümeyi kapsıyorsa tip o arayüzü kendiliğinden sağlamış oluyor.
Bu, casusBildirici’nin envanter paketinin geri kalanıyla hiçbir bağı olmadan,
yalnız Bildir yöntemini taşıdığı için StokAzalt’a geçirilebilmesinin nedeni.
Arayüzün kendisi, gerçek ve sahte gerçekleştirmenin aynı sözleşmeyi taşıdığını
garanti ediyor: ikisi de aynı imzayla (Bildir(ad string, kalanAdet int) error)
çağrılabiliyor, StokAzalt’ın gövdesi hangisiyle çalıştığını ayırt etmeden aynı satırı
(b.Bildir(ad, k.Adet)) çalıştırıyor. Ama bu sözleşme yalnız imzayı garanti ediyor —
imzanın arkasındaki davranışı değil. İki gerçekleştirmenin aynı girdiye aynı yanıtı
verip vermeyeceği, arayüzün kendisinden hiç okunamıyor; bu sorunun yanıtı aşağıdaki
ölçümde ortaya çıkıyor.
Aynı Gövde, İki Gerçekleştirme
StokAzalt’ın gövdesinde Bildirici tipiyle ilgili tek bir satır var: b.Bildir(ad, k.Adet). Bu
satırın hangi somut tiple çalıştığı, çağıranın StokAzalt’a hangi değeri geçirdiğine bağlı —
işlevin kendisi bu kararı hiç vermiyor. Aşağıdaki ölçüm bu kararı çağıran tarafına bırakarak, aynı
işlevi iki kez, iki farklı üçüncü argümanla çağırıyor.
TS11. Aynı StokAzalt çağrısı, önce gerçek gerçekleştirme (kayitliBildirici),
sonra sahte gerçekleştirme (casusBildirici) ile koşturuluyor; üç adetlik bir kalemden
iki adet azaltılıyor, kalan (1) eşiğin altında olduğu için bildirim tetikleniyor.
// olcum.go — ayni govde gercek ve sahte gerceklestirmeyle kosuyor
package main
import "fmt"
func main() {
fmt.Println("-- kosum: ayni govde gercek ve sahte ile --")
depo1 := map[string]Kalem{"kalem": {"kalem", 3, 250}}
gercek := &kayitliBildirici{}
hataGercek := StokAzalt(depo1, "kalem", 2, gercek)
fmt.Println("gercek: hata var mi:", hataGercek != nil, "kayit sayisi:", len(gercek.kayitlar))
depo2 := map[string]Kalem{"kalem": {"kalem", 3, 250}}
sahte := &casusBildirici{}
hataSahte := StokAzalt(depo2, "kalem", 2, sahte)
fmt.Println("sahte: hata var mi:", hataSahte != nil, "casus cagri sayisi:", sahte.cagrilar)
}
-- kosum: ayni govde gercek ve sahte ile -- gercek: hata var mi: false kayit sayisi: 1 sahte: hata var mi: false casus cagri sayisi: 1
Ölçüm iki ayrı depo değişkeni kuruyor (depo1, depo2), aynı içerikle ama ayrı
haritalar olarak — StokAzalt deponun kendisini değiştirdiği için, tek bir depo iki kez
kullanılsaydı ikinci çağrı ilk çağrının bıraktığı değişmiş durumdan başlardı ve iki
gerçekleştirmenin kararı artık aynı girdiye dayanmazdı. Bu yalıtım, önceki derslerdeki
araclar.Kur’un her koşum için tamamen boş bir modül kurmasıyla aynı gerekçeye
dayanıyor: bir ölçümün önceki bir ölçümün izini taşımaması.
İki koşumda da StokAzalt‘ın gövdesi bayt bayt aynı; değişen tek şey son argümana
geçirilen değer. Gerçek gerçekleştirmede kayıt listesinin uzunluğu 1’e çıkıyor — bu
gerçek bir yan etkinin izini tutuyor. Sahte gerçekleştirmede aynı çağrı cagrilar
sayacını 1’e çıkarıyor — casus nesnenin ölçtüğü tek şey bu: kaç kez çağrıldığı, ne
yaptığı değil. İki ölçüm de aynı sonuca (hata var mi: false) varıyor ve bu ölçümün
bu kadarıyla sahtenin gerçeğin yerini tuttuğu söylenebilir.
casusBildirici burada özellikle casus nesne — sahte bağımlılık ailesinin, çağrıları
kaydedip sonradan sorgulanmasına izin veren üyesi. Bildir çağrıldığında hiçbir iş
yapmıyor, yalnız bir sayacı artırıyor; bu, StokAzalt’ın bildirim tetikleyip
tetiklemediğini sınamak için yeterli, çünkü ölçümün sorusu “bildirim gönderildi mi”,
“bildirim doğru içerikte mi” değil. Aynı senaryo, çağrı sayısı yerine çağrının
argümanlarını da tutan bir casus ile genişletilebilirdi — casusBildirici bu derste
en dar hâliyle, yalnız bir sayaçla, tutuluyor çünkü ölçümün sorduğu tek şey bu.
kayitliBildirici adının kendisi de bir seçim: bu tip yalnızca çağrıları kaydeden bir
kukla değil, gerçekten karar veren bir gerçekleştirme — negatif bir kalan miktarı
reddetme kararı, sahte olmayan, üretim koduna gidecek bir mantık. Bu ders onu “gerçek”
diye anıyor çünkü StokAzalt’ın üretimde birlikte çalışacağı asıl gerçekleştirmenin
küçük bir modelini kuruyor — kendisi de bu derste hiçbir dış sisteme yazmıyor, ama
taşıdığı denetim kuralı gerçek bir dış sistemin taşıyabileceği türden.
Sınırlayıcı Ölçüm: Sahte Gerçekleştirme Daha Az Şey Sınıyor
StokAzalt’ın kendisi, adedi negatife düşürecek bir çağrıyı reddetmiyor — bu bir
tasarım kararı: stoktan fazla azaltmanın nasıl ele alınacağı StokAzalt’ın değil,
Bildirici’nin sorumluluğuna bırakılmış. Bu, önceki ölçümde görünmeyen bir dal;
üç adetlik bir kalemden iki adet azaltıldığında kalan hep pozitif kalıyordu.
TS12. Aynı ölçüm bu kez üç adetlik bir kalemden beş adet azaltarak koşturuluyor —
kalan adet negatife düşüyor (-2) ve Bildir bu negatif değerle çağrılıyor.
// sinir.go — gercek gerceklestirmede tutan bir varsayim sahtede hic sinanmiyor
package main
import "fmt"
func main() {
fmt.Println("-- sinirlayici olcum: stoktan fazla azaltma --")
depo3 := map[string]Kalem{"kalem": {"kalem", 3, 250}}
gercek2 := &kayitliBildirici{}
hataGercek2 := StokAzalt(depo3, "kalem", 5, gercek2)
fmt.Println("gercek: hata var mi:", hataGercek2 != nil)
depo4 := map[string]Kalem{"kalem": {"kalem", 3, 250}}
sahte2 := &casusBildirici{}
hataSahte2 := StokAzalt(depo4, "kalem", 5, sahte2)
fmt.Println("sahte: hata var mi:", hataSahte2 != nil, "casus cagri sayisi:", sahte2.cagrilar)
}
-- sinirlayici olcum: stoktan fazla azaltma -- gercek: hata var mi: true sahte: hata var mi: false casus cagri sayisi: 1
Çağrı sayısı iki gerçekleştirmede de aynı çıkıyor — sahte2.cagrilar yine 1, çünkü
Bildir yine tam olarak bir kez çağrılıyor. Değişen şey çağrının sonucu, çağrının
kendisi değil; casus nesne çağrının olup olmadığını doğru sayıyor, ama çağrının
taşıdığı değerin geçerli olup olmadığına hiç bakmıyor. Bu kez iki gerçekleştirme
farklı karar veriyor. kayitliBildirici, gerçek dünyada
bir dış sistemin taşıyabileceği bir varsayımı kodluyor: negatif bir kalan miktar
bildirilemez, çünkü böyle bir durum dış sistemin gözünde anlamsız. Bu varsayım ihlal
edildiğinde gerçek gerçekleştirme bir hata döndürüyor ve StokAzalt’ın kendisi de bu
hatayı olduğu gibi çağırana taşıyor. casusBildirici ise hiçbir varsayım taşımıyor —
yalnız çağrıldığını kaydediyor ve her zaman başarı bildiriyor; aynı negatif değerle
çağrıldığında sessizce cagriları 1 artırıp nil döndürüyor.
Bu, konunun ölçü ekseninin bu derse özgü sınırı: yalnız casusBildirici kullanan bir
test takımı, StokAzalt’ın stoktan fazla azaltma durumunu doğru işlediğini hiçbir zaman
sınamaz — çünkü casus nesnenin kendisi bu durumu ayırt etmiyor, her çağrıyı aynı biçimde
kabul ediyor. Gerçek gerçekleştirmede tutan varsayım, sahte gerçekleştirmede hiç
sınanmadan kalıyor; sahteleme testi bu yüzden gerçek testten daha az şey sınıyor,
daha fazla değil. Bu bir kusur değil, sahtenin doğası: bir sahte, arkasındaki gerçek
sistemin bütün kurallarını değil, yalnız yazan kişinin kodladığı kadarını taşıyor.
Bunun üretim tarafındaki karşılığı somut: yalnız casus nesneyle yazılmış bir test takımı
yeşil görünse bile, gerçek dış sistem devreye girdiğinde StokAzalt bu senaryoda bir
hata ile karşılaşacak — ve bu hatayı üreten kural, dış sistemin kendi belgelerinde
yazılı olabilir ama sahte gerçekleştirmeye hiç taşınmamış olabilir. Bu, Test Paketi
Temelleri’nde ölçülen sınırın bir başka biçimi: orada geçen bir test doğru olduğunu
değil sınadığı şeyin tuttuğunu söylüyordu, burada geçen bir sahteleme testi de yalnız
sahtenin kendi kurallarının tuttuğunu söylüyor — gerçek sistemin kurallarının değil.
İki sınır da aynı köke iniyor: bir testin geçmesi, sınamanın kapsamı dışında kalan hiçbir
şey hakkında bir iddia taşımıyor. Sahtenin
gerçekleştirdiği yöntem sayısı ile gerçek gerçekleştirmenin yöntem sayısı burada aynı
(ikisi de yalnız Bildir), ama davranış sayısı aynı değil: gerçek gerçekleştirme
iki davranış taşıyor (kabul etme, reddetme), sahte gerçekleştirme yalnız birini (kabul
etme). Arayüzün kendisi bu farkı hiç göstermiyor, çünkü arayüz yalnız imzayı
zorunlu kılıyor, davranış sayısını değil.
Bu sınırın bir çıkış yolu var, ama bu dersin kapsamı dışında: sahte gerçekleştirmeyi gerçek gerçekleştirmenin varsayımlarına göre periyodik olarak sınayan ayrı bir sözleşme testi yazmak, iki tarafın davranışının birbirinden sapmadığını ayrıca doğrulayabilirdi. Bu derste böyle bir sözleşme testi yok — amaç sahtenin sınırını göstermek, o sınırı kapatan bir çözüm sunmak değil.
casusBildirici bu ölçüm için doğru seçim, çünkü sorulan soru “kaç kez çağrıldı” — sahte
bağımlılık ailesinin öbür üyeleri farklı sorulara yanıt vermek için var. Bir güdük sabit bir değer
döndürüp hiçbir şeyi kaydetmezdi; bir kukla nesne yalnız parametre yerini doldurur, Bildir hiç
çağrılmasa bile fark etmezdi. StokAzalt’ın burada ölçülen davranışı — bildirimin tetiklenip
tetiklenmediği ve kaç kez tetiklendiği — tam olarak bir casus nesnenin ölçtüğü tür bir
bilgi; bu yüzden beş üyeden bu ders yalnız onu kullandı.
Özet
StokAzalt’ın son parametresi somut bir tip değil bir arayüz (Bildirici); bu ayrım kaynaktan doğrudan okunabiliyor ve sahtelemeyi mümkün kılan tam olarak bu — sahteyi mümkün kılan bilgiyi kaynak söylüyor.- Aynı
StokAzaltgövdesi gerçek (kayitliBildirici) ve sahte (casusBildirici) gerçekleştirmeyle koşturuldu; normal bir azaltmada ikisi de aynı kararı verdi. - Casus nesnenin ölçtüğü şey yalnız çağrı sayısı; ne yaptığı değil, kaç kez çağrıldığı.
- Sınırlayıcı ölçüm: stoktan fazla azaltan bir çağrıda gerçek gerçekleştirme negatif kalanı reddetti, sahte gerçekleştirme aynı çağrıyı sessizce kabul etti — sahteleme testi bu varsayımı hiç sınamadı.
- Sahte bağımlılık taksonomisi Birim Testi ve Test Odaklı Geliştirme kursunda kuruldu; burada yeniden anlatılmadı, yalnız Go’nun arayüz düzeneği üzerinden ölçüldü.
Sonraki Adım
Bu derste bir arayüzün, bir bağımlılığı gerçek ve sahte gerçekleştirmeler arasında
değiştirilebilir kıldığı, ama sahte gerçekleştirmenin gerçeğin bütün varsayımlarını
taşımadığı ölçüldü. Bildirici tek yöntemli, dar bir arayüz olarak kaldı; kayitliBildirici
kendi denetim kuralını taşıdı, casusBildirici yalnız bir çağrı sayacı tuttu — ve iki
gerçekleştirme normal bir azaltmada aynı, aşırı bir azaltmada farklı karar verdi. Buraya
kadarki dört ders de tek tek geçti ya da kaldı, tek tek hata verdi ya da vermedi — ama
hiçbiri, bu kararların hangi sayısının koşumdan koşuma değiştiğini sormadı. Sıradaki
ders bu soruyu soruyor: aynı işin iki yazımı karşılaştırıldığında, hangi sayı beş ayrı
koşumda hep aynı kalıyor, hangisi kalmıyor — ve konunun bağlayıcı sayıları da orada
kuruluyor.
İlerlemeni kaydetmek ve not almak için Giriş yap
Notlarım
Not almak için giriş yapmalısın.